x 等价于 >= x+1\\n if (lowerBound(arr, x + 1) - lowerBound(arr, x) > m) {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m * 3 + 2];\\n }\\n\\n // lowerBound 返回最小的满足 nums[i] >= target 的下标 i\\n // 如果数组为空,或者所有数都 < target,则返回 nums.length\\n // 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]\\n // 原理见 https://www.bilibili.com/video/BV1AP41137w7/\\n private int lowerBound(int[] nums, int target) {\\n int left = -1;\\n int right = nums.length; // 开区间 (left, right)\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // nums[left] < target\\n // nums[right] >= target\\n int mid = left + (right - left) / 2;\\n if (nums[mid] >= target) {\\n right = mid; // 范围缩小到 (left, mid)\\n } else {\\n left = mid; // 范围缩小到 (mid, right)\\n }\\n }\\n // 循环结束后 left+1 = right\\n // 此时 nums[left] < target 而 nums[right] >= target\\n // 所以 right 就是第一个 >= target 的元素下标\\n return right;\\n }\\n}\\n\\n
###cpp
\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector<int>& arr) {\\n int n = arr.size();\\n int m = n / 4;\\n for (int i : {m, m * 2 + 1}) {\\n int x = arr[i];\\n if (ranges::upper_bound(arr, x) - ranges::lower_bound(arr, x) > m) {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m * 3 + 2];\\n }\\n};\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector<int>& arr) {\\n int n = arr.size();\\n int m = n / 4;\\n for (int i : {m, m * 2 + 1}) {\\n int x = arr[i];\\n auto [p, q] = ranges::equal_range(arr, x);\\n if (q - p > m) {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m * 3 + 2];\\n }\\n};\\n
\\n###c
\\n// lowerBound 返回最小的满足 nums[i] >= target 的下标 i\\n// 如果数组为空,或者所有数都 < target,则返回 numsSize\\n// 要求 nums 是非递减的,即 nums[i] <= nums[i + 1]\\n// 原理见 https://www.bilibili.com/video/BV1AP41137w7/\\nint lowerBound(int* nums, int numsSize, int target) {\\n int left = -1, right = numsSize; // 开区间 (left, right)\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // nums[left] < target\\n // nums[right] >= target\\n int mid = left + (right - left) / 2;\\n if (nums[mid] >= target) {\\n right = mid; // 范围缩小到 (left, mid)\\n } else {\\n left = mid; // 范围缩小到 (mid, right)\\n }\\n }\\n // 循环结束后 left+1 = right\\n // 此时 nums[left] < target 而 nums[right] >= target\\n // 所以 right 就是第一个 >= target 的元素下标\\n return right;\\n}\\n\\nint findSpecialInteger(int* arr, int arrSize) {\\n int m = arrSize / 4;\\n for (int i = m; i <= m * 2 + 1; i += m + 1) {\\n int x = arr[i];\\n // > x 等价于 >= x+1\\n if (lowerBound(arr, arrSize, x + 1) - lowerBound(arr, arrSize, x) > m) {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m * 3 + 2];\\n}\\n
\\n###go
\\nfunc findSpecialInteger(arr []int) int {\\n n := len(arr)\\n m := n / 4\\n for _, i := range []int{m, m*2 + 1} {\\n x := arr[i]\\n // > x 等价于 >= x+1\\n if sort.SearchInts(arr, x+1)-sort.SearchInts(arr, x) > m {\\n return x\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m*3+2]\\n}\\n
\\n###js
\\nvar findSpecialInteger = function(arr) {\\n const n = arr.length;\\n const m = Math.floor(n / 4);\\n for (const i of [m, m * 2 + 1]) {\\n const x = arr[i];\\n if (_.sortedLastIndex(arr, x) - _.sortedIndex(arr, x) > m) {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n return arr[m * 3 + 2];\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn find_special_integer(arr: Vec<i32>) -> i32 {\\n let n = arr.len();\\n let m = n / 4;\\n for i in [m, m * 2 + 1] {\\n let x = arr[i];\\n if arr.partition_point(|&y| y <= x) - arr.partition_point(|&y| y < x) > m {\\n return x;\\n }\\n }\\n // 如果答案不是 arr[m] 也不是 arr[2m+1],那么答案一定是 arr[3m+2]\\n arr[m * 3 + 2]\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"「出现次数超过数组元素总数的 $25%$」等价于出现次数至少为 $\\\\left\\\\lfloor\\\\dfrac{n}{4}\\\\right\\\\rfloor+1$,其中 $n$ 是 $\\\\textit{arr}$ 的长度。 由于 $\\\\textit{arr}$ 是有序的,所有相同的元素必定组成一个连续子数组。利用这个性质,我们无需遍历整个数组,而是检查若干下标 $i$,然后 34. 在排序数组中查找元素的第一个和最后一个位置,从而计算等于 $\\\\textit{arr}[i]$ 的元素个数。\\n\\n例如 $n=4$,我们只需检查下标 $1$ 和 $3$,其中一定有一个…","guid":"https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array//solution/olog-n-er-fen-cha-zhao-zheng-que-xing-zh-5mu9","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-02-08T05:04:46.822Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-不同路径 II🟡","url":"https://leetcode.cn/problems/unique-paths-ii/","content":"给定一个 m x n
的整数数组 grid
。一个机器人初始位于 左上角(即 grid[0][0]
)。机器人尝试移动到 右下角(即 grid[m - 1][n - 1]
)。机器人每次只能向下或者向右移动一步。
网格中的障碍物和空位置分别用 1
和 0
来表示。机器人的移动路径中不能包含 任何 有障碍物的方格。
返回机器人能够到达右下角的不同路径数量。
\\n\\n测试用例保证答案小于等于 2 * 109
。
\\n\\n
示例 1:
\\n输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]\\n输出:2\\n解释:3x3 网格的正中间有一个障碍物。\\n从左上角到右下角一共有 2
条不同的路径:\\n1. 向右 -> 向右 -> 向下 -> 向下\\n2. 向下 -> 向下 -> 向右 -> 向右\\n
\\n\\n示例 2:
\\n输入:obstacleGrid = [[0,1],[0,0]]\\n输出:1\\n\\n\\n
\\n\\n
提示:
\\n\\nm == obstacleGrid.length
n == obstacleGrid[i].length
1 <= m, n <= 100
obstacleGrid[i][j]
为 0
或 1
给你一个正整数 n
,生成一个包含 1
到 n2
所有元素,且元素按顺时针顺序螺旋排列的 n x n
正方形矩阵 matrix
。
\\n\\n
示例 1:
\\n输入:n = 3\\n输出:[[1,2,3],[8,9,4],[7,6,5]]\\n\\n\\n
示例 2:
\\n\\n输入:n = 1\\n输出:[[1]]\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 20
思路
\\n字符串仅由小写字母组成,因此一个字符串含有的字符集合,可以用一个 $26$ 位的二进制数字表示状态。从低位到高位,如果这一位为 $1$,则表示含有对应的小写字母。遍历 $\\\\textit{words}$,并用一个哈希表 $\\\\textit{cnt}$ 记录每个状态出现的次数,对于每个 $\\\\textit{word}$,计算其对应的状态 $\\\\textit{state}$,并将结果增加 $\\\\textit{cnt}[\\\\textit{state}]$,表示当前字符串与之前遍历过的所有同状态的字符串都相似,然后将 $\\\\textit{cnt}[\\\\textit{state}]$ 自增 $1$。最后返回结果。
\\n代码
\\n###Python
\\nclass Solution:\\n def similarPairs(self, words: List[str]) -> int:\\n res = 0\\n cnt = Counter()\\n for word in words:\\n state = 0\\n for c in word:\\n state |= 1 << (ord(c) - ord(\'a\'))\\n res += cnt[state]\\n cnt[state] += 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int similarPairs(String[] words) {\\n int res = 0;\\n Map<Integer, Integer> cnt = new HashMap<Integer, Integer>();\\n for (String word : words) {\\n int state = 0;\\n int length = word.length();\\n for (int i = 0; i < length; i++) {\\n char c = word.charAt(i);\\n state |= 1 << (c - \'a\');\\n }\\n res += cnt.getOrDefault(state, 0);\\n cnt.put(state, cnt.getOrDefault(state, 0) + 1);\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int SimilarPairs(string[] words) {\\n int res = 0;\\n IDictionary<int, int> cnt = new Dictionary<int, int>();\\n foreach (string word in words) {\\n int state = 0;\\n foreach (char c in word) {\\n state |= 1 << (c - \'a\');\\n }\\n cnt.TryAdd(state, 0);\\n res += cnt[state];\\n cnt[state]++;\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int similarPairs(vector<string>& words) {\\n int res = 0;\\n unordered_map<int, int> cnt;\\n for (const string& word : words) {\\n int state = 0;\\n for (char c : word) {\\n state |= 1 << (c - \'a\');\\n }\\n res += cnt[state];\\n cnt[state]++;\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc similarPairs(words []string) int {\\n res := 0\\n cnt := make(map[int]int)\\n for _, word := range words {\\n state := 0\\n for _, c := range word {\\n state |= 1 << (c - \'a\')\\n }\\n res += cnt[state]\\n cnt[state]++\\n }\\n return res\\n}\\n
\\n###C
\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n hashAddItem(obj, key, val);\\n } else {\\n pEntry->val = val;\\n }\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nint similarPairs(char** words, int wordsSize) {\\n int res = 0;\\n HashItem *cnt = NULL;\\n for (int i = 0; i < wordsSize; i++) {\\n int state = 0;\\n for (int j = 0; words[i][j] != \'\\\\0\'; j++) {\\n char c = words[i][j];\\n state |= 1 << (c - \'a\');\\n }\\n int count = hashGetItem(&cnt, state, 0);\\n res += count;\\n hashSetItem(&cnt, state, count + 1);\\n }\\n hashFree(&cnt);\\n return res;\\n}\\n
\\n###JavaScript
\\nvar similarPairs = function(words) {\\n let res = 0;\\n const cnt = new Map();\\n for (const word of words) {\\n let state = 0;\\n for (const c of word) {\\n state |= 1 << (c.charCodeAt(0) - \'a\'.charCodeAt(0));\\n }\\n res += cnt.get(state) || 0;\\n cnt.set(state, (cnt.get(state) || 0) + 1);\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction similarPairs(words: string[]): number {\\n let res = 0;\\n const cnt = new Map<number, number>();\\n for (const word of words) {\\n let state = 0;\\n for (const c of word) {\\n state |= 1 << (c.charCodeAt(0) - \'a\'.charCodeAt(0));\\n }\\n res += cnt.get(state) || 0;\\n cnt.set(state, (cnt.get(state) || 0) + 1);\\n }\\n return res;\\n};\\n
\\n###Rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn similar_pairs(words: Vec<String>) -> i32 {\\n let mut res = 0;\\n let mut cnt = HashMap::new();\\n for word in words {\\n let mut state = 0;\\n for c in word.chars() {\\n state |= 1 << (c as u8 - b\'a\');\\n }\\n res += cnt.get(&state).unwrap_or(&0);\\n *cnt.entry(state).or_insert(0) += 1;\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m\\\\times n)$,其中 $n$ 是数组 $\\\\textit{words}$ 的长度,$m$ 是数组 $\\\\textit{words}$ 中单个字符串的平均长度。
\\n空间复杂度:$O(n)$。
\\n思路与算法
\\n用 $i = 0$ 表示当前位是奇数位,$i = 1$ 表示当前位是偶数位,并记录对应奇偶数位的结果。
\\n当 $n > 0$ 时,如果当前位是 $1$,则增加对应奇偶位的计数,然后反转 $i$ 并将 $n$ 作右移位运算。
\\n循环以上过程,直到 $n = 0$,最后返回结果。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n vector<int> res = {0, 0};\\n int i = 0;\\n while (n) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int[] evenOddBit(int n) {\\n int[] res = new int[2];\\n int i = 0;\\n while (n > 0) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def evenOddBit(self, n: int) -> List[int]:\\n res = [0, 0]\\n i = 0\\n while n:\\n res[i] += n & 1\\n n >>= 1\\n i = i ^ 1\\n return res\\n
\\n###JavaScript
\\nvar evenOddBit = function(n) {\\n const res = [0, 0];\\n let i = 0;\\n while (n > 0) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction evenOddBit(n: number): number[] {\\n const res = [0, 0];\\n let i = 0;\\n while (n > 0) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc evenOddBit(n int) []int {\\n res := []int{0, 0}\\n i := 0\\n for n > 0 {\\n res[i] += n & 1\\n n >>= 1\\n i ^= 1\\n }\\n return res\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] EvenOddBit(int n) {\\n int[] res = new int[2];\\n int i = 0;\\n while (n > 0) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nint* evenOddBit(int n, int* returnSize) {\\n *returnSize = 2;\\n int* res = (int*)malloc(sizeof(int) * 2);\\n res[0] = 0;\\n res[1] = 0;\\n int i = 0;\\n while (n > 0) {\\n res[i] += n & 1;\\n n >>= 1;\\n i ^= 1;\\n }\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn even_odd_bit(n: i32) -> Vec<i32> {\\n let mut res = vec![0, 0];\\n let mut i = 0;\\n let mut n_mut = n;\\n while n_mut > 0 {\\n res[i as usize] += n_mut & 1;\\n n_mut >>= 1;\\n i ^= 1;\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(\\\\log n)$。
\\n空间复杂度:$O(1)$。
\\n给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,1,2]\\n输出:\\n[[1,1,2],\\n [1,2,1],\\n [2,1,1]]\\n\\n\\n
示例 2:
\\n\\n输入:nums = [1,2,3]\\n输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 8
-10 <= nums[i] <= 10
给你一个整数数组 nums
,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,2]\\n输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]\\n\\n\\n
示例 2:
\\n\\n输入:nums = [0]\\n输出:[[],[0]]\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 10
-10 <= nums[i] <= 10
给定一个非负整数数组 nums
, nums
中一半整数是 奇数 ,一半整数是 偶数 。
对数组进行排序,以便当 nums[i]
为奇数时,i
也是 奇数 ;当 nums[i]
为偶数时, i
也是 偶数 。
你可以返回 任何满足上述条件的数组作为答案 。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [4,2,5,7]\\n输出:[4,5,2,7]\\n解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [2,3]\\n输出:[2,3]\\n\\n\\n
\\n\\n
提示:
\\n\\n2 <= nums.length <= 2 * 104
nums.length
是偶数nums
中一半是偶数0 <= nums[i] <= 1000
\\n\\n
进阶:可以不使用额外空间解决问题吗?
\\n","description":"给定一个非负整数数组 nums, nums 中一半整数是 奇数 ,一半整数是 偶数 。 对数组进行排序,以便当 nums[i] 为奇数时,i 也是 奇数 ;当 nums[i] 为偶数时, i 也是 偶数 。\\n\\n你可以返回 任何满足上述条件的数组作为答案 。\\n\\n \\n\\n示例 1:\\n\\n输入:nums = [4,2,5,7]\\n输出:[4,5,2,7]\\n解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。\\n\\n\\n示例 2:\\n\\n输入:nums = [2,3]\\n输出:[2,3]\\n\\n\\n \\n\\n提示:\\n\\n2 <= nums.length…","guid":"https://leetcode.cn/problems/sort-array-by-parity-ii/","author":null,"authorUrl":null,"authorAvatar":null,"publishedAt":"2025-02-03T16:00:00.446Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-验证回文串 II🟢","url":"https://leetcode.cn/problems/valid-palindrome-ii/","content":"给你一个字符串 s
,最多 可以从中删除一个字符。
请你判断 s
是否能成为回文字符串:如果能,返回 true
;否则,返回 false
。
\\n\\n
示例 1:
\\n\\n输入:s = \\"aba\\"\\n输出:true\\n\\n\\n
示例 2:
\\n\\n输入:s = \\"abca\\"\\n输出:true\\n解释:你可以删除字符 \'c\' 。\\n\\n\\n
示例 3:
\\n\\n输入:s = \\"abc\\"\\n输出:false\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s
由小写英文字母组成给你一个 m x n
的矩阵 M
和一个操作数组 op
。矩阵初始化时所有的单元格都为 0
。ops[i] = [ai, bi]
意味着当所有的 0 <= x < ai
和 0 <= y < bi
时, M[x][y]
应该加 1。
在 执行完所有操作后 ,计算并返回 矩阵中最大整数的个数 。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: m = 3, n = 3,ops = [[2,2],[3,3]]\\n输出: 4\\n解释: M 中最大的整数是 2, 而且 M 中有4个值为2的元素。因此返回 4。\\n\\n\\n
示例 2:
\\n\\n输入: m = 3, n = 3, ops = [[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3],[2,2],[3,3],[3,3],[3,3]]\\n输出: 4\\n\\n\\n
示例 3:
\\n\\n输入: m = 3, n = 3, ops = []\\n输出: 9\\n\\n\\n
\\n\\n
提示:
\\n\\n\\n\\n1 <= m, n <= 4 * 104
0 <= ops.length <= 104
ops[i].length == 2
1 <= ai <= m
1 <= bi <= n
已知存在一个按非降序排列的整数数组 nums
,数组中的值不必互不相同。
在传递给函数之前,nums
在预先未知的某个下标 k
(0 <= k < nums.length
)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]]
(下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7]
在下标 5
处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4]
。
给你 旋转后 的数组 nums
和一个整数 target
,请你编写一个函数来判断给定的目标值是否存在于数组中。如果 nums
中存在这个目标值 target
,则返回 true
,否则返回 false
。
你必须尽可能减少整个操作步骤。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [2,5,6,0,0,1,2]
, target = 0\\n输出:true\\n
\\n\\n示例 2:
\\n\\n输入:nums = [2,5,6,0,0,1,2]
, target = 3\\n输出:false
\\n\\n\\n\\n
提示:
\\n\\n1 <= nums.length <= 5000
-104 <= nums[i] <= 104
nums
在预先未知的某个下标上进行了旋转-104 <= target <= 104
\\n\\n
进阶:
\\n\\nnums
可能包含 重复 元素。这会影响到程序的时间复杂度吗?会有怎样的影响,为什么?\\n","description":"已知存在一个按非降序排列的整数数组 nums ,数组中的值不必互不相同。 在传递给函数之前,nums 在预先未知的某个下标 k(0 <= k < nums.length)上进行了 旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,4,4,5,6,6,7] 在下标 5 处经旋转后可能变为 [4,5,6,6,7,0,1,2,4,4] 。\\n\\n给你 旋转后 的数组 nums 和一个整数 targe…","guid":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii/","author":null,"authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-31T16:00:00.848Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-反转字符串 II🟢","url":"https://leetcode.cn/problems/reverse-string-ii/","content":"
给定一个字符串 s
和一个整数 k
,从字符串开头算起,每计数至 2k
个字符,就反转这 2k
字符中的前 k
个字符。
k
个,则将剩余字符全部反转。2k
但大于或等于 k
个,则反转前 k
个字符,其余字符保持原样。\\n\\n
示例 1:
\\n\\n输入:s = \\"abcdefg\\", k = 2\\n输出:\\"bacdfeg\\"\\n\\n\\n
示例 2:
\\n\\n输入:s = \\"abcd\\", k = 2\\n输出:\\"bacd\\"\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 104
s
仅由小写英文组成1 <= k <= 104
核心思路:用一个栈记录去重后的元素,如果当前元素等于栈顶下方那个数(倒数第二个数),那么不能入栈(否则会有三个一样的数),反之可以入栈。
\\n看示例 1,$\\\\textit{nums}=[1,1,1,2,2,3]$。由于数组是有序的,前两个数一定满足要求,直接入栈。现在栈中元素为 $[1,1]$,我们从 $\\\\textit{nums}[2]$ 开始思考:
\\n$i$ | \\n栈(入栈前) | \\n栈顶下方元素 | \\n$\\\\textit{nums}[i]$ | \\n$\\\\textit{nums}[i]$ 是否入栈 | \\n
---|---|---|---|---|
$2$ | \\n$[1,1]$ | \\n$1$ | \\n$1$ | \\n否 | \\n
$3$ | \\n$[1,1]$ | \\n$1$ | \\n$2$ | \\n是 | \\n
$4$ | \\n$[1,1,2]$ | \\n$1$ | \\n$2$ | \\n是 | \\n
$5$ | \\n$[1,1,2,2]$ | \\n$2$ | \\n$3$ | \\n是 | \\n
最终栈中元素为 $[1,1,2,2,3]$。
\\n为了做到 $\\\\mathcal{O}(1)$ 空间复杂度,直接把 $\\\\textit{nums}$ 当作栈,用一个变量 $\\\\textit{stackSize}$ 表示栈的大小,初始值为 $2$。
\\n那么 $\\\\textit{nums}[\\\\textit{stackSize}-2]$ 就是栈顶下方那个数。
\\n入栈就是把 $\\\\textit{nums}[\\\\textit{stackSize}]$ 置为 $\\\\textit{nums}[i]$,同时把 $\\\\textit{stackSize}$ 加一。
\\n最后返回栈的大小。由于本题数组长度 $n$ 最小是 $1$,最终返回值为 $\\\\min(\\\\textit{stackSize},n)$,这样可以兼容 $n=1$ 的情况。
\\n###py
\\nclass Solution:\\n def removeDuplicates(self, nums: List[int]) -> int:\\n stack_size = 2 # 栈的大小,前两个元素默认保留\\n for i in range(2, len(nums)):\\n if nums[i] != nums[stack_size - 2]: # 和栈顶下方的元素比较\\n nums[stack_size] = nums[i] # 入栈\\n stack_size += 1\\n return min(stack_size, len(nums))\\n
\\n###java
\\nclass Solution {\\n public int removeDuplicates(int[] nums) {\\n int stackSize = 2; // 栈的大小,前两个元素默认保留\\n for (int i = 2; i < nums.length; i++) {\\n if (nums[i] != nums[stackSize - 2]) { // 和栈顶下方的元素比较\\n nums[stackSize++] = nums[i]; // 入栈\\n }\\n }\\n return Math.min(stackSize, nums.length);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int removeDuplicates(vector<int>& nums) {\\n int n = nums.size();\\n int stack_size = 2; // 栈的大小,前两个元素默认保留\\n for (int i = 2; i < n; i++) {\\n if (nums[i] != nums[stack_size - 2]) { // 和栈顶下方的元素比较\\n nums[stack_size++] = nums[i]; // 入栈\\n }\\n }\\n return min(stack_size, n);\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint removeDuplicates(int* nums, int numsSize) {\\n int stackSize = 2; // 栈的大小,前两个元素默认保留\\n for (int i = 2; i < numsSize; i++) {\\n if (nums[i] != nums[stackSize - 2]) { // 和栈顶下方的元素比较\\n nums[stackSize++] = nums[i]; // 入栈\\n }\\n }\\n return MIN(stackSize, numsSize);\\n}\\n
\\n###go
\\nfunc removeDuplicates(nums []int) int {\\n stackSize := 2 // 栈的大小,前两个元素默认保留\\n for i := 2; i < len(nums); i++ {\\n if nums[i] != nums[stackSize-2] { // 和栈顶下方的元素比较\\n nums[stackSize] = nums[i] // 入栈\\n stackSize++\\n }\\n }\\n return min(stackSize, len(nums))\\n}\\n
\\n###js
\\nvar removeDuplicates = function(nums) {\\n let stackSize = 2; // 栈的大小,前两个元素默认保留\\n for (let i = 2; i < nums.length; i++) {\\n if (nums[i] !== nums[stackSize - 2]) { // 和栈顶下方的元素比较\\n nums[stackSize++] = nums[i]; // 入栈\\n }\\n }\\n return Math.min(stackSize, nums.length);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn remove_duplicates(nums: &mut Vec<i32>) -> i32 {\\n let mut stack_size = 2; // 栈的大小,前两个元素默认保留\\n for i in 2..nums.len() {\\n if nums[i] != nums[stack_size - 2] { // 和栈顶下方的元素比较\\n nums[stack_size] = nums[i]; // 入栈\\n stack_size += 1;\\n }\\n }\\n stack_size.min(nums.len()) as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:用一个栈记录去重后的元素,如果当前元素等于栈顶下方那个数(倒数第二个数),那么不能入栈(否则会有三个一样的数),反之可以入栈。 看示例 1,$\\\\textit{nums}=[1,1,1,2,2,3]$。由于数组是有序的,前两个数一定满足要求,直接入栈。现在栈中元素为 $[1,1]$,我们从 $\\\\textit{nums}[2]$ 开始思考:\\n\\n$i$\\t 栈(入栈前)\\t 栈顶下方元素\\t $\\\\textit{nums}[i]$\\t $\\\\textit{nums}[i]$ 是否入栈 $2$\\t $[1,1]$\\t $1$\\t $1$\\t 否 \\n $3$\\t $[1,1]$\\t $…","guid":"https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii//solution/yong-zhan-si-kao-yuan-di-shi-xian-python-zw8l","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-30T08:59:15.567Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"教你一步步思考 DP:从记忆化搜索到递推到空间优化!(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/unique-paths-ii//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-451i","content":"想一想,最后一步发生了什么?
\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n根据上面的讨论,定义状态为 $\\\\textit{dfs}(i,j)$,表示从起点 $(0,0)$ 走到 $(i,j)$ 的方案数。
\\n讨论我们是如何到达 $(i,j)$ 的:
\\n这两种情况互斥,根据加法原理,有
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\textit{dfs}(i-1,j) + \\\\textit{dfs}(i,j-1)
\\n$$
递归边界:
\\n递归入口:$\\\\textit{dfs}(m-1,n-1)$,这是原问题,也是答案。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\nclass Solution:\\n def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(一行代码实现记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i < 0 or j < 0 or obstacleGrid[i][j]:\\n return 0\\n if i == 0 and j == 0:\\n return 1\\n return dfs(i - 1, j) + dfs(i, j - 1)\\n\\n m, n = len(obstacleGrid), len(obstacleGrid[0])\\n return dfs(m - 1, n - 1)\\n
\\n###java
\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n int m = obstacleGrid.length;\\n int n = obstacleGrid[0].length;\\n int[][] memo = new int[m][n];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(m - 1, n - 1, obstacleGrid, memo);\\n }\\n\\n private int dfs(int i, int j, int[][] obstacleGrid, int[][] memo) {\\n if (i < 0 || j < 0 || obstacleGrid[i][j] == 1) {\\n return 0;\\n }\\n if (i == 0 && j == 0) {\\n return 1;\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j];\\n }\\n return memo[i][j] = dfs(i - 1, j, obstacleGrid, memo) + dfs(i, j - 1, obstacleGrid, memo);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {\\n int m = obstacleGrid.size(), n = obstacleGrid[0].size();\\n vector memo(m, vector<int>(n, -1)); // -1 表示没有计算过\\n auto dfs = [&](this auto&& dfs, int i, int j) -> int {\\n if (i < 0 || j < 0 || obstacleGrid[i][j]) {\\n return 0;\\n }\\n if (i == 0 && j == 0) {\\n return 1;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n return res = dfs(i - 1, j) + dfs(i, j - 1);\\n };\\n return dfs(m - 1, n - 1);\\n }\\n};\\n
\\n###go
\\nfunc uniquePathsWithObstacles(obstacleGrid [][]int) int {\\n m, n := len(obstacleGrid), len(obstacleGrid[0])\\n memo := make([][]int, m)\\n for i := range memo {\\n memo[i] = make([]int, n)\\n for j := range memo[i] {\\n memo[i][j] = -1 // -1 表示没有计算过\\n }\\n }\\n var dfs func(int, int) int\\n dfs = func(i, j int) int {\\n if i < 0 || j < 0 || obstacleGrid[i][j] == 1 {\\n return 0\\n }\\n if i == 0 && j == 0 {\\n return 1\\n }\\n p := &memo[i][j]\\n if *p == -1 { // 没有计算过\\n *p = dfs(i-1, j) + dfs(i, j-1)\\n }\\n return *p\\n }\\n return dfs(m-1, n-1)\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i+1][j+1]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示从起点 $(0,0)$ 走到 $(i,j)$ 的方案数。这里 $+1$ 是为了把 $\\\\textit{dfs}(-1,j)$ 和 $\\\\textit{dfs}(i,-1)$ 这些状态也翻译过来,这样我们可以把 $f[0][j]$ 和 $f[i][0]$ 作为初始值。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i+1][j+1] =
\\n\\\\begin{cases}
\\nf[i][j+1] + f[i+1][j], & \\\\textit{obstacleGrid}[i][j]=0 \\\\
\\n0, & \\\\textit{obstacleGrid}[i][j]=1 \\\\
\\n\\\\end{cases}
\\n$$
初始值:
\\n也可以把 $f[0][1]$ 初始化成 $1$,这样我们无需单独计算 $f[1][1]$。
\\n答案为 $f[m][n]$,翻译自递归入口 $\\\\textit{dfs}(m-1,n-1)$。
\\n###py
\\nclass Solution:\\n def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:\\n m, n = len(obstacleGrid), len(obstacleGrid[0])\\n f = [[0] * (n + 1) for _ in range(m + 1)]\\n f[0][1] = 1\\n for i, row in enumerate(obstacleGrid):\\n for j, x in enumerate(row):\\n if x == 0:\\n f[i + 1][j + 1] = f[i][j + 1] + f[i + 1][j]\\n return f[m][n]\\n
\\n###java
\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n int m = obstacleGrid.length;\\n int n = obstacleGrid[0].length;\\n int[][] f = new int[m + 1][n + 1];\\n f[0][1] = 1;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n if (obstacleGrid[i][j] == 0) {\\n f[i + 1][j + 1] = f[i][j + 1] + f[i + 1][j];\\n }\\n }\\n }\\n return f[m][n];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {\\n int m = obstacleGrid.size(), n = obstacleGrid[0].size();\\n vector f(m + 1, vector<int>(n + 1));\\n f[0][1] = 1;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n if (obstacleGrid[i][j] == 0) {\\n f[i + 1][j + 1] = f[i][j + 1] + f[i + 1][j];\\n }\\n }\\n }\\n return f[m][n];\\n }\\n};\\n
\\n###go
\\nfunc uniquePathsWithObstacles(obstacleGrid [][]int) int {\\n m, n := len(obstacleGrid), len(obstacleGrid[0])\\n f := make([][]int, m+1)\\n for i := range f {\\n f[i] = make([]int, n+1)\\n }\\n f[0][1] = 1\\n for i, row := range obstacleGrid {\\n for j, x := range row {\\n if x == 0 {\\n f[i+1][j+1] = f[i][j+1] + f[i+1][j]\\n }\\n }\\n }\\n return f[m][n]\\n}\\n
\\n举个例子,在计算 $f[1][1]$ 时,会用到 $f[0][1]$,但是之后就不再用到了。那么干脆把 $f[1][1]$ 记到 $f[0][1]$ 中,这样对于 $f[1][2]$ 来说,它需要的数据就在 $f[0][1]$ 和 $f[0][2]$ 中。$f[1][2]$ 算完后也可以同样记到 $f[0][2]$ 中。
\\n所以只需要一个长为 $n+1$ 的一维数组就够了。
\\n具体可以看【基础算法精讲 18】中的讲解。本题的转移方程类似完全背包,故采用正序遍历。
\\n###py
\\nclass Solution:\\n def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:\\n n = len(obstacleGrid[0])\\n f = [0] * (n + 1)\\n f[1] = 1\\n for row in obstacleGrid:\\n for j, x in enumerate(row):\\n if x == 0:\\n f[j + 1] += f[j]\\n else:\\n f[j + 1] = 0\\n return f[n]\\n
\\n###java
\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n int n = obstacleGrid[0].length;\\n int[] f = new int[n + 1];\\n f[1] = 1;\\n for (int[] row : obstacleGrid) {\\n for (int j = 0; j < n; j++) {\\n if (row[j] == 0) {\\n f[j + 1] += f[j];\\n } else {\\n f[j + 1] = 0;\\n }\\n }\\n }\\n return f[n];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {\\n int n = obstacleGrid[0].size();\\n vector<int> f(n + 1);\\n f[1] = 1;\\n for (auto& row : obstacleGrid) {\\n for (int j = 0; j < n; j++) {\\n if (row[j] == 0) {\\n f[j + 1] += f[j];\\n } else {\\n f[j + 1] = 0;\\n }\\n }\\n }\\n return f[n];\\n }\\n};\\n
\\n###go
\\nfunc uniquePathsWithObstacles(obstacleGrid [][]int) int {\\n n := len(obstacleGrid[0])\\n f := make([]int, n+1)\\n f[1] = 1\\n for _, row := range obstacleGrid {\\n for j, x := range row {\\n if x == 0 {\\n f[j+1] += f[j]\\n } else {\\n f[j+1] = 0\\n }\\n }\\n }\\n return f[n]\\n}\\n
\\n直接用 $\\\\textit{obstacleGrid}[0]$ 当作 $f$ 数组,可以做到 $\\\\mathcal{O}(1)$ 额外空间。
\\n由于 $\\\\textit{obstacleGrid}[0]$ 的长度只有 $n$,所以要按照
\\n$$
\\nf[i][j] = f[i-1][j] + f[i][j-1]
\\n$$
的方式来转移。
\\n$i=0$ 和 $j=0$ 的情况要单独计算:
\\n\\n\\n注:对比上下两份代码,你会发现长为 $n+1$ 的数组写起来是更加简洁的,因为可以避免特判位于边界的情况。
\\n
###py
\\nclass Solution:\\n def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:\\n m, n = len(obstacleGrid), len(obstacleGrid[0])\\n f = obstacleGrid[0]\\n f[0] ^= 1 # 0 变成 1,1 变成 0\\n for j in range(1, n):\\n f[j] = 0 if f[j] else f[j - 1]\\n for i in range(1, m):\\n if obstacleGrid[i][0]:\\n f[0] = 0\\n for j in range(1, n):\\n if obstacleGrid[i][j] == 0:\\n f[j] += f[j - 1]\\n else:\\n f[j] = 0\\n return f[-1]\\n
\\n###java
\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n int m = obstacleGrid.length;\\n int n = obstacleGrid[0].length;\\n int[] f = obstacleGrid[0];\\n f[0] ^= 1; // 0 变成 1,1 变成 0\\n for (int j = 1; j < n; j++) {\\n f[j] = f[j] == 1 ? 0 : f[j - 1];\\n }\\n for (int i = 1; i < m; i++) {\\n if (obstacleGrid[i][0] == 1) {\\n f[0] = 0;\\n }\\n for (int j = 1; j < n; j++) {\\n if (obstacleGrid[i][j] == 0) {\\n f[j] += f[j - 1];\\n } else {\\n f[j] = 0;\\n }\\n }\\n }\\n return f[n - 1];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {\\n int m = obstacleGrid.size(), n = obstacleGrid[0].size();\\n auto& f = obstacleGrid[0];\\n f[0] ^= 1; // 0 变成 1,1 变成 0\\n for (int j = 1; j < n; j++) {\\n f[j] = f[j] ? 0 : f[j - 1];\\n }\\n for (int i = 1; i < m; i++) {\\n if (obstacleGrid[i][0]) {\\n f[0] = 0;\\n }\\n for (int j = 1; j < n; j++) {\\n if (obstacleGrid[i][j] == 0) {\\n f[j] += f[j - 1];\\n } else {\\n f[j] = 0;\\n }\\n }\\n }\\n return f[n - 1];\\n }\\n};\\n
\\n###go
\\nfunc uniquePathsWithObstacles(obstacleGrid [][]int) int {\\n m, n := len(obstacleGrid), len(obstacleGrid[0])\\n f := obstacleGrid[0]\\n f[0] ^= 1 // 0 变成 1,1 变成 0\\n for j := 1; j < n; j++ {\\n if f[j] == 0 {\\n f[j] = f[j-1]\\n } else {\\n f[j] = 0\\n }\\n }\\n for i := 1; i < m; i++ {\\n if obstacleGrid[i][0] == 1 {\\n f[0] = 0\\n }\\n for j := 1; j < n; j++ {\\n if obstacleGrid[i][j] == 0 {\\n f[j] += f[j-1]\\n } else {\\n f[j] = 0\\n }\\n }\\n }\\n return f[n-1]\\n}\\n
\\n网格图的长和宽增大到 $10^5$,改成输入 $2000$ 个障碍物的坐标,要怎么做?
\\n这题是 Codeforces 559C. Gerald and Giant Chess。
\\n更多相似题目,见 动态规划题单 中的「二、网格图 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 想一想,最后一步发生了什么?\\n\\n如果从 $(1,2)$ 向下走到终点 $(2,2)$,那么要解决的问题是从起点 $(0,0)$ 走到 $(1,2)$ 的方案数。\\n如果从 $(2,1)$ 向右走到终点 $(2,2)$,那么要解决的问题是从起点 $(0,0)$ 走到 $(2,1)$ 的方案数。\\n\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。\\n\\n二、状态定义与状态转移方程\\n\\n根据上面的讨论,定义状态为 $\\\\textit{dfs}(i,j)$,表示从起点 $(0,0)$ 走到 $(i,j)$ 的方案数。\\n\\n讨论我们是如何到达 $(i…","guid":"https://leetcode.cn/problems/unique-paths-ii//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-451i","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-30T02:26:53.741Z","media":[{"url":"https://pic.leetcode.cn/1738201718-nNFgpn-lc63.jpg","type":"photo","width":242,"height":242,"blurhash":"LMPs#8PBY4%MPB9XI:R+cXJ74TRj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-两个数组的交集 II🟢","url":"https://leetcode.cn/problems/intersection-of-two-arrays-ii/","content":"给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
\\n\\n
示例 1:
\\n\\n输入:nums1 = [1,2,2,1], nums2 = [2,2]\\n输出:[2,2]\\n\\n\\n
示例 2:
\\n\\n输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]\\n输出:[4,9]\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums1.length, nums2.length <= 1000
0 <= nums1[i], nums2[i] <= 1000
\\n\\n
进阶:
\\n\\nnums1
的大小比 nums2
小,哪种方法更优?nums2
的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?前置题目:46. 全排列,视频讲解【基础算法精讲 16】。
\\n以示例 1 $\\\\textit{nums}=[1,1,2]$ 为例。如果按照 46 题的做法,在递归的过程中,会发生如下两种情况:
\\n这两种填法,最终都会得到排列 $[1,1,2]$,重复了。
\\n我们要保证第二种情况不会发生:
\\n推广到更多数的时候,这个规则仍然适用吗?
\\n再来看一个例子,$\\\\textit{nums}=[1,1,1,2]$,它有四个排列:
\\n第一个位置要么填 $1$,要么填 $2$。
\\n其中第一个位置填 $1$ 的三个排列,会在第一个位置填 $\\\\textit{nums}[0]$ 的时候枚举到。如果第一个位置填 $\\\\textit{nums}[1]$ 或者 $\\\\textit{nums}[2]$,那么必然会产生重复的排列。
\\n所以 $\\\\textit{nums}[1]$ 和 $\\\\textit{nums}[2]$ 绝对不能填到第一个位置上!
\\n这意味着,如果有多个 $\\\\textit{nums}[i]$ 都相同,那么我们只需枚举其中一个 $\\\\textit{nums}[i]$ 填第一个位置的情况,其余所有等于 $\\\\textit{nums}[i]$ 的数都不能填第一个位置。
\\n如果 $\\\\textit{nums}[0]$ 填在了第一个位置,那么问题变成:
\\n这是一个和原问题相似的,规模更小的子问题,处理方式同上:$\\\\textit{nums}[1]$ 可以填在排列的第二个位置,而 $\\\\textit{nums}[2]$ 不能填在排列的第二个位置,否则会导致重复的排列(就像本文开头分析的那样)。怎么判断?如果我们还没有填入 $\\\\textit{nums}[1]$,那么和 $\\\\textit{nums}[1]$ 相等的 $\\\\textit{nums}[2]$ 是不能填入的。
\\n为方便判断,先把 $\\\\textit{nums}$ 排序(从小到大或者从大到小都可以)。
\\n分类讨论:
\\n###py
\\nclass Solution:\\n def permuteUnique(self, nums: List[int]) -> List[List[int]]:\\n nums.sort()\\n n = len(nums)\\n ans = []\\n path = [0] * n # 所有排列的长度都是 n\\n on_path = [False] * n # on_path[j] 表示 nums[j] 是否已经填入排列\\n\\n # i 表示当前要填排列的第几个数\\n def dfs(i: int) -> None:\\n if i == n: # 填完了\\n ans.append(path.copy()) # 也可以写 path[:]\\n return\\n # 枚举 nums[j] 填入 path[i]\\n for j, on in enumerate(on_path):\\n # 如果 nums[j] 已填入排列,continue\\n # 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if on or j > 0 and nums[j] == nums[j - 1] and not on_path[j - 1]:\\n continue\\n path[i] = nums[j] # 填入排列\\n on_path[j] = True # nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1) # 填排列的下一个数\\n on_path[j] = False # 恢复现场\\n # 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n\\n dfs(0)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public List<List<Integer>> permuteUnique(int[] nums) {\\n Arrays.sort(nums);\\n int n = nums.length;\\n List<List<Integer>> ans = new ArrayList<>();\\n List<Integer> path = Arrays.asList(new Integer[nums.length]); // 所有排列的长度都是 n\\n boolean[] onPath = new boolean[n]; // onPath[j] 表示 nums[j] 是否已经填入排列\\n dfs(0, nums, path, onPath, ans);\\n return ans;\\n }\\n\\n // i 表示当前要填排列的第几个数\\n private void dfs(int i, int[] nums, List<Integer> path, boolean[] onPath, List<List<Integer>> ans) {\\n if (i == nums.length) { // 填完了\\n ans.add(new ArrayList<>(path));\\n return;\\n }\\n // 枚举 nums[j] 填入 path[i]\\n for (int j = 0; j < nums.length; j++) {\\n // 如果 nums[j] 已填入排列,continue\\n // 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if (onPath[j] || j > 0 && nums[j] == nums[j - 1] && !onPath[j - 1]) {\\n continue;\\n }\\n path.set(i, nums[j]); // 填入排列\\n onPath[j] = true; // nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1, nums, path, onPath, ans); // 填排列的下一个数\\n onPath[j] = false; // 恢复现场\\n // 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<vector<int>> permuteUnique(vector<int>& nums) {\\n ranges::sort(nums);\\n int n = nums.size();\\n vector<vector<int>> ans;\\n vector<int> path(n); // 所有排列的长度都是 n\\n vector<int> on_path(n); // on_path[j] 表示 nums[j] 是否已经填入排列\\n // i 表示当前要填排列的第几个数\\n auto dfs = [&](this auto&& dfs, int i) -> void {\\n if (i == n) { // 填完了\\n ans.push_back(path);\\n return;\\n }\\n // 枚举 nums[j] 填入 path[i]\\n for (int j = 0; j < n; j++) {\\n // 如果 nums[j] 已填入排列,continue\\n // 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if (on_path[j] || j > 0 && nums[j] == nums[j - 1] && !on_path[j - 1]) {\\n continue;\\n }\\n path[i] = nums[j]; // 填入排列\\n on_path[j] = true; // nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1); // 填排列的下一个数\\n on_path[j] = false; // 恢复现场\\n // 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n }\\n };\\n dfs(0);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc permuteUnique(nums []int) (ans [][]int) {\\n slices.Sort(nums)\\n n := len(nums)\\n path := make([]int, n) // 所有排列的长度都是 n\\n onPath := make([]bool, n) // onPath[j] 表示 nums[j] 是否已经填入排列\\n var dfs func(int)\\n dfs = func(i int) { // i 表示当前要填排列的第几个数\\n if i == n { // 填完了\\n ans = append(ans, slices.Clone(path))\\n return\\n }\\n // 枚举 nums[j] 填入 path[i]\\n for j, on := range onPath {\\n // 如果 nums[j] 已填入排列,continue\\n // 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if on || j > 0 && nums[j] == nums[j-1] && !onPath[j-1] {\\n continue\\n }\\n path[i] = nums[j] // 填入排列\\n onPath[j] = true // nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1) // 填排列的下一个数\\n onPath[j] = false // 恢复现场\\n // 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n }\\n }\\n dfs(0)\\n return ans\\n}\\n
\\n###js
\\nvar permuteUnique = function(nums) {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n const ans = [];\\n const path = Array(n); // 所有排列的长度都是 n\\n const onPath = Array(n).fill(false); // onPath[j] 表示 nums[j] 是否已经填入排列\\n function dfs(i) { // i 表示当前要填排列的第几个数\\n if (i === n) { // 填完了\\n ans.push([...path]);\\n return;\\n }\\n // 枚举 nums[j] 填入 path[i]\\n for (let j = 0; j < n; j++) {\\n // 如果 nums[j] 已填入排列,continue\\n // 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if (onPath[j] || j > 0 && nums[j] === nums[j - 1] && !onPath[j - 1]) {\\n continue;\\n }\\n path[i] = nums[j]; // 填入排列\\n onPath[j] = true; // nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1); // 填排列的下一个数\\n onPath[j] = false; // 恢复现场\\n // 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n }\\n }\\n dfs(0);\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn permute_unique(mut nums: Vec<i32>) -> Vec<Vec<i32>> {\\n nums.sort_unstable();\\n // i 表示当前要填排列的第几个数\\n fn dfs(i: usize, nums: &[i32], path: &mut Vec<i32>, on_path: &mut Vec<bool>, ans: &mut Vec<Vec<i32>>) {\\n if i == nums.len() { // 填完了\\n ans.push(path.clone());\\n return;\\n }\\n // 枚举 nums[j] 填入 path[i]\\n for j in 0..nums.len() {\\n // 如果 nums[j] 已填入排列,continue\\n // 如果 nums[j] 和前一个数 nums[j-1] 相等,且 nums[j-1] 没填入排列,continue\\n if on_path[j] || j > 0 && nums[j] == nums[j - 1] && !on_path[j - 1] {\\n continue;\\n }\\n path[i] = nums[j]; // 填入排列\\n on_path[j] = true; // nums[j] 已填入排列(注意标记的是下标,不是值)\\n dfs(i + 1, nums, path, on_path, ans); // 填排列的下一个数\\n on_path[j] = false; // 恢复现场\\n // 注意 path 无需恢复现场,因为排列长度固定,直接覆盖就行\\n }\\n }\\n let n = nums.len();\\n let mut ans = vec![];\\n let mut path = vec![0; n]; // 所有排列的长度都是 n\\n let mut on_path = vec![false; n]; // on_path[j] 表示 nums[j] 是否已经填入排列\\n dfs(0, &nums, &mut path, &mut on_path, &mut ans);\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面回溯题单中的「§4.7 有重复元素的回溯」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:46. 全排列,视频讲解【基础算法精讲 16】。 重复来自哪?\\n\\n以示例 1 $\\\\textit{nums}=[1,1,2]$ 为例。如果按照 46 题的做法,在递归的过程中,会发生如下两种情况:\\n\\n第一个位置填 $\\\\textit{nums}[0]$,第二个位置填 $\\\\textit{nums}[1]$。\\n第一个位置填 $\\\\textit{nums}[1]$,第二个位置填 $\\\\textit{nums}[0]$。\\n\\n这两种填法,最终都会得到排列 $[1,1,2]$,重复了。\\n\\n如何避免重复?\\n\\n我们要保证第二种情况不会发生:\\n\\n如果在填 $\\\\textit…","guid":"https://leetcode.cn/problems/permutations-ii//solution/ru-he-qu-zhong-pythonjavacgojsrust-by-en-zlwl","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-29T13:36:49.469Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/spiral-matrix-ii//solution/jian-ji-xie-fa-pythonjavaccgojsrust-by-e-fhmr","content":"根据题意,我们从左上角 $(0,0)$ 出发,按照「右下左上」的顺序前进:
\\nDIRS = (0, 1), (1, 0), (0, -1), (-1, 0) # 右下左上\\n\\nclass Solution:\\n def generateMatrix(self, n: int) -> List[List[int]]:\\n ans = [[0] * n for _ in range(n)]\\n i = j = di = 0\\n for val in range(1, n * n + 1): # 要填入的数\\n ans[i][j] = val\\n x, y = i + DIRS[di][0], j + DIRS[di][1] # 下一步的位置\\n # 如果 (x, y) 出界或者已经填入数字\\n if x < 0 or x >= n or y < 0 or y >= n or ans[x][y]:\\n di = (di + 1) % 4 # 右转 90°\\n i += DIRS[di][0]\\n j += DIRS[di][1] # 走一步\\n return ans\\n
\\nclass Solution {\\n private static final int[][] DIRS = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\n\\n public int[][] generateMatrix(int n) {\\n int[][] ans = new int[n][n];\\n int i = 0;\\n int j = 0;\\n int di = 0;\\n for (int val = 1; val <= n * n; val++) { // 要填入的数\\n ans[i][j] = val;\\n int x = i + DIRS[di][0];\\n int y = j + DIRS[di][1]; // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if (x < 0 || x >= n || y < 0 || y >= n || ans[x][y] != 0) {\\n di = (di + 1) % 4; // 右转 90°\\n }\\n i += DIRS[di][0];\\n j += DIRS[di][1]; // 走一步\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n static constexpr int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\npublic:\\n vector<vector<int>> generateMatrix(int n) {\\n vector ans(n, vector<int>(n));\\n int i = 0, j = 0, di = 0;\\n for (int val = 1; val <= n * n; val++) { // 要填入的数\\n ans[i][j] = val;\\n int x = i + DIRS[di][0];\\n int y = j + DIRS[di][1]; // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if (x < 0 || x >= n || y < 0 || y >= n || ans[x][y]) {\\n di = (di + 1) % 4; // 右转 90°\\n }\\n i += DIRS[di][0];\\n j += DIRS[di][1]; // 走一步\\n }\\n return ans;\\n }\\n};\\n
\\nconst int DIRS[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\n\\nint** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {\\n *returnSize = n;\\n *returnColumnSizes = malloc(n * sizeof(int));\\n int** ans = malloc(n * sizeof(int*));\\n for (int i = 0; i < n; i++) {\\n (*returnColumnSizes)[i] = n;\\n ans[i] = calloc(n, sizeof(int));\\n }\\n\\n int i = 0, j = 0, di = 0;\\n for (int val = 1; val <= n * n; val++) { // 要填入的数\\n ans[i][j] = val;\\n int x = i + DIRS[di][0];\\n int y = j + DIRS[di][1]; // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if (x < 0 || x >= n || y < 0 || y >= n || ans[x][y]) {\\n di = (di + 1) % 4; // 右转 90°\\n }\\n i += DIRS[di][0];\\n j += DIRS[di][1]; // 走一步\\n }\\n return ans;\\n}\\n
\\nvar dirs = [4][2]int{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 右下左上\\n\\nfunc generateMatrix(n int) [][]int {\\n ans := make([][]int, n)\\n for i := range ans {\\n ans[i] = make([]int, n)\\n }\\n i, j, di := 0, 0, 0\\n for val := 1; val <= n*n; val++ {\\n ans[i][j] = val\\n x, y := i+dirs[di][0], j+dirs[di][1] // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if x < 0 || x >= n || y < 0 || y >= n || ans[x][y] != 0 {\\n di = (di + 1) % 4 // 右转 90°\\n }\\n i += dirs[di][0]\\n j += dirs[di][1] // 走一步\\n }\\n return ans\\n}\\n
\\nconst DIRS = [[0, 1], [1, 0], [0, -1], [-1, 0]]; // 右下左上\\n\\nvar generateMatrix = function(n) {\\n const ans = Array.from({ length: n }, () => Array(n).fill(0));\\n let i = 0, j = 0, di = 0;\\n for (let val = 1; val <= n * n; val++) { // 要填入的数\\n ans[i][j] = val;\\n const x = i + DIRS[di][0];\\n const y = j + DIRS[di][1]; // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if (x < 0 || x >= n || y < 0 || y >= n || ans[x][y] !== 0) {\\n di = (di + 1) % 4; // 右转 90°\\n }\\n i += DIRS[di][0];\\n j += DIRS[di][1]; // 走一步\\n }\\n return ans;\\n};\\n
\\nconst DIRS: [(i32, i32); 4] = [(0, 1), (1, 0), (0, -1), (-1, 0)]; // 右下左上\\n\\nimpl Solution {\\n pub fn generate_matrix(n: i32) -> Vec<Vec<i32>> {\\n let mut ans = vec![vec![0; n as usize]; n as usize];\\n let mut i = 0;\\n let mut j = 0;\\n let mut di = 0;\\n for val in 1..=n * n {\\n ans[i as usize][j as usize] = val;\\n let x = i + DIRS[di].0;\\n let y = j + DIRS[di].1; // 下一步的位置\\n // 如果 (x, y) 出界或者已经填入数字\\n if x < 0 || x >= n || y < 0 || y >= n || ans[x as usize][y as usize] != 0 {\\n di = (di + 1) % 4; // 右转 90°\\n }\\n i += DIRS[di].0;\\n j += DIRS[di].1; // 走一步\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 根据题意,我们从左上角 $(0,0)$ 出发,按照「右下左上」的顺序前进:\\n\\n首先向右走,如果到达矩阵边界,则向右转 $90^\\\\circ$,前进方向变为向下。\\n然后向下走,如果到达矩阵边界,则向右转 $90^\\\\circ$,前进方向变为向左。\\n然后向左走,如果到达矩阵边界,则向右转 $90^\\\\circ$,前进方向变为向上。\\n然后向上走,先从 $7$ 走到 $8$,然后从 $8$ 准备向上走,但上面的 $1$ 是一个已经填过数字的位置,那么向右转 $90^\\\\circ$,前进方向变为向右。\\n重复上述过程,直到填入数字 $n^2$ 为止。\\n实现细节…","guid":"https://leetcode.cn/problems/spiral-matrix-ii//solution/jian-ji-xie-fa-pythonjavaccgojsrust-by-e-fhmr","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-29T12:14:27.047Z","media":[{"url":"https://pic.leetcode.cn/1738151955-oVYghO-lc59.jpg","type":"photo","width":242,"height":242,"blurhash":"L8Q9_@~qWB~q~qt7ofofRjt7xuRj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-存在重复元素 II🟢","url":"https://leetcode.cn/problems/contains-duplicate-ii/","content":"给你一个整数数组 nums
和一个整数 k
,判断数组中是否存在两个 不同的索引 i
和 j
,满足 nums[i] == nums[j]
且 abs(i - j) <= k
。如果存在,返回 true
;否则,返回 false
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3,1], k = 3\\n输出:true\\n\\n
示例 2:
\\n\\n输入:nums = [1,0,1,1], k = 1\\n输出:true\\n\\n
示例 3:
\\n\\n输入:nums = [1,2,3,1,2,3], k = 2\\n输出:false\\n\\n
\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 105
-109 <= nums[i] <= 109
0 <= k <= 105
给定一个非负索引 rowIndex
,返回「杨辉三角」的第 rowIndex
行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: rowIndex = 3\\n输出: [1,3,3,1]\\n\\n\\n
示例 2:
\\n\\n输入: rowIndex = 0\\n输出: [1]\\n\\n\\n
示例 3:
\\n\\n输入: rowIndex = 1\\n输出: [1,1]\\n\\n\\n
\\n\\n
提示:
\\n\\n0 <= rowIndex <= 33
\\n\\n
进阶:
\\n\\n你可以优化你的算法到 O(rowIndex)
空间复杂度吗?
前置题目:905. 按奇偶排序数组。
\\n本题可以把所有偶数下标当作是「数组靠前的位置」,奇数下标当作「数组靠后的位置」。
\\n或者说,把所有偶数下标记作集合 $A$,所有奇数下标记作集合 $B$,我们要把 $A$ 中的奇数和 $B$ 中的偶数交换。
\\n找到偶数下标中最左边的奇数 $\\\\textit{nums}[i]$ 和奇数下标中最左边的偶数 $\\\\textit{nums}[j]$,交换这两个数。
\\n\\n\\n注:由于偶数下标和奇数下标互相独立,从左边开始找还是从右边开始找,都可以。
\\n
交换后,下标在 $0,2,4,\\\\ldots,i$ 中的数都是偶数,下标在 $1,3,5,\\\\ldots,j$ 中的数都是奇数。于是问题变成下标 $i+2,i+4,\\\\cdots$ 与下标 $j+2,j+4,\\\\cdots$ 中的数如何交换,这是个规模更小的子问题,处理方式同上。
\\n重复该过程,直到 $i\\\\ge n$ 为止,此时没有元素需要交换,算法结束。
\\n\\n\\n注:题目保证数组中恰有一半是偶数,恰有一半是奇数。
\\n
###py
\\nclass Solution:\\n def sortArrayByParityII(self, nums: List[int]) -> List[int]:\\n i, j = 0, 1\\n while i < len(nums):\\n if nums[i] % 2 == 0: # 寻找偶数下标中最左边的奇数\\n i += 2\\n elif nums[j] % 2 == 1: # 寻找奇数下标中最左边的偶数\\n j += 2\\n else:\\n nums[i], nums[j] = nums[j], nums[i]\\n i += 2\\n j += 2\\n return nums\\n
\\n###java
\\nclass Solution {\\n public int[] sortArrayByParityII(int[] nums) {\\n int i = 0;\\n int j = 1;\\n while (i < nums.length) {\\n if (nums[i] % 2 == 0) { // 寻找偶数下标中最左边的奇数\\n i += 2;\\n } else if (nums[j] % 2 == 1) { // 寻找奇数下标中最左边的偶数\\n j += 2;\\n } else {\\n int tmp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = tmp;\\n i += 2;\\n j += 2;\\n }\\n }\\n return nums;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> sortArrayByParityII(vector<int>& nums) {\\n int i = 0, j = 1;\\n while (i < nums.size()) {\\n if (nums[i] % 2 == 0) { // 寻找偶数下标中最左边的奇数\\n i += 2;\\n } else if (nums[j] % 2 == 1) { // 寻找奇数下标中最左边的偶数\\n j += 2;\\n } else {\\n swap(nums[i], nums[j]);\\n i += 2;\\n j += 2;\\n }\\n }\\n return nums;\\n }\\n};\\n
\\n###c
\\nint* sortArrayByParityII(int* nums, int numsSize, int* returnSize) {\\n int i = 0, j = 1;\\n while (i < numsSize) {\\n if (nums[i] % 2 == 0) {\\n i += 2; // 寻找偶数下标中最左边的奇数\\n } else if (nums[j] % 2 == 1) {\\n j += 2; // 寻找奇数下标中最左边的偶数\\n } else {\\n int tmp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = tmp;\\n i += 2;\\n j += 2;\\n }\\n }\\n *returnSize = numsSize;\\n return nums;\\n}\\n
\\n###go
\\nfunc sortArrayByParityII(nums []int) []int {\\n i, j := 0, 1\\n for i < len(nums) {\\n if nums[i]%2 == 0 {\\n i += 2 // 寻找偶数下标中最左边的奇数\\n } else if nums[j]%2 == 1 {\\n j += 2 // 寻找奇数下标中最左边的偶数\\n } else {\\n nums[i], nums[j] = nums[j], nums[i]\\n i += 2\\n j += 2\\n }\\n }\\n return nums\\n}\\n
\\n###js
\\nvar sortArrayByParityII = function(nums) {\\n let i = 0, j = 1;\\n while (i < nums.length) {\\n if (nums[i] % 2 === 0) {\\n i += 2; // 寻找偶数下标中最左边的奇数\\n } else if (nums[j] % 2 === 1) {\\n j += 2; // 寻找奇数下标中最左边的偶数\\n } else {\\n [nums[i], nums[j]] = [nums[j], nums[i]];\\n i += 2;\\n j += 2;\\n }\\n }\\n return nums;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn sort_array_by_parity_ii(mut nums: Vec<i32>) -> Vec<i32> {\\n let mut i = 0;\\n let mut j = 1;\\n while i < nums.len() {\\n if nums[i] % 2 == 0 {\\n i += 2; // 寻找偶数下标中最左边的奇数\\n } else if nums[j] % 2 == 1 {\\n j += 2; // 寻找奇数下标中最左边的偶数\\n } else {\\n nums.swap(i, j);\\n i += 2;\\n j += 2;\\n }\\n }\\n nums\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:905. 按奇偶排序数组。 本题可以把所有偶数下标当作是「数组靠前的位置」,奇数下标当作「数组靠后的位置」。\\n\\n或者说,把所有偶数下标记作集合 $A$,所有奇数下标记作集合 $B$,我们要把 $A$ 中的奇数和 $B$ 中的偶数交换。\\n\\n找到偶数下标中最左边的奇数 $\\\\textit{nums}[i]$ 和奇数下标中最左边的偶数 $\\\\textit{nums}[j]$,交换这两个数。\\n\\n注:由于偶数下标和奇数下标互相独立,从左边开始找还是从右边开始找,都可以。\\n\\n交换后,下标在 $0,2,4,\\\\ldots,i$ 中的数都是偶数,下标在 $1,3,5…","guid":"https://leetcode.cn/problems/sort-array-by-parity-ii//solution/shuang-zhi-zhen-pythonjavaccgojsrust-by-yvxco","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-27T06:38:25.265Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"基于 33 题的简洁写法,只需增加一个 if(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii//solution/ji-yu-33-ti-de-jian-ji-xie-fa-zhi-xu-zen-uayi","content":"前置题目:33. 搜索旋转排序数组,我的题解。
\\n\\n\\n本文的二分区间是开区间 $(\\\\textit{left},\\\\textit{right})$。
\\n
本题与 33 题的区别是有相同元素,这会导致在二分查找时,可能会遇到恰好二分元素 $\\\\textit{nums}[\\\\textit{mid}]$ 与数组末尾元素 $\\\\textit{nums}[n-1]$ 相同的情况,此时无法确定答案在左半区间中还是右半区间中。
\\n既然无法确定最小值所在区间,那么干脆去掉 $\\\\textit{nums}$ 的最后一个数,继续二分。换句话说,此时问题变成了一个规模为 $n-1$ 的子问题。
\\n你可能会有疑问:这会不会碰巧去掉了 $\\\\textit{target}$?
\\n这是不会的:
\\n为了方便写代码,我们可以把 $\\\\textit{right}$ 当作「数组最后一个数的下标」:
\\n下面的代码用的开区间写法,其他二分写法也是可以的,原理见【基础算法精讲 04】。
\\nclass Solution:\\n def search(self, nums: List[int], target: int) -> int:\\n def check(i: int) -> bool:\\n x = nums[i]\\n if x > nums[right]:\\n return target > nums[right] and x >= target\\n return target > nums[right] or x >= target\\n\\n left, right = -1, len(nums) - 1 # 开区间 (-1, n-1)\\n while left + 1 < right: # 开区间不为空\\n mid = (left + right) // 2\\n if nums[mid] == nums[right]:\\n right -= 1\\n elif check(mid):\\n right = mid\\n else:\\n left = mid\\n return nums[right] == target\\n
\\nclass Solution {\\n public boolean search(int[] nums, int target) {\\n int left = -1;\\n int right = nums.length - 1; // 开区间 (-1, n-1)\\n while (left + 1 < right) { // 开区间不为空\\n int mid = (left + right) >>> 1;\\n if (nums[mid] == nums[right]) {\\n right--;\\n } else if (check(nums, target, right, mid)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right] == target;\\n }\\n\\n private boolean check(int[] nums, int target, int right, int i) {\\n int x = nums[i];\\n if (x > nums[right]) {\\n return target > nums[right] && x >= target;\\n }\\n return target > nums[right] || x >= target;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int search(vector<int>& nums, int target) {\\n int left = -1, right = nums.size() - 1; // 开区间 (-1, n-1)\\n\\n auto check = [&](int i) -> bool {\\n int x = nums[i];\\n if (x > nums[right]) {\\n return target > nums[right] && x >= target;\\n }\\n return target > nums[right] || x >= target;\\n };\\n\\n while (left + 1 < right) { // 开区间不为空\\n int mid = left + (right - left) / 2;\\n if (nums[mid] == nums[right]) {\\n right--;\\n } else if (check(mid)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right] == target;\\n }\\n};\\n
\\nbool search(int* nums, int numsSize, int target) {\\n int left = -1, right = numsSize - 1; // 开区间 (-1, n-1)\\n\\n bool check(int i) {\\n int x = nums[i];\\n if (x > nums[right]) {\\n return target > nums[right] && x >= target;\\n }\\n return target > nums[right] || x >= target;\\n }\\n\\n while (left + 1 < right) { // 开区间不为空\\n int mid = left + (right - left) / 2;\\n if (nums[mid] == nums[right]) {\\n right--;\\n } else if (check(mid)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right] == target;\\n}\\n
\\nfunc search(nums []int, target int) bool {\\n left, right := -1, len(nums)-1 // 开区间 (-1, n-1)\\n\\n check := func(i int) bool {\\n x := nums[i]\\n if x > nums[right] {\\n return target > nums[right] && x >= target\\n }\\n return target > nums[right] || x >= target\\n }\\n\\n for left+1 < right { // 开区间不为空\\n mid := (left + right) / 2\\n if nums[mid] == nums[right] {\\n right--\\n } else if check(mid) {\\n right = mid\\n } else {\\n left = mid\\n }\\n }\\n return nums[right] == target\\n}\\n
\\nvar search = function(nums, target) {\\n function check(i) {\\n const x = nums[i];\\n if (x > nums[right]) {\\n return target > nums[right] && x >= target;\\n }\\n return target > nums[right] || x >= target;\\n }\\n\\n let left = -1, right = nums.length - 1; // 开区间 (-1, n-1)\\n while (left + 1 < right) { // 开区间不为空\\n const mid = Math.floor((left + right) / 2);\\n if (nums[mid] === nums[right]) {\\n right--;\\n } else if (check(mid, right)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right] === target;\\n};\\n
\\nimpl Solution {\\n pub fn search(nums: Vec<i32>, target: i32) -> bool {\\n let check = |i: usize, right: usize| -> bool {\\n let x = nums[i];\\n if x > nums[right] {\\n return target > nums[right] && x >= target;\\n }\\n target > nums[right] || x >= target\\n };\\n\\n let mut left = 0;\\n let mut right = nums.len() - 1; // 左闭右开区间 [0, n-1)\\n while left < right { // 区间不为空\\n let mid = left + (right - left) / 2;\\n if nums[mid] == nums[right] {\\n right -= 1;\\n } else if check(mid, right) {\\n right = mid;\\n } else {\\n left = mid + 1;\\n }\\n }\\n nums[right] == target\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:33. 搜索旋转排序数组,我的题解。 本文的二分区间是开区间 $(\\\\textit{left},\\\\textit{right})$。\\n\\n本题与 33 题的区别是有相同元素,这会导致在二分查找时,可能会遇到恰好二分元素 $\\\\textit{nums}[\\\\textit{mid}]$ 与数组末尾元素 $\\\\textit{nums}[n-1]$ 相同的情况,此时无法确定答案在左半区间中还是右半区间中。\\n\\n既然无法确定最小值所在区间,那么干脆去掉 $\\\\textit{nums}$ 的最后一个数,继续二分。换句话说,此时问题变成了一个规模为 $n-1$ 的子问题。…","guid":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii//solution/ji-yu-33-ti-de-jian-ji-xie-fa-zhi-xu-zen-uayi","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-27T03:23:40.611Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-跳跃游戏 II🟡","url":"https://leetcode.cn/problems/jump-game-ii/","content":"给定一个长度为 n
的 0 索引整数数组 nums
。初始位置为 nums[0]
。
每个元素 nums[i]
表示从索引 i
向后跳转的最大长度。换句话说,如果你在 nums[i]
处,你可以跳转到任意 nums[i + j]
处:
0 <= j <= nums[i]
i + j < n
返回到达 nums[n - 1]
的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]
。
\\n\\n
示例 1:
\\n\\n输入: nums = [2,3,1,1,4]\\n输出: 2\\n解释: 跳到最后一个位置的最小跳跃数是\\n\\n2
。\\n 从下标为 0 跳到下标为 1 的位置,跳1
步,然后跳3
步到达数组的最后一个位置。\\n
示例 2:
\\n\\n输入: nums = [2,3,0,1,4]\\n输出: 2\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 104
0 <= nums[i] <= 1000
nums[n-1]
给定一个候选人编号的集合 candidates
和一个目标数 target
,找出 candidates
中所有可以使数字和为 target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: candidates =\\n\\n[10,1,2,7,6,1,5]
, target =8
,\\n输出:\\n[\\n[1,1,6],\\n[1,2,5],\\n[1,7],\\n[2,6]\\n]
示例 2:
\\n\\n输入: candidates = [2,5,2,1,2], target = 5,\\n输出:\\n[\\n[1,2,2],\\n[5]\\n]\\n\\n
\\n\\n
提示:
\\n\\n1 <= candidates.length <= 100
1 <= candidates[i] <= 50
1 <= target <= 30
给你一个下标从 0 开始的二维整数数组 transactions
,其中transactions[i] = [costi, cashbacki]
。
数组描述了若干笔交易。其中每笔交易必须以 某种顺序 恰好完成一次。在任意一个时刻,你有一定数目的钱 money
,为了完成交易 i
,money >= costi
这个条件必须为真。执行交易后,你的钱数 money
变成 money - costi + cashbacki
。
请你返回 任意一种 交易顺序下,你都能完成所有交易的最少钱数 money
是多少。
\\n\\n
示例 1:
\\n\\n输入:transactions = [[2,1],[5,0],[4,2]]\\n输出:10\\n解释:\\n刚开始 money = 10 ,交易可以以任意顺序进行。\\n可以证明如果 money < 10 ,那么某些交易无法进行。\\n\\n\\n
示例 2:
\\n\\n输入:transactions = [[3,0],[0,3]]\\n输出:3\\n解释:\\n- 如果交易执行的顺序是 [[3,0],[0,3]] ,完成所有交易需要的最少钱数是 3 。\\n- 如果交易执行的顺序是 [[0,3],[3,0]] ,完成所有交易需要的最少钱数是 0 。\\n所以,刚开始钱数为 3 ,任意顺序下交易都可以全部完成。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= transactions.length <= 105
transactions[i].length == 2
0 <= costi, cashbacki <= 109
根据题意,我们要反转的下标区间为:
\\n$$
\\n[0,k),[2k,3k),[4k,5k),\\\\cdots
\\n$$
注意最后一个区间的右开端点要和 $n$ 取最小值,防止下标越界。
\\n如何反转字符串见 344 题的题解。
\\nclass Solution:\\n def reverseStr(self, s: str, k: int) -> str:\\n s = list(s)\\n for i in range(0, len(s), k * 2):\\n s[i: i + k] = s[i: i + k][::-1] # 右端点自动和 n 取最小值\\n return \'\'.join(s)\\n
\\nclass Solution {\\n public String reverseStr(String S, int k) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n for (int i = 0; i < n; i += k * 2) {\\n reverse(s, i, Math.min(i + k, n) - 1);\\n }\\n return new String(s);\\n }\\n\\n private void reverse(char[] s, int left, int right) {\\n while (left < right) {\\n char tmp = s[left];\\n s[left++] = s[right];\\n s[right--] = tmp;\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string reverseStr(string s, int k) {\\n int n = s.size();\\n for (int i = 0; i < n; i += k * 2) {\\n reverse(s.begin() + i, s.begin() + min(i + k, n));\\n }\\n return s;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nvoid reverse(char* s, int left, int right) {\\n while (left < right) {\\n char tmp = s[left];\\n s[left++] = s[right];\\n s[right--] = tmp;\\n }\\n}\\n\\nchar* reverseStr(char* s, int k) {\\n int n = strlen(s);\\n for (int i = 0; i < n; i += k * 2) {\\n reverse(s, i, MIN(i + k, n) - 1);\\n }\\n return s;\\n}\\n
\\nfunc reverseStr(S string, k int) string {\\n s := []byte(S)\\n n := len(s)\\n for i := 0; i < n; i += k * 2 {\\n slices.Reverse(s[i:min(i+k, n)])\\n }\\n return string(s)\\n}\\n
\\nvar reverse = function(s, left, right) {\\n while (left < right) {\\n const tmp = s[left];\\n s[left++] = s[right];\\n s[right--] = tmp;\\n }\\n};\\n\\nvar reverseStr = function(s, k) {\\n s = s.split(\'\');\\n const n = s.length;\\n for (let i = 0; i < n; i += k * 2) {\\n reverse(s, i, Math.min(i + k, n) - 1);\\n }\\n return s.join(\'\');\\n};\\n
\\nimpl Solution {\\n pub fn reverse_str(mut S: String, k: i32) -> String {\\n let mut s = unsafe { S.as_bytes_mut() };\\n let k = k as usize;\\n let n = s.len();\\n for i in (0..n).step_by(k * 2) {\\n s[i..n.min(i + k)].reverse();\\n }\\n S\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据题意,我们要反转的下标区间为: $$\\n [0,k),[2k,3k),[4k,5k),\\\\cdots\\n $$\\n\\n注意最后一个区间的右开端点要和 $n$ 取最小值,防止下标越界。\\n\\n如何反转字符串见 344 题的题解。\\n\\nclass Solution:\\n def reverseStr(self, s: str, k: int) -> str:\\n s = list(s)\\n for i in range(0, len(s), k * 2):\\n s[i: i + k] = s[i: i + k][::-1…","guid":"https://leetcode.cn/problems/reverse-string-ii//solution/jian-dan-ti-jian-dan-zuo-pythonjavaccgoj-ig15","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-24T05:46:56.115Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历,简洁写法,附进阶问题解答(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/yi-ci-bian-li-jian-ji-xie-fa-fu-jin-jie-szdb1","content":"前置题目:349. 两个数组的交集。请先完成这题,并阅读 我的题解。
\\n本题只需把哈希集合改成哈希表,其中哈希表的 value 表示 key 的出现次数。
\\n在 $\\\\textit{nums}_2[i]$ 加入答案列表的同时,把 $\\\\textit{nums}_2[i]$ 在哈希表中的出现次数减一。如果出现次数变成 $0$,后面遍历到相同的元素时,不加入答案列表。
\\n###py
\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:\\n cnt = Counter(nums1)\\n ans = []\\n for x in nums2:\\n if cnt[x] > 0:\\n cnt[x] -= 1\\n ans.append(x)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] intersect(int[] nums1, int[] nums2) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int x : nums1) {\\n cnt.merge(x, 1, Integer::sum); // cnt[x]++\\n }\\n List<Integer> ans = new ArrayList<>();\\n for (int x : nums2) {\\n int c = cnt.getOrDefault(x, 0);\\n if (c > 0) {\\n cnt.put(x, c - 1);\\n ans.add(x);\\n }\\n }\\n // 由于返回值是 int[],需要额外遍历一次\\n return ans.stream().mapToInt(i -> i).toArray();\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n unordered_map<int, int> cnt;\\n for (int x : nums1) {\\n cnt[x]++;\\n }\\n vector<int> ans;\\n for (int x : nums2) {\\n if (cnt[x] > 0) {\\n cnt[x]--;\\n ans.push_back(x);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n unordered_multiset<int> st(nums1.begin(), nums1.end());\\n vector<int> ans;\\n for (int x : nums2) {\\n auto it = st.find(x);\\n if (it != st.end()) {\\n st.erase(it);\\n ans.push_back(x);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc intersect(nums1, nums2 []int) (ans []int) {\\n cnt := map[int]int{}\\n for _, x := range nums1 {\\n cnt[x]++\\n }\\n for _, x := range nums2 {\\n if cnt[x] > 0 {\\n cnt[x]--\\n ans = append(ans, x)\\n }\\n }\\n return\\n}\\n
\\n###js
\\nvar intersect = function(nums1, nums2) {\\n const cnt = new Map();\\n for (const x of nums1) {\\n cnt.set(x, (cnt.get(x) ?? 0) + 1);\\n }\\n const ans = [];\\n for (const x of nums2) {\\n const c = cnt.get(x) ?? 0;\\n if (c > 0) {\\n cnt.set(x, c - 1);\\n ans.push(x);\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn intersect(nums1: Vec<i32>, nums2: Vec<i32>) -> Vec<i32> {\\n let mut cnt = HashMap::new();\\n for x in nums1 {\\n *cnt.entry(x).or_insert(0) += 1;\\n }\\n let mut ans = vec![];\\n for x in nums2 {\\n if let Some(c) = cnt.get_mut(&x) {\\n if *c > 0 {\\n *c -= 1;\\n ans.push(x);\\n }\\n }\\n }\\n ans\\n }\\n}\\n
\\n问:如果给定的数组已经排好序呢?你将如何优化你的算法?
\\n答:用双指针解决,具体见下面的双指针写法。空间复杂度优化至 $\\\\mathcal{O}(1)$。
\\n问:如果 $\\\\textit{nums}_1$ 的大小比 $\\\\textit{nums}_2$ 小,哪种方法更优?
\\n答:这里讨论哈希表做法。可以把长度小的数组转成哈希表,这样可以做到 $\\\\mathcal{O}(\\\\min(n,m))$ 的空间复杂度。
\\n问:如果 $\\\\textit{nums}_2$ 的元素存储在磁盘上,内存是有限的,并且你不能一次加载所有的元素到内存中,你该怎么办?
\\n答:用一个小型缓冲区(buffer)一边读数据一边遍历数据。这等价于问 $\\\\textit{nums}_2$ 是一个流(Stream)的情况要怎么做。由于我们写的是一次遍历的代码,所以已经符合这个要求。
\\n###py
\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:\\n # 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n nums1.sort()\\n nums2.sort()\\n\\n ans = []\\n i = j = 0\\n while i < len(nums1) and j < len(nums2):\\n x, y = nums1[i], nums2[j]\\n if x < y:\\n i += 1\\n elif x > y:\\n j += 1\\n else:\\n ans.append(x)\\n i += 1\\n j += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] intersect(int[] nums1, int[] nums2) {\\n // 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n Arrays.sort(nums1);\\n Arrays.sort(nums2);\\n\\n List<Integer> ans = new ArrayList<>();\\n int i = 0;\\n int j = 0;\\n while (i < nums1.length && j < nums2.length) {\\n int x = nums1[i];\\n int y = nums2[j];\\n if (x < y) {\\n i++;\\n } else if (x > y) {\\n j++;\\n } else {\\n ans.add(x);\\n i++;\\n j++;\\n }\\n }\\n\\n // 由于返回值是 int[],需要额外遍历一次\\n return ans.stream().mapToInt(x -> x).toArray();\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n // 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n ranges::sort(nums1);\\n ranges::sort(nums2);\\n\\n vector<int> ans;\\n int i = 0, j = 0;\\n while (i < nums1.size() && j < nums2.size()) {\\n int x = nums1[i], y = nums2[j];\\n if (x < y) {\\n i++;\\n } else if (x > y) {\\n j++;\\n } else {\\n ans.push_back(x);\\n i++;\\n j++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc intersect(nums1, nums2 []int) (ans []int) {\\n // 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n slices.Sort(nums1)\\n slices.Sort(nums2)\\n\\n i, j := 0, 0\\n for i < len(nums1) && j < len(nums2) {\\n x, y := nums1[i], nums2[j]\\n if x < y {\\n i++\\n } else if x > y {\\n j++\\n } else {\\n ans = append(ans, x)\\n i++\\n j++\\n }\\n }\\n return\\n}\\n
\\n###js
\\nvar intersect = function(nums1, nums2) {\\n // 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n nums1.sort((a, b) => a - b);\\n nums2.sort((a, b) => a - b);\\n\\n const ans = [];\\n let i = 0, j = 0;\\n while (i < nums1.length && j < nums2.length) {\\n const x = nums1[i], y = nums2[j];\\n if (x < y) {\\n i++;\\n } else if (x > y) {\\n j++;\\n } else {\\n ans.push(x);\\n i++;\\n j++;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn intersect(mut nums1: Vec<i32>, mut nums2: Vec<i32>) -> Vec<i32> {\\n // 如果保证 nums1 和 nums2 是有序的,排序可以去掉\\n nums1.sort_unstable();\\n nums2.sort_unstable();\\n\\n let mut ans = vec![];\\n let mut i = 0;\\n let mut j = 0;\\n while i < nums1.len() && j < nums2.len() {\\n let x = nums1[i];\\n let y = nums2[j];\\n if x < y {\\n i += 1;\\n } else if x > y {\\n j += 1;\\n } else {\\n ans.push(x);\\n i += 1;\\n j += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:349. 两个数组的交集。请先完成这题,并阅读 我的题解。 本题只需把哈希集合改成哈希表,其中哈希表的 value 表示 key 的出现次数。\\n\\n在 $\\\\textit{nums}_2[i]$ 加入答案列表的同时,把 $\\\\textit{nums}_2[i]$ 在哈希表中的出现次数减一。如果出现次数变成 $0$,后面遍历到相同的元素时,不加入答案列表。\\n\\n###py\\n\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:…","guid":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/yi-ci-bian-li-jian-ji-xie-fa-fu-jin-jie-szdb1","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-24T02:30:46.001Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题三解:记忆化搜索 & 动态规划+单调队列优化(清晰题解)","url":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits//solution/python3javacgotypescript-yi-ti-san-jie-j-v6pc","content":"我们定义一个函数 $\\\\textit{dfs}(i)$,表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $\\\\textit{dfs}(1)$。
\\n函数 $\\\\textit{dfs}(i)$ 的执行逻辑如下:
\\n为了避免重复计算,我们使用记忆化搜索的方法,将已经计算过的结果保存起来,下次遇到相同的情况时,直接返回结果即可。
\\n###python
\\nclass Solution:\\n def minimumCoins(self, prices: List[int]) -> int:\\n @cache\\n def dfs(i: int) -> int:\\n if i * 2 >= len(prices):\\n return prices[i - 1]\\n return prices[i - 1] + min(dfs(j) for j in range(i + 1, i * 2 + 2))\\n\\n return dfs(1)\\n
\\n###java
\\nclass Solution {\\n private int[] prices;\\n private int[] f;\\n private int n;\\n\\n public int minimumCoins(int[] prices) {\\n n = prices.length;\\n f = new int[n + 1];\\n this.prices = prices;\\n return dfs(1);\\n }\\n\\n private int dfs(int i) {\\n if (i * 2 >= n) {\\n return prices[i - 1];\\n }\\n if (f[i] == 0) {\\n f[i] = 1 << 30;\\n for (int j = i + 1; j <= i * 2 + 1; ++j) {\\n f[i] = Math.min(f[i], prices[i - 1] + dfs(j));\\n }\\n }\\n return f[i];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n int f[n + 1];\\n memset(f, 0x3f, sizeof(f));\\n function<int(int)> dfs = [&](int i) {\\n if (i * 2 >= n) {\\n return prices[i - 1];\\n }\\n if (f[i] == 0x3f3f3f3f) {\\n for (int j = i + 1; j <= i * 2 + 1; ++j) {\\n f[i] = min(f[i], prices[i - 1] + dfs(j));\\n }\\n }\\n return f[i];\\n };\\n return dfs(1);\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(prices []int) int {\\nn := len(prices)\\nf := make([]int, n+1)\\nvar dfs func(int) int\\ndfs = func(i int) int {\\nif i*2 >= n {\\nreturn prices[i-1]\\n}\\nif f[i] == 0 {\\nf[i] = 1 << 30\\nfor j := i + 1; j <= i*2+1; j++ {\\nf[i] = min(f[i], dfs(j)+prices[i-1])\\n}\\n}\\nreturn f[i]\\n}\\nreturn dfs(1)\\n}\\n
\\n###ts
\\nfunction minimumCoins(prices: number[]): number {\\n const n = prices.length;\\n const f: number[] = Array(n + 1).fill(0);\\n const dfs = (i: number): number => {\\n if (i * 2 >= n) {\\n return prices[i - 1];\\n }\\n if (f[i] === 0) {\\n f[i] = 1 << 30;\\n for (let j = i + 1; j <= i * 2 + 1; ++j) {\\n f[i] = Math.min(f[i], prices[i - 1] + dfs(j));\\n }\\n }\\n return f[i];\\n };\\n return dfs(1);\\n}\\n
\\n时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\\\\textit{prices}$ 的长度。
\\n我们可以将方法一中的记忆化搜索改写成动态规划的形式。
\\n与方法一类似,我们定义 $f[i]$ 表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $f[1]$。
\\n状态转移方程为 $f[i] = \\\\min_{i + 1 \\\\le j \\\\le 2i + 1} f[j] + \\\\textit{prices}[i - 1]$。
\\n###python
\\nclass Solution:\\n def minimumCoins(self, prices: List[int]) -> int:\\n n = len(prices)\\n for i in range((n - 1) // 2, 0, -1):\\n prices[i - 1] += min(prices[i : i * 2 + 1])\\n return prices[0]\\n
\\n###java
\\nclass Solution {\\n public int minimumCoins(int[] prices) {\\n int n = prices.length;\\n for (int i = (n - 1) / 2; i > 0; --i) {\\n int mi = 1 << 30;\\n for (int j = i; j <= i * 2; ++j) {\\n mi = Math.min(mi, prices[j]);\\n }\\n prices[i - 1] += mi;\\n }\\n return prices[0];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n for (int i = (n - 1) / 2; i; --i) {\\n prices[i - 1] += *min_element(prices.begin() + i, prices.begin() + 2 * i + 1);\\n }\\n return prices[0];\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(prices []int) int {\\nfor i := (len(prices) - 1) / 2; i > 0; i-- {\\nprices[i-1] += slices.Min(prices[i : i*2+1])\\n}\\nreturn prices[0]\\n}\\n
\\n###ts
\\nfunction minimumCoins(prices: number[]): number {\\n for (let i = (prices.length - 1) >> 1; i; --i) {\\n prices[i - 1] += Math.min(...prices.slice(i, i * 2 + 1));\\n }\\n return prices[0];\\n}\\n
\\n时间复杂度 $O(n^2)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\\\\textit{prices}$ 的长度。
\\n在代码实现上,我们可以直接使用 $\\\\textit{prices}$ 数组来存储 $f$ 数组,那么空间复杂度可以优化到 $O(1)$。
\\n我们观察方法二中的状态转移方程,可以发现,对于每个 $i$,我们需要求出 $f[i + 1], f[i + 2], \\\\cdots, f[2i + 1]$ 的最小值,并且随着 $i$ 的减小,这些值的范围也在减小。这实际上是求一个单调收窄的滑动窗口的最小值,我们可以使用单调队列来优化。
\\n我们从后往前计算,维护一个单调递增的队列 $q$,队列中存储的是下标。如果 $q$ 的队首元素大于 $i \\\\times 2 + 1$,说明 $i$ 之后的元素都不会被用到,所以我们将队首元素出队。如果 $i$ 不大于 $(n - 1) / 2$,那么我们可以将 $\\\\textit{prices}[q[0] - 1]$ 加到 $\\\\textit{prices}[i - 1]$ 上,然后将 $i$ 加入队尾。如果 $q$ 的队尾元素对应的水果价格大于等于 $\\\\textit{prices}[i - 1]$,那么我们将队尾元素出队,直到队尾元素对应的水果价格小于 $\\\\textit{prices}[i - 1]$ 或者队列为空,然后将 $i$ 加入队尾。
\\n###python
\\nclass Solution:\\n def minimumCoins(self, prices: List[int]) -> int:\\n n = len(prices)\\n q = deque()\\n for i in range(n, 0, -1):\\n while q and q[0] > i * 2 + 1:\\n q.popleft()\\n if i <= (n - 1) // 2:\\n prices[i - 1] += prices[q[0] - 1]\\n while q and prices[q[-1] - 1] >= prices[i - 1]:\\n q.pop()\\n q.append(i)\\n return prices[0]\\n
\\n###java
\\nclass Solution {\\n public int minimumCoins(int[] prices) {\\n int n = prices.length;\\n Deque<Integer> q = new ArrayDeque<>();\\n for (int i = n; i > 0; --i) {\\n while (!q.isEmpty() && q.peek() > i * 2 + 1) {\\n q.poll();\\n }\\n if (i <= (n - 1) / 2) {\\n prices[i - 1] += prices[q.peek() - 1];\\n }\\n while (!q.isEmpty() && prices[q.peekLast() - 1] >= prices[i - 1]) {\\n q.pollLast();\\n }\\n q.offer(i);\\n }\\n return prices[0];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n deque<int> q;\\n for (int i = n; i; --i) {\\n while (q.size() && q.front() > i * 2 + 1) {\\n q.pop_front();\\n }\\n if (i <= (n - 1) / 2) {\\n prices[i - 1] += prices[q.front() - 1];\\n }\\n while (q.size() && prices[q.back() - 1] >= prices[i - 1]) {\\n q.pop_back();\\n }\\n q.push_back(i);\\n }\\n return prices[0];\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(prices []int) int {\\nn := len(prices)\\nq := Deque{}\\nfor i := n; i > 0; i-- {\\nfor q.Size() > 0 && q.Front() > i*2+1 {\\nq.PopFront()\\n}\\nif i <= (n-1)/2 {\\nprices[i-1] += prices[q.Front()-1]\\n}\\nfor q.Size() > 0 && prices[q.Back()-1] >= prices[i-1] {\\nq.PopBack()\\n}\\nq.PushBack(i)\\n}\\nreturn prices[0]\\n}\\n\\n// template\\ntype Deque struct{ l, r []int }\\n\\nfunc (q Deque) Empty() bool {\\nreturn len(q.l) == 0 && len(q.r) == 0\\n}\\n\\nfunc (q Deque) Size() int {\\nreturn len(q.l) + len(q.r)\\n}\\n\\nfunc (q *Deque) PushFront(v int) {\\nq.l = append(q.l, v)\\n}\\n\\nfunc (q *Deque) PushBack(v int) {\\nq.r = append(q.r, v)\\n}\\n\\nfunc (q *Deque) PopFront() (v int) {\\nif len(q.l) > 0 {\\nq.l, v = q.l[:len(q.l)-1], q.l[len(q.l)-1]\\n} else {\\nv, q.r = q.r[0], q.r[1:]\\n}\\nreturn\\n}\\n\\nfunc (q *Deque) PopBack() (v int) {\\nif len(q.r) > 0 {\\nq.r, v = q.r[:len(q.r)-1], q.r[len(q.r)-1]\\n} else {\\nv, q.l = q.l[0], q.l[1:]\\n}\\nreturn\\n}\\n\\nfunc (q Deque) Front() int {\\nif len(q.l) > 0 {\\nreturn q.l[len(q.l)-1]\\n}\\nreturn q.r[0]\\n}\\n\\nfunc (q Deque) Back() int {\\nif len(q.r) > 0 {\\nreturn q.r[len(q.r)-1]\\n}\\nreturn q.l[0]\\n}\\n\\nfunc (q Deque) Get(i int) int {\\nif i < len(q.l) {\\nreturn q.l[len(q.l)-1-i]\\n}\\nreturn q.r[i-len(q.l)]\\n}\\n
\\n###ts
\\nfunction minimumCoins(prices: number[]): number {\\n const n = prices.length;\\n const q = new Deque<number>();\\n for (let i = n; i; --i) {\\n while (q.getSize() && q.frontValue()! > i * 2 + 1) {\\n q.popFront();\\n }\\n if (i <= (n - 1) >> 1) {\\n prices[i - 1] += prices[q.frontValue()! - 1];\\n }\\n while (q.getSize() && prices[q.backValue()! - 1] >= prices[i - 1]) {\\n q.popBack();\\n }\\n q.pushBack(i);\\n }\\n return prices[0];\\n}\\n\\nclass Node<T> {\\n value: T;\\n next: Node<T> | null;\\n prev: Node<T> | null;\\n\\n constructor(value: T) {\\n this.value = value;\\n this.next = null;\\n this.prev = null;\\n }\\n}\\n\\nclass Deque<T> {\\n private front: Node<T> | null;\\n private back: Node<T> | null;\\n private size: number;\\n\\n constructor() {\\n this.front = null;\\n this.back = null;\\n this.size = 0;\\n }\\n\\n pushFront(val: T): void {\\n const newNode = new Node(val);\\n if (this.isEmpty()) {\\n this.front = newNode;\\n this.back = newNode;\\n } else {\\n newNode.next = this.front;\\n this.front!.prev = newNode;\\n this.front = newNode;\\n }\\n this.size++;\\n }\\n\\n pushBack(val: T): void {\\n const newNode = new Node(val);\\n if (this.isEmpty()) {\\n this.front = newNode;\\n this.back = newNode;\\n } else {\\n newNode.prev = this.back;\\n this.back!.next = newNode;\\n this.back = newNode;\\n }\\n this.size++;\\n }\\n\\n popFront(): T | undefined {\\n if (this.isEmpty()) {\\n return undefined;\\n }\\n const value = this.front!.value;\\n this.front = this.front!.next;\\n if (this.front !== null) {\\n this.front.prev = null;\\n } else {\\n this.back = null;\\n }\\n this.size--;\\n return value;\\n }\\n\\n popBack(): T | undefined {\\n if (this.isEmpty()) {\\n return undefined;\\n }\\n const value = this.back!.value;\\n this.back = this.back!.prev;\\n if (this.back !== null) {\\n this.back.next = null;\\n } else {\\n this.front = null;\\n }\\n this.size--;\\n return value;\\n }\\n\\n frontValue(): T | undefined {\\n return this.front?.value;\\n }\\n\\n backValue(): T | undefined {\\n return this.back?.value;\\n }\\n\\n getSize(): number {\\n return this.size;\\n }\\n\\n isEmpty(): boolean {\\n return this.size === 0;\\n }\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\\\\textit{prices}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:记忆化搜索 我们定义一个函数 $\\\\textit{dfs}(i)$,表示从第 $i$ 个水果开始购买所有水果所需要的最少金币数。那么答案就是 $\\\\textit{dfs}(1)$。\\n\\n函数 $\\\\textit{dfs}(i)$ 的执行逻辑如下:\\n\\n如果 $i \\\\times 2 \\\\geq n$,说明只要买第 $i - 1$ 个水果即可,剩余的水果都可以免费获得,所以返回 $\\\\textit{prices}[i - 1]$。\\n否则,我们可以购买水果 $i$,然后在接下来的 $i + 1$ 到 $2i + 1$ 个水果中选择一个水果 $j$ 开始购买,那么…","guid":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits//solution/python3javacgotypescript-yi-ti-san-jie-j-v6pc","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-24T00:18:07.531Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-购买水果需要的最少金币数🟡","url":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits/","content":"给你一个 下标从 1 开始的 整数数组 prices
,其中 prices[i]
表示你购买第 i
个水果需要花费的金币数目。
水果超市有如下促销活动:
\\n\\nprices[i]
购买了下标为 i
的水果,那么你可以免费获得下标范围在 [i + 1, i + i]
的水果。注意 ,即使你 可以 免费获得水果 j
,你仍然可以花费 prices[j]
个金币去购买它以获得它的奖励。
请你返回获得所有水果所需要的 最少 金币数。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:prices = [3,1,2]
\\n\\n输出:4
\\n\\n解释:
\\n\\nprices[0] = 3
个金币购买第 1 个水果,你可以免费获得第 2 个水果。prices[1] = 1
个金币购买第 2 个水果,你可以免费获得第 3 个水果。请注意,即使您可以免费获得第 2 个水果作为购买第 1 个水果的奖励,但您购买它是为了获得其奖励,这是更优化的。
\\n示例 2:
\\n\\n输入:prices = [1,10,1,1]
\\n\\n输出:2
\\n\\n解释:
\\n\\nprices[0] = 1
个金币购买第 1 个水果,你可以免费获得第 2 个水果。prices[2] = 1
个金币购买第 3 个水果,你可以免费获得第 4 个水果。示例 3:
\\n\\n输入:prices = [26,18,6,12,49,7,45,45]
\\n\\n输出:39
\\n\\n解释:
\\n\\nprices[0] = 26
个金币购买第 1 个水果,你可以免费获得第 2 个水果。prices[2] = 6
个金币购买第 3 个水果,你可以免费获得第 4,5,6(接下来的三个)水果。prices[5] = 7
个金币购买第 6 个水果,你可以免费获得第 7 和 第 8 个水果。请注意,即使您可以免费获得第 6 个水果作为购买第 3 个水果的奖励,但您购买它是为了获得其奖励,这是更优化的。
\\n\\n\\n
提示:
\\n\\n1 <= prices.length <= 1000
1 <= prices[i] <= 105
有一棵由 n
个节点组成的无向树,以 0
为根节点,节点编号从 0
到 n - 1
。给你一个长度为 n - 1
的二维 整数 数组 edges
,其中 edges[i] = [ai, bi]
表示在树上的节点 ai
和 bi
之间存在一条边。另给你一个下标从 0 开始、长度为 n
的数组 coins
和一个整数 k
,其中 coins[i]
表示节点 i
处的金币数量。
从根节点开始,你必须收集所有金币。要想收集节点上的金币,必须先收集该节点的祖先节点上的金币。
\\n\\n节点 i
上的金币可以用下述方法之一进行收集:
coins[i] - k
点积分。如果 coins[i] - k
是负数,你将会失去 abs(coins[i] - k)
点积分。floor(coins[i] / 2)
点积分。如果采用这种方法,节点 i
子树中所有节点 j
的金币数 coins[j]
将会减少至 floor(coins[j] / 2)
。返回收集 所有 树节点的金币之后可以获得的最大积分。
\\n\\n\\n\\n
示例 1:
\\n输入:edges = [[0,1],[1,2],[2,3]], coins = [10,10,3,3], k = 5\\n输出:11 \\n解释:\\n使用第一种方法收集节点 0 上的所有金币。总积分 = 10 - 5 = 5 。\\n使用第一种方法收集节点 1 上的所有金币。总积分 = 5 + (10 - 5) = 10 。\\n使用第二种方法收集节点 2 上的所有金币。所以节点 3 上的金币将会变为 floor(3 / 2) = 1 ,总积分 = 10 + floor(3 / 2) = 11 。\\n使用第二种方法收集节点 3 上的所有金币。总积分 = 11 + floor(1 / 2) = 11.\\n可以证明收集所有节点上的金币能获得的最大积分是 11 。 \\n\\n\\n
示例 2:
\\n输入:edges = [[0,1],[0,2]], coins = [8,4,4], k = 0\\n输出:16\\n解释:\\n使用第一种方法收集所有节点上的金币,因此,总积分 = (8 - 0) + (4 - 0) + (4 - 0) = 16 。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == coins.length
2 <= n <= 105
0 <= coins[i] <= 104
edges.length == n - 1
0 <= edges[i][0], edges[i][1] < n
0 <= k <= 104
有 3n 堆数目不一的硬币,你和你的朋友们打算按以下方式分硬币:
\\n\\n给你一个整数数组 piles
,其中 piles[i]
是第 i
堆中硬币的数目。
返回你可以获得的最大硬币数目。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:piles = [2,4,1,2,7,8]\\n输出:9\\n解释:选出 (2, 7, 8) ,Alice 取走 8 枚硬币的那堆,你取走 7 枚硬币的那堆,Bob 取走最后一堆。\\n选出 (1, 2, 4) , Alice 取走 4 枚硬币的那堆,你取走 2 枚硬币的那堆,Bob 取走最后一堆。\\n你可以获得的最大硬币数目:7 + 2 = 9.\\n考虑另外一种情况,如果选出的是 (1, 2, 8) 和 (2, 4, 7) ,你就只能得到 2 + 4 = 6 枚硬币,这不是最优解。\\n\\n\\n
示例 2:
\\n\\n输入:piles = [2,4,5]\\n输出:4\\n\\n\\n
示例 3:
\\n\\n输入:piles = [9,8,7,6,5,1,2,3,4]\\n输出:18\\n\\n\\n
\\n\\n
提示:
\\n\\n3 <= piles.length <= 10^5
piles.length % 3 == 0
1 <= piles[i] <= 10^4
每个 $\\\\textit{ops}[i]$ 会把一个子矩形中的数都加一,那么这些子矩形的交集中的数,被加一的次数最多,是最终矩阵中的最大整数。
\\n矩形的交集仍然是矩形。
\\n由于子矩形的左上角都是 $(0,0)$,所以交集矩形的左上角也是 $(0,0)$。
\\n知道了交集矩形的右下角,就知道了交集的大小。
\\n所有 $\\\\textit{ops}[i][0]$ 的最小值 $\\\\textit{minA}$,是交集的横坐标加一。
\\n所有 $\\\\textit{ops}[i][1]$ 的最小值 $\\\\textit{minB}$,是交集的纵坐标加一。
\\n那么交集的大小为
\\n$$
\\n\\\\textit{minA}\\\\cdot \\\\textit{minB}
\\n$$
如果 $\\\\textit{ops}$ 是空的,那么交集大小就是原矩阵的大小 $mn$。
\\nclass Solution:\\n def maxCount(self, m: int, n: int, ops: List[List[int]]) -> int:\\n min_a = min((op[0] for op in ops), default=m)\\n min_b = min((op[1] for op in ops), default=n)\\n return min_a * min_b\\n
\\nclass Solution {\\n public int maxCount(int m, int n, int[][] ops) {\\n int minA = m;\\n int minB = n;\\n for (int[] op : ops) {\\n minA = Math.min(minA, op[0]);\\n minB = Math.min(minB, op[1]);\\n }\\n return minA * minB;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxCount(int m, int n, vector<vector<int>>& ops) {\\n int min_a = m, min_b = n;\\n for (auto& op : ops) {\\n min_a = min(min_a, op[0]);\\n min_b = min(min_b, op[1]);\\n }\\n return min_a * min_b;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint maxCount(int m, int n, int** ops, int opsSize, int* opsColSize) {\\n int min_a = m, min_b = n;\\n for (int i = 0; i < opsSize; i++) {\\n min_a = MIN(min_a, ops[i][0]);\\n min_b = MIN(min_b, ops[i][1]);\\n }\\n return min_a * min_b;\\n}\\n
\\nfunc maxCount(m, n int, ops [][]int) int {\\n minA, minB := m, n\\n for _, op := range ops {\\n minA = min(minA, op[0])\\n minB = min(minB, op[1])\\n }\\n return minA * minB\\n}\\n
\\nvar maxCount = function(m, n, ops) {\\n let minA = m, minB = n;\\n for (const [a, b] of ops) {\\n minA = Math.min(minA, a);\\n minB = Math.min(minB, b);\\n }\\n return minA * minB;\\n};\\n
\\nimpl Solution {\\n pub fn max_count(m: i32, n: i32, ops: Vec<Vec<i32>>) -> i32 {\\n let mut min_a = m;\\n let mut min_b = n;\\n for op in ops {\\n min_a = min_a.min(op[0]);\\n min_b = min_b.min(op[1]);\\n }\\n min_a * min_b\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"每个 $\\\\textit{ops}[i]$ 会把一个子矩形中的数都加一,那么这些子矩形的交集中的数,被加一的次数最多,是最终矩阵中的最大整数。 矩形的交集仍然是矩形。\\n\\n由于子矩形的左上角都是 $(0,0)$,所以交集矩形的左上角也是 $(0,0)$。\\n\\n知道了交集矩形的右下角,就知道了交集的大小。\\n\\n所有 $\\\\textit{ops}[i][0]$ 的最小值 $\\\\textit{minA}$,是交集的横坐标加一。\\n\\n所有 $\\\\textit{ops}[i][1]$ 的最小值 $\\\\textit{minB}$,是交集的纵坐标加一。\\n\\n那么交集的大小为\\n\\n$$\\n \\\\textit…","guid":"https://leetcode.cn/problems/range-addition-ii//solution/nao-jin-ji-zhuan-wan-pythonjavaccgojsrus-7166","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-21T05:24:51.206Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心:为什么相等的时候不需要删(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/valid-palindrome-ii//solution/tan-xin-wei-shi-yao-xiang-deng-de-shi-ho-wtll","content":"如果不能删字母,判断 $s[i]=s[n-1-i]$ 是否对每个 $i$ 成立即可。其中 $n$ 是 $s$ 的长度。
\\n如果能删一个字母,分类讨论:
\\n###py
\\nclass Solution:\\n def isPalindrome(self, s: str) -> bool:\\n return s == s[::-1]\\n\\n def validPalindrome(self, s: str) -> bool:\\n i, j = 0, len(s) - 1\\n while i < j:\\n if s[i] != s[j]:\\n # 删除 s[i] 或者 s[j]\\n return self.isPalindrome(s[i + 1: j + 1]) or self.isPalindrome(s[i: j])\\n i += 1\\n j -= 1\\n return True # s 本身就是回文串\\n
\\n###java
\\nclass Solution {\\n public boolean validPalindrome(String s) {\\n int i = 0;\\n int j = s.length() - 1;\\n while (i < j) {\\n if (s.charAt(i) != s.charAt(j)) {\\n // 删除 s[i] 或者 s[j]\\n return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);\\n }\\n i++;\\n j--;\\n }\\n return true; // s 本身就是回文串\\n }\\n\\n private boolean isPalindrome(String s, int i, int j) {\\n while (i < j) {\\n if (s.charAt(i) != s.charAt(j)) {\\n return false;\\n }\\n i++;\\n j--;\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n bool isPalindrome(string& s, int i, int j) {\\n while (i < j) {\\n if (s[i] != s[j]) {\\n return false;\\n }\\n i++;\\n j--;\\n }\\n return true;\\n }\\n\\npublic:\\n bool validPalindrome(string s) {\\n int i = 0, j = s.size() - 1;\\n while (i < j) {\\n if (s[i] != s[j]) {\\n // 删除 s[i] 或者 s[j]\\n return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);\\n }\\n i++;\\n j--;\\n }\\n return true; // s 本身就是回文串\\n }\\n};\\n
\\n###c
\\nbool isPalindrome(char* s, int i, int j) {\\n while (i < j) {\\n if (s[i] != s[j]) {\\n return false;\\n }\\n i++;\\n j--;\\n }\\n return true;\\n}\\n\\nbool validPalindrome(char* s) {\\n int i = 0, j = strlen(s) - 1;\\n while (i < j) {\\n if (s[i] != s[j]) {\\n // 删除 s[i] 或者 s[j]\\n return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);\\n }\\n i++;\\n j--;\\n }\\n return true; // s 本身就是回文串\\n}\\n
\\n###go
\\nfunc isPalindrome(s string) bool {\\n i, j := 0, len(s)-1\\n for i < j {\\n if s[i] != s[j] {\\n return false\\n }\\n i++\\n j--\\n }\\n return true\\n}\\n\\nfunc validPalindrome(s string) bool {\\n i, j := 0, len(s)-1\\n for i < j {\\n if s[i] != s[j] {\\n // 删除 s[i] 或者 s[j](注意 Go 的切片是 O(1) 的,不会生成新字符串)\\n return isPalindrome(s[i+1:j+1]) || isPalindrome(s[i:j])\\n }\\n i++\\n j--\\n }\\n return true // s 本身就是回文串\\n}\\n
\\n###js
\\nvar isPalindrome = function(s, i, j) {\\n while (i < j) {\\n if (s[i] !== s[j]) {\\n return false;\\n }\\n i++;\\n j--;\\n }\\n return true;\\n};\\n\\nvar validPalindrome = function(s) {\\n let i = 0, j = s.length - 1;\\n while (i < j) {\\n if (s[i] !== s[j]) {\\n // 删除 s[i] 或者 s[j]\\n return isPalindrome(s, i + 1, j) || isPalindrome(s, i, j - 1);\\n }\\n i++;\\n j--;\\n }\\n return true; // s 本身就是回文串\\n};\\n
\\n###rust
\\nimpl Solution {\\n fn is_palindrome(s: &[u8]) -> bool {\\n let mut i = 0;\\n let mut j = s.len() - 1;\\n while i < j {\\n if s[i] != s[j] {\\n return false;\\n }\\n i += 1;\\n j -= 1;\\n }\\n true\\n }\\n\\n pub fn valid_palindrome(s: String) -> bool {\\n let s = s.as_bytes();\\n let mut i = 0;\\n let mut j = s.len() - 1;\\n while i < j {\\n if s[i] != s[j] {\\n // 删除 s[i] 或者 s[j]\\n return Self::is_palindrome(&s[i + 1..=j]) || Self::is_palindrome(&s[i..j]);\\n }\\n i += 1;\\n j -= 1;\\n }\\n true // s 本身就是回文串\\n }\\n}\\n
\\n更多相似题目,见下面贪心题单中的「§3.2 回文串贪心」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"如果不能删字母,判断 $s[i]=s[n-1-i]$ 是否对每个 $i$ 成立即可。其中 $n$ 是 $s$ 的长度。 如果能删一个字母,分类讨论:\\n\\n如果 $s[0]\\\\ne s[n-1]$,那么必须删字母,可以删 $s[0]$ 或 $s[n-1]$,问题变成判断剩余子串(在不能删字母的情况下)是不是回文的。\\n如果 $s[0] = s[n-1]$,是否需要删字母呢?比如 $s=\\\\texttt{aabcba}$ 满足 $s[0] = s[n-1]$,并且删掉 $s[0]$ 后可以得到回文串 $\\\\texttt{abcba}$。但如果删除 $s[0…","guid":"https://leetcode.cn/problems/valid-palindrome-ii//solution/tan-xin-wei-shi-yao-xiang-deng-de-shi-ho-wtll","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-21T02:17:25.052Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-从栈中取出 K 个硬币的最大面值和🔴","url":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles/","content":"一张桌子上总共有 n
个硬币 栈 。每个栈有 正整数 个带面值的硬币。
每一次操作中,你可以从任意一个栈的 顶部 取出 1 个硬币,从栈中移除它,并放入你的钱包里。
\\n\\n给你一个列表 piles
,其中 piles[i]
是一个整数数组,分别表示第 i
个栈里 从顶到底 的硬币面值。同时给你一个正整数 k
,请你返回在 恰好 进行 k
次操作的前提下,你钱包里硬币面值之和 最大为多少 。
\\n\\n
示例 1:
\\n\\n输入:piles = [[1,100,3],[7,8,9]], k = 2\\n输出:101\\n解释:\\n上图展示了几种选择 k 个硬币的不同方法。\\n我们可以得到的最大面值为 101 。\\n\\n\\n
示例 2:
\\n\\n输入:piles = [[100],[100],[100],[100],[100],[100],[1,1,1,1,1,1,700]], k = 7\\n输出:706\\n解释:\\n如果我们所有硬币都从最后一个栈中取,可以得到最大面值和。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == piles.length
1 <= n <= 1000
1 <= piles[i][j] <= 105
1 <= k <= sum(piles[i].length) <= 2000
我们定义一个变量 $\\\\textit{d}$ 来记录当前最小的距离,初始时 $\\\\textit{d}=\\\\infty$。然后我们遍历数组,对于每个元素 $x$,我们计算 $y=|x|$,如果 $y \\\\lt d$ 或者 $y=d$ 且 $x \\\\gt \\\\textit{ans}$,我们就更新答案 $\\\\textit{ans}=x$ 和 $\\\\textit{d}=y$。
\\n遍历结束后返回答案即可。
\\n###python
\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n ans, d = 0, inf\\n for x in nums:\\n if (y := abs(x)) < d or (y == d and x > ans):\\n ans, d = x, y\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int findClosestNumber(int[] nums) {\\n int ans = 0, d = 1 << 30;\\n for (int x : nums) {\\n int y = Math.abs(x);\\n if (y < d || (y == d && x > ans)) {\\n ans = x;\\n d = y;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int findClosestNumber(vector<int>& nums) {\\n int ans = 0, d = 1 << 30;\\n for (int x : nums) {\\n int y = abs(x);\\n if (y < d || (y == d && x > ans)) {\\n ans = x;\\n d = y;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc findClosestNumber(nums []int) int {\\nans, d := 0, 1<<30\\nfor _, x := range nums {\\nif y := abs(x); y < d || (y == d && x > ans) {\\nans, d = x, y\\n}\\n}\\nreturn ans\\n}\\n\\nfunc abs(x int) int {\\nif x < 0 {\\nreturn -x\\n}\\nreturn x\\n}\\n
\\n###ts
\\nfunction findClosestNumber(nums: number[]): number {\\n let [ans, d] = [0, 1 << 30];\\n for (const x of nums) {\\n const y = Math.abs(x);\\n if (y < d || (y == d && x > ans)) {\\n [ans, d] = [x, y];\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们定义一个变量 $\\\\textit{d}$ 来记录当前最小的距离,初始时 $\\\\textit{d}=\\\\infty$。然后我们遍历数组,对于每个元素 $x$,我们计算 $y=|x|$,如果 $y \\\\lt d$ 或者 $y=d$ 且 $x \\\\gt \\\\textit{ans}$,我们就更新答案 $\\\\textit{ans}=x$ 和 $\\\\textit{d}=y$。\\n\\n遍历结束后返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def findClosestNumber(self, nums: List[int…","guid":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/python3javacgotypescript-yi-ti-yi-jie-yi-l57o","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-20T00:53:30.962Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-找到最接近 0 的数字🟢","url":"https://leetcode.cn/problems/find-closest-number-to-zero/","content":"给你一个长度为 n
的整数数组 nums
,请你返回 nums
中最 接近 0
的数字。如果有多个答案,请你返回它们中的 最大值 。
\\n\\n
示例 1:
\\n\\n输入:nums = [-4,-2,1,4,8]\\n输出:1\\n解释:\\n-4 到 0 的距离为 |-4| = 4 。\\n-2 到 0 的距离为 |-2| = 2 。\\n1 到 0 的距离为 |1| = 1 。\\n4 到 0 的距离为 |4| = 4 。\\n8 到 0 的距离为 |8| = 8 。\\n所以,数组中距离 0 最近的数字为 1 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [2,-1,1]\\n输出:1\\n解释:1 和 -1 都是距离 0 最近的数字,所以返回较大值 1 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 1000
-105 <= nums[i] <= 105
Alice 在给 Bob 用手机打字。数字到字母的 对应 如下图所示。
\\n\\n为了 打出 一个字母,Alice 需要 按 对应字母 i
次,i
是该字母在这个按键上所处的位置。
\'s\'
,Alice 需要按 \'7\'
四次。类似的, Alice 需要按 \'5\'
两次得到字母 \'k\'
。\'0\'
和 \'1\'
不映射到任何字母,所以 Alice 不 使用它们。但是,由于传输的错误,Bob 没有收到 Alice 打字的字母信息,反而收到了 按键的字符串信息 。
\\n\\n\\"bob\\"
,Bob 将收到字符串 \\"2266622\\"
。给你一个字符串 pressedKeys
,表示 Bob 收到的字符串,请你返回 Alice 总共可能发出多少种文字信息 。
由于答案可能很大,将它对 109 + 7
取余 后返回。
\\n\\n
示例 1:
\\n\\n输入:pressedKeys = \\"22233\\"\\n输出:8\\n解释:\\nAlice 可能发出的文字信息包括:\\n\\"aaadd\\", \\"abdd\\", \\"badd\\", \\"cdd\\", \\"aaae\\", \\"abe\\", \\"bae\\" 和 \\"ce\\" 。\\n由于总共有 8 种可能的信息,所以我们返回 8 。\\n\\n\\n
示例 2:
\\n\\n输入:pressedKeys = \\"222222222222222222222222222222222222\\"\\n输出:82876089\\n解释:\\n总共有 2082876103 种 Alice 可能发出的文字信息。\\n由于我们需要将答案对 109 + 7 取余,所以我们返回 2082876103 % (109 + 7) = 82876089 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= pressedKeys.length <= 105
pressedKeys
只包含数字 \'2\'
到 \'9\'
。给你一个整数数组 nums
和一个 正 整数 k
。
定义长度为 2 * x
的序列 seq
的 值 为:
(seq[0] OR seq[1] OR ... OR seq[x - 1]) XOR (seq[x] OR seq[x + 1] OR ... OR seq[2 * x - 1])
.请你求出 nums
中所有长度为 2 * k
的 子序列 的 最大值 。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,6,7], k = 1
\\n\\n输出:5
\\n\\n解释:
\\n\\n子序列 [2, 7]
的值最大,为 2 XOR 7 = 5
。
示例 2:
\\n\\n输入:nums = [4,2,5,6,7], k = 2
\\n\\n输出:2
\\n\\n解释:
\\n\\n子序列 [4, 5, 6, 7]
的值最大,为 (4 OR 5) XOR (6 OR 7) = 2
。
\\n\\n
提示:
\\n\\n2 <= nums.length <= 400
1 <= nums[i] < 27
1 <= k <= nums.length / 2
给你一个 非负 整数数组 nums
和一个整数 k
。
如果一个数组中所有元素的按位或运算 OR
的值 至少 为 k
,那么我们称这个数组是 特别的 。
请你返回 nums
中 最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3], k = 2
\\n\\n输出:1
\\n\\n解释:
\\n\\n子数组 [3]
的按位 OR
值为 3
,所以我们返回 1
。
示例 2:
\\n\\n输入:nums = [2,1,8], k = 10
\\n\\n输出:3
\\n\\n解释:
\\n\\n子数组 [2,1,8]
的按位 OR
值为 11
,所以我们返回 3
。
示例 3:
\\n\\n输入:nums = [1,2], k = 0
\\n\\n输出:1
\\n\\n解释:
\\n\\n子数组 [1]
的按位 OR
值为 1
,所以我们返回 1
。
\\n\\n
提示:
\\n\\n1 <= nums.length <= 2 * 105
0 <= nums[i] <= 109
0 <= k <= 109
我们可以发现,如果我们固定子数组的左端点,随着右端点向右移动,子数组的按位或值只会增大,不会减小。因此我们可以使用双指针的方法,维护一个满足条件的子数组。
\\n具体地,我们使用两个指针 $i$ 和 $j$ 分别表示子数组的左右端点,初始时两个指针都位于数组的第一个元素。用一个变量 $s$ 表示子数组的按位或值,初始时 $s$ 的值为 $0$。我们还需要维护一个长度为 $32$ 的数组 $cnt$,表示子数组中每个元素的二进制表示中每一位的出现次数。
\\n在每一步操作中,我们将 $j$ 向右移动一位,更新 $s$ 和 $cnt$。如果 $s$ 的值大于等于 $k$,我们不断更新子数组的最小长度,并将 $i$ 向右移动一位,直到 $s$ 的值小于 $k$。在这个过程中,我们也需要更新 $s$ 和 $cnt$。
\\n最后,我们返回最小长度,如果不存在满足条件的子数组,则返回 $-1$。
\\n###python
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n cnt = [0] * 32\\n ans = n + 1\\n s = i = 0\\n for j, x in enumerate(nums):\\n s |= x\\n for h in range(32):\\n if x >> h & 1:\\n cnt[h] += 1\\n while s >= k and i <= j:\\n ans = min(ans, j - i + 1)\\n y = nums[i]\\n for h in range(32):\\n if y >> h & 1:\\n cnt[h] -= 1\\n if cnt[h] == 0:\\n s ^= 1 << h\\n i += 1\\n return -1 if ans > n else ans\\n
\\n###java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int[] cnt = new int[32];\\n int ans = n + 1;\\n for (int i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n for (int h = 0; h < 32; ++h) {\\n if ((nums[j] >> h & 1) == 1) {\\n ++cnt[h];\\n }\\n }\\n for (; s >= k && i <= j; ++i) {\\n ans = Math.min(ans, j - i + 1);\\n for (int h = 0; h < 32; ++h) {\\n if ((nums[i] >> h & 1) == 1) {\\n if (--cnt[h] == 0) {\\n s ^= 1 << h;\\n }\\n }\\n }\\n }\\n }\\n return ans > n ? -1 : ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int n = nums.size();\\n int cnt[32]{};\\n int ans = n + 1;\\n for (int i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n for (int h = 0; h < 32; ++h) {\\n if ((nums[j] >> h & 1) == 1) {\\n ++cnt[h];\\n }\\n }\\n for (; s >= k && i <= j; ++i) {\\n ans = min(ans, j - i + 1);\\n for (int h = 0; h < 32; ++h) {\\n if ((nums[i] >> h & 1) == 1) {\\n if (--cnt[h] == 0) {\\n s ^= 1 << h;\\n }\\n }\\n }\\n }\\n }\\n return ans > n ? -1 : ans;\\n }\\n};\\n
\\n###go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\nn := len(nums)\\ncnt := [32]int{}\\nans := n + 1\\ns, i := 0, 0\\nfor j, x := range nums {\\ns |= x\\nfor h := 0; h < 32; h++ {\\nif x>>h&1 == 1 {\\ncnt[h]++\\n}\\n}\\nfor ; s >= k && i <= j; i++ {\\nans = min(ans, j-i+1)\\nfor h := 0; h < 32; h++ {\\nif nums[i]>>h&1 == 1 {\\ncnt[h]--\\nif cnt[h] == 0 {\\ns ^= 1 << h\\n}\\n}\\n}\\n}\\n}\\nif ans == n+1 {\\nreturn -1\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction minimumSubarrayLength(nums: number[], k: number): number {\\n const n = nums.length;\\n let ans = n + 1;\\n const cnt: number[] = new Array<number>(32).fill(0);\\n for (let i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n for (let h = 0; h < 32; ++h) {\\n if (((nums[j] >> h) & 1) === 1) {\\n ++cnt[h];\\n }\\n }\\n for (; s >= k && i <= j; ++i) {\\n ans = Math.min(ans, j - i + 1);\\n for (let h = 0; h < 32; ++h) {\\n if (((nums[i] >> h) & 1) === 1 && --cnt[h] === 0) {\\n s ^= 1 << h;\\n }\\n }\\n }\\n }\\n return ans === n + 1 ? -1 : ans;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {\\n let n = nums.len();\\n let mut cnt = vec![0; 32];\\n let mut ans = n as i32 + 1;\\n let mut s = 0;\\n let mut i = 0;\\n\\n for (j, &x) in nums.iter().enumerate() {\\n s |= x;\\n for h in 0..32 {\\n if (x >> h) & 1 == 1 {\\n cnt[h] += 1;\\n }\\n }\\n\\n while s >= k && i <= j {\\n ans = ans.min((j - i + 1) as i32);\\n let y = nums[i];\\n for h in 0..32 {\\n if (y >> h) & 1 == 1 {\\n cnt[h] -= 1;\\n if cnt[h] == 0 {\\n s ^= 1 << h;\\n }\\n }\\n }\\n i += 1;\\n }\\n }\\n if ans > n as i32 { -1 } else { ans }\\n }\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log M)$,空间复杂度 $O(\\\\log M)$,其中 $n$ 和 $M$ 分别是数组的长度和数组中元素的最大值。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:双指针 + 计数 我们可以发现,如果我们固定子数组的左端点,随着右端点向右移动,子数组的按位或值只会增大,不会减小。因此我们可以使用双指针的方法,维护一个满足条件的子数组。\\n\\n具体地,我们使用两个指针 $i$ 和 $j$ 分别表示子数组的左右端点,初始时两个指针都位于数组的第一个元素。用一个变量 $s$ 表示子数组的按位或值,初始时 $s$ 的值为 $0$。我们还需要维护一个长度为 $32$ 的数组 $cnt$,表示子数组中每个元素的二进制表示中每一位的出现次数。\\n\\n在每一步操作中,我们将 $j$ 向右移动一位,更新 $s$ 和 $cnt$。如果…","guid":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i//solution/python3javacgotypescript-yi-ti-yi-jie-sh-xd91","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-16T00:39:33.868Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-或值至少 K 的最短子数组 I🟢","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i/","content":"给你一个 非负 整数数组 nums
和一个整数 k
。
如果一个数组中所有元素的按位或运算 OR
的值 至少 为 k
,那么我们称这个数组是 特别的 。
请你返回 nums
中 最短特别非空 子数组的长度,如果特别子数组不存在,那么返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3], k = 2
\\n\\n输出:1
\\n\\n解释:
\\n\\n子数组 [3]
的按位 OR
值为 3
,所以我们返回 1
。
注意,[2]
也是一个特别子数组。
示例 2:
\\n\\n输入:nums = [2,1,8], k = 10
\\n\\n输出:3
\\n\\n解释:
\\n\\n子数组 [2,1,8]
的按位 OR
值为 11
,所以我们返回 3
。
示例 3:
\\n\\n输入:nums = [1,2], k = 0
\\n\\n输出:1
\\n\\n解释:
\\n\\n子数组 [1]
的按位 OR
值为 1
,所以我们返回 1
。
\\n\\n
提示:
\\n\\n1 <= nums.length <= 50
0 <= nums[i] <= 50
0 <= k < 64
我们可以使用优先队列(小根堆)来模拟这个过程。
\\n具体地,我们先将数组中的元素加入优先队列 $pq$ 中。然后我们不断地从优先队列中取出两个最小的元素 $x$ 和 $y$,将 $\\\\min(x, y) \\\\times 2 + \\\\max(x, y)$ 放回优先队列中。每次操作后,我们将操作次数加一。当队列中的元素个数小于 $2$ 或者队列中的最小元素大于等于 $k$ 时,我们停止操作。
\\n###python
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n heapify(nums)\\n ans = 0\\n while len(nums) > 1 and nums[0] < k:\\n x, y = heappop(nums), heappop(nums)\\n heappush(nums, min(x, y) * 2 + max(x, y))\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n PriorityQueue<Long> pq = new PriorityQueue<>();\\n for (int x : nums) {\\n pq.offer((long) x);\\n }\\n int ans = 0;\\n for (; pq.size() > 1 && pq.peek() < k; ++ans) {\\n long x = pq.poll(), y = pq.poll();\\n pq.offer(Math.min(x, y) * 2 + Math.max(x, y));\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n using ll = long long;\\n priority_queue<ll, vector<ll>, greater<ll>> pq;\\n for (int x : nums) {\\n pq.push(x);\\n }\\n int ans = 0;\\n for (; pq.size() > 1 && pq.top() < k; ++ans) {\\n ll x = pq.top();\\n pq.pop();\\n ll y = pq.top();\\n pq.pop();\\n pq.push(min(x, y) * 2 + max(x, y));\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int, k int) (ans int) {\\npq := &hp{nums}\\nheap.Init(pq)\\nfor ; pq.Len() > 1 && pq.IntSlice[0] < k; ans++ {\\nx, y := heap.Pop(pq).(int), heap.Pop(pq).(int)\\nheap.Push(pq, min(x, y)*2+max(x, y))\\n}\\nreturn\\n}\\n\\ntype hp struct{ sort.IntSlice }\\n\\nfunc (h *hp) Less(i, j int) bool { return h.IntSlice[i] < h.IntSlice[j] }\\nfunc (h *hp) Pop() interface{} {\\nold := h.IntSlice\\nn := len(old)\\nx := old[n-1]\\nh.IntSlice = old[0 : n-1]\\nreturn x\\n}\\nfunc (h *hp) Push(x interface{}) {\\nh.IntSlice = append(h.IntSlice, x.(int))\\n}\\n
\\n###ts
\\nfunction minOperations(nums: number[], k: number): number {\\n const pq = new MinPriorityQueue();\\n for (const x of nums) {\\n pq.enqueue(x);\\n }\\n let ans = 0;\\n for (; pq.size() > 1 && pq.front().element < k; ++ans) {\\n const x = pq.dequeue().element;\\n const y = pq.dequeue().element;\\n pq.enqueue(Math.min(x, y) * 2 + Math.max(x, y));\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 为数组长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~c
\\n","description":"方法一:优先队列(小根堆) 我们可以使用优先队列(小根堆)来模拟这个过程。\\n\\n具体地,我们先将数组中的元素加入优先队列 $pq$ 中。然后我们不断地从优先队列中取出两个最小的元素 $x$ 和 $y$,将 $\\\\min(x, y) \\\\times 2 + \\\\max(x, y)$ 放回优先队列中。每次操作后,我们将操作次数加一。当队列中的元素个数小于 $2$ 或者队列中的最小元素大于等于 $k$ 时,我们停止操作。\\n\\n###python\\n\\nclass Solution:\\n def minOperations(self, nums: List[int], k…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/python3javacgotypescript-yi-ti-yi-jie-yo-2p03","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-15T01:03:16.098Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-超过阈值的最少操作数 II🟡","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii/","content":"给你一个下标从 0 开始的整数数组 nums
和一个整数 k
。
一次操作中,你将执行:
\\n\\nnums
中最小的两个整数 x
和 y
。x
和 y
从 nums
中删除。min(x, y) * 2 + max(x, y)
添加到数组中的任意位置。注意,只有当 nums
至少包含两个元素时,你才可以执行以上操作。
你需要使数组中的所有元素都大于或等于 k
,请你返回需要的 最少 操作次数。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,11,10,1,3], k = 10\\n输出:2\\n解释:第一次操作中,我们删除元素 1 和 2 ,然后添加 1 * 2 + 2 到 nums 中,nums 变为 [4, 11, 10, 3] 。\\n第二次操作中,我们删除元素 3 和 4 ,然后添加 3 * 2 + 4 到 nums 中,nums 变为 [10, 11, 10] 。\\n此时,数组中的所有元素都大于等于 10 ,所以我们停止操作。\\n使数组中所有元素都大于等于 10 需要的最少操作次数为 2 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [1,1,2,4,9], k = 20\\n输出:4\\n解释:第一次操作后,nums 变为 [2, 4, 9, 3] 。\\n第二次操作后,nums 变为 [7, 4, 9] 。\\n第三次操作后,nums 变为 [15, 9] 。\\n第四次操作后,nums 变为 [33] 。\\n此时,数组中的所有元素都大于等于 20 ,所以我们停止操作。\\n使数组中所有元素都大于等于 20 需要的最少操作次数为 4 。\\n\\n
\\n\\n
提示:
\\n\\n2 <= nums.length <= 2 * 105
1 <= nums[i] <= 109
1 <= k <= 109
k
。根据题意,如果节点 $\\\\textit{node}$ 的所有祖先节点总共做了 $f$ 次除二操作,那么节点 $\\\\textit{node}$ 的值变成 $\\\\lfloor \\\\dfrac{\\\\textit{coins}[\\\\textit{node}]}{2^f} \\\\rfloor$。令 $\\\\text{dfs}(\\\\textit{node}, f)$ 表示所有祖先节点总共做了 $f$ 次除二操作,以节点 $\\\\textit{node}$ 为根节点的子树可以获得的最大积分,$U$ 为节点 $\\\\textit{node}$ 的子节点集合:
\\n当 $\\\\textit{node}$ 进行操作一时,有 $\\\\text{dfs}(\\\\textit{node}, f) = \\\\sum_{\\\\textit{child} \\\\in U} \\\\text{dfs}(\\\\textit{child}, f) + \\\\lfloor \\\\dfrac{\\\\textit{coins}[\\\\textit{node}]}{2^f} \\\\rfloor - k$。
\\n当 $\\\\textit{node}$ 进行操作二时,有 $\\\\text{dfs}(\\\\textit{node}, f) = \\\\sum_{\\\\textit{child} \\\\in U} \\\\text{dfs}(\\\\textit{child}, f + 1) + \\\\lfloor \\\\dfrac{\\\\textit{coins}[\\\\textit{node}]}{2^{f+1}} \\\\rfloor$
\\n那么 $\\\\text{dfs}(\\\\textit{node}, f)$ 取以上两种情况的最大值。基于以上递推公式,答案可以通过深度优先搜索求解,同时为了不重复计算,使用 $\\\\textit{memo}$ 来记录求解过程的中间值。
\\n按照以上方法,$f$ 的取值范围取决于树的高度,最坏情况下可能有 $f = n - 1$。考虑到当一个子树的所有节点的金币数量都等于 $0$ 时,它可以获得的最大积分为 $0$,而当 $f \\\\ge 14$ 时,由于题目范围的 $\\\\textit{coins}$ 小于等于 $10^4$,而 $2^{14} \\\\gt 10^4$,所以此时 $\\\\textit{coins}$ 都等于 $0$。那么,在记忆化搜索过程中,当 $f + 1 \\\\ge 14$ 时,可以直接计算 $\\\\sum_{\\\\textit{child} \\\\in U} \\\\text{dfs}(\\\\textit{child}, f + 1) = 0$,不需要再进行搜索。
\\n###C++
\\nclass Solution {\\nprivate:\\n vector<vector<int>> memo;\\n vector<vector<int>> children;\\n\\npublic:\\n int dfs(int node, int parent, int f, vector<int> &coins, int k) {\\n if (memo[node][f] >= 0) {\\n return memo[node][f];\\n }\\n int res0 = (coins[node] >> f) - k, res1 = coins[node] >> (f + 1);\\n for (int child : children[node]) {\\n if (child == parent) {\\n continue;\\n }\\n res0 += dfs(child, node, f, coins, k);\\n if (f + 1 < 14) {\\n res1 += dfs(child, node, f + 1, coins, k);\\n }\\n }\\n return memo[node][f] = max(res0, res1);\\n }\\n\\n int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {\\n int n = coins.size();\\n children = vector<vector<int>>(n);\\n for (const auto &edge : edges) {\\n children[edge[0]].push_back(edge[1]);\\n children[edge[1]].push_back(edge[0]);\\n }\\n memo = vector<vector<int>>(n, vector<int>(14, -1));\\n return dfs(0, -1, 0, coins, k);\\n }\\n};\\n
\\n###Go
\\nfunc maximumPoints(edges [][]int, coins []int, k int) int {\\n n := len(coins)\\n children := make([][]int, n)\\n for _, edge := range edges {\\n u, v := edge[0], edge[1]\\n children[u] = append(children[u], v)\\n children[v] = append(children[v], u)\\n }\\n\\n memo := make([][]int, n)\\n for i := range memo {\\n memo[i] = make([]int, 14)\\n for j := range memo[i] {\\n memo[i][j] = -1\\n }\\n }\\n\\n var dfs func(node, parent, f int) int\\n dfs = func(node, parent, f int) int {\\n if memo[node][f] >= 0 {\\n return memo[node][f]\\n }\\n res0, res1 := (coins[node] >> f) - k, coins[node] >> (f + 1)\\n for _, child := range children[node] {\\n if child == parent {\\n continue\\n }\\n res0 += dfs(child, node, f)\\n if f+1 < 14 {\\n res1 += dfs(child, node, f+1)\\n }\\n }\\n memo[node][f] = max(res0, res1)\\n return memo[node][f]\\n }\\n\\n return dfs(0, -1, 0)\\n}\\n
\\n###Python
\\nclass Solution:\\n def maximumPoints(self, edges: List[List[int]], coins: List[int], k: int) -> int:\\n n = len(coins)\\n children = [[] for _ in range(n)]\\n for u, v in edges:\\n children[u].append(v)\\n children[v].append(u)\\n\\n @cache\\n def dfs(node, parent, f):\\n res0 = (coins[node] >> f) - k\\n res1 = coins[node] >> (f + 1) if f + 1 < 14 else 0\\n for child in children[node]:\\n if child == parent:\\n continue\\n res0 += dfs(child, node, f)\\n if f + 1 < 14:\\n res1 += dfs(child, node, f + 1)\\n return max(res0, res1)\\n\\n return dfs(0, -1, 0)\\n
\\n###Java
\\nclass Solution {\\n public int maximumPoints(int[][] edges, int[] coins, int k) {\\n int n = coins.length;\\n List<List<Integer>> children = new ArrayList<>();\\n for (int i = 0; i < n; i++) {\\n children.add(new ArrayList<>());\\n }\\n for (int[] edge : edges) {\\n children.get(edge[0]).add(edge[1]);\\n children.get(edge[1]).add(edge[0]);\\n }\\n int[][] memo = new int[n][14];\\n for (int i = 0; i < n; i++) {\\n Arrays.fill(memo[i], -1);\\n }\\n return dfs(0, -1, 0, coins, k, children, memo);\\n }\\n\\n private int dfs(int node, int parent, int f, int[] coins, int k, List<List<Integer>> children, int[][] memo) {\\n if (memo[node][f] != -1) {\\n return memo[node][f];\\n }\\n int res0 = (coins[node] >> f) - k;\\n int res1 = coins[node] >> (f + 1);\\n for (int child : children.get(node)) {\\n if (child == parent) {\\n continue;\\n }\\n res0 += dfs(child, node, f, coins, k, children, memo);\\n if (f + 1 < 14) {\\n res1 += dfs(child, node, f + 1, coins, k, children, memo);\\n }\\n }\\n memo[node][f] = Math.max(res0, res1);\\n return memo[node][f];\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaximumPoints(int[][] edges, int[] coins, int k) {\\n int n = coins.Length;\\n var children = new List<List<int>>();\\n for (int i = 0; i < n; i++) {\\n children.Add(new List<int>());\\n }\\n foreach (var edge in edges) {\\n children[edge[0]].Add(edge[1]);\\n children[edge[1]].Add(edge[0]);\\n }\\n var memo = new int[n, 14];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < 14; j++) {\\n memo[i, j] = -1;\\n }\\n }\\n return Dfs(0, -1, 0, coins, k, children, memo);\\n }\\n\\n private int Dfs(int node, int parent, int f, int[] coins, int k, List<List<int>> children, int[,] memo) {\\n if (memo[node, f] != -1) {\\n return memo[node, f];\\n }\\n int res0 = (coins[node] >> f) - k;\\n int res1 = coins[node] >> (f + 1);\\n foreach (int child in children[node]) {\\n if (child == parent) {\\n continue;\\n }\\n res0 += Dfs(child, node, f, coins, k, children, memo);\\n if (f + 1 < 14) {\\n res1 += Dfs(child, node, f + 1, coins, k, children, memo);\\n }\\n }\\n memo[node, f] = Math.Max(res0, res1);\\n return memo[node, f];\\n }\\n}\\n
\\n###JavaScript
\\nvar maximumPoints = function(edges, coins, k) {\\n const n = coins.length;\\n const children = Array.from({ length: n }, () => []);\\n for (const edge of edges) {\\n children[edge[0]].push(edge[1]);\\n children[edge[1]].push(edge[0]);\\n }\\n const memo = Array.from({ length: n }, () => Array(14).fill(-1));\\n\\n const dfs = (node, parent, f) => {\\n if (memo[node][f] >= 0) {\\n return memo[node][f];\\n }\\n let res0 = (coins[node] >> f) - k, res1 = coins[node] >> (f + 1);\\n for (const child of children[node]) {\\n if (child === parent) {\\n continue;\\n }\\n res0 += dfs(child, node, f);\\n if (f + 1 < 14) {\\n res1 += dfs(child, node, f + 1);\\n }\\n }\\n return memo[node][f] = Math.max(res0, res1);\\n };\\n\\n return dfs(0, -1, 0);\\n};\\n
\\n###TypeScript
\\nfunction maximumPoints(edges: number[][], coins: number[], k: number): number {\\n const n = coins.length;\\n const children: number[][] = Array.from({ length: n }, () => []);\\n for (const edge of edges) {\\n children[edge[0]].push(edge[1]);\\n children[edge[1]].push(edge[0]);\\n }\\n const memo: number[][] = Array.from({ length: n }, () => Array(14).fill(-1));\\n\\n const dfs = (node: number, parent: number, f: number): number => {\\n if (memo[node][f] >= 0) {\\n return memo[node][f];\\n }\\n let res0 = (coins[node] >> f) - k;\\n let res1 = coins[node] >> (f + 1);\\n for (const child of children[node]) {\\n if (child === parent) {\\n continue;\\n }\\n res0 += dfs(child, node, f);\\n if (f + 1 < 14) {\\n res1 += dfs(child, node, f + 1);\\n }\\n }\\n return memo[node][f] = Math.max(res0, res1);\\n };\\n\\n return dfs(0, -1, 0);\\n}\\n
\\n###C
\\nint dfs(int node, int parent, int f, int* coins, int k, int **children, int *childCount, int **memo) {\\n if (memo[node][f] != -1) {\\n return memo[node][f];\\n }\\n int res0 = (coins[node] >> f) - k;\\n int res1 = coins[node] >> (f + 1);\\n for (int i = 0; i < childCount[node]; ++i) {\\n int child = children[node][i];\\n if (child == parent) {\\n continue;\\n }\\n res0 += dfs(child, node, f, coins, k, children, childCount, memo);\\n if (f + 1 < 14) {\\n res1 += dfs(child, node, f + 1, coins, k, children, childCount, memo);\\n }\\n }\\n return memo[node][f] = fmax(res0, res1);\\n}\\n\\nint maximumPoints(int **edges, int edgesSize, int *edgesColSize, int *coins, int coinsSize, int k) {\\n int* childCount = (int*)malloc(coinsSize * sizeof(int));\\n memset(childCount, 0, coinsSize * sizeof(int));\\n for (int i = 0; i < edgesSize; ++i) {\\n int u = edges[i][0];\\n int v = edges[i][1];\\n childCount[u]++;\\n childCount[v]++;\\n }\\n\\n int** children = (int**)malloc(coinsSize * sizeof(int*));\\n for (int i = 0; i < coinsSize; ++i) {\\n children[i] = (int*)malloc(childCount[i] * sizeof(int));\\n }\\n memset(childCount, 0, coinsSize * sizeof(int));\\n for (int i = 0; i < edgesSize; ++i) {\\n int u = edges[i][0];\\n int v = edges[i][1];\\n children[u][childCount[u]++] = v;\\n children[v][childCount[v]++] = u;\\n }\\n\\n int **memo = (int **)malloc(coinsSize * sizeof(int *));\\n for (int i = 0; i < coinsSize; i++) {\\n memo[i] = (int *)malloc(14 * sizeof(int));\\n memset(memo[i], -1, 14 * sizeof(int));\\n }\\n int result = dfs(0, -1, 0, coins, k, children, childCount, memo);\\n for (int i = 0; i < coinsSize; ++i) {\\n free(children[i]);\\n free(memo[i]);\\n }\\n free(children);\\n free(memo);\\n free(childCount);\\n return result;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n fn dfs(node: usize, parent: Option<usize>, f: usize, coins: &Vec<i32>, k: i32, children: &Vec<Vec<usize>>, memo: &mut Vec<Vec<i32>>) -> i32 {\\n if memo[node][f] >= 0 {\\n return memo[node][f];\\n }\\n let mut res0 = (coins[node] >> f) - k;\\n let mut res1 = coins[node] >> (f + 1);\\n for &child in &children[node] {\\n if Some(child) == parent {\\n continue;\\n }\\n res0 += Solution::dfs(child, Some(node), f, coins, k, children, memo);\\n if f + 1 < 14 {\\n res1 += Solution::dfs(child, Some(node), f + 1, coins, k, children, memo);\\n }\\n }\\n memo[node][f] = res0.max(res1);\\n memo[node][f]\\n }\\n\\n pub fn maximum_points(edges: Vec<Vec<i32>>, coins: Vec<i32>, k: i32) -> i32 {\\n let n = coins.len();\\n let mut children = vec![vec![]; n];\\n for edge in edges {\\n children[edge[0] as usize].push(edge[1] as usize);\\n children[edge[1] as usize].push(edge[0] as usize);\\n }\\n let mut memo = vec![vec![-1; 14]; n];\\n Solution::dfs(0, None, 0, &coins, k, &children, &mut memo)\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times C)$,其中 $n$ 是节点的数目,$C = 14$ 取决于 $\\\\textit{coins}$ 的取值范围。
\\n空间复杂度:$O(n \\\\times C)$。
\\n我们只需要遍历一遍数组,统计小于 $k$ 的元素个数即可。
\\n###python
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return sum(x < k for x in nums)\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int ans = 0;\\n for (int x : nums) {\\n if (x < k) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n int ans = 0;\\n for (int x : nums) {\\n if (x < k) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int, k int) (ans int) {\\nfor _, x := range nums {\\nif x < k {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction minOperations(nums: number[], k: number): number {\\n return nums.filter(x => x < k).length;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:遍历计数 我们只需要遍历一遍数组,统计小于 $k$ 的元素个数即可。\\n\\n###python\\n\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return sum(x < k for x in nums)\\n\\n\\n###java\\n\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int ans = 0;\\n for (int…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i//solution/python3javacgotypescript-yi-ti-yi-jie-bi-6cuw","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-14T01:00:48.061Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-超过阈值的最少操作数 I🟢","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i/","content":"给你一个下标从 0 开始的整数数组 nums
和一个整数 k
。
一次操作中,你可以删除 nums
中的最小元素。
你需要使数组中的所有元素都大于或等于 k
,请你返回需要的 最少 操作次数。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,11,10,1,3], k = 10\\n输出:3\\n解释:第一次操作后,nums 变为 [2, 11, 10, 3] 。\\n第二次操作后,nums 变为 [11, 10, 3] 。\\n第三次操作后,nums 变为 [11, 10] 。\\n此时,数组中的所有元素都大于等于 10 ,所以我们停止操作。\\n使数组中所有元素都大于等于 10 需要的最少操作次数为 3 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [1,1,2,4,9], k = 1\\n输出:0\\n解释:数组中的所有元素都大于等于 1 ,所以不需要对 nums 做任何操作。\\n\\n
示例 3:
\\n\\n输入:nums = [1,1,2,4,9], k = 9\\n输出:4\\n解释:nums 中只有一个元素大于等于 9 ,所以需要执行 4 次操作。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 50
1 <= nums[i] <= 109
1 <= k <= 109
nums[i] >= k
的下标 i
存在。我们首先计算数组 $\\\\textit{nums}$ 的总和 $s$,然后遍历数组 $\\\\textit{nums}$ 的前 $n-1$ 个元素,用变量 $t$ 记录前缀和,如果 $t \\\\geq s - t$,则将答案加一。
\\n遍历结束后,返回答案即可。
\\n###python
\\nclass Solution:\\n def waysToSplitArray(self, nums: List[int]) -> int:\\n s = sum(nums)\\n ans = t = 0\\n for x in nums[:-1]:\\n t += x\\n ans += t >= s - t\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n long s = 0;\\n for (int x : nums) {\\n s += x;\\n }\\n long t = 0;\\n int ans = 0;\\n for (int i = 0; i + 1 < nums.length; ++i) {\\n t += nums[i];\\n ans += t >= s - t ? 1 : 0;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int waysToSplitArray(vector<int>& nums) {\\n long long s = accumulate(nums.begin(), nums.end(), 0LL);\\n long long t = 0;\\n int ans = 0;\\n for (int i = 0; i + 1 < nums.size(); ++i) {\\n t += nums[i];\\n ans += t >= s - t;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc waysToSplitArray(nums []int) (ans int) {\\nvar s, t int\\nfor _, x := range nums {\\ns += x\\n}\\nfor _, x := range nums[:len(nums)-1] {\\nt += x\\nif t >= s-t {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction waysToSplitArray(nums: number[]): number {\\n const s = nums.reduce((acc, cur) => acc + cur, 0);\\n let [ans, t] = [0, 0];\\n for (const x of nums.slice(0, -1)) {\\n t += x;\\n if (t >= s - t) {\\n ++ans;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{nums}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:前缀和 我们首先计算数组 $\\\\textit{nums}$ 的总和 $s$,然后遍历数组 $\\\\textit{nums}$ 的前 $n-1$ 个元素,用变量 $t$ 记录前缀和,如果 $t \\\\geq s - t$,则将答案加一。\\n\\n遍历结束后,返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def waysToSplitArray(self, nums: List[int]) -> int:\\n s = sum(nums)\\n ans = t = 0\\n for x in nums[:-1…","guid":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/python3javacgotypescript-yi-ti-yi-jie-qi-29nv","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-13T01:01:59.547Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-分割数组的方案数🟡","url":"https://leetcode.cn/problems/number-of-ways-to-split-array/","content":"给你一个下标从 0 开始长度为 n
的整数数组 nums
。
\\n如果以下描述为真,那么 nums
在下标 i
处有一个 合法的分割 :
i + 1
个元素的和 大于等于 剩下的 n - i - 1
个元素的和。i
的右边 至少有一个 元素,也就是说下标 i
满足 0 <= i < n - 1
。请你返回 nums
中的 合法分割 方案数。
\\n\\n
示例 1:
\\n\\n输入:nums = [10,4,-8,7]\\n输出:2\\n解释:\\n总共有 3 种不同的方案可以将 nums 分割成两个非空的部分:\\n- 在下标 0 处分割 nums 。那么第一部分为 [10] ,和为 10 。第二部分为 [4,-8,7] ,和为 3 。因为 10 >= 3 ,所以 i = 0 是一个合法的分割。\\n- 在下标 1 处分割 nums 。那么第一部分为 [10,4] ,和为 14 。第二部分为 [-8,7] ,和为 -1 。因为 14 >= -1 ,所以 i = 1 是一个合法的分割。\\n- 在下标 2 处分割 nums 。那么第一部分为 [10,4,-8] ,和为 6 。第二部分为 [7] ,和为 7 。因为 6 < 7 ,所以 i = 2 不是一个合法的分割。\\n所以 nums 中总共合法分割方案受为 2 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [2,3,1,0]\\n输出:2\\n解释:\\n总共有 2 种 nums 的合法分割:\\n- 在下标 1 处分割 nums 。那么第一部分为 [2,3] ,和为 5 。第二部分为 [1,0] ,和为 1 。因为 5 >= 1 ,所以 i = 1 是一个合法的分割。\\n- 在下标 2 处分割 nums 。那么第一部分为 [2,3,1] ,和为 6 。第二部分为 [0] ,和为 0 。因为 6 >= 0 ,所以 i = 2 是一个合法的分割。\\n\\n\\n
\\n\\n
提示:
\\n\\n2 <= nums.length <= 105
-105 <= nums[i] <= 105
对数组 nums
执行 按位与 相当于对数组 nums
中的所有整数执行 按位与 。
nums = [1, 5, 3]
来说,按位与等于 1 & 5 & 3 = 1
。nums = [7]
而言,按位与等于 7
。给你一个正整数数组 candidates
。计算 candidates
中的数字每种组合下 按位与 的结果。
返回按位与结果大于 0
的 最长 组合的长度。
\\n\\n
示例 1:
\\n\\n输入:candidates = [16,17,71,62,12,24,14]\\n输出:4\\n解释:组合 [16,17,62,24] 的按位与结果是 16 & 17 & 62 & 24 = 16 > 0 。\\n组合长度是 4 。\\n可以证明不存在按位与结果大于 0 且长度大于 4 的组合。\\n注意,符合长度最大的组合可能不止一种。\\n例如,组合 [62,12,24,14] 的按位与结果是 62 & 12 & 24 & 14 = 8 > 0 。\\n\\n\\n
示例 2:
\\n\\n输入:candidates = [8,8]\\n输出:2\\n解释:最长组合是 [8,8] ,按位与结果 8 & 8 = 8 > 0 。\\n组合长度是 2 ,所以返回 2 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= candidates.length <= 105
1 <= candidates[i] <= 107
我们可以直接模拟这个过程,定义一个变量 $\\\\textit{ans}$ 用于存储答案,定义一个变量 $\\\\textit{k}$ 用于表示当前位数,其中 $\\\\textit{k} = 1$ 表示个位数,而 $\\\\textit{k} = 10$ 表示十位数,以此类推。
\\n我们从个位数开始,对于每一位,我们分别计算 $\\\\textit{num1}$, $\\\\textit{num2}$ 和 $\\\\textit{num3}$ 的当前位数,取三者的最小值,然后将这个最小值乘以 $\\\\textit{k}$ 加到答案上。然后将 $\\\\textit{k}$ 乘以 10,继续计算下一位。
\\n最后返回答案即可。
\\n###python
\\nclass Solution:\\n def generateKey(self, num1: int, num2: int, num3: int) -> int:\\n ans, k = 0, 1\\n for _ in range(4):\\n x = min(num1 // k % 10, num2 // k % 10, num3 // k % 10)\\n ans += x * k\\n k *= 10\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int generateKey(int num1, int num2, int num3) {\\n int ans = 0, k = 1;\\n for (int i = 0; i < 4; ++i) {\\n int x = Math.min(Math.min(num1 / k % 10, num2 / k % 10), num3 / k % 10);\\n ans += x * k;\\n k *= 10;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int generateKey(int num1, int num2, int num3) {\\n int ans = 0, k = 1;\\n for (int i = 0; i < 4; ++i) {\\n int x = min({num1 / k % 10, num2 / k % 10, num3 / k % 10});\\n ans += x * k;\\n k *= 10;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc generateKey(num1 int, num2 int, num3 int) (ans int) {\\nk := 1\\nfor i := 0; i < 4; i++ {\\nx := min(min(num1/k%10, num2/k%10), num3/k%10)\\nans += x * k\\nk *= 10\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction generateKey(num1: number, num2: number, num3: number): number {\\n let [ans, k] = [0, 1];\\n for (let i = 0; i < 4; ++i) {\\n const x = Math.min(((num1 / k) | 0) % 10, ((num2 / k) | 0) % 10, ((num3 / k) | 0) % 10);\\n ans += x * k;\\n k *= 10;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(1)$,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们可以直接模拟这个过程,定义一个变量 $\\\\textit{ans}$ 用于存储答案,定义一个变量 $\\\\textit{k}$ 用于表示当前位数,其中 $\\\\textit{k} = 1$ 表示个位数,而 $\\\\textit{k} = 10$ 表示十位数,以此类推。\\n\\n我们从个位数开始,对于每一位,我们分别计算 $\\\\textit{num1}$, $\\\\textit{num2}$ 和 $\\\\textit{num3}$ 的当前位数,取三者的最小值,然后将这个最小值乘以 $\\\\textit{k}$ 加到答案上。然后将 $\\\\textit{k}$ 乘以 10…","guid":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/python3javacgotypescript-yi-ti-yi-jie-mo-utdi","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-10T23:50:19.579Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-求出数字答案🟢","url":"https://leetcode.cn/problems/find-the-key-of-the-numbers/","content":"给你三个 正 整数 num1
,num2
和 num3
。
数字 num1
,num2
和 num3
的数字答案 key
是一个四位数,定义如下:
key
的第 i
个数位(1 <= i <= 4
)为 num1
,num2
和 num3
第 i
个数位中的 最小 值。请你返回三个数字 没有 前导 0 的数字答案。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:num1 = 1, num2 = 10, num3 = 1000
\\n\\n输出:0
\\n\\n解释:
\\n\\n补前导 0 后,num1
变为 \\"0001\\"
,num2
变为 \\"0010\\"
,num3
保持不变,为 \\"1000\\"
。
key
的第 1
个数位为 min(0, 0, 1)
。key
的第 2
个数位为 min(0, 0, 0)
。key
的第 3
个数位为 min(0, 1, 0)
。key
的第 4
个数位为 min(1, 0, 0)
。所以数字答案为 \\"0000\\"
,也就是 0 。
示例 2:
\\n\\n输入: num1 = 987, num2 = 879, num3 = 798
\\n\\n输出:777
\\n示例 3:
\\n\\n输入:num1 = 1, num2 = 2, num3 = 3
\\n\\n输出:1
\\n\\n\\n
提示:
\\n\\n1 <= num1, num2, num3 <= 9999
题目实际上是求在 $\\\\textit{word1}$ 中,有多少个子串包含了 $\\\\textit{word2}$ 中的所有字符。我们可以使用滑动窗口来处理。
\\n首先,如果 $\\\\textit{word1}$ 的长度小于 $\\\\textit{word2}$ 的长度,那么 $\\\\textit{word1}$ 中不可能包含 $\\\\textit{word2}$ 的所有字符,直接返回 $0$。
\\n接下来,我们用一个哈希表或长度为 $26$ 的数组 $\\\\textit{cnt}$ 来统计 $\\\\textit{word2}$ 中的字符出现的次数。然后,我们用 $\\\\textit{need}$ 来记录还需要多少个字符才能满足条件,初始化为 $\\\\textit{cnt}$ 的长度。
\\n接着,我们用一个滑动窗口 $\\\\textit{win}$ 来记录当前窗口中的字符出现的次数。我们用 $\\\\textit{ans}$ 来记录满足条件的子串的个数,用 $\\\\textit{l}$ 来记录窗口的左边界。
\\n遍历 $\\\\textit{word1}$ 中的每个字符,对于当前字符 $c$,我们将其加入到 $\\\\textit{win}$ 中,如果 $\\\\textit{win}[c]$ 的值等于 $\\\\textit{cnt}[c]$,那么说明当前窗口中已经包含了 $\\\\textit{word2}$ 中的所有字符之一,那么 $\\\\textit{need}$ 减一。如果 $\\\\textit{need}$ 等于 $0$,说明当前窗口中包含了 $\\\\textit{word2}$ 中的所有字符,我们需要缩小窗口的左边界,直到 $\\\\textit{need}$ 大于 $0$。具体地,如果 $\\\\textit{win}[\\\\textit{word1}[l]]$ 等于 $\\\\textit{cnt}[\\\\textit{word1}[l]]$,那么说明当前窗口中包含了 $\\\\textit{word2}$ 中的所有字符之一,那么缩小窗口的左边界之后,就不满足条件了,所以 $\\\\textit{need}$ 加一,同时 $\\\\textit{win}[\\\\textit{word1}[l]]$ 减一。然后,我们将 $\\\\textit{l}$ 加一。此时窗口为 $[l, r]$,那么对于任意 $0 \\\\leq l\' \\\\lt l$,$[l\', r]$ 都是满足条件的子串,一共有 $l$ 个,我们累加到答案中。
\\n遍历完 $\\\\textit{word1}$ 中的所有字符之后,我们就得到了答案。
\\n###python
\\nclass Solution:\\n def validSubstringCount(self, word1: str, word2: str) -> int:\\n if len(word1) < len(word2):\\n return 0\\n cnt = Counter(word2)\\n need = len(cnt)\\n ans = l = 0\\n win = Counter()\\n for c in word1:\\n win[c] += 1\\n if win[c] == cnt[c]:\\n need -= 1\\n while need == 0:\\n if win[word1[l]] == cnt[word1[l]]:\\n need += 1\\n win[word1[l]] -= 1\\n l += 1\\n ans += l\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n if (word1.length() < word2.length()) {\\n return 0;\\n }\\n int[] cnt = new int[26];\\n int need = 0;\\n for (int i = 0; i < word2.length(); ++i) {\\n if (++cnt[word2.charAt(i) - \'a\'] == 1) {\\n ++need;\\n }\\n }\\n long ans = 0;\\n int[] win = new int[26];\\n for (int l = 0, r = 0; r < word1.length(); ++r) {\\n int c = word1.charAt(r) - \'a\';\\n if (++win[c] == cnt[c]) {\\n --need;\\n }\\n while (need == 0) {\\n c = word1.charAt(l) - \'a\';\\n if (win[c] == cnt[c]) {\\n ++need;\\n }\\n --win[c];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string word1, string word2) {\\n if (word1.size() < word2.size()) {\\n return 0;\\n }\\n int cnt[26]{};\\n int need = 0;\\n for (char& c : word2) {\\n if (++cnt[c - \'a\'] == 1) {\\n ++need;\\n }\\n }\\n long long ans = 0;\\n int win[26]{};\\n int l = 0;\\n for (char& c : word1) {\\n int i = c - \'a\';\\n if (++win[i] == cnt[i]) {\\n --need;\\n }\\n while (need == 0) {\\n i = word1[l] - \'a\';\\n if (win[i] == cnt[i]) {\\n ++need;\\n }\\n --win[i];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc validSubstringCount(word1 string, word2 string) (ans int64) {\\nif len(word1) < len(word2) {\\nreturn 0\\n}\\ncnt := [26]int{}\\nneed := 0\\nfor _, c := range word2 {\\ncnt[c-\'a\']++\\nif cnt[c-\'a\'] == 1 {\\nneed++\\n}\\n}\\nwin := [26]int{}\\nl := 0\\nfor _, c := range word1 {\\ni := int(c - \'a\')\\nwin[i]++\\nif win[i] == cnt[i] {\\nneed--\\n}\\nfor need == 0 {\\ni = int(word1[l] - \'a\')\\nif win[i] == cnt[i] {\\nneed++\\n}\\nwin[i]--\\nl++\\n}\\nans += int64(l)\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction validSubstringCount(word1: string, word2: string): number {\\n if (word1.length < word2.length) {\\n return 0;\\n }\\n const cnt: number[] = Array(26).fill(0);\\n let need: number = 0;\\n for (const c of word2) {\\n if (++cnt[c.charCodeAt(0) - 97] === 1) {\\n ++need;\\n }\\n }\\n const win: number[] = Array(26).fill(0);\\n let [ans, l] = [0, 0];\\n for (const c of word1) {\\n const i = c.charCodeAt(0) - 97;\\n if (++win[i] === cnt[i]) {\\n --need;\\n }\\n while (need === 0) {\\n const j = word1[l].charCodeAt(0) - 97;\\n if (win[j] === cnt[j]) {\\n ++need;\\n }\\n --win[j];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是 $\\\\textit{word1}$ 和 $\\\\textit{word2}$ 的长度。空间复杂度 $O(|\\\\Sigma|)$,其中 $\\\\Sigma$ 是字符集,这里是小写字母集合,所以空间复杂度是常数级别的。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:滑动窗口 题目实际上是求在 $\\\\textit{word1}$ 中,有多少个子串包含了 $\\\\textit{word2}$ 中的所有字符。我们可以使用滑动窗口来处理。\\n\\n首先,如果 $\\\\textit{word1}$ 的长度小于 $\\\\textit{word2}$ 的长度,那么 $\\\\textit{word1}$ 中不可能包含 $\\\\textit{word2}$ 的所有字符,直接返回 $0$。\\n\\n接下来,我们用一个哈希表或长度为 $26$ 的数组 $\\\\textit{cnt}$ 来统计 $\\\\textit{word2}$ 中的字符出现的次数。然后,我们用…","guid":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii//solution/python3javacgotypescript-yi-ti-yi-jie-hu-2f4s","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-10T00:47:39.521Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计重新排列后包含另一个字符串的子字符串数目 II🔴","url":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii/","content":"给你两个字符串 word1
和 word2
。
如果一个字符串 x
重新排列后,word2
是重排字符串的 前缀 ,那么我们称字符串 x
是 合法的 。
请你返回 word1
中 合法 子字符串 的数目。
注意 ,这个问题中的内存限制比其他题目要 小 ,所以你 必须 实现一个线性复杂度的解法。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:word1 = \\"bcca\\", word2 = \\"abc\\"
\\n\\n输出:1
\\n\\n解释:
\\n\\n唯一合法的子字符串是 \\"bcca\\"
,可以重新排列得到 \\"abcc\\"
,\\"abc\\"
是它的前缀。
示例 2:
\\n\\n输入:word1 = \\"abcabc\\", word2 = \\"abc\\"
\\n\\n输出:10
\\n\\n解释:
\\n\\n除了长度为 1 和 2 的所有子字符串都是合法的。
\\n示例 3:
\\n\\n输入:word1 = \\"abcabc\\", word2 = \\"aaabc\\"
\\n\\n输出:0
\\n\\n\\n
解释:
\\n\\n1 <= word1.length <= 106
1 <= word2.length <= 104
word1
和 word2
都只包含小写英文字母。题目实际上是求在 $\\\\textit{word1}$ 中,有多少个子串包含了 $\\\\textit{word2}$ 中的所有字符。我们可以使用滑动窗口来处理。
\\n首先,如果 $\\\\textit{word1}$ 的长度小于 $\\\\textit{word2}$ 的长度,那么 $\\\\textit{word1}$ 中不可能包含 $\\\\textit{word2}$ 的所有字符,直接返回 $0$。
\\n接下来,我们用一个哈希表或长度为 $26$ 的数组 $\\\\textit{cnt}$ 来统计 $\\\\textit{word2}$ 中的字符出现的次数。然后,我们用 $\\\\textit{need}$ 来记录还需要多少个字符才能满足条件,初始化为 $\\\\textit{cnt}$ 的长度。
\\n接着,我们用一个滑动窗口 $\\\\textit{win}$ 来记录当前窗口中的字符出现的次数。我们用 $\\\\textit{ans}$ 来记录满足条件的子串的个数,用 $\\\\textit{l}$ 来记录窗口的左边界。
\\n遍历 $\\\\textit{word1}$ 中的每个字符,对于当前字符 $c$,我们将其加入到 $\\\\textit{win}$ 中,如果 $\\\\textit{win}[c]$ 的值等于 $\\\\textit{cnt}[c]$,那么说明当前窗口中已经包含了 $\\\\textit{word2}$ 中的所有字符之一,那么 $\\\\textit{need}$ 减一。如果 $\\\\textit{need}$ 等于 $0$,说明当前窗口中包含了 $\\\\textit{word2}$ 中的所有字符,我们需要缩小窗口的左边界,直到 $\\\\textit{need}$ 大于 $0$。具体地,如果 $\\\\textit{win}[\\\\textit{word1}[l]]$ 等于 $\\\\textit{cnt}[\\\\textit{word1}[l]]$,那么说明当前窗口中包含了 $\\\\textit{word2}$ 中的所有字符之一,那么缩小窗口的左边界之后,就不满足条件了,所以 $\\\\textit{need}$ 加一,同时 $\\\\textit{win}[\\\\textit{word1}[l]]$ 减一。然后,我们将 $\\\\textit{l}$ 加一。此时窗口为 $[l, r]$,那么对于任意 $0 \\\\leq l\' \\\\lt l$,$[l\', r]$ 都是满足条件的子串,一共有 $l$ 个,我们累加到答案中。
\\n遍历完 $\\\\textit{word1}$ 中的所有字符之后,我们就得到了答案。
\\n###python
\\nclass Solution:\\n def validSubstringCount(self, word1: str, word2: str) -> int:\\n if len(word1) < len(word2):\\n return 0\\n cnt = Counter(word2)\\n need = len(cnt)\\n ans = l = 0\\n win = Counter()\\n for c in word1:\\n win[c] += 1\\n if win[c] == cnt[c]:\\n need -= 1\\n while need == 0:\\n if win[word1[l]] == cnt[word1[l]]:\\n need += 1\\n win[word1[l]] -= 1\\n l += 1\\n ans += l\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n if (word1.length() < word2.length()) {\\n return 0;\\n }\\n int[] cnt = new int[26];\\n int need = 0;\\n for (int i = 0; i < word2.length(); ++i) {\\n if (++cnt[word2.charAt(i) - \'a\'] == 1) {\\n ++need;\\n }\\n }\\n long ans = 0;\\n int[] win = new int[26];\\n for (int l = 0, r = 0; r < word1.length(); ++r) {\\n int c = word1.charAt(r) - \'a\';\\n if (++win[c] == cnt[c]) {\\n --need;\\n }\\n while (need == 0) {\\n c = word1.charAt(l) - \'a\';\\n if (win[c] == cnt[c]) {\\n ++need;\\n }\\n --win[c];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string word1, string word2) {\\n if (word1.size() < word2.size()) {\\n return 0;\\n }\\n int cnt[26]{};\\n int need = 0;\\n for (char& c : word2) {\\n if (++cnt[c - \'a\'] == 1) {\\n ++need;\\n }\\n }\\n long long ans = 0;\\n int win[26]{};\\n int l = 0;\\n for (char& c : word1) {\\n int i = c - \'a\';\\n if (++win[i] == cnt[i]) {\\n --need;\\n }\\n while (need == 0) {\\n i = word1[l] - \'a\';\\n if (win[i] == cnt[i]) {\\n ++need;\\n }\\n --win[i];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc validSubstringCount(word1 string, word2 string) (ans int64) {\\nif len(word1) < len(word2) {\\nreturn 0\\n}\\ncnt := [26]int{}\\nneed := 0\\nfor _, c := range word2 {\\ncnt[c-\'a\']++\\nif cnt[c-\'a\'] == 1 {\\nneed++\\n}\\n}\\nwin := [26]int{}\\nl := 0\\nfor _, c := range word1 {\\ni := int(c - \'a\')\\nwin[i]++\\nif win[i] == cnt[i] {\\nneed--\\n}\\nfor need == 0 {\\ni = int(word1[l] - \'a\')\\nif win[i] == cnt[i] {\\nneed++\\n}\\nwin[i]--\\nl++\\n}\\nans += int64(l)\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction validSubstringCount(word1: string, word2: string): number {\\n if (word1.length < word2.length) {\\n return 0;\\n }\\n const cnt: number[] = Array(26).fill(0);\\n let need: number = 0;\\n for (const c of word2) {\\n if (++cnt[c.charCodeAt(0) - 97] === 1) {\\n ++need;\\n }\\n }\\n const win: number[] = Array(26).fill(0);\\n let [ans, l] = [0, 0];\\n for (const c of word1) {\\n const i = c.charCodeAt(0) - 97;\\n if (++win[i] === cnt[i]) {\\n --need;\\n }\\n while (need === 0) {\\n const j = word1[l].charCodeAt(0) - 97;\\n if (win[j] === cnt[j]) {\\n ++need;\\n }\\n --win[j];\\n ++l;\\n }\\n ans += l;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是 $\\\\textit{word1}$ 和 $\\\\textit{word2}$ 的长度。空间复杂度 $O(|\\\\Sigma|)$,其中 $\\\\Sigma$ 是字符集,这里是小写字母集合,所以空间复杂度是常数级别的。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:滑动窗口 题目实际上是求在 $\\\\textit{word1}$ 中,有多少个子串包含了 $\\\\textit{word2}$ 中的所有字符。我们可以使用滑动窗口来处理。\\n\\n首先,如果 $\\\\textit{word1}$ 的长度小于 $\\\\textit{word2}$ 的长度,那么 $\\\\textit{word1}$ 中不可能包含 $\\\\textit{word2}$ 的所有字符,直接返回 $0$。\\n\\n接下来,我们用一个哈希表或长度为 $26$ 的数组 $\\\\textit{cnt}$ 来统计 $\\\\textit{word2}$ 中的字符出现的次数。然后,我们用…","guid":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-i//solution/python3javacgotypescript-yi-ti-yi-jie-hu-t852","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-09T01:00:32.240Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计重新排列后包含另一个字符串的子字符串数目 I🟡","url":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-i/","content":"给你两个字符串 word1
和 word2
。
如果一个字符串 x
重新排列后,word2
是重排字符串的 前缀 ,那么我们称字符串 x
是 合法的 。
请你返回 word1
中 合法 子字符串 的数目。
\\n\\n
示例 1:
\\n\\n输入:word1 = \\"bcca\\", word2 = \\"abc\\"
\\n\\n输出:1
\\n\\n解释:
\\n\\n唯一合法的子字符串是 \\"bcca\\"
,可以重新排列得到 \\"abcc\\"
,\\"abc\\"
是它的前缀。
示例 2:
\\n\\n输入:word1 = \\"abcabc\\", word2 = \\"abc\\"
\\n\\n输出:10
\\n\\n解释:
\\n\\n除了长度为 1 和 2 的所有子字符串都是合法的。
\\n示例 3:
\\n\\n输入:word1 = \\"abcabc\\", word2 = \\"aaabc\\"
\\n\\n输出:0
\\n\\n\\n
解释:
\\n\\n1 <= word1.length <= 105
1 <= word2.length <= 104
word1
和 word2
都只包含小写英文字母。目标是让第一排或者第二排的所有数都相同,这个「所有」必然包含第一个骨牌中的数。所以要么都变成 $\\\\textit{tops}[0]$,要么都变成 $\\\\textit{bottoms}[0]$。计算这两种情况,取最小值。
\\n写一个函数 $\\\\text{minRot}(\\\\textit{target})$,表示都变成 $\\\\textit{target}$ 的最小旋转次数。我们要计算的是 $\\\\min(\\\\text{minRot}(\\\\textit{tops}[0]),\\\\text{minRot}(\\\\textit{bottoms}[0]))$。
\\n定义变量 $\\\\textit{toTop}$,表示把第一排(上半)都变成 $\\\\textit{target}$ 的最小旋转次数,初始值为 $0$。
\\n定义变量 $\\\\textit{toBottom}$,表示把第二排(下半)都变成 $\\\\textit{target}$ 的最小旋转次数,初始值为 $0$。
\\n遍历骨牌,设 $x=\\\\textit{tops}[i]$ 和 $y=\\\\textit{bottoms}[i]$,分类讨论:
\\n遍历结束,都变成 $\\\\textit{target}$ 的最小旋转次数为 $\\\\min(\\\\textit{toTop},\\\\textit{toBottom})$,即 $\\\\text{minRot}(\\\\textit{target})$ 的返回值。
\\n如果 $\\\\min(\\\\text{minRot}(\\\\textit{tops}[0]),\\\\text{minRot}(\\\\textit{bottoms}[0]))=\\\\infty$,说明两种情况都无法满足要求,返回 $-1$。
\\n###py
\\nclass Solution:\\n def minDominoRotations(self, tops: List[int], bottoms: List[int]) -> int:\\n def min_rot(target: int) -> int:\\n to_top = to_bottom = 0\\n for x, y in zip(tops, bottoms):\\n if x != target and y != target:\\n return inf\\n if x != target:\\n to_top += 1 # 把 y 旋转到上半\\n elif y != target:\\n to_bottom += 1 # 把 x 旋转到下半\\n return min(to_top, to_bottom)\\n\\n ans = min(min_rot(tops[0]), min_rot(bottoms[0]))\\n return -1 if ans == inf else ans\\n
\\n###java
\\nclass Solution {\\n public int minDominoRotations(int[] tops, int[] bottoms) {\\n int ans = Math.min(minRot(tops, bottoms, tops[0]), minRot(tops, bottoms, bottoms[0]));\\n return ans == Integer.MAX_VALUE ? -1 : ans;\\n }\\n\\n private int minRot(int[] tops, int[] bottoms, int target) {\\n int toTop = 0;\\n int toBottom = 0;\\n for (int i = 0; i < tops.length; i++) {\\n int x = tops[i];\\n int y = bottoms[i];\\n if (x != target && y != target) {\\n return Integer.MAX_VALUE;\\n }\\n if (x != target) {\\n toTop++; // 把 y 旋转到上半\\n } else if (y != target) {\\n toBottom++; // 把 x 旋转到下半\\n }\\n }\\n return Math.min(toTop, toBottom);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minDominoRotations(vector<int>& tops, vector<int>& bottoms) {\\n auto min_rot = [&](int target) -> int {\\n int to_top = 0, to_bottom = 0;\\n for (int i = 0; i < tops.size(); i++) {\\n int x = tops[i], y = bottoms[i];\\n if (x != target && y != target) {\\n return INT_MAX;\\n }\\n if (x != target) {\\n to_top++; // 把 y 旋转到上半\\n } else if (y != target) {\\n to_bottom++; // 把 x 旋转到下半\\n }\\n }\\n return min(to_top, to_bottom);\\n };\\n\\n int ans = min(min_rot(tops[0]), min_rot(bottoms[0]));\\n return ans == INT_MAX ? -1 : ans;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minDominoRotations(int* tops, int topsSize, int* bottoms, int bottomsSize) {\\n int min_rot(int target) {\\n int to_top = 0, to_bottom = 0;\\n for (int i = 0; i < topsSize; i++) {\\n int x = tops[i], y = bottoms[i];\\n if (x != target && y != target) {\\n return INT_MAX;\\n }\\n if (x != target) {\\n to_top++; // 把 y 旋转到上半\\n } else if (y != target) {\\n to_bottom++; // 把 x 旋转到下半\\n }\\n }\\n return MIN(to_top, to_bottom);\\n }\\n\\n int res1 = min_rot(tops[0]);\\n int res2 = min_rot(bottoms[0]);\\n int ans = MIN(res1, res2);\\n return ans == INT_MAX ? -1 : ans;\\n}\\n
\\n###go
\\nfunc minDominoRotations(tops, bottoms []int) int {\\n minRot := func(target int) int {\\n toTop, toBottom := 0, 0\\n for i, x := range tops {\\n y := bottoms[i]\\n if x != target && y != target {\\n return math.MaxInt\\n }\\n if x != target {\\n toTop++ // 把 y 旋转到上半\\n } else if y != target {\\n toBottom++ // 把 x 旋转到下半\\n }\\n }\\n return min(toTop, toBottom)\\n }\\n\\n ans := min(minRot(tops[0]), minRot(bottoms[0]))\\n if ans == math.MaxInt {\\n return -1\\n }\\n return ans\\n}\\n
\\n###js
\\nvar minDominoRotations = function(tops, bottoms) {\\n function minRot(target) {\\n let toTop = 0, toBottom = 0;\\n for (let i = 0; i < tops.length; i++) {\\n const x = tops[i], y = bottoms[i];\\n if (x !== target && y !== target) {\\n return Infinity;\\n }\\n if (x !== target) {\\n toTop++; // 把 y 旋转到上半\\n } else if (y !== target) {\\n toBottom++; // 把 x 旋转到下半\\n }\\n }\\n return Math.min(toTop, toBottom);\\n }\\n\\n const ans = Math.min(minRot(tops[0]), minRot(bottoms[0]));\\n return ans === Infinity ? -1 : ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_domino_rotations(tops: Vec<i32>, bottoms: Vec<i32>) -> i32 {\\n let min_rot = |target: i32| -> i32 {\\n let mut to_top = 0;\\n let mut to_bottom = 0;\\n for (&x, &y) in tops.iter().zip(bottoms.iter()) {\\n if x != target && y != target {\\n return i32::MAX;\\n }\\n if x != target {\\n to_top += 1; // 把 y 旋转到上半\\n } else if y != target {\\n to_bottom += 1; // 把 x 旋转到下半\\n }\\n }\\n to_top.min(to_bottom)\\n };\\n\\n let ans = min_rot(tops[0]).min(min_rot(bottoms[0]));\\n if ans == i32::MAX { -1 } else { ans }\\n }\\n}\\n
\\n如果可以去掉一块多米诺骨牌呢?
\\n欢迎在评论区发表你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"目标是让第一排或者第二排的所有数都相同,这个「所有」必然包含第一个骨牌中的数。所以要么都变成 $\\\\textit{tops}[0]$,要么都变成 $\\\\textit{bottoms}[0]$。计算这两种情况,取最小值。 写一个函数 $\\\\text{minRot}(\\\\textit{target})$,表示都变成 $\\\\textit{target}$ 的最小旋转次数。我们要计算的是 $\\\\min(\\\\text{minRot}(\\\\textit{tops}[0]),\\\\text{minRot}(\\\\textit{bottoms}[0]))$。\\n\\n定义变量 $\\\\textit…","guid":"https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row//solution/du-bian-cheng-tops0-huo-zhe-bottoms0pyth-zvnj","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-08T12:15:20.362Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"预处理(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/pascals-triangle-ii//solution/yu-chu-li-pythonjavaccgojsrust-by-endles-9wtq","content":"把杨辉三角的每一排左对齐:
\\n$$
\\n\\\\begin{align}
\\n&[1]\\\\
\\n&[1,1]\\\\
\\n&[1,2,1]\\\\
\\n&[1,3,3,1]\\\\
\\n&[1,4,6,4,1]
\\n\\\\end{align}
\\n$$
设要计算的二维数组是 $c$,计算方法如下:
\\n对于本题,我们可以预处理 $c[0]$ 到 $c[33]$,这样每次调用 $\\\\texttt{getRow}$ 都是 $\\\\mathcal{O}(1)$ 时间。
\\nMX = 34\\nc = [[1] * (i + 1) for i in range(MX)]\\nfor i in range(2, MX):\\n for j in range(1, i):\\n # 左上方的数 + 正上方的数\\n c[i][j] = c[i - 1][j - 1] + c[i - 1][j]\\n\\nclass Solution:\\n def getRow(self, rowIndex: int) -> List[int]:\\n return c[rowIndex]\\n
\\nclass Solution {\\n private static final List<Integer>[] c = new List[34];\\n\\n static {\\n c[0] = List.of(1);\\n for (int i = 1; i < c.length; i++) {\\n List<Integer> row = new ArrayList<>(i + 1); // 预分配空间\\n row.add(1);\\n for (int j = 1; j < i; j++) {\\n // 左上方的数 + 正上方的数\\n row.add(c[i - 1].get(j - 1) + c[i - 1].get(j));\\n }\\n row.add(1);\\n c[i] = row;\\n }\\n }\\n\\n public List<Integer> getRow(int rowIndex) {\\n return c[rowIndex];\\n }\\n}\\n
\\nconst int MX = 34;\\nvector<int> c[MX];\\n\\nauto init = []() {\\n for (int i = 0; i < MX; i++) {\\n c[i].resize(i + 1, 1);\\n for (int j = 1; j < i; j++) {\\n // 左上方的数 + 正上方的数\\n c[i][j] = c[i - 1][j - 1] + c[i - 1][j];\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n vector<int> getRow(int rowIndex) {\\n return c[rowIndex];\\n }\\n};\\n
\\n#define MX 34\\nbool initialized;\\nint c[MX][MX];\\n\\nvoid init() {\\n if (initialized) {\\n return;\\n }\\n initialized = true;\\n for (int i = 0; i < MX; i++) {\\n c[i][0] = c[i][i] = 1;\\n for (int j = 1; j < i; j++) {\\n // 左上方的数 + 正上方的数\\n c[i][j] = c[i - 1][j - 1] + c[i - 1][j];\\n }\\n }\\n}\\n\\nint* getRow(int rowIndex, int* returnSize) {\\n init();\\n *returnSize = rowIndex + 1;\\n return c[rowIndex];\\n}\\n
\\nvar c [34][]int\\n\\nfunc init() {\\n for i := range c {\\n c[i] = make([]int, i+1)\\n c[i][0], c[i][i] = 1, 1\\n for j := 1; j < i; j++ {\\n // 左上方的数 + 正上方的数\\n c[i][j] = c[i-1][j-1] + c[i-1][j]\\n }\\n }\\n}\\n\\nfunc getRow(rowIndex int) []int {\\n return c[rowIndex]\\n}\\n
\\nconst c = Array.from({ length: 34 }, () => []);\\nfor (let i = 0; i < c.length; i++) {\\n c[i] = Array(i + 1).fill(1);\\n for (let j = 1; j < i; j++) {\\n // 左上方的数 + 正上方的数\\n c[i][j] = c[i - 1][j - 1] + c[i - 1][j];\\n }\\n}\\n\\nvar getRow = function(rowIndex) {\\n return c[rowIndex];\\n};\\n
\\nstatic mut INITIALIZED: bool = false;\\nstatic mut C: [[i32; 34]; 34] = [[0; 34]; 34];\\n\\nunsafe fn init_once() {\\n if INITIALIZED {\\n return;\\n }\\n INITIALIZED = true;\\n for i in 0..C.len() {\\n C[i][0] = 1;\\n C[i][i] = 1;\\n for j in 1..i {\\n // 左上方的数 + 正上方的数\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n}\\n\\nimpl Solution {\\n pub fn get_row(row_index: i32) -> Vec<i32> {\\n let row_index = row_index as usize;\\n unsafe {\\n init_once();\\n C[row_index][..=row_index].to_vec()\\n }\\n }\\n}\\n
\\n初始化的时间和空间不计入。
\\n递推式
\\n$$
\\nc[i][j] = c[i - 1][j - 1] + c[i - 1][j]
\\n$$
本质上是一个组合数恒等式,其中 $c[i][j]$ 表示从 $i$ 个不同物品中选出 $j$ 个物品的方案数。
\\n如何理解上式呢?考虑其中某个物品选或不选:
\\n二者相加(加法原理),得
\\n$$
\\nc[i][j] = c[i - 1][j - 1] + c[i - 1][j]
\\n$$
欢迎关注 B站@灵茶山艾府
\\n","description":"把杨辉三角的每一排左对齐: $$\\n \\\\begin{align}\\n &[1]\\\\\\n &[1,1]\\\\\\n &[1,2,1]\\\\\\n &[1,3,3,1]\\\\\\n &[1,4,6,4,1]\\n \\\\end{align}\\n $$\\n\\n设要计算的二维数组是 $c$,计算方法如下:\\n\\n每一排的第一个数和最后一个数都是 $1$,即 $c[i][0]=c[i][i] = 1$。\\n其余数字,等于左上方的数,加上正上方的数,即 $c[i][j] = c[i - 1][j - 1] + c[i - 1][j]$。例如 $4=1+3,\\\\ 6=3+3$ 等。\\n\\n对于本题,我们可以预处理 $c[0]$ 到 $c[33…","guid":"https://leetcode.cn/problems/pascals-triangle-ii//solution/yu-chu-li-pythonjavaccgojsrust-by-endles-9wtq","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-08T06:27:47.703Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举右维护左/定长滑动窗口(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/contains-duplicate-ii//solution/liang-chong-fang-fa-mei-ju-you-wei-hu-zu-kwjf","content":"看示例 1,$\\\\textit{nums}=[1,2,3,1],\\\\ k=3$。
\\n我们可以在遍历 $\\\\textit{nums}$ 的同时,用一个哈希表 $\\\\textit{last}$ 记录每个数 $x$ 上一次出现的位置(下标)$\\\\textit{last}[x]$。
\\n比如遍历到 $\\\\textit{nums}[3]=1$,整数 $1$ 上一次出现的下标为 $\\\\textit{last}[1]=0$。
\\n因为 $3-0=3\\\\le k$,满足题目要求,返回 $\\\\texttt{true}$。
\\n问:为什么只需要看 $x$ 左边的数,如果 $x$ 右边也有一个等于 $x$ 的数呢?
\\n答:如果 $x$ 右边也有等于 $x$ 的数 $\\\\textit{nums}[j]$,那么在遍历到 $\\\\textit{nums}[j]$ 的时候,此时哈希表中记录的 $\\\\textit{last}[x]=i$,我们会判断 $j-\\\\textit{last}[x]=j-i\\\\le k$ 是否成立。换句话说,对于临近的相同元素,我们都会去判断,不会漏掉。
\\nclass Solution:\\n def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:\\n last = {}\\n for i, x in enumerate(nums):\\n if x in last and i - last[x] <= k:\\n return True\\n last[x] = i\\n return False\\n
\\nclass Solution {\\n public boolean containsNearbyDuplicate(int[] nums, int k) {\\n Map<Integer, Integer> last = new HashMap<>();\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n if (last.containsKey(x) && i - last.get(x) <= k) {\\n return true;\\n }\\n last.put(x, i);\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool containsNearbyDuplicate(vector<int>& nums, int k) {\\n unordered_map<int, int> last;\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n if (last.contains(x) && i - last[x] <= k) {\\n return true;\\n }\\n last[x] = i;\\n }\\n return false;\\n }\\n};\\n
\\nfunc containsNearbyDuplicate(nums []int, k int) bool {\\n last := map[int]int{}\\n for i, x := range nums {\\n if j, ok := last[x]; ok && i-j <= k {\\n return true\\n }\\n last[x] = i\\n }\\n return false\\n}\\n
\\nvar containsNearbyDuplicate = function(nums, k) {\\n const last = new Map();\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n if (last.has(x) && i - last.get(x) <= k) {\\n return true;\\n }\\n last.set(x, i);\\n }\\n return false;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn contains_nearby_duplicate(nums: Vec<i32>, k: i32) -> bool {\\n let mut last = HashMap::new();\\n for (i, x) in nums.iter().enumerate() {\\n if let Some(&j) = last.get(x) {\\n if i - j <= k as usize {\\n return true;\\n }\\n }\\n last.insert(x, i);\\n }\\n false\\n }\\n}\\n
\\n问题等价于:
\\n这可以用定长滑动窗口解决,原理见【套路】教你解决定长滑窗!适用于所有定长滑窗题目!
\\n核心思路:维护一个长为 $\\\\min(k+1,n)$ 的滑动窗口,用哈希集合维护窗口内的元素。在元素 $x$ 进入窗口之前,判断 $x$ 是否在哈希集合中,如果在,则说明把 $x$ 加入窗口之后,窗口内有重复元素。
\\n具体思路:
\\nclass Solution:\\n def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:\\n st = set()\\n for i, x in enumerate(nums):\\n if x in st:\\n return True\\n st.add(x)\\n if i >= k:\\n st.remove(nums[i - k])\\n return False\\n
\\nclass Solution {\\n public boolean containsNearbyDuplicate(int[] nums, int k) {\\n HashSet<Integer> set = new HashSet<>();\\n for (int i = 0; i < nums.length; i++) {\\n if (!set.add(nums[i])) { // set 中有 nums[i]\\n return true;\\n }\\n if (i >= k) {\\n set.remove(nums[i - k]);\\n }\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool containsNearbyDuplicate(vector<int>& nums, int k) {\\n unordered_set<int> st;\\n for (int i = 0; i < nums.size(); i++) {\\n if (!st.insert(nums[i]).second) { // st 中有 nums[i]\\n return true;\\n }\\n if (i >= k) {\\n st.erase(nums[i - k]);\\n }\\n }\\n return false;\\n }\\n};\\n
\\nfunc containsNearbyDuplicate(nums []int, k int) bool {\\n set := map[int]struct{}{}\\n for i, x := range nums {\\n if _, ok := set[x]; ok {\\n return true\\n }\\n set[x] = struct{}{}\\n if i >= k {\\n delete(set, nums[i-k])\\n }\\n }\\n return false\\n}\\n
\\nvar containsNearbyDuplicate = function(nums, k) {\\n const set = new Set();\\n for (let i = 0; i < nums.length; i++) {\\n if (set.has(nums[i])) {\\n return true;\\n }\\n set.add(nums[i]);\\n if (i >= k) {\\n set.delete(nums[i - k]);\\n }\\n }\\n return false;\\n};\\n
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn contains_nearby_duplicate(nums: Vec<i32>, k: i32) -> bool {\\n let k = k as usize;\\n let mut set = HashSet::new();\\n for (i, &x) in nums.iter().enumerate() {\\n if !set.insert(x) { // set 中有 x\\n return true;\\n }\\n if i >= k {\\n set.remove(&nums[i - k]);\\n }\\n }\\n false\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举右,维护左 看示例 1,$\\\\textit{nums}=[1,2,3,1],\\\\ k=3$。\\n\\n我们可以在遍历 $\\\\textit{nums}$ 的同时,用一个哈希表 $\\\\textit{last}$ 记录每个数 $x$ 上一次出现的位置(下标)$\\\\textit{last}[x]$。\\n\\n比如遍历到 $\\\\textit{nums}[3]=1$,整数 $1$ 上一次出现的下标为 $\\\\textit{last}[1]=0$。\\n\\n因为 $3-0=3\\\\le k$,满足题目要求,返回 $\\\\texttt{true}$。\\n\\n答疑\\n\\n问:为什么只需要看 $x$ 左边的数,如果 $x…","guid":"https://leetcode.cn/problems/contains-duplicate-ii//solution/liang-chong-fang-fa-mei-ju-you-wei-hu-zu-kwjf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-08T02:40:38.207Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:枚举(清晰题解)","url":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/python3javacgotypescript-yi-ti-yi-jie-me-a5hk","content":"我们可以从大到小枚举每个数字 $i$,其中 $0 \\\\le i \\\\le 9$,然后判断连续的三个 $i$ 构成的字符串 $s$ 是否是 $num$ 的子串,若是,直接返回 $s$ 即可。
\\n若枚举完所有的 $i$ 都没有找到满足条件的字符串,则返回空字符串。
\\n###python
\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n for i in range(9, -1, -1):\\n if (s := str(i) * 3) in num:\\n return s\\n return \\"\\"\\n
\\n###java
\\nclass Solution {\\n public String largestGoodInteger(String num) {\\n for (int i = 9; i >= 0; i--) {\\n String s = String.valueOf(i).repeat(3);\\n if (num.contains(s)) {\\n return s;\\n }\\n }\\n return \\"\\";\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string largestGoodInteger(string num) {\\n for (char i = \'9\'; i >= \'0\'; --i) {\\n string s(3, i);\\n if (num.find(s) != string::npos) {\\n return s;\\n }\\n }\\n return \\"\\";\\n }\\n};\\n
\\n###go
\\nfunc largestGoodInteger(num string) string {\\nfor c := \'9\'; c >= \'0\'; c-- {\\nif s := strings.Repeat(string(c), 3); strings.Contains(num, s) {\\nreturn s\\n}\\n}\\nreturn \\"\\"\\n}\\n
\\n###ts
\\nfunction largestGoodInteger(num: string): string {\\n for (let i = 9; i >= 0; i--) {\\n const s = String(i).repeat(3);\\n if (num.includes(s)) {\\n return s;\\n }\\n }\\n return \'\';\\n}\\n
\\n时间复杂度 $O(10 \\\\times n)$,其中 $n$ 是字符串 $num$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:枚举 我们可以从大到小枚举每个数字 $i$,其中 $0 \\\\le i \\\\le 9$,然后判断连续的三个 $i$ 构成的字符串 $s$ 是否是 $num$ 的子串,若是,直接返回 $s$ 即可。\\n\\n若枚举完所有的 $i$ 都没有找到满足条件的字符串,则返回空字符串。\\n\\n###python\\n\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n for i in range(9, -1, -1):\\n if (s := str(i) * 3…","guid":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/python3javacgotypescript-yi-ti-yi-jie-me-a5hk","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-07T23:59:11.549Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-字符串中最大的 3 位相同数字🟢","url":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string/","content":"给你一个字符串 num
,表示一个大整数。如果一个整数满足下述所有条件,则认为该整数是一个 优质整数 :
num
的一个长度为 3
的 子字符串 。3
次组成。以字符串形式返回 最大的优质整数 。如果不存在满足要求的整数,则返回一个空字符串 \\"\\"
。
注意:
\\n\\nnum
或优质整数中可能存在 前导零 。\\n\\n
示例 1:
\\n\\n输入:num = \\"6777133339\\"\\n输出:\\"777\\"\\n解释:num 中存在两个优质整数:\\"777\\" 和 \\"333\\" 。\\n\\"777\\" 是最大的那个,所以返回 \\"777\\" 。\\n\\n\\n
示例 2:
\\n\\n输入:num = \\"2300019\\"\\n输出:\\"000\\"\\n解释:\\"000\\" 是唯一一个优质整数。\\n\\n\\n
示例 3:
\\n\\n输入:num = \\"42352338\\"\\n输出:\\"\\"\\n解释:不存在长度为 3 且仅由一个唯一数字组成的整数。因此,不存在优质整数。\\n\\n\\n
\\n\\n
提示:
\\n\\n3 <= num.length <= 1000
num
仅由数字(0
- 9
)组成我们可以遍历字符串,每次判断当前字符的小写形式是否与前一个字符的小写形式相同,如果不同则说明发生了按键变更,将答案加一即可。
\\n###python
\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(a.lower() != b.lower() for a, b in pairwise(s))\\n
\\n###java
\\nclass Solution {\\n public int countKeyChanges(String s) {\\n int ans = 0;\\n for (int i = 1; i < s.length(); ++i) {\\n if (Character.toLowerCase(s.charAt(i)) != Character.toLowerCase(s.charAt(i - 1))) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countKeyChanges(string s) {\\n transform(s.begin(), s.end(), s.begin(), ::tolower);\\n int ans = 0;\\n for (int i = 1; i < s.size(); ++i) {\\n ans += s[i] != s[i - 1];\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKeyChanges(s string) (ans int) {\\ns = strings.ToLower(s)\\nfor i, c := range s[1:] {\\nif byte(c) != s[i] {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countKeyChanges(s: string): number {\\n s = s.toLowerCase();\\n let ans = 0;\\n for (let i = 1; i < s.length; ++i) {\\n if (s[i] !== s[i - 1]) {\\n ++ans;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是字符串 $s$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们可以遍历字符串,每次判断当前字符的小写形式是否与前一个字符的小写形式相同,如果不同则说明发生了按键变更,将答案加一即可。\\n\\n###python\\n\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(a.lower() != b.lower() for a, b in pairwise(s))\\n\\n\\n###java\\n\\nclass Solution {\\n public int countKeyChanges(String s) {…","guid":"https://leetcode.cn/problems/number-of-changing-keys//solution/python3javacgotypescript-yi-ti-yi-jie-yi-msba","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-06T23:59:24.776Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-按键变更的次数🟢","url":"https://leetcode.cn/problems/number-of-changing-keys/","content":"给你一个下标从 0 开始的字符串 s
,该字符串由用户输入。按键变更的定义是:使用与上次使用的按键不同的键。例如 s = \\"ab\\"
表示按键变更一次,而 s = \\"bBBb\\"
不存在按键变更。
返回用户输入过程中按键变更的次数。
\\n\\n注意:shift
或 caps lock
等修饰键不计入按键变更,也就是说,如果用户先输入字母 \'a\'
然后输入字母 \'A\'
,不算作按键变更。
\\n\\n
示例 1:
\\n\\n输入:s = \\"aAbBcC\\"\\n输出:2\\n解释: \\n从 s[0] = \'a\' 到 s[1] = \'A\',不存在按键变更,因为不计入 caps lock 或 shift 。\\n从 s[1] = \'A\' 到 s[2] = \'b\',按键变更。\\n从 s[2] = \'b\' 到 s[3] = \'B\',不存在按键变更,因为不计入 caps lock 或 shift 。\\n从 s[3] = \'B\' 到 s[4] = \'c\',按键变更。\\n从 s[4] = \'c\' 到 s[5] = \'C\',不存在按键变更,因为不计入 caps lock 或 shift 。\\n\\n\\n
示例 2:
\\n\\n输入:s = \\"AaAaAaaA\\"\\n输出:0\\n解释: 不存在按键变更,因为这个过程中只按下字母 \'a\' 和 \'A\' ,不需要进行按键变更。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 100
s
仅由英文大写字母和小写字母组成。思路与算法
\\n本题使用「最小堆」实现的「优先队列」来进行模拟。
\\n首先把所有数加入堆中。当堆顶元素小于 $k$ 时,就把取出堆中最小的两个数,把更新后的数加入堆中。
\\n最后返回操作的数目。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minOperations(vector<int> &nums, int k) {\\n int res = 0;\\n priority_queue<long long, vector<long long>, greater<long long>> pq(nums.begin(), nums.end());\\n while (pq.top() < k) {\\n long long x = pq.top(); pq.pop();\\n long long y = pq.top(); pq.pop();\\n pq.push(x + x + y);\\n res++;\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int res = 0;\\n PriorityQueue<Long> pq = new PriorityQueue<>();\\n for (long num : nums) {\\n pq.offer(num);\\n }\\n while (pq.peek() < k) {\\n long x = pq.poll(), y = pq.poll();\\n pq.offer(x + x + y);\\n res++;\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n res = 0\\n h = nums[:]\\n heapify(h)\\n while h[0] < k:\\n x = heappop(h)\\n y = heappop(h)\\n heappush(h, x + x + y)\\n res += 1\\n return res\\n
\\n###JavaScript
\\nvar minOperations = function(nums, k) {\\n let res = 0;\\n const pq = new MinHeap();\\n\\n for (const num of nums) {\\n pq.push(num);\\n }\\n while (pq.peek() < k) {\\n const x = pq.pop();\\n const y = pq.pop();\\n pq.push(x + x + y);\\n res++;\\n }\\n\\n return res;\\n};\\n\\nclass MinHeap {\\n constructor() {\\n this.heap = [];\\n }\\n\\n size() {\\n return this.heap.length;\\n }\\n\\n isEmpty() {\\n return this.size() === 0;\\n }\\n\\n peek() {\\n return this.heap[0];\\n }\\n\\n push(val) {\\n this.heap.push(val);\\n this._heapifyUp();\\n }\\n\\n pop() {\\n if (this.size() === 1) return this.heap.pop();\\n\\n const root = this.heap[0];\\n this.heap[0] = this.heap.pop();\\n this._heapifyDown();\\n return root;\\n }\\n\\n _heapifyUp() {\\n let index = this.size() - 1;\\n const element = this.heap[index];\\n\\n while (index > 0) {\\n const parentIndex = Math.floor((index - 1) / 2);\\n const parent = this.heap[parentIndex];\\n\\n if (element >= parent) break;\\n\\n this.heap[index] = parent;\\n this.heap[parentIndex] = element;\\n index = parentIndex;\\n }\\n }\\n\\n _heapifyDown() {\\n let index = 0;\\n const length = this.size();\\n const element = this.heap[0];\\n\\n while (true) {\\n let leftChildIndex = 2 * index + 1;\\n let rightChildIndex = 2 * index + 2;\\n let smallest = index;\\n\\n if (\\n leftChildIndex < length &&\\n this.heap[leftChildIndex] < this.heap[smallest]\\n ) {\\n smallest = leftChildIndex;\\n }\\n\\n if (\\n rightChildIndex < length &&\\n this.heap[rightChildIndex] < this.heap[smallest]\\n ) {\\n smallest = rightChildIndex;\\n }\\n\\n if (smallest === index) break;\\n\\n this.heap[index] = this.heap[smallest];\\n this.heap[smallest] = element;\\n index = smallest;\\n }\\n }\\n}\\n
\\n###TypeScript
\\nfunction minOperations(nums: number[], k: number): number {\\n let res = 0;\\n const pq = new MinHeap();\\n\\n for (const num of nums) {\\n pq.push(num);\\n }\\n\\n while (pq.peek() < k) {\\n const x = pq.pop();\\n const y = pq.pop();\\n pq.push(x + x + y);\\n res++;\\n }\\n\\n return res;\\n};\\n\\nclass MinHeap {\\n private heap: number[];\\n\\n constructor() {\\n this.heap = [];\\n }\\n\\n size(): number {\\n return this.heap.length;\\n }\\n\\n isEmpty(): boolean {\\n return this.size() === 0;\\n }\\n\\n peek(): number {\\n if (this.isEmpty()) {\\n throw new Error(\\"Heap is empty\\");\\n }\\n return this.heap[0];\\n }\\n\\n push(val: number): void {\\n this.heap.push(val);\\n this._heapifyUp();\\n }\\n\\n pop(): number {\\n if (this.isEmpty()) {\\n throw new Error(\\"Heap is empty\\");\\n }\\n if (this.size() === 1) {\\n return this.heap.pop() as number;\\n }\\n\\n const root = this.heap[0];\\n this.heap[0] = this.heap.pop() as number;\\n this._heapifyDown();\\n return root;\\n }\\n\\n private _heapifyUp(): void {\\n let index = this.size() - 1;\\n const element = this.heap[index];\\n\\n while (index > 0) {\\n const parentIndex = Math.floor((index - 1) / 2);\\n const parent = this.heap[parentIndex];\\n\\n if (element >= parent) break;\\n\\n this.heap[index] = parent;\\n this.heap[parentIndex] = element;\\n index = parentIndex;\\n }\\n }\\n\\n private _heapifyDown(): void {\\n let index = 0;\\n const length = this.size();\\n const element = this.heap[0];\\n\\n while (true) {\\n let leftChildIndex = 2 * index + 1;\\n let rightChildIndex = 2 * index + 2;\\n let smallest = index;\\n\\n if (\\n leftChildIndex < length &&\\n this.heap[leftChildIndex] < this.heap[smallest]\\n ) {\\n smallest = leftChildIndex;\\n }\\n\\n if (\\n rightChildIndex < length &&\\n this.heap[rightChildIndex] < this.heap[smallest]\\n ) {\\n smallest = rightChildIndex;\\n }\\n\\n if (smallest === index) break;\\n\\n this.heap[index] = this.heap[smallest];\\n this.heap[smallest] = element;\\n index = smallest;\\n }\\n }\\n}\\n
\\n###Go
\\nfunc minOperations(nums []int, k int) int {\\nres := 0\\npq := &MinHeap{}\\nheap.Init(pq)\\nfor _, num := range nums {\\nheap.Push(pq, num)\\n}\\n\\nfor (*pq)[0] < k {\\nx := heap.Pop(pq).(int)\\ny := heap.Pop(pq).(int)\\nheap.Push(pq, x+x+y)\\nres++\\n}\\n\\nreturn res\\n}\\n\\n// MinHeap\\ntype MinHeap []int\\n\\nfunc (h MinHeap) Len() int { return len(h) }\\nfunc (h MinHeap) Less(i, j int) bool { return h[i] < h[j] }\\nfunc (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\n\\nfunc (h *MinHeap) Push(x interface{}) {\\n*h = append(*h, x.(int))\\n}\\n\\nfunc (h *MinHeap) Pop() interface{} {\\nold := *h\\nn := len(old)\\nx := old[n-1]\\n*h = old[0 : n-1]\\nreturn x\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums, int k) {\\n int res = 0;\\n PriorityQueue<long, long> pq = new PriorityQueue<long, long>();\\n foreach (int num in nums) {\\n pq.Enqueue(num, num);\\n }\\n while (pq.Peek() < k) {\\n long x = pq.Dequeue(), y = pq.Dequeue();\\n pq.Enqueue(x + x + y, x + x + y);\\n res++;\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\n#define MIN_QUEUE_SIZE 64\\n\\ntypedef struct Element {\\n long long data[1];\\n} Element;\\n\\ntypedef bool (*compare)(const void *, const void *);\\n\\ntypedef struct PriorityQueue {\\n Element *arr;\\n int capacity;\\n int queueSize;\\n compare lessFunc;\\n} PriorityQueue;\\n\\nElement *createElement(int x, int y) {\\n Element *obj = (Element *)malloc(sizeof(Element));\\n obj->data[0] = x;\\n obj->data[1] = y;\\n return obj;\\n}\\n\\nstatic bool less(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] > e2->data[0];\\n}\\n\\nstatic bool greater(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] < e2->data[0];\\n}\\n\\nstatic void memswap(void *m1, void *m2, size_t size){\\n unsigned char *a = (unsigned char*)m1;\\n unsigned char *b = (unsigned char*)m2;\\n while (size--) {\\n *b ^= *a ^= *b ^= *a;\\n a++;\\n b++;\\n }\\n}\\n\\nstatic void swap(Element *arr, int i, int j) {\\n memswap(&arr[i], &arr[j], sizeof(Element));\\n}\\n\\nstatic void down(Element *arr, int size, int i, compare cmpFunc) {\\n for (int k = 2 * i + 1; k < size; k = 2 * k + 1) {\\n if (k + 1 < size && cmpFunc(&arr[k], &arr[k + 1])) {\\n k++;\\n }\\n if (cmpFunc(&arr[k], &arr[(k - 1) / 2])) {\\n break;\\n }\\n swap(arr, k, (k - 1) / 2);\\n }\\n}\\n\\nPriorityQueue *createPriorityQueue(compare cmpFunc) {\\n PriorityQueue *obj = (PriorityQueue *)malloc(sizeof(PriorityQueue));\\n obj->capacity = MIN_QUEUE_SIZE;\\n obj->arr = (Element *)malloc(sizeof(Element) * obj->capacity);\\n obj->queueSize = 0;\\n obj->lessFunc = cmpFunc;\\n return obj;\\n}\\n\\nvoid heapfiy(PriorityQueue *obj) {\\n for (int i = obj->queueSize / 2 - 1; i >= 0; i--) {\\n down(obj->arr, obj->queueSize, i, obj->lessFunc);\\n }\\n}\\n\\nvoid enQueue(PriorityQueue *obj, Element *e) {\\n // we need to alloc more space, just twice space size\\n if (obj->queueSize == obj->capacity) {\\n obj->capacity *= 2;\\n obj->arr = realloc(obj->arr, sizeof(Element) * obj->capacity);\\n }\\n memcpy(&obj->arr[obj->queueSize], e, sizeof(Element));\\n for (int i = obj->queueSize; i > 0 && obj->lessFunc(&obj->arr[(i - 1) / 2], &obj->arr[i]); i = (i - 1) / 2) {\\n swap(obj->arr, i, (i - 1) / 2);\\n }\\n obj->queueSize++;\\n}\\n\\nElement* deQueue(PriorityQueue *obj) {\\n swap(obj->arr, 0, obj->queueSize - 1);\\n down(obj->arr, obj->queueSize - 1, 0, obj->lessFunc);\\n Element *e = &obj->arr[obj->queueSize - 1];\\n obj->queueSize--;\\n return e;\\n}\\n\\nbool isEmpty(const PriorityQueue *obj) {\\n return obj->queueSize == 0;\\n}\\n\\nElement* front(const PriorityQueue *obj) {\\n if (obj->queueSize == 0) {\\n return NULL;\\n } else {\\n return &obj->arr[0];\\n }\\n}\\n\\nvoid clear(PriorityQueue *obj) {\\n obj->queueSize = 0;\\n}\\n\\nint size(const PriorityQueue *obj) {\\n return obj->queueSize;\\n}\\n\\nvoid freeQueue(PriorityQueue *obj) {\\n free(obj->arr);\\n free(obj);\\n}\\n\\nint minOperations(int* nums, int numsSize, int k) {\\n int res = 0;\\n PriorityQueue *pq = createPriorityQueue(less);\\n struct Element e;\\n for (int i = 0; i < numsSize; i++) {\\n e.data[0] = nums[i];\\n enQueue(pq, &e);\\n }\\n while (front(pq)->data[0] < k) {\\n long long x = front(pq)->data[0]; deQueue(pq);\\n long long y = front(pq)->data[0]; deQueue(pq);\\n e.data[0] = x + x + y;\\n enQueue(pq, &e);\\n res++;\\n }\\n freeQueue(pq);\\n return res;\\n}\\n
\\n###Rust
\\nuse std::collections::BinaryHeap;\\nuse core::cmp::Reverse;\\n\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {\\n let mut res = 0;\\n let mut pq: BinaryHeap<Reverse<i64>> = BinaryHeap::new();\\n for &num in &nums {\\n pq.push(Reverse(num as i64));\\n }\\n while let Some(Reverse(x)) = pq.pop() {\\n if x >= k as i64 {\\n break;\\n }\\n if let Some(Reverse(y)) = pq.pop() {\\n pq.push(Reverse(x + x + y));\\n res += 1;\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n\\\\log n)$,其中 $n$ 是数组的长度。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组的长度。
\\n思路与算法
\\n根据或运算的性质可以知道,给定的元素 $A$ 与任意元素 $B$ 进行或运算的结果一定满足 $A|B \\\\ge A$,由此可以知道对于任意子数组的长度增加元素后按位或运算的结果一定大于等于增加前的结果,满足单调性。由此可知当每次固定数组的右端点时,我们可以使用二分查找或者滑动窗口来找到最短特别子数组的长度。
\\n我们每次用 $[\\\\text{left}, \\\\textit{right}]$ 表示滑动窗口,每次固定右端点 $\\\\textit{right}$,如果当前窗口内子数组按位或的结果大于等于 $k$,此时向右移动左端点 $\\\\textit{left}$ 直到窗口内子数组的值按位或的结果刚好满足小于 $k$ 为止,此时以 $\\\\textit{right}$ 为右端点的最短特别子数组长度即为 $\\\\textit{right} - \\\\textit{left} + 1$。
\\n为了实时计算当前窗口中子数组或运算的结果,我们需要维护二进制位每一位中 $1$ 出现的次数,如果当前位中 $1$ 的出现的次数为 $0$,则按位或运算后该位为 $0$,否则该位则为 $1$。由于给定数组 $\\\\textit{nums}$ 中的元素大小不超过 $10^9$ ,因此最多需要考虑二进制表示的前 $30$ 位。我们需要维护一个长度为 $30$ 的数组 $\\\\textit{bits}$,其中 $\\\\textit{bits}[i]$ 表示滑动窗口中满足二进制表示的从低到高第 $i$ 位的值为 $1$ 的元素个数。
\\n具体计算过程如下:
\\n初始时 $\\\\textit{left} = \\\\textit{right} = 0$。每次将滑动窗口的右端点 $\\\\textit{right}$ 向右移动一位,并更新 $\\\\textit{bits}$ 数组,通过计算 $\\\\textit{bits}$ 数组,如果当前窗口内子数组按位或的结果大于等于 $k$ 时,窗口内的子数组一定为特别子数组,则尝试应向右移动左端点 $\\\\textit{right}$,并更新 $\\\\textit{bits}$ 数组。
\\n向右移动左端点直到当窗口内子数组按位或的结果小于 $k$ 或者 $\\\\textit{left} > \\\\textit{right}$ 时,此时不再移动左端点 $\\\\textit{left}$。在移动窗口的过程中,我们同时更新最短特别子数组长度的长度。
\\n由于可能存在整个数组中所有元素按位或的结果小于 $k$,此时不存在特别子数组,此时应返回 $-1$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int n = nums.size();\\n vector<int> bits(30);\\n auto calc = [](vector<int> &bits) -> int {\\n int ans = 0;\\n for (int i = 0; i < bits.size(); i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n int res = INT_MAX;\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n \\n return res == INT_MAX ? -1: res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int[] bits = new int[30];\\n int res = Integer.MAX_VALUE;\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == Integer.MAX_VALUE ? -1 : res;\\n }\\n\\n private int calc(int[] bits) {\\n int ans = 0;\\n for (int i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumSubarrayLength(int[] nums, int k) {\\n int n = nums.Length;\\n int[] bits = new int[30];\\n int res = int.MaxValue;\\n\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && Calc(bits) >= k) {\\n res = Math.Min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == int.MaxValue ? -1 : res;\\n }\\n\\n private int Calc(int[] bits) {\\n int ans = 0;\\n for (int i = 0; i < bits.Length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\n n := len(nums)\\nbits := make([]int, 30)\\nres := math.MaxInt32\\n\\nfor left, right := 0, 0; right < n; right++ {\\nfor i := 0; i < 30; i++ {\\nbits[i] += (nums[right] >> i) & 1\\n}\\nfor left <= right && calc(bits) >= k {\\nres = min(res, right - left + 1)\\nfor i := 0; i < 30; i++ {\\nbits[i] -= (nums[left] >> i) & 1\\n}\\nleft++\\n}\\n}\\n\\nif res == math.MaxInt32 {\\nreturn -1\\n}\\nreturn res\\n}\\n\\nfunc calc(bits []int) int {\\nans := 0\\nfor i := 0; i < len(bits); i++ {\\nif bits[i] > 0 {\\nans |= 1 << i\\n}\\n}\\nreturn ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n bits = [0] * 30\\n res = inf\\n def calc(bits):\\n return sum(1 << i for i in range(30) if bits[i] > 0)\\n\\n left = 0\\n for right in range(n):\\n for i in range(30):\\n bits[i] += (nums[right] >> i) & 1\\n while left <= right and calc(bits) >= k:\\n res = min(res, right - left + 1)\\n for i in range(30):\\n bits[i] -= (nums[left] >> i) & 1\\n left += 1\\n\\n return -1 if res == inf else res\\n
\\n###C
\\nint calc(int* bits) {\\n int ans = 0;\\n for (int i = 0; i < 30; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n}\\n\\nint minimumSubarrayLength(int* nums, int numsSize, int k) {\\n int bits[30] = {0};\\n int res = INT_MAX;\\n\\n for (int left = 0, right = 0; right < numsSize; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = fmin(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == INT_MAX ? -1 : res;\\n}\\n
\\n###JavaScript
\\nvar minimumSubarrayLength = function(nums, k) {\\n const n = nums.length;\\n const bits = new Array(30).fill(0);\\n let res = Number.MAX_SAFE_INTEGER;;\\n\\n const calc = (bits) => {\\n let ans = 0;\\n for (let i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n let left = 0;\\n for (let right = 0; right < n; right++) {\\n for (let i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (let i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res === Number.MAX_SAFE_INTEGER ? -1 : res;\\n};\\n
\\n###TypeScript
\\nfunction minimumSubarrayLength(nums: number[], k: number): number {\\n const n = nums.length;\\n const bits = new Array(30).fill(0);\\n let res = Infinity;\\n\\n const calc = (bits: number[]): number => {\\n let ans = 0;\\n for (let i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n let left = 0;\\n for (let right = 0; right < n; right++) {\\n for (let i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (let i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res === Infinity ? -1 : res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {\\n let n = nums.len();\\n let mut bits = [0; 30];\\n let mut res = i32::MAX;\\n\\n let calc = |bits: &[i32]| -> i32 {\\n let mut ans = 0;\\n for i in 0..30 {\\n if bits[i] > 0 {\\n ans |= 1 << i;\\n }\\n }\\n ans\\n };\\n\\n let mut left = 0;\\n for right in 0..n {\\n for i in 0..30 {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while left <= right && calc(&bits) >= k {\\n res = res.min((right - left + 1) as i32);\\n for i in 0..30 {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left += 1;\\n }\\n }\\n\\n if res == i32::MAX {\\n -1\\n } else {\\n res\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\log U)$,其中 $n$ 表示给定数组 $\\\\textit{nums}$ 的长度,$U$ 表示数组中的最大的元素。由于使用滑动窗口遍历需要的时间为 $O(n)$,每次更新窗口元素时需要实时计算当前子数组按位或的值需要的时间为 $O(\\\\log U)$,此时需要的总时间即为 $O(n \\\\log U)$。
\\n空间复杂度:$O(\\\\log U)$。计算时需要存储当前子数组中每一个二进制位中的统计情况,最多有 $\\\\log U$ 位需要记录,因此需要的空间为 $\\\\log U$。
\\n思路与算法
\\n题目要求找到数组中最短特别非空子数组的长度,即找到最短的子数组且该子数组满足所有元素的按位或运算 $\\\\text{OR}$ 的值大于等于 $k$。我们依次枚举数组中以每个下标 $i$,并找到以 $i$ 为起始位置的最短特别子数组,并记录最短长度即可,如果数组中不存在特别子数组,则返回 $-1$。
\\n代码
\\n###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int res = Integer.MAX_VALUE;\\n for (int i = 0; i < n; i++) {\\n int value = 0;\\n for (int j = i; j < n; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = Math.min(res, j - i + 1);\\n break;\\n }\\n }\\n }\\n return res == Integer.MAX_VALUE ? -1 : res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumSubarrayLength(int[] nums, int k) {\\n int n = nums.Length;\\n int res = int.MaxValue;\\n for (int i = 0; i < n; i++) {\\n int value = 0;\\n for (int j = i; j < n; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = Math.Min(res, j - i + 1);\\n break;\\n }\\n }\\n }\\n return res == int.MaxValue ? -1 : res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int n = nums.size();\\n int res = INT_MAX;\\n for (int i = 0; i < n; i++) {\\n int value = 0;\\n for (int j = i; j < n; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = min(res, j - i + 1);\\n break;\\n }\\n }\\n }\\n return res == INT_MAX ? -1 : res;\\n }\\n};\\n
\\n###Go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\n n := len(nums)\\n res := n + 1\\n for i := 0; i < n; i++ {\\n value := 0\\n for j := i; j < n; j++ {\\n value |= nums[j]\\n if value >= k {\\n res = min(res, j-i+1)\\n break\\n }\\n }\\n }\\n if res == n + 1 {\\n return -1\\n }\\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n res = inf\\n for i in range(n):\\n value = 0\\n for j in range(i, n):\\n value |= nums[j]\\n if value >= k:\\n res = min(res, j - i + 1)\\n break\\n return res if res != inf else -1\\n
\\n###C
\\nint minimumSubarrayLength(int* nums, int numsSize, int k) {\\n int res = INT_MAX;\\n for (int i = 0; i < numsSize; i++) {\\n int value = 0;\\n for (int j = i; j < numsSize; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = (res < j - i + 1) ? res : j - i + 1;\\n break;\\n }\\n }\\n }\\n return res == INT_MAX ? -1 : res;\\n}\\n
\\n###JavaScript
\\nvar minimumSubarrayLength = function(nums, k) {\\n let n = nums.length;\\n let res = Number.MAX_VALUE;\\n for (let i = 0; i < n; i++) {\\n let value = 0;\\n for (let j = i; j < n; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = Math.min(res, j - i + 1);\\n break;\\n }\\n }\\n }\\n return res === Number.MAX_VALUE ? -1 : res;\\n};\\n
\\n###TypeScript
\\nfunction minimumSubarrayLength(nums: number[], k: number): number {\\n const n = nums.length;\\n let res = Number.MAX_VALUE;\\n for (let i = 0; i < n; i++) {\\n let value = 0;\\n for (let j = i; j < n; j++) {\\n value |= nums[j];\\n if (value >= k) {\\n res = Math.min(res, j - i + 1);\\n break;\\n }\\n }\\n }\\n return res === Number.MAX_VALUE ? -1 : res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {\\n let n = nums.len();\\n let mut res = i32::MAX;\\n for i in 0..n {\\n let mut value = 0;\\n for j in i..n {\\n value |= nums[j];\\n if value >= k {\\n res = res.min((j - i + 1) as i32);\\n break;\\n }\\n }\\n }\\n if res == i32::MAX { -1 } else { res }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n^2)$,其中 $n$ 表示给定数组 $\\\\textit{nums}$ 的长度。以当前索引 $i$ 为起始位置的子数组元素进行或运算,需要的时间为 $O(n)$,需要枚举每个索引 $i$,一共需要枚举 $n$ 次,因此总的时间为 $O(n^2)$。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n根据或运算的性质可以知道,给定的元素 $A$ 与任意元素 $B$ 进行或运算的结果一定满足 $A|B \\\\ge A$,由此可以知道对于任意子数组的长度增加元素后按位或运算的结果一定大于等于增加前的结果,满足单调性。由此可知当每次固定数组的右端点时,我们可以使用二分查找或者滑动窗口来找到最短特别子数组的长度。
\\n我们每次用 $[\\\\text{left}, \\\\textit{right}]$ 表示滑动窗口,每次固定右端点 $\\\\textit{right}$,如果当前窗口内子数组按位或的结果大于等于 $k$,此时向右移动左端点 $\\\\textit{left}$ 直到窗口内子数组的值按位或的结果刚好满足小于 $k$ 为止,此时以 $\\\\textit{right}$ 为右端点的最短特别子数组长度即为 $\\\\textit{right} - \\\\textit{left} + 1$。
\\n为了实时计算当前窗口中子数组或运算的结果,我们需要维护二进制位每一位中 $1$ 出现的次数,如果当前位中 $1$ 的出现的次数为 $0$,则按位或运算后该位为 $0$,否则该位则为 $1$。由于给定数组 $\\\\textit{nums}$ 中的元素大小不超过 $10^9$ ,因此最多需要考虑二进制表示的前 $30$ 位。我们需要维护一个长度为 $30$ 的数组 $\\\\textit{bits}$,其中 $\\\\textit{bits}[i]$ 表示滑动窗口中满足二进制表示的从低到高第 $i$ 位的值为 $1$ 的元素个数。
\\n具体计算过程如下:
\\n初始时 $\\\\textit{left} = \\\\textit{right} = 0$。每次将滑动窗口的右端点 $\\\\textit{right}$ 向右移动一位,并更新 $\\\\textit{bits}$ 数组,通过计算 $\\\\textit{bits}$ 数组,如果当前窗口内子数组按位或的结果大于等于 $k$ 时,窗口内的子数组一定为特别子数组,则尝试应向右移动左端点 $\\\\textit{right}$,并更新 $\\\\textit{bits}$ 数组。
\\n向右移动左端点直到当窗口内子数组按位或的结果小于 $k$ 或者 $\\\\textit{left} > \\\\textit{right}$ 时,此时不再移动左端点 $\\\\textit{left}$。在移动窗口的过程中,我们同时更新最短特别子数组长度的长度。
\\n由于可能存在整个数组中所有元素按位或的结果小于 $k$,此时不存在特别子数组,此时应返回 $-1$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int n = nums.size();\\n vector<int> bits(30);\\n auto calc = [](vector<int> &bits) -> int {\\n int ans = 0;\\n for (int i = 0; i < bits.size(); i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n int res = INT_MAX;\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n \\n return res == INT_MAX ? -1: res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int[] bits = new int[30];\\n int res = Integer.MAX_VALUE;\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == Integer.MAX_VALUE ? -1 : res;\\n }\\n\\n private int calc(int[] bits) {\\n int ans = 0;\\n for (int i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumSubarrayLength(int[] nums, int k) {\\n int n = nums.Length;\\n int[] bits = new int[30];\\n int res = int.MaxValue;\\n\\n for (int left = 0, right = 0; right < n; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && Calc(bits) >= k) {\\n res = Math.Min(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == int.MaxValue ? -1 : res;\\n }\\n\\n private int Calc(int[] bits) {\\n int ans = 0;\\n for (int i = 0; i < bits.Length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\n n := len(nums)\\nbits := make([]int, 30)\\nres := math.MaxInt32\\n\\nfor left, right := 0, 0; right < n; right++ {\\nfor i := 0; i < 30; i++ {\\nbits[i] += (nums[right] >> i) & 1\\n}\\nfor left <= right && calc(bits) >= k {\\nres = min(res, right - left + 1)\\nfor i := 0; i < 30; i++ {\\nbits[i] -= (nums[left] >> i) & 1\\n}\\nleft++\\n}\\n}\\n\\nif res == math.MaxInt32 {\\nreturn -1\\n}\\nreturn res\\n}\\n\\nfunc calc(bits []int) int {\\nans := 0\\nfor i := 0; i < len(bits); i++ {\\nif bits[i] > 0 {\\nans |= 1 << i\\n}\\n}\\nreturn ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n bits = [0] * 30\\n res = inf\\n def calc(bits):\\n return sum(1 << i for i in range(30) if bits[i] > 0)\\n\\n left = 0\\n for right in range(n):\\n for i in range(30):\\n bits[i] += (nums[right] >> i) & 1\\n while left <= right and calc(bits) >= k:\\n res = min(res, right - left + 1)\\n for i in range(30):\\n bits[i] -= (nums[left] >> i) & 1\\n left += 1\\n\\n return -1 if res == inf else res\\n
\\n###C
\\nint calc(int* bits) {\\n int ans = 0;\\n for (int i = 0; i < 30; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n}\\n\\nint minimumSubarrayLength(int* nums, int numsSize, int k) {\\n int bits[30] = {0};\\n int res = INT_MAX;\\n\\n for (int left = 0, right = 0; right < numsSize; right++) {\\n for (int i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = fmin(res, right - left + 1);\\n for (int i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res == INT_MAX ? -1 : res;\\n}\\n
\\n###JavaScript
\\nvar minimumSubarrayLength = function(nums, k) {\\n const n = nums.length;\\n const bits = new Array(30).fill(0);\\n let res = Number.MAX_SAFE_INTEGER;;\\n\\n const calc = (bits) => {\\n let ans = 0;\\n for (let i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n let left = 0;\\n for (let right = 0; right < n; right++) {\\n for (let i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (let i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res === Number.MAX_SAFE_INTEGER ? -1 : res;\\n};\\n
\\n###TypeScript
\\nfunction minimumSubarrayLength(nums: number[], k: number): number {\\n const n = nums.length;\\n const bits = new Array(30).fill(0);\\n let res = Infinity;\\n\\n const calc = (bits: number[]): number => {\\n let ans = 0;\\n for (let i = 0; i < bits.length; i++) {\\n if (bits[i] > 0) {\\n ans |= 1 << i;\\n }\\n }\\n return ans;\\n };\\n\\n let left = 0;\\n for (let right = 0; right < n; right++) {\\n for (let i = 0; i < 30; i++) {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while (left <= right && calc(bits) >= k) {\\n res = Math.min(res, right - left + 1);\\n for (let i = 0; i < 30; i++) {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left++;\\n }\\n }\\n\\n return res === Infinity ? -1 : res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(nums: Vec<i32>, k: i32) -> i32 {\\n let n = nums.len();\\n let mut bits = [0; 30];\\n let mut res = i32::MAX;\\n\\n let calc = |bits: &[i32]| -> i32 {\\n let mut ans = 0;\\n for i in 0..30 {\\n if bits[i] > 0 {\\n ans |= 1 << i;\\n }\\n }\\n ans\\n };\\n\\n let mut left = 0;\\n for right in 0..n {\\n for i in 0..30 {\\n bits[i] += (nums[right] >> i) & 1;\\n }\\n while left <= right && calc(&bits) >= k {\\n res = res.min((right - left + 1) as i32);\\n for i in 0..30 {\\n bits[i] -= (nums[left] >> i) & 1;\\n }\\n left += 1;\\n }\\n }\\n\\n if res == i32::MAX {\\n -1\\n } else {\\n res\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\log U)$,其中 $n$ 表示给定数组 $\\\\textit{nums}$ 的长度,$U$ 表示数组中的最大的元素。由于使用滑动窗口遍历需要的时间为 $O(n)$,每次更新窗口元素时需要实时计算当前子数组按位或的值需要的时间为 $O(\\\\log U)$,此时需要的总时间即为 $O(n \\\\log U)$。
\\n空间复杂度:$O(\\\\log U)$。计算时需要存储当前子数组中每一个二进制位中的统计情况,最多有 $\\\\log U$ 位需要记录,因此需要的空间为 $\\\\log U$。
\\n我们可以将特殊楼层按照升序排序,然后计算相邻两个特殊楼层之间的楼层数,最后再计算第一个特殊楼层和 $\\\\textit{bottom}$ 之间的楼层数,以及最后一个特殊楼层和 $\\\\textit{top}$ 之间的楼层数,取这些楼层数的最大值即可。
\\n###python
\\nclass Solution:\\n def maxConsecutive(self, bottom: int, top: int, special: List[int]) -> int:\\n special.sort()\\n ans = max(special[0] - bottom, top - special[-1])\\n for x, y in pairwise(special):\\n ans = max(ans, y - x - 1)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] special) {\\n Arrays.sort(special);\\n int n = special.length;\\n int ans = Math.max(special[0] - bottom, top - special[n - 1]);\\n for (int i = 1; i < n; ++i) {\\n ans = Math.max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxConsecutive(int bottom, int top, vector<int>& special) {\\n ranges::sort(special);\\n int ans = max(special[0] - bottom, top - special.back());\\n for (int i = 1; i < special.size(); ++i) {\\n ans = max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxConsecutive(bottom int, top int, special []int) int {\\nsort.Ints(special)\\nans := max(special[0]-bottom, top-special[len(special)-1])\\nfor i, x := range special[1:] {\\nans = max(ans, x-special[i]-1)\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction maxConsecutive(bottom: number, top: number, special: number[]): number {\\n special.sort((a, b) => a - b);\\n const n = special.length;\\n let ans = Math.max(special[0] - bottom, top - special[n - 1]);\\n for (let i = 1; i < n; ++i) {\\n ans = Math.max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n)$,空间复杂度 $O(\\\\log n)$。其中 $n$ 是数组 $\\\\textit{special}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:排序 我们可以将特殊楼层按照升序排序,然后计算相邻两个特殊楼层之间的楼层数,最后再计算第一个特殊楼层和 $\\\\textit{bottom}$ 之间的楼层数,以及最后一个特殊楼层和 $\\\\textit{top}$ 之间的楼层数,取这些楼层数的最大值即可。\\n\\n###python\\n\\nclass Solution:\\n def maxConsecutive(self, bottom: int, top: int, special: List[int]) -> int:\\n special.sort()\\n ans = max…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/python3javacgotypescript-yi-ti-yi-jie-pa-19wl","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-05T23:56:56.953Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-不含特殊楼层的最大连续楼层数🟡","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors/","content":"Alice 管理着一家公司,并租用大楼的部分楼层作为办公空间。Alice 决定将一些楼层作为 特殊楼层 ,仅用于放松。
\\n\\n给你两个整数 bottom
和 top
,表示 Alice 租用了从 bottom
到 top
(含 bottom
和 top
在内)的所有楼层。另给你一个整数数组 special
,其中 special[i]
表示 Alice 指定用于放松的特殊楼层。
返回不含特殊楼层的 最大 连续楼层数。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:bottom = 2, top = 9, special = [4,6]\\n输出:3\\n解释:下面列出的是不含特殊楼层的连续楼层范围:\\n- (2, 3) ,楼层数为 2 。\\n- (5, 5) ,楼层数为 1 。\\n- (7, 9) ,楼层数为 3 。\\n因此,返回最大连续楼层数 3 。\\n\\n\\n
示例 2:
\\n\\n输入:bottom = 6, top = 8, special = [7,6,8]\\n输出:0\\n解释:每层楼都被规划为特殊楼层,所以返回 0 。\\n\\n\\n
\\n\\n
提示
\\n\\n1 <= special.length <= 105
1 <= bottom <= special[i] <= top <= 109
special
中的所有值 互不相同我们用一个数组 $\\\\textit{d}$ 记录钞票面额,用一个数组 $\\\\textit{cnt}$ 记录每种面额的钞票数量。
\\n对于 deposit
操作,我们只需要将对应面额的钞票数量加上即可。时间复杂度 $O(1)$。
对于 withdraw
操作,我们从大到小枚举每种面额的钞票,取出尽可能多且不超过 $\\\\textit{amount}$ 的钞票,然后将 $\\\\textit{amount}$ 减去取出的钞票面额之和,如果最后 $\\\\textit{amount}$ 仍大于 $0$,说明无法取出 $\\\\textit{amount}$ 的钞票,返回 $-1$ 即可。否则,返回取出的钞票数量即可。时间复杂度 $O(1)$。
###python
\\nclass ATM:\\n def __init__(self):\\n self.d = [20, 50, 100, 200, 500]\\n self.m = len(self.d)\\n self.cnt = [0] * self.m\\n\\n def deposit(self, banknotesCount: List[int]) -> None:\\n for i, x in enumerate(banknotesCount):\\n self.cnt[i] += x\\n\\n def withdraw(self, amount: int) -> List[int]:\\n ans = [0] * self.m\\n for i in reversed(range(self.m)):\\n ans[i] = min(amount // self.d[i], self.cnt[i])\\n amount -= ans[i] * self.d[i]\\n if amount > 0:\\n return [-1]\\n for i, x in enumerate(ans):\\n self.cnt[i] -= x\\n return ans\\n\\n\\n# Your ATM object will be instantiated and called as such:\\n# obj = ATM()\\n# obj.deposit(banknotesCount)\\n# param_2 = obj.withdraw(amount)\\n
\\n###java
\\nclass ATM {\\n private int[] d = {20, 50, 100, 200, 500};\\n private int m = d.length;\\n private long[] cnt = new long[5];\\n\\n public ATM() {\\n }\\n\\n public void deposit(int[] banknotesCount) {\\n for (int i = 0; i < banknotesCount.length; ++i) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n public int[] withdraw(int amount) {\\n int[] ans = new int[m];\\n for (int i = m - 1; i >= 0; --i) {\\n ans[i] = (int) Math.min(amount / d[i], cnt[i]);\\n amount -= ans[i] * d[i];\\n }\\n if (amount > 0) {\\n return new int[] {-1};\\n }\\n for (int i = 0; i < m; ++i) {\\n cnt[i] -= ans[i];\\n }\\n return ans;\\n }\\n}\\n\\n/**\\n * Your ATM object will be instantiated and called as such:\\n * ATM obj = new ATM();\\n * obj.deposit(banknotesCount);\\n * int[] param_2 = obj.withdraw(amount);\\n */\\n
\\n###cpp
\\nclass ATM {\\npublic:\\n ATM() {\\n }\\n\\n void deposit(vector<int> banknotesCount) {\\n for (int i = 0; i < banknotesCount.size(); ++i) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n vector<int> withdraw(int amount) {\\n vector<int> ans(m);\\n for (int i = m - 1; ~i; --i) {\\n ans[i] = min(1ll * amount / d[i], cnt[i]);\\n amount -= ans[i] * d[i];\\n }\\n if (amount > 0) {\\n return {-1};\\n }\\n for (int i = 0; i < m; ++i) {\\n cnt[i] -= ans[i];\\n }\\n return ans;\\n }\\n\\nprivate:\\n static constexpr int d[5] = {20, 50, 100, 200, 500};\\n static constexpr int m = size(d);\\n long long cnt[m] = {0};\\n};\\n\\n/**\\n * Your ATM object will be instantiated and called as such:\\n * ATM* obj = new ATM();\\n * obj->deposit(banknotesCount);\\n * vector<int> param_2 = obj->withdraw(amount);\\n */\\n
\\n###go
\\nvar d = [...]int{20, 50, 100, 200, 500}\\n\\nconst m = len(d)\\n\\ntype ATM [m]int\\n\\nfunc Constructor() ATM {\\nreturn ATM{}\\n}\\n\\nfunc (this *ATM) Deposit(banknotesCount []int) {\\nfor i, x := range banknotesCount {\\nthis[i] += x\\n}\\n}\\n\\nfunc (this *ATM) Withdraw(amount int) []int {\\nans := make([]int, m)\\nfor i := m - 1; i >= 0; i-- {\\nans[i] = min(amount/d[i], this[i])\\namount -= ans[i] * d[i]\\n}\\nif amount > 0 {\\nreturn []int{-1}\\n}\\nfor i, x := range ans {\\nthis[i] -= x\\n}\\nreturn ans\\n}\\n\\n/**\\n * Your ATM object will be instantiated and called as such:\\n * obj := Constructor();\\n * obj.Deposit(banknotesCount);\\n * param_2 := obj.Withdraw(amount);\\n */\\n
\\n###ts
\\nconst d: number[] = [20, 50, 100, 200, 500];\\nconst m = d.length;\\n\\nclass ATM {\\n private cnt: number[];\\n\\n constructor() {\\n this.cnt = Array(m).fill(0);\\n }\\n\\n deposit(banknotesCount: number[]): void {\\n for (let i = 0; i < banknotesCount.length; ++i) {\\n this.cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n withdraw(amount: number): number[] {\\n const ans: number[] = Array(m).fill(0);\\n for (let i = m - 1; i >= 0; --i) {\\n ans[i] = Math.min(Math.floor(amount / d[i]), this.cnt[i]);\\n amount -= ans[i] * d[i];\\n }\\n if (amount > 0) {\\n return [-1];\\n }\\n for (let i = 0; i < m; ++i) {\\n this.cnt[i] -= ans[i];\\n }\\n return ans;\\n }\\n}\\n\\n/**\\n * Your ATM object will be instantiated and called as such:\\n * var obj = new ATM()\\n * obj.deposit(banknotesCount)\\n * var param_2 = obj.withdraw(amount)\\n */\\n
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们用一个数组 $\\\\textit{d}$ 记录钞票面额,用一个数组 $\\\\textit{cnt}$ 记录每种面额的钞票数量。\\n\\n对于 deposit 操作,我们只需要将对应面额的钞票数量加上即可。时间复杂度 $O(1)$。\\n\\n对于 withdraw 操作,我们从大到小枚举每种面额的钞票,取出尽可能多且不超过 $\\\\textit{amount}$ 的钞票,然后将 $\\\\textit{amount}$ 减去取出的钞票面额之和,如果最后 $\\\\textit{amount}$ 仍大于 $0$,说明无法取出 $\\\\textit{amount}$ 的钞票,返回…","guid":"https://leetcode.cn/problems/design-an-atm-machine//solution/python3javacgotypescript-yi-ti-yi-jie-mo-gwe5","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-05T00:48:45.758Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-设计一个 ATM 机器🟡","url":"https://leetcode.cn/problems/design-an-atm-machine/","content":"一个 ATM 机器,存有 5
种面值的钞票:20
,50
,100
,200
和 500
美元。初始时,ATM 机是空的。用户可以用它存或者取任意数目的钱。
取款时,机器会优先取 较大 数额的钱。
\\n\\n$300
,并且机器里有 2
张 $50
的钞票,1
张 $100
的钞票和1
张 $200
的钞票,那么机器会取出 $100
和 $200
的钞票。$600
,机器里有 3
张 $200
的钞票和1
张 $500
的钞票,那么取款请求会被拒绝,因为机器会先取出 $500
的钞票,然后无法取出剩余的 $100
。注意,因为有 $500
钞票的存在,机器 不能 取 $200
的钞票。请你实现 ATM 类:
\\n\\nATM()
初始化 ATM 对象。void deposit(int[] banknotesCount)
分别存入 $20
,$50
,$100
,$200
和 $500
钞票的数目。int[] withdraw(int amount)
返回一个长度为 5
的数组,分别表示 $20
,$50
,$100
,$200
和 $500
钞票的数目,并且更新 ATM 机里取款后钞票的剩余数量。如果无法取出指定数额的钱,请返回 [-1]
(这种情况下 不 取出任何钞票)。\\n\\n
示例 1:
\\n\\n输入:\\n[\\"ATM\\", \\"deposit\\", \\"withdraw\\", \\"deposit\\", \\"withdraw\\", \\"withdraw\\"]\\n[[], [[0,0,1,2,1]], [600], [[0,1,0,1,1]], [600], [550]]\\n输出:\\n[null, null, [0,0,1,0,1], null, [-1], [0,1,0,0,1]]\\n\\n解释:\\nATM atm = new ATM();\\natm.deposit([0,0,1,2,1]); // 存入 1 张 $100 ,2 张 $200 和 1 张 $500 的钞票。\\natm.withdraw(600); // 返回 [0,0,1,0,1] 。机器返回 1 张 $100 和 1 张 $500 的钞票。机器里剩余钞票的数量为 [0,0,0,2,0] 。\\natm.deposit([0,1,0,1,1]); // 存入 1 张 $50 ,1 张 $200 和 1 张 $500 的钞票。\\n // 机器中剩余钞票数量为 [0,1,0,3,1] 。\\natm.withdraw(600); // 返回 [-1] 。机器会尝试取出 $500 的钞票,然后无法得到剩余的 $100 ,所以取款请求会被拒绝。\\n // 由于请求被拒绝,机器中钞票的数量不会发生改变。\\natm.withdraw(550); // 返回 [0,1,0,0,1] ,机器会返回 1 张 $50 的钞票和 1 张 $500 的钞票。\\n\\n
\\n\\n
提示:
\\n\\nbanknotesCount.length == 5
0 <= banknotesCount[i] <= 109
1 <= amount <= 109
5000
次 withdraw
和 deposit
的调用。withdraw
和 deposit
至少各有 一次 调用。当 k
个日程存在一些非空交集时(即, k
个日程包含了一些相同时间),就会产生 k
次预订。
给你一些日程安排 [startTime, endTime)
,请你在每个日程安排添加后,返回一个整数 k
,表示所有先前日程安排会产生的最大 k
次预订。
实现一个 MyCalendarThree
类来存放你的日程安排,你可以一直添加新的日程安排。
MyCalendarThree()
初始化对象。int book(int startTime, int endTime)
返回一个整数 k
,表示日历中存在的 k
次预订的最大值。\\n\\n
示例:
\\n\\n输入:\\n[\\"MyCalendarThree\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\"]\\n[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]\\n输出:\\n[null, 1, 1, 2, 3, 3, 3]\\n\\n解释:\\nMyCalendarThree myCalendarThree = new MyCalendarThree();\\nmyCalendarThree.book(10, 20); // 返回 1 ,第一个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。\\nmyCalendarThree.book(50, 60); // 返回 1 ,第二个日程安排可以预订并且不存在相交,所以最大 k 次预订是 1 次预订。\\nmyCalendarThree.book(10, 40); // 返回 2 ,第三个日程安排 [10, 40) 与第一个日程安排相交,所以最大 k 次预订是 2 次预订。\\nmyCalendarThree.book(5, 15); // 返回 3 ,剩下的日程安排的最大 k 次预订是 3 次预订。\\nmyCalendarThree.book(5, 10); // 返回 3\\nmyCalendarThree.book(25, 55); // 返回 3\\n\\n\\n
\\n\\n
提示:
\\n\\n0 <= startTime < endTime <= 109
book
函数最多不超过 400
次思路
\\n长度为 $2k$ 的序列的值的计算方式为,先将其拆成长度相同的前后子串,然后分别计算各子串的所有元素的按位或的结果,再将两个按位或的结果进行按位异或。为了求出 $\\\\textit{nums}$ 的长度为 $2k$ 的子序列的最大值,可以将 $\\\\textit{nums}$ 分成左右两半,分别求出每一半中,长度为 $k$ 的子序列的按位或的所有可能性,再两两求异或的最大值。这样的分法一共有 $n-2k+1$ 种,其中 $n$ 为数组 $\\\\textit{nums}$ 的长度。并且我们可以用动态规划的方法来求出长度为 $k$ 的子序列的按位或的结果的所有可能性。
\\n记 $\\\\textit{dp}[i][j]$ 为哈希集合,表示 $\\\\textit{nums}[0...i]$ 中长度为 $j$ 的自序列按位或的所有可能性。$j \\\\leq k$,且当 $j$ 为 $0$ 时,$\\\\textit{dp}[i][j]$ 为空集。又根据题目限制,$\\\\textit{nums}$ 元素的值为 $0$ 到 $127$,因此 $\\\\textit{dp}[i][j]$ 最多包含 $128$ 个元素。进行转移时,$\\\\textit{dp}[i][j]$ 首先会包含 $\\\\textit{dp}[i-1][j]$ 的所有元素,表示不选取 $\\\\textit{nums}[i]$。还可以将 $\\\\textit{dp}[i-1][j-1]$ 中的所有元素与 $\\\\textit{nums}[i]$ 进行按位或,将结果添加进 $\\\\textit{dp}[i][j]$,表示选取$\\\\textit{nums}[i]$。我们可以将上述步骤抽象成一个函数,并进一步降低空间复杂度,只返回值 $j=k$ 时的 $dp$值,可以算出左一半的长度为 $k$ 的子序列的按位或的所有可能性。再将 $\\\\textit{nums}$ 进行逆序后再次应用这个函数,即可计算出右一半的的长度为 $k$ 的子序列的按位或的所有可能性。再两两求异或的最大值返回。
\\n我们可以将
\\n代码
\\n###Python
\\nclass Solution:\\n def maxValue(self, nums: List[int], k: int) -> int:\\n def findORs(nums: List[int], k: int) -> List[set[int]]:\\n dp = []\\n prev = [set() for _ in range(k + 1)]\\n prev[0].add(0) \\n for i, num in enumerate(nums):\\n for j in range(min(k - 1, i + 1), -1, -1):\\n for x in prev[j]:\\n prev[j + 1].add(x | num)\\n dp.append(prev[k].copy())\\n return dp\\n\\n A = findORs(nums, k)\\n B = findORs(nums[::-1], k)\\n\\n mx = 0\\n for i in range(k - 1, len(nums) - k):\\n mx = max(mx, *(a ^ b for a in A[i] for b in B[-i - 2]))\\n return mx\\n
\\n###Java
\\nclass Solution {\\n public int maxValue(int[] nums, int k) {\\n List<Set<Integer>> A = findORs(nums, k);\\n List<Set<Integer>> B = findORs(reverse(nums), k);\\n int mx = 0;\\n for (int i = k - 1; i < nums.length - k; i++) {\\n for (int a : A.get(i)) {\\n for (int b : B.get(nums.length - i - 2)) {\\n mx = Math.max(mx, a ^ b);\\n }\\n }\\n }\\n return mx;\\n }\\n\\n private List<Set<Integer>> findORs(int[] nums, int k) {\\n List<Set<Integer>> dp = new ArrayList<>();\\n List<Set<Integer>> prev = new ArrayList<>();\\n for (int i = 0; i <= k; i++) {\\n prev.add(new HashSet<>());\\n }\\n prev.get(0).add(0);\\n for (int i = 0; i < nums.length; i++) {\\n for (int j = Math.min(k - 1, i + 1); j >= 0; j--) {\\n for (int x : prev.get(j)) {\\n prev.get(j + 1).add(x | nums[i]);\\n }\\n }\\n dp.add(new HashSet<>(prev.get(k)));\\n }\\n return dp;\\n }\\n\\n private int[] reverse(int[] nums) {\\n int[] reversed = new int[nums.length];\\n for (int i = 0; i < nums.length; i++) {\\n reversed[i] = nums[nums.length - 1 - i];\\n }\\n return reversed;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxValue(int[] nums, int k) {\\n var A = FindORs(nums, k);\\n Array.Reverse(nums);\\n var B = FindORs(nums, k);\\n int mx = 0;\\n for (int i = k - 1; i < nums.Length - k; i++) {\\n foreach (int a in A[i]) {\\n foreach (int b in B[nums.Length - i - 2]) {\\n mx = Math.Max(mx, a ^ b);\\n }\\n }\\n }\\n return mx;\\n }\\n\\n public List<HashSet<int>> FindORs(int[] nums, int k) {\\n var dp = new List<HashSet<int>>();\\n var prev = new List<HashSet<int>>();\\n for (int i = 0; i <= k; i++) {\\n prev.Add(new HashSet<int>());\\n }\\n prev[0].Add(0);\\n for (int i = 0; i < nums.Length; i++) {\\n for (int j = Math.Min(k - 1, i + 1); j >= 0; j--) {\\n foreach (int x in prev[j]) {\\n prev[j + 1].Add(x | nums[i]);\\n }\\n }\\n dp.Add(new HashSet<int>(prev[k]));\\n }\\n return dp;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxValue(vector<int>& nums, int k) {\\n auto findORs = [&](const vector<int>& nums, int k) -> vector<unordered_set<int>> {\\n vector<unordered_set<int>> dp;\\n vector<unordered_set<int>> prev(k + 1);\\n prev[0].insert(0);\\n for (int i = 0; i < nums.size(); ++i) {\\n for (int j = min(k - 1, i + 1); j >= 0; --j) {\\n for (int x : prev[j]) {\\n prev[j + 1].insert(x | nums[i]);\\n }\\n }\\n dp.push_back(prev[k]);\\n }\\n return dp;\\n };\\n\\n vector<unordered_set<int>> A = findORs(nums, k);\\n vector<unordered_set<int>> B = findORs(vector<int>(nums.rbegin(), nums.rend()), k);\\n int mx = 0;\\n for (size_t i = k - 1; i < nums.size() - k; ++i) {\\n for (int a : A[i]) {\\n for (int b : B[nums.size() - i - 2]) {\\n mx = max(mx, a ^ b);\\n }\\n }\\n }\\n return mx;\\n }\\n};\\n
\\n###Go
\\nfunc maxValue(nums []int, k int) int {\\n findORs := func(nums []int, k int) []map[int]bool {\\ndp := make([]map[int]bool, 0)\\nprev := make([]map[int]bool, k + 1)\\nfor i := 0; i <= k; i++ {\\nprev[i] = make(map[int]bool)\\n}\\nprev[0][0] = true\\n\\nfor i := 0; i < len(nums); i++ {\\nfor j := min(k - 1, i + 1); j >= 0; j-- {\\nfor x := range prev[j] {\\nprev[j + 1][x | nums[i]] = true\\n}\\n}\\ncurrent := make(map[int]bool)\\nfor key := range prev[k] {\\ncurrent[key] = true\\n}\\ndp = append(dp, current)\\n}\\nreturn dp\\n}\\n\\nA := findORs(nums, k)\\nreverse(nums)\\nB := findORs(nums, k)\\nmx := 0\\n\\nfor i := k - 1; i < len(nums) - k; i++ {\\nfor a := range A[i] {\\nfor b := range B[len(nums) - i - 2] {\\nif a ^ b > mx {\\nmx = a ^ b\\n}\\n}\\n}\\n}\\nreturn mx\\n}\\n\\nfunc reverse(nums []int) {\\nfor i, j := 0, len(nums)-1; i < j; i, j = i+1, j-1 {\\nnums[i], nums[j] = nums[j], nums[i]\\n}\\n}\\n
\\n###C
\\ntypedef struct {\\n int key;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nvoid findORs(int* nums, int numsSize, int k, HashItem** dp) {\\n HashItem *prev[k + 1];\\n for (int i = 0; i <= k; i++) {\\n prev[i] = NULL;\\n }\\n hashAddItem(&prev[0], 0);\\n for (int i = 0; i < numsSize; ++i) {\\n for (int j = fmin(k - 1, i + 1); j >= 0; --j) {\\n for (HashItem *pEntry = prev[j]; pEntry; pEntry = pEntry->hh.next) {\\n int x = pEntry->key;\\n hashAddItem(&prev[j + 1], x | nums[i]);\\n }\\n }\\n for (HashItem *pEntry = prev[k]; pEntry; pEntry = pEntry->hh.next) {\\n int x = pEntry->key;\\n hashAddItem(&dp[i], x);\\n }\\n }\\n for (int i = 0; i <= k; i++) {\\n hashFree(&prev[i]);\\n }\\n};\\n\\nvoid reverse(int *nums, int numsSize) {\\n for (int i = 0, j = numsSize - 1; i < j; i++, j--) {\\n int x = nums[i];\\n nums[i] = nums[j];\\n nums[j] = x;\\n }\\n}\\n\\nint maxValue(int* nums, int numsSize, int k) {\\n HashItem *A[numsSize];\\n HashItem *B[numsSize];\\n\\n for (int i = 0; i < numsSize; i++) {\\n A[i] = B[i] = NULL;\\n }\\n findORs(nums, numsSize, k, A);\\n reverse(nums, numsSize);\\n findORs(nums, numsSize, k, B);\\n int mx = 0;\\n for (size_t i = k - 1; i < numsSize - k; ++i) {\\n for (HashItem *pEntry = A[i]; pEntry; pEntry = pEntry->hh.next) {\\n int a = pEntry->key;\\n for (HashItem *pEntry = B[numsSize - i - 2]; pEntry; pEntry = pEntry->hh.next) {\\n int b = pEntry->key;\\n mx = fmax(mx, a ^ b);\\n }\\n }\\n }\\n for (int i = 0; i < numsSize; i++) {\\n hashFree(&A[i]);\\n hashFree(&B[i]);\\n }\\n return mx;\\n}\\n
\\n###JavaScript
\\nvar maxValue = function(nums, k) {\\n function findORs(nums, k) {\\n const dp = [];\\n const prev = Array.from({ length: k + 1 }, () => new Set());\\n prev[0].add(0);\\n\\n for (let i = 0; i < nums.length; i++) {\\n for (let j = Math.min(k - 1, i + 1); j >= 0; j--) {\\n for (const x of prev[j]) {\\n prev[j + 1].add(x | nums[i]);\\n }\\n }\\n dp.push(new Set(prev[k]));\\n }\\n return dp;\\n }\\n\\n const A = findORs(nums, k);\\n nums.reverse();\\n const B = findORs(nums, k);\\n let mx = 0;\\n\\n for (let i = k - 1; i < nums.length - k; i++) {\\n for (const a of A[i]) {\\n for (const b of B[nums.length - i - 2]) {\\n mx = Math.max(mx, a ^ b);\\n }\\n }\\n }\\n return mx;\\n};\\n
\\n###TypeScript
\\nfunction maxValue(nums: number[], k: number): number {\\n function findORs(nums: number[], k: number): Set<number>[] {\\n const dp: Set<number>[] = [];\\n const prev: Set<number>[] = Array.from({ length: k + 1 }, () => new Set());\\n prev[0].add(0);\\n\\n for (let i = 0; i < nums.length; i++) {\\n for (let j = Math.min(k - 1, i + 1); j >= 0; j--) {\\n for (const x of prev[j]) {\\n prev[j + 1].add(x | nums[i]);\\n }\\n }\\n dp.push(new Set(prev[k]));\\n }\\n return dp;\\n }\\n\\n const A = findORs(nums, k);\\n nums.reverse();\\n const B = findORs(nums, k);\\n let mx = 0;\\n\\n for (let i = k - 1; i < nums.length - k; i++) {\\n for (const a of A[i]) {\\n for (const b of B[nums.length - i - 2]) {\\n mx = Math.max(mx, a ^ b);\\n }\\n }\\n }\\n return mx;\\n};\\n
\\n###Rust
\\nuse std::collections::HashSet;\\nuse std::cmp::{max, min};\\n\\nimpl Solution {\\n pub fn max_value(nums: Vec<i32>, k: i32) -> i32 {\\n fn find_ors(nums: &Vec<i32>, k: i32) -> Vec<HashSet<i32>> {\\n let mut dp = Vec::new();\\n let mut prev = vec![HashSet::new(); k as usize + 1];\\n prev[0].insert(0);\\n\\n for i in 0..nums.len() {\\n for j in (0..= min(k as usize - 1, i + 1)).rev() {\\n let (before, after) = prev.split_at_mut(j + 1);\\n for &x in &before[j] {\\n after[0].insert(x | nums[i]);\\n }\\n }\\n dp.push(prev[k as usize].clone());\\n }\\n dp\\n }\\n\\n let a = find_ors(&nums, k);\\n let reversed_nums: Vec<i32> = nums.iter().rev().cloned().collect();\\n let b = find_ors(&reversed_nums, k);\\n let mut mx = 0;\\n for i in k as usize - 1..nums.len() - k as usize {\\n for &a_val in &a[i] {\\n for &b_val in &b[nums.len() - i - 2] {\\n mx = mx.max(a_val ^ b_val);\\n }\\n }\\n }\\n mx\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times k \\\\times U + n \\\\times U^2)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$U$ 是数组元素的可能性集合大小。$\\\\textit{dp}$ 的状态有 $O(n\\\\times k)$ 种,每个状态消耗 $O(U)$ 计算。最后两两求异或消耗 $O(n\\\\times U^2)$ 的时间。
\\n空间复杂度:$O(n \\\\times U)$。
\\n思路与算法
\\n我们的目标是求解 $\\\\textit{word1}$ 中有多少子串经过重新排列后存在一个前缀是 $\\\\textit{word2}$,也就是说要求解有多少子串包含 $\\\\textit{word2}$ 中的全部字符。
\\n对于每个 $l(0 \\\\le l \\\\lt n)$,找到最小的 $r(l \\\\le r \\\\lt n)$,使得 $\\\\textit{word1}$ 区间 $[l, r]$ 内包含 $\\\\textit{word2}$ 的全部字符,可以发现子串 $[l, r+1], [l, r+2], \\\\cdots, [l, n]$ 都是满足要求的,计数 $n - r + 1$ 。将所有的计数都加起来就是答案。
\\n注意到每次找到 $l$ 匹配的 $r$ 后,将 $l$ 增加 $1$,区间内的字符减少,相应的 $r$ 势必会增加。因此得到结论:随着 $l$ 的增加,$r$ 也会增加,我们可以使用滑动窗口来求解。
\\n具体来说,我们用哈希表维护当前窗口内每种字符的出现次数,初始时窗口长度为 $0$,每次我们向右移动 $r$,并将字符加入哈希表,直到每种字符的出现次数都大于 $\\\\textit{word2}$ 中的出现次数,此时将答案累加 $n - r + 1$。接着我们要计算 $l + 1$ 作为左边界的情况,将 $l$ 处的字符移除哈希表,并继续向右移动 $r$,重复前面的过程即可。
\\n我们在上面采用了 $O(C)$ 的时间复杂度判断滑动窗口内每种字符的出现次数是否都大于 $\\\\textit{word2}$ 中的出现次数,其实还可以进一步优化。我们将维护的对象更改为滑动窗口内每种字符出现次数与 $\\\\textit{word2}$ 中每种字符出现次数的差值,当所有差值都大于等于 $0$ 则表示合法。但到这一步还没有与之前的方法拉开差距,需要再维护一个变量 $\\\\textit{cnt}$ 表示差值小于 $0$ 的字符种类数,当 $\\\\textit{cnt}$ 等于 $0$ 则表示合法。在滑动窗口移动时,可以仅消耗 $O(1)$ 的时间来维护 $\\\\textit{cnt}$,这样一来就降低了时间复杂度。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string word1, string word2) {\\n vector<int> diff(26, 0);\\n for (auto c : word2) {\\n diff[c - \'a\']--;\\n }\\n\\n long long res = 0;\\n int cnt = count_if(diff.begin(), diff.end(), [](int c) { return c < 0; });\\n auto update = [&](int c, int add) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n for (int l = 0, r = 0; l < word1.size(); l++) {\\n while (r < word1.size() && cnt > 0) {\\n update(word1[r] - \'a\', 1);\\n r++;\\n }\\n if (cnt == 0) {\\n res += word1.size() - r + 1;\\n }\\n update(word1[l] - \'a\', -1);\\n }\\n return res;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def validSubstringCount(self, word1: str, word2: str) -> int:\\n diff = [0] * 26\\n for c in word2:\\n diff[ord(c) - ord(\'a\')] -= 1\\n\\n res = 0\\n cnt = sum(1 for c in diff if c < 0)\\n\\n def update(c: int, add: int):\\n nonlocal cnt\\n diff[c] += add\\n if add == 1 and diff[c] == 0:\\n # 表明 diff[c] 由 -1 变为 0\\n cnt -= 1\\n elif add == -1 and diff[c] == -1:\\n # 表明 diff[c] 由 0 变为 -1\\n cnt += 1\\n\\n l, r = 0, 0\\n while l < len(word1):\\n while r < len(word1) and cnt > 0:\\n update(ord(word1[r]) - ord(\'a\'), 1)\\n r += 1\\n if cnt == 0:\\n res += len(word1) - r + 1\\n update(ord(word1[l]) - ord(\'a\'), -1)\\n l += 1\\n\\n return res\\n
\\n###Rust
\\nimpl Solution {\\n pub fn valid_substring_count(word1: String, word2: String) -> i64 {\\n let mut diff = vec![0; 26];\\n for c in word2.chars() {\\n diff[(c as u8 - b\'a\') as usize] -= 1;\\n }\\n\\n let mut res = 0;\\n let mut cnt = diff.iter().filter(|&&c| c < 0).count();\\n\\n let mut update = |c: usize, add: i32, cnt: &mut usize| {\\n diff[c] += add;\\n if add == 1 && diff[c] == 0 {\\n // 表明 diff[c] 由 -1 变为 0\\n *cnt -= 1;\\n } else if add == -1 && diff[c] == -1 {\\n // 表明 diff[c] 由 0 变为 -1\\n *cnt += 1;\\n }\\n };\\n\\n let (mut l, mut r) = (0, 0);\\n let n = word1.len();\\n let bytes = word1.as_bytes();\\n\\n while l < n {\\n while r < n && cnt > 0 {\\n update((bytes[r] - b\'a\') as usize, 1, &mut cnt);\\n r += 1;\\n }\\n if cnt == 0 {\\n res += (n - r) as i64 + 1;\\n }\\n update((bytes[l] - b\'a\') as usize, -1, &mut cnt);\\n l += 1;\\n }\\n\\n res\\n }\\n}\\n
\\n###Java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n int[] diff = new int[26];\\n for (char c : word2.toCharArray()) {\\n diff[c - \'a\']--;\\n }\\n\\n long res = 0;\\n int[] cnt = { (int) Arrays.stream(diff).filter(c -> c < 0).count() };\\n int l = 0, r = 0;\\n while (l < word1.length()) {\\n while (r < word1.length() && cnt[0] > 0) {\\n update(diff, word1.charAt(r) - \'a\', 1, cnt);\\n r++;\\n }\\n if (cnt[0] == 0) {\\n res += word1.length() - r + 1;\\n }\\n update(diff, word1.charAt(l) - \'a\', -1, cnt);\\n l++;\\n }\\n return res;\\n }\\n\\n private void update(int[] diff, int c, int add, int[] cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt[0]--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt[0]++;\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long ValidSubstringCount(string word1, string word2) {\\n int[] diff = new int[26];\\n foreach (char c in word2) {\\n diff[c - \'a\']--;\\n }\\n long res = 0;\\n int cnt = diff.Count(c => c < 0);\\n int l = 0, r = 0;\\n while (l < word1.Length) {\\n while (r < word1.Length && cnt > 0) {\\n Update(diff, word1[r] - \'a\', 1, ref cnt);\\n r++;\\n }\\n if (cnt == 0) {\\n res += word1.Length - r + 1;\\n }\\n Update(diff, word1[l] - \'a\', -1, ref cnt);\\n l++;\\n }\\n return res;\\n }\\n\\n private void Update(int[] diff, int c, int add, ref int cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n }\\n}\\n
\\n###Go
\\nfunc validSubstringCount(word1 string, word2 string) int64 {\\n diff := make([]int, 26)\\n for _, c := range word2 {\\n diff[c - \'a\']--\\n }\\n cnt := 0\\n for _, c := range diff {\\n if c < 0 {\\n cnt++\\n }\\n }\\n var res int64\\n l, r := 0, 0\\n for l < len(word1) {\\n for r < len(word1) && cnt > 0 {\\n update(diff, int(word1[r] - \'a\'), 1, &cnt)\\n r++\\n }\\n if cnt == 0 {\\n res += int64(len(word1) - r + 1)\\n }\\n update(diff, int(word1[l]-\'a\'), -1, &cnt)\\n l++\\n }\\n\\n return res\\n}\\n\\nfunc update(diff []int, c, add int, cnt *int) {\\n diff[c] += add\\n if add == 1 && diff[c] == 0 {\\n // 表明 diff[c] 由 -1 变为 0\\n *cnt--\\n } else if add == -1 && diff[c] == -1 {\\n // 表明 diff[c] 由 0 变为 -1\\n *cnt++\\n }\\n}\\n
\\n###C
\\nvoid update(int *diff, int c, int add, int *cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n (*cnt)--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n (*cnt)++;\\n }\\n}\\n\\nlong long validSubstringCount(char* word1, char* word2) {\\n int diff[26] = {0};\\n for (const char *c = word2; *c; c++) {\\n diff[*c - \'a\']--;\\n }\\n\\n int cnt = 0;\\n for (int i = 0; i < 26; i++) {\\n if (diff[i] < 0) {\\n cnt++;\\n }\\n }\\n long long res = 0;\\n int l = 0, r = 0;\\n int len1 = strlen(word1);\\n while (l < len1) {\\n while (r < len1 && cnt > 0) {\\n update(diff, word1[r] - \'a\', 1, &cnt);\\n r++;\\n }\\n if (cnt == 0) {\\n res += len1 - r + 1;\\n }\\n update(diff, word1[l] - \'a\', -1, &cnt);\\n l++;\\n }\\n\\n return res;\\n}\\n
\\n###JavaScript
\\nvar validSubstringCount = function(word1, word2) {\\n const diff = new Array(26).fill(0);\\n for (const c of word2) {\\n diff[c.charCodeAt(0) - \'a\'.charCodeAt(0)]--;\\n }\\n\\n let res = 0;\\n let cnt = diff.filter(c => c < 0).length;\\n const update = (c, add) => {\\n diff[c] += add;\\n if (add === 1 && diff[c] === 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add === -1 && diff[c] === -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n let l = 0, r = 0;\\n while (l < word1.length) {\\n while (r < word1.length && cnt > 0) {\\n update(word1.charCodeAt(r) - \'a\'.charCodeAt(0), 1);\\n r++;\\n }\\n if (cnt === 0) {\\n res += word1.length - r + 1;\\n }\\n update(word1.charCodeAt(l) - \'a\'.charCodeAt(0), -1);\\n l++;\\n }\\n\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction validSubstringCount(word1: string, word2: string): number {\\n const diff: number[] = new Array(26).fill(0);\\n for (const c of word2) {\\n diff[c.charCodeAt(0) - \'a\'.charCodeAt(0)]--;\\n }\\n\\n let res = 0;\\n let cnt = diff.filter(c => c < 0).length;\\n const update = (c: number, add: number): void => {\\n diff[c] += add;\\n if (add === 1 && diff[c] === 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add === -1 && diff[c] === -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n let l = 0, r = 0;\\n while (l < word1.length) {\\n while (r < word1.length && cnt > 0) {\\n update(word1.charCodeAt(r) - \'a\'.charCodeAt(0), 1);\\n r++;\\n }\\n if (cnt === 0) {\\n res += word1.length - r + 1;\\n }\\n update(word1.charCodeAt(l) - \'a\'.charCodeAt(0), -1);\\n l++;\\n }\\n\\n return res;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n + m)$,其中 $n$ 是 $\\\\textit{word1}$ 的长度,$m$ 是 $\\\\textit{word2}$ 的长度。
\\n空间复杂度:$O(C)$,其中 $C$ 是字符种类数,本题中等于 $\\\\textit{26}$。
\\n思路与算法
\\n我们的目标是求解 $\\\\textit{word1}$ 中有多少子串经过重新排列后存在一个前缀是 $\\\\textit{word2}$,也就是说要求解有多少子串包含 $\\\\textit{word2}$ 中的全部字符。
\\n对于每个 $l(1 \\\\le l \\\\le n)$,找到最小的 $r(l \\\\le r \\\\le n)$,使得 $\\\\textit{word1}$ 区间 $[l, r]$ 内包含 $\\\\textit{word2}$ 的全部字符,可以发现子串 $[l, r+1], [l, r+2], \\\\cdots, [l, n]$ 都是满足要求的,计数 $n - r + 1$ 。将所有的计数都加起来就是答案。
\\n而找到每个 $l$ 对应的最小的 $r$ 可以使用二分算法,我们提前预处理出 $\\\\textit{word2}$ 中所有字符的出现次数,再预处理 $\\\\textit{word1}$ 每个前缀中每种字符的出现次数。因此在二分查找 $r$ 的过程中,可以 $O(C)$ 时间判断是否满足要求($C$ 是字符数量,此处等于 $\\\\textit{26}$),而那个最小的那个满足要求的下标就是我们要找的 $r$。
\\n由于本方法时间复杂度较高,有些语言可能会超时,建议学习方法二。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string word1, string word2) {\\n vector<int> count(26, 0);\\n for (auto c : word2) {\\n count[c - \'a\']++;\\n }\\n\\n int n = word1.size();\\n vector<vector<int>> pre_count(n + 1, vector<int>(26, 0));\\n for (int i = 1; i <= n; i++) {\\n pre_count[i].assign(pre_count[i - 1].begin(), pre_count[i - 1].end());\\n pre_count[i][word1[i - 1] - \'a\']++;\\n }\\n\\n auto get = [&](int l, int r) {\\n int border = l;\\n while (l < r) {\\n int m = l + r >> 1;\\n bool f = true;\\n for (int i = 0; i < 26; i++) {\\n if (pre_count[m][i] - pre_count[border - 1][i] < count[i]) {\\n f = false;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n };\\n\\n long long res = 0;\\n for (int l = 1; l <= n; l++) {\\n int r = get(l, n + 1);\\n res += n - r + 1;\\n }\\n return res;\\n }\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn valid_substring_count(word1: String, word2: String) -> i64 {\\n let mut count = vec![0; 26];\\n for c in word2.chars() {\\n count[(c as u8 - b\'a\') as usize] += 1;\\n }\\n\\n let n = word1.len();\\n let word1_bytes = word1.as_bytes();\\n\\n let mut pre_count = vec![vec![0; 26]; n + 1];\\n for i in 1..=n {\\n pre_count[i] = pre_count[i - 1].clone();\\n pre_count[i][(word1_bytes[i - 1] - b\'a\') as usize] += 1;\\n }\\n\\n let get = |mut l: usize, mut r: usize| {\\n let border = l - 1;\\n while l < r {\\n let m = (l + r) / 2;\\n let mut valid = true;\\n for i in 0..26 {\\n if pre_count[m][i] - pre_count[border][i] < count[i] {\\n valid = false;\\n break;\\n }\\n }\\n if valid {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n l\\n };\\n\\n let mut res = 0;\\n for l in 1..=n {\\n let r = get(l, n + 1);\\n res += (n + 1 - r) as i64;\\n }\\n res\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def validSubstringCount(self, word1: str, word2: str) -> int:\\n count = [0] * 26\\n for c in word2:\\n count[ord(c) - ord(\'a\')] += 1\\n n = len(word1)\\n pre_count = [[0] * 26 for _ in range(n + 1)]\\n for i in range(1, n + 1):\\n pre_count[i] = pre_count[i - 1][:]\\n pre_count[i][ord(word1[i - 1]) - ord(\'a\')] += 1\\n\\n def get(l, r):\\n border = l\\n while l < r:\\n m = (l + r) // 2\\n if all(pre_count[m][i] - pre_count[border - 1][i] >= count[i] for i in range(26)):\\n r = m\\n else:\\n l = m + 1\\n return l\\n\\n res = 0\\n for l in range(1, n + 1):\\n r = get(l, n + 1)\\n res += n - r + 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n int[] count = new int[26];\\n for (char c : word2.toCharArray()) {\\n count[c - \'a\']++;\\n }\\n int n = word1.length();\\n int[][] preCount = new int[n + 1][26];\\n for (int i = 1; i <= n; i++) {\\n for (int j = 0; j < 26; j++) {\\n preCount[i][j] = preCount[i - 1][j];\\n }\\n preCount[i][word1.charAt(i - 1) - \'a\']++;\\n }\\n long res = 0;\\n for (int l = 1; l <= n; l++) {\\n int r = get(l, n + 1, preCount, count);\\n res += n - r + 1;\\n }\\n return res;\\n }\\n\\n private int get(int l, int r, int[][] preCount, int[] count) {\\n int border = l;\\n while (l < r) {\\n int m = (l + r) >> 1;\\n boolean f = true;\\n for (int i = 0; i < 26; i++) {\\n if (preCount[m][i] - preCount[border - 1][i] < count[i]) {\\n f = false;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long ValidSubstringCount(string word1, string word2) {\\n int[] count = new int[26];\\n foreach (char c in word2) {\\n count[c - \'a\']++;\\n }\\n\\n int n = word1.Length;\\n int[,] preCount = new int[n + 1, 26];\\n for (int i = 1; i <= n; i++) {\\n for (int j = 0; j < 26; j++) {\\n preCount[i, j] = preCount[i - 1, j];\\n }\\n preCount[i, word1[i - 1] - \'a\']++;\\n }\\n long res = 0;\\n for (int l = 1; l <= n; l++) {\\n int r = Get(l, n + 1, preCount, count);\\n res += n - r + 1;\\n }\\n return res;\\n }\\n\\n private int Get(int l, int r, int[,] preCount, int[] count) {\\n int border = l;\\n while (l < r) {\\n int m = (l + r) >> 1;\\n bool f = true;\\n for (int i = 0; i < 26; i++) {\\n if (preCount[m, i] - preCount[border - 1, i] < count[i]) {\\n f = false;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n }\\n}\\n
\\n###Go
\\nfunc validSubstringCount(word1 string, word2 string) int64 {\\n count := make([]int, 26)\\nfor _, c := range word2 {\\ncount[c - \'a\']++\\n}\\nn := len(word1)\\npreCount := make([][]int, n + 1)\\nfor i := range preCount {\\npreCount[i] = make([]int, 26)\\n}\\nfor i := 1; i <= n; i++ {\\ncopy(preCount[i], preCount[i - 1])\\npreCount[i][word1[i - 1] - \'a\']++\\n}\\n\\nvar res int64\\nfor l := 1; l <= n; l++ {\\nr := get(l, n + 1, preCount, count)\\nres += int64(n - r + 1)\\n}\\nreturn res\\n}\\n\\nfunc get(l, r int, preCount [][]int, count []int) int {\\nborder := l\\nfor l < r {\\nm := (l + r) >> 1\\nf := true\\nfor i := 0; i < 26; i++ {\\nif preCount[m][i]-preCount[border - 1][i] < count[i] {\\nf = false\\nbreak\\n}\\n}\\nif f {\\nr = m\\n} else {\\nl = m + 1\\n}\\n}\\nreturn l\\n}\\n
\\n###C
\\nint get(int l, int r, int preCount[][26], int* count) {\\n int border = l;\\n while (l < r) {\\n int m = (l + r) >> 1;\\n int f = 1;\\n for (int i = 0; i < 26; i++) {\\n if (preCount[m][i] - preCount[border - 1][i] < count[i]) {\\n f = 0;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n}\\n\\nlong long validSubstringCount(char* word1, char* word2) {\\n int count[26] = {0};\\n for (int i = 0; word2[i]; i++) {\\n count[word2[i] - \'a\']++;\\n }\\n\\n int n = strlen(word1);\\n int preCount[n + 1][26];\\n memset(preCount, 0, sizeof(preCount));\\n for (int i = 1; i <= n; i++) {\\n memcpy(preCount[i], preCount[i - 1], sizeof(preCount[i]));\\n preCount[i][word1[i - 1] - \'a\']++;\\n }\\n\\n long long res = 0;\\n for (int l = 1; l <= n; l++) {\\n int r = get(l, n + 1, preCount, count);\\n res += n - r + 1;\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar validSubstringCount = function(word1, word2) {\\n const count = Array(26).fill(0);\\n for (let c of word2) {\\n count[c.charCodeAt(0) - \'a\'.charCodeAt(0)]++;\\n }\\n const n = word1.length;\\n const preCount = Array.from({ length: n + 1 }, () => Array(26).fill(0));\\n for (let i = 1; i <= n; i++) {\\n for (let j = 0; j < 26; j++) {\\n preCount[i][j] = preCount[i - 1][j];\\n }\\n preCount[i][word1.charCodeAt(i - 1) - \'a\'.charCodeAt(0)]++;\\n }\\n let res = 0;\\n for (let l = 1; l <= n; l++) {\\n const r = get(l, n + 1, preCount, count);\\n res += n - r + 1;\\n }\\n return res;\\n};\\n\\nconst get = (l, r, preCount, count) => {\\n let border = l;\\n while (l < r) {\\n const m = Math.floor((l + r) / 2);\\n let f = true;\\n for (let i = 0; i < 26; i++) {\\n if (preCount[m][i] - preCount[border - 1][i] < count[i]) {\\n f = false;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n}\\n
\\n###TypeScript
\\nfunction validSubstringCount(word1: string, word2: string): number {\\n const count = Array(26).fill(0);\\n for (let c of word2) {\\n count[c.charCodeAt(0) - \'a\'.charCodeAt(0)]++;\\n }\\n const n = word1.length;\\n const preCount: number[][] = Array.from({ length: n + 1 }, () => Array(26).fill(0));\\n for (let i = 1; i <= n; i++) {\\n for (let j = 0; j < 26; j++) {\\n preCount[i][j] = preCount[i - 1][j];\\n }\\n preCount[i][word1.charCodeAt(i - 1) - \'a\'.charCodeAt(0)]++;\\n }\\n let res = 0;\\n for (let l = 1; l <= n; l++) {\\n const r = get(l, n + 1, preCount, count);\\n res += n - r + 1;\\n }\\n return res;\\n};\\n\\nfunction get(l: number, r: number, preCount: number[][], count: number[]): number {\\n let border = l;\\n while (l < r) {\\n const m = Math.floor((l + r) / 2);\\n let f = true;\\n for (let i = 0; i < 26; i++) {\\n if (preCount[m][i] - preCount[border - 1][i] < count[i]) {\\n f = false;\\n break;\\n }\\n }\\n if (f) {\\n r = m;\\n } else {\\n l = m + 1;\\n }\\n }\\n return l;\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(nC\\\\log n + m)$,其中 $n$ 是 $\\\\textit{word1}$ 的长度,$m$ 是 $\\\\textit{word2}$ 的长度,$C$ 是字符种类数,此题中等于 $\\\\textit{26}$。初始化的时间复杂度分别为 $O(m)$ 和 $O(nC)$,每次二分的时间复杂度为 $O(C\\\\log n)$,因此总的时间复杂度为 $O(nC\\\\log n + m)$。
\\n空间复杂度:$O(nC)$。
\\n思路与算法
\\n每次消耗 $O(C\\\\log n)$ 的时间去找 $r$ 太过奢侈,我们需要发现一些性质来加速。注意到每次找到 $l$ 匹配的 $r$ 后,将 $l$ 增加 $1$,区间内的字符减少,相应的 $r$ 势必会增加。因此得到结论:随着 $l$ 的增加,$r$ 也会增加,我们可以使用滑动窗口来避免二分过程中的重复查找。
\\n具体来说,我们用哈希表维护当前窗口内每种字符的出现次数,初始时窗口长度为 $0$,每次我们向右移动 $r$,并将字符加入哈希表,直到每种字符的出现次数都大于 $\\\\textit{word2}$ 中的出现次数,此时将答案累加 $n - r + 1$。接着我们要计算 $l + 1$ 作为左边界的情况,将 $l$ 处的字符移除哈希表,并继续向右移动 $r$,重复前面的过程即可。
\\n我们在上面采用了 $O(C)$ 的时间复杂度判断滑动窗口内每种字符的出现次数是否都大于 $\\\\textit{word2}$ 中的出现次数,其实还可以进一步优化。我们将维护的对象更改为滑动窗口内每种字符出现次数与 $\\\\textit{word2}$ 中每种字符出现次数的差值,当所有差值都大于等于 $0$ 则表示合法。但到这一步还没有与之前的方法拉开差距,需要再维护一个变量 $\\\\textit{cnt}$ 表示差值小于 $0$ 的字符种类数,当 $\\\\textit{cnt}$ 等于 $0$ 则表示合法。在滑动窗口移动时,可以仅消耗 $O(1)$ 的时间来维护 $\\\\textit{cnt}$,这样一来就降低了时间复杂度。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string word1, string word2) {\\n vector<int> diff(26, 0);\\n for (auto c : word2) {\\n diff[c - \'a\']--;\\n }\\n\\n long long res = 0;\\n int cnt = count_if(diff.begin(), diff.end(), [](int c) { return c < 0; });\\n auto update = [&](int c, int add) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n for (int l = 0, r = 0; l < word1.size(); l++) {\\n while (r < word1.size() && cnt > 0) {\\n update(word1[r] - \'a\', 1);\\n r++;\\n }\\n if (cnt == 0) {\\n res += word1.size() - r + 1;\\n }\\n update(word1[l] - \'a\', -1);\\n }\\n return res;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def validSubstringCount(self, word1: str, word2: str) -> int:\\n diff = [0] * 26\\n for c in word2:\\n diff[ord(c) - ord(\'a\')] -= 1\\n\\n res = 0\\n cnt = sum(1 for c in diff if c < 0)\\n\\n def update(c: int, add: int):\\n nonlocal cnt\\n diff[c] += add\\n if add == 1 and diff[c] == 0:\\n # 表明 diff[c] 由 -1 变为 0\\n cnt -= 1\\n elif add == -1 and diff[c] == -1:\\n # 表明 diff[c] 由 0 变为 -1\\n cnt += 1\\n\\n l, r = 0, 0\\n while l < len(word1):\\n while r < len(word1) and cnt > 0:\\n update(ord(word1[r]) - ord(\'a\'), 1)\\n r += 1\\n if cnt == 0:\\n res += len(word1) - r + 1\\n update(ord(word1[l]) - ord(\'a\'), -1)\\n l += 1\\n\\n return res\\n
\\n###Rust
\\nimpl Solution {\\n pub fn valid_substring_count(word1: String, word2: String) -> i64 {\\n let mut diff = vec![0; 26];\\n for c in word2.chars() {\\n diff[(c as u8 - b\'a\') as usize] -= 1;\\n }\\n\\n let mut res = 0;\\n let mut cnt = diff.iter().filter(|&&c| c < 0).count();\\n\\n let mut update = |c: usize, add: i32, cnt: &mut usize| {\\n diff[c] += add;\\n if add == 1 && diff[c] == 0 {\\n // 表明 diff[c] 由 -1 变为 0\\n *cnt -= 1;\\n } else if add == -1 && diff[c] == -1 {\\n // 表明 diff[c] 由 0 变为 -1\\n *cnt += 1;\\n }\\n };\\n\\n let (mut l, mut r) = (0, 0);\\n let n = word1.len();\\n let bytes = word1.as_bytes();\\n\\n while l < n {\\n while r < n && cnt > 0 {\\n update((bytes[r] - b\'a\') as usize, 1, &mut cnt);\\n r += 1;\\n }\\n if cnt == 0 {\\n res += (n - r) as i64 + 1;\\n }\\n update((bytes[l] - b\'a\') as usize, -1, &mut cnt);\\n l += 1;\\n }\\n\\n res\\n }\\n}\\n
\\n###Java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n int[] diff = new int[26];\\n for (char c : word2.toCharArray()) {\\n diff[c - \'a\']--;\\n }\\n\\n long res = 0;\\n int[] cnt = { (int) Arrays.stream(diff).filter(c -> c < 0).count() };\\n int l = 0, r = 0;\\n while (l < word1.length()) {\\n while (r < word1.length() && cnt[0] > 0) {\\n update(diff, word1.charAt(r) - \'a\', 1, cnt);\\n r++;\\n }\\n if (cnt[0] == 0) {\\n res += word1.length() - r + 1;\\n }\\n update(diff, word1.charAt(l) - \'a\', -1, cnt);\\n l++;\\n }\\n return res;\\n }\\n\\n private void update(int[] diff, int c, int add, int[] cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt[0]--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt[0]++;\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long ValidSubstringCount(string word1, string word2) {\\n int[] diff = new int[26];\\n foreach (char c in word2) {\\n diff[c - \'a\']--;\\n }\\n long res = 0;\\n int cnt = diff.Count(c => c < 0);\\n int l = 0, r = 0;\\n while (l < word1.Length) {\\n while (r < word1.Length && cnt > 0) {\\n Update(diff, word1[r] - \'a\', 1, ref cnt);\\n r++;\\n }\\n if (cnt == 0) {\\n res += word1.Length - r + 1;\\n }\\n Update(diff, word1[l] - \'a\', -1, ref cnt);\\n l++;\\n }\\n return res;\\n }\\n\\n private void Update(int[] diff, int c, int add, ref int cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n }\\n}\\n
\\n###Go
\\nfunc validSubstringCount(word1 string, word2 string) int64 {\\n diff := make([]int, 26)\\n for _, c := range word2 {\\n diff[c - \'a\']--\\n }\\n cnt := 0\\n for _, c := range diff {\\n if c < 0 {\\n cnt++\\n }\\n }\\n var res int64\\n l, r := 0, 0\\n for l < len(word1) {\\n for r < len(word1) && cnt > 0 {\\n update(diff, int(word1[r] - \'a\'), 1, &cnt)\\n r++\\n }\\n if cnt == 0 {\\n res += int64(len(word1) - r + 1)\\n }\\n update(diff, int(word1[l]-\'a\'), -1, &cnt)\\n l++\\n }\\n\\n return res\\n}\\n\\nfunc update(diff []int, c, add int, cnt *int) {\\n diff[c] += add\\n if add == 1 && diff[c] == 0 {\\n // 表明 diff[c] 由 -1 变为 0\\n *cnt--\\n } else if add == -1 && diff[c] == -1 {\\n // 表明 diff[c] 由 0 变为 -1\\n *cnt++\\n }\\n}\\n
\\n###C
\\nvoid update(int *diff, int c, int add, int *cnt) {\\n diff[c] += add;\\n if (add == 1 && diff[c] == 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n (*cnt)--;\\n } else if (add == -1 && diff[c] == -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n (*cnt)++;\\n }\\n}\\n\\nlong long validSubstringCount(char* word1, char* word2) {\\n int diff[26] = {0};\\n for (const char *c = word2; *c; c++) {\\n diff[*c - \'a\']--;\\n }\\n\\n int cnt = 0;\\n for (int i = 0; i < 26; i++) {\\n if (diff[i] < 0) {\\n cnt++;\\n }\\n }\\n long long res = 0;\\n int l = 0, r = 0;\\n int len1 = strlen(word1);\\n while (l < len1) {\\n while (r < len1 && cnt > 0) {\\n update(diff, word1[r] - \'a\', 1, &cnt);\\n r++;\\n }\\n if (cnt == 0) {\\n res += len1 - r + 1;\\n }\\n update(diff, word1[l] - \'a\', -1, &cnt);\\n l++;\\n }\\n\\n return res;\\n}\\n
\\n###JavaScript
\\nvar validSubstringCount = function(word1, word2) {\\n const diff = new Array(26).fill(0);\\n for (const c of word2) {\\n diff[c.charCodeAt(0) - \'a\'.charCodeAt(0)]--;\\n }\\n\\n let res = 0;\\n let cnt = diff.filter(c => c < 0).length;\\n const update = (c, add) => {\\n diff[c] += add;\\n if (add === 1 && diff[c] === 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add === -1 && diff[c] === -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n let l = 0, r = 0;\\n while (l < word1.length) {\\n while (r < word1.length && cnt > 0) {\\n update(word1.charCodeAt(r) - \'a\'.charCodeAt(0), 1);\\n r++;\\n }\\n if (cnt === 0) {\\n res += word1.length - r + 1;\\n }\\n update(word1.charCodeAt(l) - \'a\'.charCodeAt(0), -1);\\n l++;\\n }\\n\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction validSubstringCount(word1: string, word2: string): number {\\n const diff: number[] = new Array(26).fill(0);\\n for (const c of word2) {\\n diff[c.charCodeAt(0) - \'a\'.charCodeAt(0)]--;\\n }\\n\\n let res = 0;\\n let cnt = diff.filter(c => c < 0).length;\\n const update = (c: number, add: number): void => {\\n diff[c] += add;\\n if (add === 1 && diff[c] === 0) {\\n // 表明 diff[c] 由 -1 变为 0\\n cnt--;\\n } else if (add === -1 && diff[c] === -1) {\\n // 表明 diff[c] 由 0 变为 -1\\n cnt++;\\n }\\n };\\n\\n let l = 0, r = 0;\\n while (l < word1.length) {\\n while (r < word1.length && cnt > 0) {\\n update(word1.charCodeAt(r) - \'a\'.charCodeAt(0), 1);\\n r++;\\n }\\n if (cnt === 0) {\\n res += word1.length - r + 1;\\n }\\n update(word1.charCodeAt(l) - \'a\'.charCodeAt(0), -1);\\n l++;\\n }\\n\\n return res;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n + m)$,其中 $n$ 是 $\\\\textit{word1}$ 的长度,$m$ 是 $\\\\textit{word2}$ 的长度。
\\n空间复杂度:$O(C)$,其中 $C$ 是字符种类数,本题中等于 $\\\\textit{26}$。
\\n实现一个程序来存放你的日程安排。如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。
\\n\\n当三个日程安排有一些时间上的交叉时(例如三个日程安排都在同一时间内),就会产生 三重预订。
\\n\\n事件能够用一对整数 startTime
和 endTime
表示,在一个半开区间的时间 [startTime, endTime)
上预定。实数 x
的范围为 startTime <= x < endTime
。
实现 MyCalendarTwo
类:
MyCalendarTwo()
初始化日历对象。boolean book(int startTime, int endTime)
如果可以将日程安排成功添加到日历中而不会导致三重预订,返回 true
。否则,返回 false
并且不要将该日程安排添加到日历中。\\n\\n
示例 1:
\\n\\n输入:\\n[\\"MyCalendarTwo\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\", \\"book\\"]\\n[[], [10, 20], [50, 60], [10, 40], [5, 15], [5, 10], [25, 55]]\\n输出:\\n[null, true, true, true, false, true, true]\\n\\n解释:\\nMyCalendarTwo myCalendarTwo = new MyCalendarTwo();\\nmyCalendarTwo.book(10, 20); // 返回 True,能够预定该日程。\\nmyCalendarTwo.book(50, 60); // 返回 True,能够预定该日程。\\nmyCalendarTwo.book(10, 40); // 返回 True,该日程能够被重复预定。\\nmyCalendarTwo.book(5, 15); // 返回 False,该日程导致了三重预定,所以不能预定。\\nmyCalendarTwo.book(5, 10); // 返回 True,能够预定该日程,因为它不使用已经双重预订的时间 10。\\nmyCalendarTwo.book(25, 55); // 返回 True,能够预定该日程,因为时间段 [25, 40) 将被第三个日程重复预定,时间段 [40, 50) 将被单独预定,而时间段 [50, 55) 将被第二个日程重复预定。\\n\\n\\n
\\n\\n
提示:
\\n\\n0 <= start < end <= 109
book
1000 次。前置题目:请先完成没有重复元素的版本 78. 子集。
\\n视频讲解:回溯算法套路①子集型回溯【基础算法精讲 14】。
\\n为了方便跳过相同元素,在递归前,把 $\\\\textit{nums}$ 排序(从小到大或者从大到小都可以)。
\\n回溯的做法和 78 题类似。需要注意的是,在不选 $\\\\textit{nums}[i]$ 时,要跳过后续所有等于 $\\\\textit{nums}[i]$ 的数。如果不跳过这些数,设 $x=\\\\textit{nums}[i],\\\\ x\'=\\\\textit{nums}[i+1]$,那么「选 $x$ 不选 $x\'$」和「不选 $x$ 选 $x\'$」这两种情况都会加到答案中,这就重复了。
\\nclass Solution:\\n def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:\\n nums.sort()\\n n = len(nums)\\n ans = []\\n path = []\\n\\n def dfs(i: int) -> None:\\n if i == n:\\n ans.append(path.copy()) # 也可以写 path[:]\\n return\\n\\n # 选 x\\n x = nums[i]\\n path.append(x)\\n dfs(i + 1)\\n path.pop() # 恢复现场\\n\\n # 不选 x,那么后面所有等于 x 的数都不选\\n # 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i += 1\\n while i < n and nums[i] == x:\\n i += 1\\n dfs(i)\\n\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n public List<List<Integer>> subsetsWithDup(int[] nums) {\\n Arrays.sort(nums);\\n List<List<Integer>> ans = new ArrayList<>();\\n List<Integer> path = new ArrayList<>();\\n dfs(0, nums, ans, path);\\n return ans;\\n }\\n\\n private void dfs(int i, int[] nums, List<List<Integer>> ans, List<Integer> path) {\\n int n = nums.length;\\n if (i == n) {\\n ans.add(new ArrayList<>(path));\\n return;\\n }\\n\\n // 选 x\\n int x = nums[i];\\n path.add(x);\\n dfs(i + 1, nums, ans, path);\\n path.remove(path.size() - 1); // 恢复现场\\n\\n // 不选 x,跳过所有等于 x 的数\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && nums[i] == x) {\\n i++;\\n }\\n dfs(i, nums, ans, path);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> subsetsWithDup(vector<int>& nums) {\\n ranges::sort(nums);\\n int n = nums.size();\\n vector<vector<int>> ans;\\n vector<int> path;\\n\\n auto dfs = [&](this auto&& dfs, int i) -> void {\\n if (i == n) {\\n ans.push_back(path);\\n return;\\n }\\n\\n // 选 x\\n int x = nums[i];\\n path.push_back(x);\\n dfs(i + 1);\\n path.pop_back(); // 恢复现场\\n\\n // 不选 x,跳过所有等于 x 的数\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && nums[i] == x) {\\n i++;\\n }\\n dfs(i);\\n };\\n\\n dfs(0);\\n return ans;\\n }\\n};\\n
\\nint cmp(const void *a, const void *b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nvoid dfs(int i, int n, int* nums, int** ans, int* returnSize, int** columnSizes, int* path, int pathSize) {\\n if (i == n) {\\n ans[*returnSize] = malloc(sizeof(int) * pathSize);\\n memcpy(ans[*returnSize], path, sizeof(int) * pathSize);\\n (*columnSizes)[(*returnSize)++] = pathSize;\\n return;\\n }\\n\\n // 选 x\\n int x = nums[i];\\n path[pathSize] = x;\\n dfs(i + 1, n, nums, ans, returnSize, columnSizes, path, pathSize + 1);\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && nums[i] == x) {\\n i++;\\n }\\n dfs(i, n, nums, ans, returnSize, columnSizes, path, pathSize);\\n}\\n\\nint** subsetsWithDup(int* nums, int n, int* returnSize, int** columnSizes) {\\n qsort(nums, n, sizeof(int), cmp);\\n\\n int m = 1 << n; // 至多有 2^n 个子集\\n int** ans = malloc(sizeof(int*) * m);\\n *returnSize = 0;\\n *columnSizes = malloc(sizeof(int) * m);\\n\\n int* path = malloc(sizeof(int) * n);\\n dfs(0, n, nums, ans, returnSize, columnSizes, path, 0);\\n\\n free(path);\\n return ans;\\n}\\n
\\nfunc subsetsWithDup(nums []int) (ans [][]int) {\\n slices.Sort(nums)\\n n := len(nums)\\n path := []int{}\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == n {\\n ans = append(ans, slices.Clone(path))\\n return\\n }\\n\\n // 选 x\\n x := nums[i]\\n path = append(path, x)\\n dfs(i + 1)\\n path = path[:len(path)-1] // 恢复现场\\n\\n // 不选 x,跳过所有等于 x 的数\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++\\n for i < n && nums[i] == x {\\n i++\\n }\\n dfs(i)\\n }\\n dfs(0)\\n return ans\\n}\\n
\\nvar subsetsWithDup = function(nums) {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n const ans = [];\\n const path = [];\\n\\n function dfs(i) {\\n if (i === n) {\\n ans.push([...path]);\\n return;\\n }\\n\\n // 选 x\\n const x = nums[i];\\n path.push(x);\\n dfs(i + 1);\\n path.pop(); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && nums[i] === x) {\\n i++;\\n }\\n dfs(i);\\n }\\n\\n dfs(0);\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn subsets_with_dup(mut nums: Vec<i32>) -> Vec<Vec<i32>> {\\n nums.sort_unstable();\\n\\n fn dfs(mut i: usize, nums: &[i32], ans: &mut Vec<Vec<i32>>, path: &mut Vec<i32>) {\\n if i == nums.len() {\\n ans.push(path.clone());\\n return;\\n }\\n\\n // 选 x\\n let x = nums[i];\\n path.push(x);\\n dfs(i + 1, nums, ans, path);\\n path.pop(); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i += 1;\\n while i < nums.len() && nums[i] == x {\\n i += 1;\\n }\\n dfs(i, nums, ans, path);\\n }\\n\\n let mut ans = vec![];\\n let mut path = vec![];\\n dfs(0, &nums, &mut ans, &mut path);\\n ans\\n }\\n}\\n
\\n前置题目:请先完成没有重复元素的版本 78. 子集。
\\n视频讲解:回溯算法套路①子集型回溯【基础算法精讲 14】。
\\n在 $[i,n-1]$ 中枚举要加到 $\\\\textit{path}$ 中的数 $\\\\textit{nums}[j]$,然后递归到 $\\\\textit{dfs}(j+1)$。注意,如果 $j>i$,说明我们没选 $\\\\textit{nums}[j-1]$,那么根据方法一的跳过规则,如果此时 $\\\\textit{nums}[j]=\\\\textit{nums}[j-1]$,则跳过,枚举下一个 $j$。
\\n注意不需要判断 $i=n$,因为此时一定不会进入循环。
\\nclass Solution:\\n def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:\\n nums.sort()\\n n = len(nums)\\n ans = []\\n path = []\\n\\n def dfs(i: int) -> None:\\n ans.append(path.copy()) # 也可以写 path[:]\\n\\n # 在 [i,n-1] 中选一个 nums[j]\\n # 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for j in range(i, n):\\n # 如果 j>i,说明 nums[j-1] 没有选\\n # 同方法一,所有等于 nums[j-1] 的数都不选\\n if j > i and nums[j] == nums[j - 1]:\\n continue\\n path.append(nums[j])\\n dfs(j + 1)\\n path.pop() # 恢复现场\\n\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n public List<List<Integer>> subsetsWithDup(int[] nums) {\\n Arrays.sort(nums);\\n List<List<Integer>> ans = new ArrayList<>();\\n List<Integer> path = new ArrayList<>();\\n dfs(0, nums, ans, path);\\n return ans;\\n }\\n\\n private void dfs(int i, int[] nums, List<List<Integer>> ans, List<Integer> path) {\\n ans.add(new ArrayList<>(path));\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for (int j = i; j < nums.length; j++) {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if (j > i && nums[j] == nums[j - 1]) {\\n continue;\\n }\\n path.add(nums[j]);\\n dfs(j + 1, nums, ans, path);\\n path.remove(path.size() - 1); // 恢复现场\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> subsetsWithDup(vector<int>& nums) {\\n ranges::sort(nums);\\n int n = nums.size();\\n vector<vector<int>> ans;\\n vector<int> path;\\n\\n auto dfs = [&](this auto&& dfs, int i) -> void {\\n ans.push_back(path);\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for (int j = i; j < n; j++) {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if (j > i && nums[j] == nums[j - 1]) {\\n continue;\\n }\\n path.push_back(nums[j]);\\n dfs(j + 1);\\n path.pop_back(); // 恢复现场\\n }\\n };\\n\\n dfs(0);\\n return ans;\\n }\\n};\\n
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nvoid dfs(int i, int n, int* nums, int** ans, int* returnSize, int** columnSizes, int* path, int pathSize) {\\n ans[*returnSize] = malloc(sizeof(int) * pathSize);\\n memcpy(ans[*returnSize], path, sizeof(int) * pathSize);\\n (*columnSizes)[(*returnSize)++] = pathSize;\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for (int j = i; j < n; j++) {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if (j > i && nums[j] == nums[j - 1]) {\\n continue;\\n }\\n path[pathSize] = nums[j];\\n dfs(j + 1, n, nums, ans, returnSize, columnSizes, path, pathSize + 1);\\n }\\n}\\n\\nint** subsetsWithDup(int* nums, int n, int* returnSize, int** columnSizes) {\\n qsort(nums, n, sizeof(int), cmp);\\n\\n int m = 1 << n; // 至多有 2^n 个子集\\n int** ans = malloc(sizeof(int*) * m);\\n *returnSize = 0;\\n *columnSizes = malloc(sizeof(int) * m);\\n\\n int* path = malloc(sizeof(int) * n);\\n dfs(0, n, nums, ans, returnSize, columnSizes, path, 0);\\n\\n free(path);\\n return ans;\\n}\\n
\\nfunc subsetsWithDup(nums []int) (ans [][]int) {\\n slices.Sort(nums)\\n n := len(nums)\\n path := []int{}\\n var dfs func(int)\\n dfs = func(i int) {\\n ans = append(ans, slices.Clone(path))\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for j := i; j < n; j++ {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if j > i && nums[j] == nums[j-1] {\\n continue\\n }\\n path = append(path, nums[j])\\n dfs(j + 1)\\n path = path[:len(path)-1] // 恢复现场\\n }\\n }\\n dfs(0)\\n return ans\\n}\\n
\\nvar subsetsWithDup = function(nums) {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n const ans = [];\\n const path = [];\\n\\n function dfs(i) {\\n ans.push([...path]);\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for (let j = i; j < n; j++) {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if (j > i && nums[j] === nums[j - 1]) {\\n continue;\\n }\\n path.push(nums[j]);\\n dfs(j + 1);\\n path.pop(); // 恢复现场\\n }\\n }\\n\\n dfs(0);\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn subsets_with_dup(mut nums: Vec<i32>) -> Vec<Vec<i32>> {\\n nums.sort_unstable();\\n\\n fn dfs(i: usize, nums: &[i32], ans: &mut Vec<Vec<i32>>, path: &mut Vec<i32>) {\\n ans.push(path.clone());\\n\\n // 在 [i,n-1] 中选一个 nums[j]\\n // 注意选 nums[j] 意味着 [i,j-1] 中的数都没有选\\n for j in i..nums.len() {\\n // 如果 j>i,说明 nums[j-1] 没有选\\n // 同方法一,所有等于 nums[j-1] 的数都不选\\n if j > i && nums[j] == nums[j - 1] {\\n continue;\\n }\\n path.push(nums[j]);\\n dfs(j + 1, nums, ans, path);\\n path.pop(); // 恢复现场\\n }\\n }\\n\\n let mut ans = vec![];\\n let mut path = vec![];\\n dfs(0, &nums, &mut ans, &mut path);\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:选或不选 前置题目:请先完成没有重复元素的版本 78. 子集。\\n\\n视频讲解:回溯算法套路①子集型回溯【基础算法精讲 14】。\\n\\n为了方便跳过相同元素,在递归前,把 $\\\\textit{nums}$ 排序(从小到大或者从大到小都可以)。\\n\\n回溯的做法和 78 题类似。需要注意的是,在不选 $\\\\textit{nums}[i]$ 时,要跳过后续所有等于 $\\\\textit{nums}[i]$ 的数。如果不跳过这些数,设 $x=\\\\textit{nums}[i],\\\\ x\'=\\\\textit{nums}[i+1]$,那么「选 $x$ 不选 $x\'$」和「不选 $x$ 选…","guid":"https://leetcode.cn/problems/subsets-ii//solution/liang-chong-fang-fa-xuan-huo-bu-xuan-mei-v0js","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-02T03:16:17.923Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"求出数字答案","url":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/qiu-chu-shu-zi-da-an-by-leetcode-solutio-84cv","content":"思路与算法
\\n我们可以从低到高枚举三个数的数位,三者的最小值就是答案 $\\\\textit{key}$ 对应数位上的值。无需补充前导 $0$,因为当某个数在当前枚举的数位上没有值时,后续数位的最小值都为 $0$,对于答案 $\\\\textit{key}$ 就是需要删去的前导 $0$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int generateKey(int num1, int num2, int num3) {\\n int key = 0;\\n for (int p = 1; num1 && num2 && num3; p *= 10) {\\n key += min({num1 % 10, num2 % 10, num3 % 10}) * p;\\n num1 /= 10;\\n num2 /= 10;\\n num3 /= 10;\\n }\\n return key;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int generateKey(int num1, int num2, int num3) {\\n int key = 0;\\n for (int p = 1; num1 > 0 && num2 > 0 && num3 > 0; p *= 10) {\\n key += Math.min(Math.min(num1 % 10, num2 % 10), num3 % 10) * p;\\n num1 /= 10;\\n num2 /= 10;\\n num3 /= 10;\\n }\\n return key;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int GenerateKey(int num1, int num2, int num3) {\\n int key = 0;\\n for (int p = 1; num1 > 0 && num2 > 0 && num3 > 0; p *= 10) {\\n key += Math.Min(Math.Min(num1 % 10, num2 % 10), num3 % 10) * p;\\n num1 /= 10;\\n num2 /= 10;\\n num3 /= 10;\\n }\\n return key;\\n }\\n}\\n
\\n###Go
\\nfunc generateKey(num1 int, num2 int, num3 int) int {\\n key := 0\\n for p := 1; num1 > 0 && num2 > 0 && num3 > 0; p *= 10 {\\n key += min(num1 % 10, min(num2 % 10, num3 % 10)) * p\\n num1, num2, num3 = num1 / 10, num2 / 10, num3 / 10\\n }\\n return key\\n}\\n
\\n###Python
\\nclass Solution:\\n def generateKey(self, num1: int, num2: int, num3: int) -> int:\\n key, p = 0, 1\\n while num1 and num2 and num3:\\n key += min(num1 % 10, num2 % 10, num3 % 10) * p\\n p *= 10\\n num1, num2, num3 = num1 // 10, num2 // 10, num3 // 10\\n return key\\n
\\n###C
\\nint generateKey(int num1, int num2, int num3) {\\n int key = 0;\\n for (int p = 1; num1 && num2 && num3; p *= 10) {\\n key += fmin(num1 % 10, fmin(num2 % 10, num3 % 10)) * p;\\n num1 /= 10;\\n num2 /= 10;\\n num3 /= 10;\\n }\\n return key;\\n}\\n
\\n###JavaScript
\\nvar generateKey = function(num1, num2, num3) {\\n let key = 0;\\n for (let p = 1; num1 > 0 && num2 > 0 && num3 > 0; p *= 10) {\\n key += Math.min(num1 % 10, num2 % 10, num3 % 10) * p;\\n num1 = Math.floor(num1 / 10);\\n num2 = Math.floor(num2 / 10);\\n num3 = Math.floor(num3 / 10);\\n }\\n return key;\\n};\\n
\\n###TypeScript
\\nfunction generateKey(num1: number, num2: number, num3: number): number {\\n let key = 0;\\n for (let p = 1; num1 > 0 && num2 > 0 && num3 > 0; p *= 10) {\\n key += Math.min(num1 % 10, num2 % 10, num3 % 10) * p;\\n num1 = Math.floor(num1 / 10);\\n num2 = Math.floor(num2 / 10);\\n num3 = Math.floor(num3 / 10);\\n }\\n return key;\\n}\\n
\\n###Rust
\\nuse std::cmp::min;\\nimpl Solution {\\n pub fn generate_key(mut num1: i32, mut num2: i32, mut num3: i32) -> i32 {\\n let mut key = 0;\\n let mut p = 1;\\n while num1 > 0 && num2 > 0 && num3 > 0 {\\n key += min(min(num1 % 10, num2 % 10), num3 % 10) * p;\\n num1 /= 10;\\n num2 /= 10;\\n num3 /= 10;\\n p *= 10;\\n }\\n key\\n }\\n}\\n
\\n复杂度分析
\\n实现一个 MyCalendar
类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ,则可以存储这个新的日程安排。
当两个日程安排有一些时间上的交叉时(例如两个日程安排都在同一时间内),就会产生 重复预订 。
\\n\\n日程可以用一对整数 startTime
和 endTime
表示,这里的时间是半开区间,即 [startTime, endTime)
, 实数 x
的范围为, startTime <= x < endTime
。
实现 MyCalendar
类:
MyCalendar()
初始化日历对象。boolean book(int startTime, int endTime)
如果可以将日程安排成功添加到日历中而不会导致重复预订,返回 true
。否则,返回 false
并且不要将该日程安排添加到日历中。\\n\\n
示例:
\\n\\n输入:\\n[\\"MyCalendar\\", \\"book\\", \\"book\\", \\"book\\"]\\n[[], [10, 20], [15, 25], [20, 30]]\\n输出:\\n[null, true, false, true]\\n\\n解释:\\nMyCalendar myCalendar = new MyCalendar();\\nmyCalendar.book(10, 20); // return True\\nmyCalendar.book(15, 25); // return False ,这个日程安排不能添加到日历中,因为时间 15 已经被另一个日程安排预订了。\\nmyCalendar.book(20, 30); // return True ,这个日程安排可以添加到日历中,因为第一个日程安排预订的每个时间都小于 20 ,且不包含时间 20 。\\n\\n
\\n\\n
提示:
\\n\\n0 <= start < end <= 109
book
方法的次数最多不超过 1000
次。前置题目:78. 子集
\\n\\n为了方便跳过相同元素和剪枝,在递归前,把 $\\\\textit{candidates}$ 从小到大排序。
\\n用 $\\\\textit{dfs}(i,\\\\textit{left})$ 来回溯,设当前枚举到 $\\\\textit{candidates}[i]$,剩余要选的元素之和为 $\\\\textit{left}$,按照选或不选分类讨论:
\\n递归边界:
\\n递归入口:$\\\\textit{dfs}(0, \\\\textit{target})$。
\\n###py
\\nclass Solution:\\n def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:\\n candidates.sort()\\n n = len(candidates)\\n ans = []\\n path = []\\n\\n def dfs(i: int, left: int) -> None:\\n # 所选元素之和恰好等于 target\\n if left == 0:\\n ans.append(path.copy()) # 也可以写 path[:]\\n return\\n\\n # 没有可以选的数字\\n if i == n:\\n return\\n\\n # 所选元素之和无法恰好等于 target\\n x = candidates[i]\\n if left < x:\\n return\\n\\n # 选 x\\n path.append(x)\\n dfs(i + 1, left - x)\\n path.pop() # 恢复现场\\n\\n # 不选 x,那么后面所有等于 x 的数都不选\\n # 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i += 1\\n while i < n and candidates[i] == x:\\n i += 1\\n dfs(i, left)\\n\\n dfs(0, target)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public List<List<Integer>> combinationSum2(int[] candidates, int target) {\\n Arrays.sort(candidates);\\n List<List<Integer>> ans = new ArrayList<>();\\n List<Integer> path = new ArrayList<>();\\n dfs(0, target, candidates, ans, path);\\n return ans;\\n }\\n\\n private void dfs(int i, int left, int[] candidates, List<List<Integer>> ans, List<Integer> path) {\\n // 所选元素之和恰好等于 target\\n if (left == 0) {\\n ans.add(new ArrayList<>(path));\\n return;\\n }\\n\\n // 没有可以选的数字\\n if (i == candidates.length) {\\n return;\\n }\\n\\n // 所选元素之和无法恰好等于 target\\n int x = candidates[i];\\n if (left < x) {\\n return;\\n }\\n\\n // 选 x\\n path.add(x);\\n dfs(i + 1, left - x, candidates, ans, path);\\n path.remove(path.size() - 1); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < candidates.length && candidates[i] == x) {\\n i++;\\n }\\n dfs(i, left, candidates, ans, path);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {\\n ranges::sort(candidates);\\n int n = candidates.size();\\n vector<vector<int>> ans;\\n vector<int> path;\\n auto dfs = [&](this auto&& dfs, int i, int left) -> void {\\n // 所选元素之和恰好等于 target\\n if (left == 0) {\\n ans.push_back(path);\\n return;\\n }\\n\\n // 没有可以选的数字\\n if (i == n) {\\n return;\\n }\\n\\n // 所选元素之和无法恰好等于 target\\n int x = candidates[i];\\n if (left < x) {\\n return;\\n }\\n\\n // 选 x\\n path.push_back(x);\\n dfs(i + 1, left - x);\\n path.pop_back(); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && candidates[i] == x) {\\n i++;\\n }\\n dfs(i, left);\\n };\\n dfs(0, target);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc combinationSum2(candidates []int, target int) (ans [][]int) {\\n slices.Sort(candidates)\\n n := len(candidates)\\n path := []int{}\\n var dfs func(int, int)\\n dfs = func(i, left int) {\\n // 所选元素之和恰好等于 target\\n if left == 0 {\\n ans = append(ans, slices.Clone(path))\\n return\\n }\\n\\n // 没有可以选的数字\\n if i == n {\\n return\\n }\\n\\n // 所选元素之和无法恰好等于 target\\n x := candidates[i]\\n if left < x {\\n return\\n }\\n\\n // 选 x\\n path = append(path, x)\\n dfs(i+1, left-x)\\n path = path[:len(path)-1] // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++\\n for i < n && candidates[i] == x {\\n i++\\n }\\n dfs(i, left)\\n }\\n dfs(0, target)\\n return ans\\n}\\n
\\n###js
\\nvar combinationSum2 = function(candidates, target) {\\n candidates.sort((a, b) => a - b);\\n const n = candidates.length;\\n const ans = [];\\n const path = [];\\n function dfs(i, left) {\\n // 所选元素之和恰好等于 target\\n if (left === 0) {\\n ans.push([...path]);\\n return;\\n }\\n\\n // 没有可以选的数字\\n if (i === n) {\\n return;\\n }\\n\\n // 所选元素之和无法恰好等于 target\\n const x = candidates[i];\\n if (left < x) {\\n return;\\n }\\n\\n // 选 x\\n path.push(x);\\n dfs(i + 1, left - x);\\n path.pop(); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i++;\\n while (i < n && candidates[i] === x) {\\n i++;\\n }\\n dfs(i, left);\\n }\\n dfs(0, target);\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn combination_sum2(mut candidates: Vec<i32>, target: i32) -> Vec<Vec<i32>> {\\n candidates.sort_unstable();\\n fn dfs(mut i: usize, left: i32, candidates: &[i32], ans: &mut Vec<Vec<i32>>, path: &mut Vec<i32>) {\\n // 所选元素之和恰好等于 target\\n if left == 0 {\\n ans.push(path.clone());\\n return;\\n }\\n\\n // 没有可以选的数字\\n if i == candidates.len() {\\n return;\\n }\\n\\n // 所选元素之和无法恰好等于 target\\n let x = candidates[i];\\n if left < x {\\n return;\\n }\\n\\n // 选 x\\n path.push(x);\\n dfs(i + 1, left - x, candidates, ans, path);\\n path.pop(); // 恢复现场\\n\\n // 不选 x,那么后面所有等于 x 的数都不选\\n // 如果不跳过这些数,会导致「选 x 不选 x\'」和「不选 x 选 x\'」这两种情况都会加到 ans 中,这就重复了\\n i += 1;\\n while i < candidates.len() && candidates[i] == x {\\n i += 1;\\n }\\n dfs(i, left, candidates, ans, path);\\n }\\n let mut ans = vec![];\\n let mut path = vec![];\\n dfs(0, target, &candidates, &mut ans, &mut path);\\n ans\\n }\\n}\\n
\\n考虑极端情况,$\\\\textit{candidates}$ 由 $30$ 个 $1$,$15$ 个 $2$,$10$ 个 $3$,……,$1$ 个 $30$ 组成,这一共有 $111$ 个数。虽然 $111>100$,但我们目的是计算搜索次数的上界,实际搜索次数不会超过这个上界。
\\n对于这样的数据,相当于 $[1,30]$ 中的每个数有无限多个可以选。
\\n由如下完全背包代码可知,当 $\\\\textit{target}=30$ 时,搜索次数不会超过 $28629$。
\\n###py
\\nf = [1] + [0] * 30\\nfor i in range(1, 31):\\n for j in range(i, 31):\\n f[j] += f[j - i]\\nprint(sum(f)) # 28629\\n
\\n\\n\\n读者可以在
\\npath.append(x)
这行代码前添加计数器,验证这一结论。
进一步地,计算 A000041 的前 $\\\\textit{target}$ 项之和,即 A000070,可得:
\\n同样地,把 $\\\\textit{candidates}$ 从小到大排序,用 $\\\\textit{dfs}(i,\\\\textit{left})$ 来回溯。
\\n在 $[i,n-1]$ 中枚举要加到 $\\\\textit{path}$ 中的数 $\\\\textit{candidates}[j]$,然后递归到 $\\\\textit{dfs}(j+1, \\\\textit{left} - \\\\textit{candidates}[j])$。注意,如果 $j>i$,说明我们没选 $\\\\textit{candidates}[j-1]$,那么根据方法一的跳过规则,如果此时 $\\\\textit{candidates}[j]=\\\\textit{candidates}[j-1]$,则跳过,枚举下一个 $j$。
\\n剪枝:在枚举 $j$ 的过程中,如果 $\\\\textit{left}< \\\\textit{candidates}[j]$,由于后面的数都比 $\\\\textit{left}$ 大,所以 $\\\\textit{left}$ 无法减成 $0$,退出循环。
\\n注意不需要判断 $i=n$,因为此时一定不会进入循环。
\\n###py
\\nclass Solution:\\n def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:\\n candidates.sort()\\n n = len(candidates)\\n ans = []\\n path = []\\n\\n def dfs(i: int, left: int) -> None:\\n # 所选元素之和恰好等于 target\\n if left == 0:\\n ans.append(path.copy()) # 也可以写 path[:]\\n return\\n\\n # 在 [i,n-1] 中选一个 candidates[j]\\n # 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for j in range(i, n):\\n # 后面的数不需要选了,元素之和必然无法恰好等于 target\\n if left < candidates[j]:\\n break\\n # 考虑选 candidates[j]\\n # 如果 j>i,说明 candidates[j-1] 没有选 \\n # 同方法一,所有等于 candidates[j-1] 的数都不选\\n if j > i and candidates[j] == candidates[j - 1]:\\n continue\\n path.append(candidates[j])\\n dfs(j + 1, left - candidates[j])\\n path.pop() # 恢复现场\\n\\n dfs(0, target)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public List<List<Integer>> combinationSum2(int[] candidates, int target) {\\n Arrays.sort(candidates);\\n List<List<Integer>> ans = new ArrayList<>();\\n List<Integer> path = new ArrayList<>();\\n dfs(0, target, candidates, ans, path);\\n return ans;\\n }\\n\\n private void dfs(int i, int left, int[] candidates, List<List<Integer>> ans, List<Integer> path) {\\n // 所选元素之和恰好等于 target\\n if (left == 0) {\\n ans.add(new ArrayList<>(path));\\n return;\\n }\\n\\n // 在 [i, n-1] 中选一个 candidates[j]\\n // 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for (int j = i; j < candidates.length && candidates[j] <= left; j++) {\\n // 如果 j>i,说明 candidates[j-1] 没有选\\n // 同方法一,所有等于 candidates[j-1] 的数都不选\\n if (j > i && candidates[j] == candidates[j - 1]) {\\n continue;\\n }\\n path.add(candidates[j]);\\n dfs(j + 1, left - candidates[j], candidates, ans, path);\\n path.remove(path.size() - 1); // 恢复现场\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {\\n ranges::sort(candidates);\\n int n = candidates.size();\\n vector<vector<int>> ans;\\n vector<int> path;\\n auto dfs = [&](this auto&& dfs, int i, int left) -> void {\\n // 所选元素之和恰好等于 target\\n if (left == 0) {\\n ans.push_back(path);\\n return;\\n }\\n\\n // 在 [i, n-1] 中选一个 candidates[j]\\n // 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for (int j = i; j < n && candidates[j] <= left; j++) {\\n // 如果 j>i,说明 candidates[j-1] 没有选\\n // 同方法一,所有等于 candidates[j-1] 的数都不选\\n if (j > i && candidates[j] == candidates[j - 1]) {\\n continue;\\n }\\n path.push_back(candidates[j]);\\n dfs(j + 1, left - candidates[j]);\\n path.pop_back(); // 恢复现场\\n }\\n };\\n dfs(0, target);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc combinationSum2(candidates []int, target int) (ans [][]int) {\\n slices.Sort(candidates)\\n path := []int{}\\n var dfs func(int, int)\\n dfs = func(i, left int) {\\n // 所选元素之和恰好等于 target\\n if left == 0 {\\n ans = append(ans, slices.Clone(path))\\n return\\n }\\n\\n // 在 [i, len(candidates)-1] 中选一个 candidates[j]\\n // 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for j := i; j < len(candidates) && candidates[j] <= left; j++ {\\n // 如果 j>i,说明 candidates[j-1] 没有选 \\n // 同方法一,所有等于 candidates[j-1] 的数都不选\\n if j > i && candidates[j] == candidates[j-1] {\\n continue\\n }\\n path = append(path, candidates[j])\\n dfs(j+1, left-candidates[j])\\n path = path[:len(path)-1] // 恢复现场\\n }\\n }\\n dfs(0, target)\\n return ans\\n}\\n
\\n###js
\\nvar combinationSum2 = function(candidates, target) {\\n candidates.sort((a, b) => a - b);\\n const ans = [];\\n const path = [];\\n var dfs = function(i, left) {\\n // 所选元素之和恰好等于 target\\n if (left === 0) {\\n ans.push([...path]);\\n return;\\n }\\n\\n // 在 [i, candidates.length-1] 中选一个 candidates[j]\\n // 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for (let j = i; j < candidates.length && candidates[j] <= left; j++) {\\n // 如果 j>i,说明 candidates[j-1] 没有选 \\n // 同方法一,所有等于 candidates[j-1] 的数都不选\\n if (j > i && candidates[j] === candidates[j - 1]) {\\n continue;\\n }\\n path.push(candidates[j]);\\n dfs(j + 1, left - candidates[j]);\\n path.pop(); // 恢复现场\\n }\\n };\\n dfs(0, target);\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn combination_sum2(mut candidates: Vec<i32>, target: i32) -> Vec<Vec<i32>> {\\n candidates.sort_unstable();\\n fn dfs(i: usize, left: i32, candidates: &[i32], path: &mut Vec<i32>, ans: &mut Vec<Vec<i32>>) {\\n // 所选元素之和恰好等于 target\\n if left == 0 {\\n ans.push(path.clone());\\n return;\\n }\\n\\n // 在 [i, candidates.len()-1] 中选一个 candidates[j]\\n // 注意选 candidates[j] 意味着 [i,j-1] 中的数都没有选\\n for j in i..candidates.len() {\\n // 后面的数不需要选了,元素之和必然无法恰好等于 target\\n if left < candidates[j] {\\n break;\\n }\\n // 考虑选 candidates[j]\\n // 如果 j>i,说明 candidates[j-1] 没有选 \\n // 同方法一,所有等于 candidates[j-1] 的数都不选\\n if j > i && candidates[j] == candidates[j - 1] {\\n continue;\\n }\\n path.push(candidates[j]);\\n dfs(j + 1, left - candidates[j], candidates, path, ans);\\n path.pop(); // 恢复现场\\n }\\n }\\n let mut ans = vec![];\\n let mut path = vec![];\\n dfs(0, target, &candidates, &mut path, &mut ans);\\n ans\\n }\\n}\\n
\\n同方法一。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:选或不选 前置题目:78. 子集\\n\\n视频讲解:回溯算法套路①子集型回溯【基础算法精讲 14】\\n\\n为了方便跳过相同元素和剪枝,在递归前,把 $\\\\textit{candidates}$ 从小到大排序。\\n\\n用 $\\\\textit{dfs}(i,\\\\textit{left})$ 来回溯,设当前枚举到 $\\\\textit{candidates}[i]$,剩余要选的元素之和为 $\\\\textit{left}$,按照选或不选分类讨论:\\n\\n选 $\\\\textit{candidates}[i]$:递归到 $\\\\textit{dfs}(i+1,\\\\textit{left…","guid":"https://leetcode.cn/problems/combination-sum-ii//solution/liang-chong-fang-fa-xuan-huo-bu-xuan-mei-a7be","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-01T11:27:49.240Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/tan-xin-jian-ji-xie-fa-pythonjavaccgojsr-2ptn","content":"问题相当于每次选三堆硬币,Alice 先取一堆,我们再取一堆,Bob 取剩下的那一堆。
\\n因此,把 $\\\\textit{piles}$ 从大到小排序后,我们可以取走下标为
\\n$$
\\n1,3,5,\\\\ldots,2m-3,2m-1
\\n$$
的硬币堆,其中 $m=n/3$。(注意题目保证 $n$ 是 $3$ 的倍数)
\\n也可以把 $\\\\textit{piles}$ 从小到大排序,取走下标为
\\n$$
\\nm,m+2,m+4,\\\\ldots,n-4,n-2
\\n$$
的硬币堆。
\\n###py
\\nclass Solution:\\n def maxCoins(self, piles: List[int]) -> int:\\n piles.sort()\\n return sum(piles[len(piles)//3::2])\\n
\\n###java
\\nclass Solution {\\n public int maxCoins(int[] piles) {\\n Arrays.sort(piles);\\n int n = piles.length;\\n int ans = 0;\\n for (int i = n / 3; i < n; i += 2) {\\n ans += piles[i];\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxCoins(vector<int>& piles) {\\n ranges::sort(piles);\\n int n = piles.size();\\n int ans = 0;\\n for (int i = n / 3; i < n; i += 2) {\\n ans += piles[i];\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nint maxCoins(int* piles, int n) {\\n qsort(piles, n, sizeof(int), cmp);\\n int ans = 0;\\n for (int i = n / 3; i < n; i += 2) {\\n ans += piles[i];\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc maxCoins(piles []int) (ans int) {\\n slices.Sort(piles)\\n n := len(piles)\\n for i := n / 3; i < n; i += 2 {\\n ans += piles[i]\\n }\\n return\\n}\\n
\\n###js
\\nvar maxCoins = function(piles) {\\n piles.sort((a, b) => a - b);\\n const n = piles.length;\\n let ans = 0;\\n for (let i = n / 3; i < n; i += 2) {\\n ans += piles[i];\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn max_coins(mut piles: Vec<i32>) -> i32 {\\n piles.sort_unstable();\\n piles[piles.len() / 3..].iter().step_by(2).sum()\\n }\\n}\\n
\\n更多相似题目,见下面贪心题单中的「§1.1 从最小/最大开始贪心」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"问题相当于每次选三堆硬币,Alice 先取一堆,我们再取一堆,Bob 取剩下的那一堆。 由于 Bob 总是最后取,每一轮,我们可以把(剩余硬币堆中的)最少的那一堆硬币给 Bob。\\n由于 Alice 总是先取,那么我们不可能取到 $\\\\textit{piles}$ 的最大值,但可以取到次大值。同理,第三大的给 Alice,我们取第四大的。依此类推。\\n\\n因此,把 $\\\\textit{piles}$ 从大到小排序后,我们可以取走下标为\\n\\n$$\\n 1,3,5,\\\\ldots,2m-3,2m-1\\n $$\\n\\n的硬币堆,其中 $m=n/3$。(注意题目保证 $n$ 是 $3…","guid":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/tan-xin-jian-ji-xie-fa-pythonjavaccgojsr-2ptn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-01T07:57:45.321Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:模拟(清晰题解)","url":"https://leetcode.cn/problems/convert-date-to-binary//solution/python3javacgotypescript-yi-ti-yi-jie-mo-uyrl","content":"我们先将字符串 $\\\\textit{date}$ 按照 -
分割,然后将每个部分转换为二进制表示,最后将这三个部分用 -
连接起来即可。
###python
\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n return \\"-\\".join(f\\"{int(s):b}\\" for s in date.split(\\"-\\"))\\n
\\n###java
\\nclass Solution {\\n public String convertDateToBinary(String date) {\\n List<String> ans = new ArrayList<>();\\n for (var s : date.split(\\"-\\")) {\\n int x = Integer.parseInt(s);\\n ans.add(Integer.toBinaryString(x));\\n }\\n return String.join(\\"-\\", ans);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string convertDateToBinary(string date) {\\n auto bin = [](string s) -> string {\\n string t = bitset<32>(stoi(s)).to_string();\\n return t.substr(t.find(\'1\'));\\n };\\n return bin(date.substr(0, 4)) + \\"-\\" + bin(date.substr(5, 2)) + \\"-\\" + bin(date.substr(8, 2));\\n }\\n};\\n
\\n###go
\\nfunc convertDateToBinary(date string) string {\\nans := []string{}\\nfor _, s := range strings.Split(date, \\"-\\") {\\nx, _ := strconv.Atoi(s)\\nans = append(ans, strconv.FormatUint(uint64(x), 2))\\n}\\nreturn strings.Join(ans, \\"-\\")\\n}\\n
\\n###ts
\\nfunction convertDateToBinary(date: string): string {\\n return date\\n .split(\'-\')\\n .map(s => (+s).toString(2))\\n .join(\'-\');\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为字符串 $\\\\textit{date}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们先将字符串 $\\\\textit{date}$ 按照 - 分割,然后将每个部分转换为二进制表示,最后将这三个部分用 - 连接起来即可。\\n\\n###python\\n\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n return \\"-\\".join(f\\"{int(s):b}\\" for s in date.split(\\"-\\"))\\n\\n\\n###java\\n\\nclass Solution {\\n public String…","guid":"https://leetcode.cn/problems/convert-date-to-binary//solution/python3javacgotypescript-yi-ti-yi-jie-mo-uyrl","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2025-01-01T00:02:41.450Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"力扣tv之千万不要学隔壁的姐姐跨年夜一个人坐在电脑前敲敲敲还高喊什么噫好我AC了","url":"https://leetcode.cn/problems/convert-date-to-binary//solution/li-kou-tvzhi-qian-mo-bu-yao-xue-ge-bi-de-untl","content":"如题。
\\n###C#
\\npublic class Solution {\\n public string ConvertDateToBinary(string date) {\\n return string.Join(\'-\', date.Split(\'-\').Select(s => Convert.ToString(int.Parse(s), 2)));\\n }\\n}\\n
\\n###C++
\\n#include <ranges>\\n\\nclass Solution {\\npublic:\\n static auto convertDateToBinary(const std::string &date) -> std::string {\\n return date\\n | std::views::split(\'-\')\\n | std::views::transform([](auto &&num) { return std::format(\\"{:b}\\", std::stoi(num.data())); })\\n | std::views::join_with(\'-\')\\n | std::ranges::to<std::string>();\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public final String convertDateToBinary(final String date) {\\n return Arrays.stream(date.split(\\"-\\"))\\n .map(s -> Integer.toBinaryString(Integer.parseInt(s)))\\n .collect(Collectors.joining(\\"-\\"));\\n }\\n}\\n
\\n###Py
\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n return \'-\'.join(map(lambda s: bin(int(s))[2:], date.split(\'-\')))\\n \\n
\\n###JS
\\nvar convertDateToBinary = function (date) {\\n return date.split(\'-\')\\n .map(s => (parseInt(s, 10).toString(2)))\\n .join(\'-\');\\n};\\n
\\n###TS
\\nfunction convertDateToBinary(date: string): string {\\n return date.split(\'-\')\\n .map(s => (parseInt(s, 10).toString(2)))\\n .join(\'-\');\\n};\\n
\\n###Kotlin
\\nclass Solution {\\n fun convertDateToBinary(date: String): String {\\n return date.split(\\"-\\").map { it.toInt().toString(2) }.joinToString(\\"-\\")\\n }\\n}\\n
\\n给你一个字符串 date
,它的格式为 yyyy-mm-dd
,表示一个公历日期。
date
可以重写为二进制表示,只需要将年、月、日分别转换为对应的二进制表示(不带前导零)并遵循 year-month-day
的格式。
返回 date
的 二进制 表示。
\\n\\n
示例 1:
\\n\\n输入: date = \\"2080-02-29\\"
\\n\\n输出: \\"100000100000-10-11101\\"
\\n\\n解释:
\\n\\n100000100000, 10 和 11101 分别是 2080, 02 和 29 的二进制表示。
\\n示例 2:
\\n\\n输入: date = \\"1900-01-01\\"
\\n\\n输出: \\"11101101100-1-1\\"
\\n\\n解释:
\\n\\n11101101100, 1 和 1 分别是 1900, 1 和 1 的二进制表示。
\\n\\n\\n
提示:
\\n\\ndate.length == 10
date[4] == date[7] == \'-\'
,其余的 date[i]
都是数字。date
代表一个有效的公历日期,日期范围从 1900 年 1 月 1 日到 2100 年 12 月 31 日(包括这两天)。对于一个位置,越早切,所需要切的次数越少,因此,显然是开销越大的位置越早切。
\\n所以,我们可以对数组 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 按照从大到小的顺序排序,然后使用两个指针 $i$ 和 $j$ 分别指向 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的开销,每次选择开销较大的位置进行切割,同时更新对应的行数和列数。
\\n每次在水平方向上切割时,如果此前列数为 $v$,那么此次的开销为 $\\\\textit{horizontalCut}[i] \\\\times v$,然后行数 $h$ 加一;同理,每次在垂直方向上切割时,如果此前行数为 $h$,那么此次的开销为 $\\\\textit{verticalCut}[j] \\\\times h$,然后列数 $v$ 加一。
\\n最后,当 $i$ 和 $j$ 都到达末尾时,返回总开销即可。
\\n###python
\\nclass Solution:\\n def minimumCost(\\n self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]\\n ) -> int:\\n horizontalCut.sort(reverse=True)\\n verticalCut.sort(reverse=True)\\n ans = i = j = 0\\n h = v = 1\\n while i < m - 1 or j < n - 1:\\n if j == n - 1 or (i < m - 1 and horizontalCut[i] > verticalCut[j]):\\n ans += horizontalCut[i] * v\\n h, i = h + 1, i + 1\\n else:\\n ans += verticalCut[j] * h\\n v, j = v + 1, j + 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n Arrays.sort(horizontalCut);\\n Arrays.sort(verticalCut);\\n long ans = 0;\\n int i = m - 2, j = n - 2;\\n int h = 1, v = 1;\\n while (i >= 0 || j >= 0) {\\n if (j < 0 || (i >= 0 && horizontalCut[i] > verticalCut[j])) {\\n ans += 1L * horizontalCut[i--] * v;\\n ++h;\\n } else {\\n ans += 1L * verticalCut[j--] * h;\\n ++v;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {\\n sort(horizontalCut.rbegin(), horizontalCut.rend());\\n sort(verticalCut.rbegin(), verticalCut.rend());\\n long long ans = 0;\\n int i = 0, j = 0;\\n int h = 1, v = 1;\\n while (i < m - 1 || j < n - 1) {\\n if (j == n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {\\n ans += 1LL * horizontalCut[i++] * v;\\n h++;\\n } else {\\n ans += 1LL * verticalCut[j++] * h;\\n v++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minimumCost(m int, n int, horizontalCut []int, verticalCut []int) (ans int64) {\\nsort.Sort(sort.Reverse(sort.IntSlice(horizontalCut)))\\nsort.Sort(sort.Reverse(sort.IntSlice(verticalCut)))\\ni, j := 0, 0\\nh, v := 1, 1\\nfor i < m-1 || j < n-1 {\\nif j == n-1 || (i < m-1 && horizontalCut[i] > verticalCut[j]) {\\nans += int64(horizontalCut[i] * v)\\nh++\\ni++\\n} else {\\nans += int64(verticalCut[j] * h)\\nv++\\nj++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {\\n horizontalCut.sort((a, b) => b - a);\\n verticalCut.sort((a, b) => b - a);\\n let ans = 0;\\n let [i, j] = [0, 0];\\n let [h, v] = [1, 1];\\n while (i < m - 1 || j < n - 1) {\\n if (j === n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {\\n ans += horizontalCut[i] * v;\\n h++;\\n i++;\\n } else {\\n ans += verticalCut[j] * h;\\n v++;\\n j++;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(m \\\\times \\\\log m + n \\\\times \\\\log n)$,空间复杂度 $O(\\\\log m + \\\\log n)$。其中 $m$ 和 $n$ 分别为 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:贪心 + 双指针 对于一个位置,越早切,所需要切的次数越少,因此,显然是开销越大的位置越早切。\\n\\n所以,我们可以对数组 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 按照从大到小的顺序排序,然后使用两个指针 $i$ 和 $j$ 分别指向 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的开销,每次选择开销较大的位置进行切割,同时更新对应的行数和列数。\\n\\n每次在水平方向上切割时,如果此前列数为 $v$,那么此次的开销为 $\\\\textit…","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-ii//solution/python3javacgotypescript-yi-ti-yi-jie-ta-nrvw","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-31T00:16:39.657Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-切蛋糕的最小总开销 II🔴","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-ii/","content":"有一个 m x n
大小的矩形蛋糕,需要切成 1 x 1
的小块。
给你整数 m
,n
和两个数组:
horizontalCut
的大小为 m - 1
,其中 horizontalCut[i]
表示沿着水平线 i
切蛋糕的开销。verticalCut
的大小为 n - 1
,其中 verticalCut[j]
表示沿着垂直线 j
切蛋糕的开销。一次操作中,你可以选择任意不是 1 x 1
大小的矩形蛋糕并执行以下操作之一:
i
切开蛋糕,开销为 horizontalCut[i]
。j
切开蛋糕,开销为 verticalCut[j]
。每次操作后,这块蛋糕都被切成两个独立的小蛋糕。
\\n\\n每次操作的开销都为最开始对应切割线的开销,并且不会改变。
\\n\\n请你返回将蛋糕全部切成 1 x 1
的蛋糕块的 最小 总开销。
\\n\\n
示例 1:
\\n\\n输入:m = 3, n = 2, horizontalCut = [1,3], verticalCut = [5]
\\n\\n输出:13
\\n\\n解释:
\\n\\n3 x 1
的蛋糕块,开销为 1 。3 x 1
的蛋糕块,开销为 1 。2 x 1
的蛋糕块,开销为 3 。2 x 1
的蛋糕块,开销为 3 。总开销为 5 + 1 + 1 + 3 + 3 = 13
。
示例 2:
\\n\\n输入:m = 2, n = 2, horizontalCut = [7], verticalCut = [4]
\\n\\n输出:15
\\n\\n解释:
\\n\\n1 x 2
的蛋糕块,开销为 4 。1 x 2
的蛋糕块,开销为 4 。总开销为 7 + 4 + 4 = 15
。
\\n\\n
提示:
\\n\\n1 <= m, n <= 105
horizontalCut.length == m - 1
verticalCut.length == n - 1
1 <= horizontalCut[i], verticalCut[i] <= 103
\\n\\nProblem: 3403. 从盒子中找出字典序最大的字符串 I
\\n
[TOC]
\\n贪心思路,字符串比对计算
\\n执行用时分布3ms击败100.00%;消耗内存分布17.43MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def answerString(self, word: str, numFriends: int) -> str:\\n if numFriends == 1: return word\\n n, ma = len(word) - numFriends + 1, max(word)\\n return max(word[i : i + n] for i, c in enumerate(word) if c == ma)\\n
\\n###C
\\nchar* answerString(char* word, int numFriends) {\\n if (numFriends == 1) return word;\\n int len = strlen(word), n = len - numFriends + 1, ans = 0;\\n for (int i = 0; i < len; ++ i)\\n if (strncmp(word + i, word + ans, n) > 0) \\n ans = i;\\n if (ans + n < len) \\n *(word + ans + n) = \'\\\\0\';\\n return word + ans;\\n}\\n
\\n","description":"Problem: 3403. 从盒子中找出字典序最大的字符串 I [TOC]\\n\\n贪心思路,字符串比对计算\\n\\n执行用时分布3ms击败100.00%;消耗内存分布17.43MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def answerString(self, word: str, numFriends: int) -> str:\\n if numFriends == 1: return word\\n n, ma = len(word) - numFriends + 1, max(word…","guid":"https://leetcode.cn/problems/find-the-lexicographically-largest-string-from-the-box-i//solution/tan-xin-bi-jiao-by-admiring-meninskyuli-m51g","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-30T06:30:37.109Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"单递归写法(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/dan-di-gui-xie-fa-pythonjavacgo-by-endle-00js","content":"官解写的是递归套递归的写法,这里提供只有一个递归函数的写法。
\\n仍然是两个参数,链表节点 $s$ 和二叉树节点 $t$。分类讨论:
\\n###py
\\nclass Solution:\\n def isSubPath(self, head: ListNode, root: TreeNode) -> bool:\\n def dfs(s: Optional[ListNode], t: Optional[TreeNode]) -> bool:\\n if s is None: # 整个链表匹配完毕\\n return True\\n # 否则需要继续匹配\\n if t is None: # 无法继续匹配\\n return False\\n # 节点值相同则继续匹配,否则从 head 开始重新匹配\\n return s.val == t.val and (dfs(s.next, t.left) or dfs(s.next, t.right)) or \\\\\\n s is head and (dfs(head, t.left) or dfs(head, t.right))\\n return dfs(head, root)\\n
\\n###java
\\nclass Solution {\\n private ListNode head;\\n\\n public boolean isSubPath(ListNode head, TreeNode root) {\\n this.head = head;\\n return dfs(head, root);\\n }\\n\\n private boolean dfs(ListNode s, TreeNode t) {\\n if (s == null) { // 整个链表匹配完毕\\n return true;\\n }\\n // 否则需要继续匹配\\n if (t == null) { // 无法继续匹配\\n return false;\\n }\\n // 节点值相同则继续匹配,否则从 head 开始重新匹配\\n return s.val == t.val && (dfs(s.next, t.left) || dfs(s.next, t.right)) ||\\n s == head && (dfs(head, t.left) || dfs(head, t.right));\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isSubPath(ListNode* head, TreeNode* root) {\\n auto dfs = [&](this auto&& dfs, ListNode* s, TreeNode* t) -> bool {\\n if (s == nullptr) { // 整个链表匹配完毕\\n return true;\\n }\\n // 否则需要继续匹配\\n if (t == nullptr) { // 无法继续匹配\\n return false;\\n }\\n // 节点值相同则继续匹配,否则从 head 开始重新匹配\\n return s->val == t->val && (dfs(s->next, t->left) || dfs(s->next, t->right)) ||\\n s == head && (dfs(head, t->left) || dfs(head, t->right));\\n };\\n return dfs(head, root);\\n }\\n};\\n
\\n###go
\\nfunc isSubPath(head *ListNode, root *TreeNode) bool {\\n var dfs func(*ListNode, *TreeNode) bool\\n dfs = func(s *ListNode, t *TreeNode) bool {\\n if s == nil { // 整个链表匹配完毕\\n return true\\n }\\n // 否则需要继续匹配\\n if t == nil { // 无法继续匹配\\n return false\\n }\\n // 节点值相同则继续匹配,否则从 head 开始重新匹配\\n return s.Val == t.Val && (dfs(s.Next, t.Left) || dfs(s.Next, t.Right)) ||\\n s == head && (dfs(head, t.Left) || dfs(head, t.Right))\\n }\\n return dfs(head, root)\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"官解写的是递归套递归的写法,这里提供只有一个递归函数的写法。 仍然是两个参数,链表节点 $s$ 和二叉树节点 $t$。分类讨论:\\n\\n如果 $s$ 是空的,说明整个链表匹配完毕,返回 $\\\\texttt{true}$。\\n否则需要继续匹配。如果 $t$ 是空的,说明匹配失败,返回 $\\\\texttt{false}$。\\n如果 $s$ 的节点值等于 $t$ 的节点值,那么继续匹配,递归 $s$ 的下一个节点和 $t$ 的左右儿子。\\n如果 $s$ 的节点值不等于 $t$ 的节点值,那么从 $\\\\textit{head}$ 开始重新匹配,递归 $\\\\textit{head}…","guid":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/dan-di-gui-xie-fa-pythonjavacgo-by-endle-00js","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-30T01:10:53.684Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-二叉树中的链表🟡","url":"https://leetcode.cn/problems/linked-list-in-binary-tree/","content":"给你一棵以 root
为根的二叉树和一个 head
为第一个节点的链表。
如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head
为首的链表中每个节点的值,那么请你返回 True
,否则返回 False
。
一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:head = [4,2,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]\\n输出:true\\n解释:树中蓝色的节点构成了与链表对应的子路径。\\n\\n\\n
示例 2:
\\n\\n输入:head = [1,4,2,6], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]\\n输出:true\\n\\n\\n
示例 3:
\\n\\n输入:head = [1,4,2,6,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]\\n输出:false\\n解释:二叉树中不存在一一对应链表的路径。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= node.val <= 100
。1
到 100
之间。1
到 2500
之间。结果的可能的最大长度是 n-numFriends+1 , 因为字符串 前面都相同,那越长的字符串字典序越大,其他剩余的字符串长度都是1.
\\n那就定长滑窗。
\\n最后一段定长滑窗 要特殊处理一下,各种开头到最后都要截取下,因为 这些开头可能字典序大。
\\n用 TreeSet<String> ts 保存可能的结果字符串,最后返回 ts.last()
\\n注意 numFriends为1,即只分成一段,那要直接返回输入。
\\n###Java
\\nclass Solution {\\n public String answerString(String word, int numFriends) {\\n if (numFriends == 1) return word; //注意 numFriends为1,即只分成一段,那要直接返回输入。\\n\\n int n = word.length();\\n int wLen = n - numFriends + 1; //定长滑窗长度。\\n TreeSet<String> ts = new TreeSet<>(); //保存可能的结果字符串\\n for (int i = 0; i < n; i++) {\\n if (i + wLen <= n) ts.add(word.substring(i, i + wLen)); //定长滑窗截取\\n else ts.add(word.substring(i, n)); //最后一段定长滑窗 要特殊处理一下\\n }\\n\\n return ts.last(); //返回 字典序最大的 字符串。\\n }\\n}\\n
\\n","description":"结果的可能的最大长度是 n-numFriends+1 , 因为字符串 前面都相同,那越长的字符串字典序越大,其他剩余的字符串长度都是1. 那就定长滑窗。\\n\\n最后一段定长滑窗 要特殊处理一下,各种开头到最后都要截取下,因为 这些开头可能字典序大。\\n\\n用 TreeSet本题题意不是很清晰,其实问的是:将字符串分成 numFriends
段后,字典序最大的段最大能是多少。
当 numFriends >= 2
时,每段子串的长度最大是 n - numFriends + 1
,其中 n
是原串的长度。因为我们必须保证其它子串的长度至少是 1
。
另外,对于开头下标相同的子串,本身肯定越长越好,这样子串的字典序才能尽量大。
\\n因此我们可以枚举子串的开头下标,然后取长度至多为 n - numFriends + 1
的子串,取其中字典序最大的作为答案即可。
还有一个特殊情况,当 numFriends == 1
时,只能直接返回原串。
这样做的复杂度是 $\\\\mathcal{O}(n^2)$,因为 $n$ 不大可以通过。
\\nclass Solution {\\npublic:\\n string answerString(string word, int numFriends) {\\n if (numFriends == 1) return word;\\n int n = word.size();\\n string ans = \\"\\";\\n // 枚举子串的开头下标\\n for (int i = 0; i < n; i++) {\\n // 取长度至多为 n - numFriends + 1 的子串\\n int len = min(n + 1 - numFriends, n - i);\\n ans = max(ans, word.substr(i, len));\\n }\\n return ans;\\n }\\n};\\n
\\n有没有复杂度更低的做法呢?其实我们只要找到字典序最大的子串,然后取它长度为 n - numFriends + 1
的前缀即可。怎么快速找到字典序最大的子串?可以使用字符串的 最小(大)表示法,复杂度 $\\\\mathcal{O}(n)$。
class Solution {\\npublic:\\n string answerString(string word, int numFriends) {\\n if (numFriends == 1) return word;\\n int n = word.size();\\n \\n // 取字符串中下标为 idx 的字符,若超出原串长度则返回 0\\n auto chr = [&](int idx) {\\n if (idx >= n) return (char) 0;\\n return word[idx];\\n };\\n \\n // 字符串最大表示模板\\n int k = 0, i = 0, j = 1;\\n while (k < n && i < n && j < n) {\\n if (chr(i + k) == chr(j + k)) k++;\\n else {\\n if (chr(i + k) < chr(j + k)) i += k + 1;\\n else j += k + 1;\\n if (i == j) i++;\\n k = 0;\\n }\\n }\\n \\n // 取最大子串长度为 n - numFriends + 1 的前缀\\n int idx = min(i, j);\\n int len = min(n + 1 - numFriends, n - idx);\\n return word.substr(idx, len);\\n }\\n};\\n
\\n","description":"解法:枚举 or 字符串最大表示 本题题意不是很清晰,其实问的是:将字符串分成 numFriends 段后,字典序最大的段最大能是多少。\\n\\n当 numFriends >= 2 时,每段子串的长度最大是 n - numFriends + 1,其中 n 是原串的长度。因为我们必须保证其它子串的长度至少是 1。\\n\\n另外,对于开头下标相同的子串,本身肯定越长越好,这样子串的字典序才能尽量大。\\n\\n因此我们可以枚举子串的开头下标,然后取长度至多为 n - numFriends + 1 的子串,取其中字典序最大的作为答案即可。\\n\\n还有一个特殊情况,当 numFriends =…","guid":"https://leetcode.cn/problems/find-the-lexicographically-largest-string-from-the-box-i//solution/mei-ju-or-zi-fu-chuan-zui-da-biao-shi-xi-aemx","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-29T04:11:39.703Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举左端点/最大表示法(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-lexicographically-largest-string-from-the-box-i//solution/mei-ju-zuo-duan-dian-tan-xin-pythonjavac-y2em","content":"为方便描述,下文把 $\\\\textit{word}$ 简称为 $s$,把 $\\\\textit{numFriends}$ 简称为 $k$。
\\n把 $s$ 分割为 $k$ 个非空子串,返回其中字典序最大的子串。
\\n如果固定子串的左端点,那么子串越长,字典序越大。
\\n所以核心思路是:枚举子串的左端点,计算最大子串。
\\n单个子串的长度不能超过多少?
\\n由于其余 $k-1$ 个子串必须是非空的,取长度为 $1$,其余子串的长度之和至少为 $k-1$。
\\n所以我们枚举的子串,长度至多为 $n-(k-1)$。
\\n注意特判 $k=1$ 的情况,此时无法分割,子串左端点只能是 $0$,答案是 $s$。
\\n###py
\\nclass Solution:\\n def answerString(self, s: str, k: int) -> str:\\n if k == 1:\\n return s\\n n = len(s)\\n return max(s[i: i + n - k + 1] for i in range(n))\\n
\\n###java
\\nclass Solution {\\n public String answerString(String s, int k) {\\n if (k == 1) {\\n return s;\\n }\\n int n = s.length();\\n String ans = \\"\\";\\n for (int i = 0; i < n; i++) {\\n String sub = s.substring(i, Math.min(i + n - k + 1, n));\\n if (sub.compareTo(ans) > 0) {\\n ans = sub;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string answerString(string s, int k) {\\n if (k == 1) {\\n return s;\\n }\\n int n = s.size();\\n string ans;\\n for (int i = 0; i < n; i++) {\\n ans = max(ans, s.substr(i, n - max(k - 1, i)));\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc answerString(s string, k int) (ans string) {\\nif k == 1 {\\nreturn s\\n}\\nn := len(s)\\nfor i := range n {\\nans = max(ans, s[i:min(i+n-k+1, n)])\\n}\\nreturn\\n}\\n
\\n###js
\\nvar answerString = function(s, k) {\\n if (k === 1) {\\n return s;\\n }\\n const n = s.length;\\n let ans = \\"\\";\\n for (let i = 0; i < n; i++) {\\n const sub = s.substring(i, Math.min(i + n - k + 1, n));\\n if (sub > ans) {\\n ans = sub;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn answer_string(s: String, k: i32) -> String {\\n if k == 1 {\\n return s;\\n }\\n let n = s.len();\\n (0..n).map(|i| &s[i..n.min(i + n - k as usize + 1)])\\n .max()\\n .unwrap()\\n .to_string()\\n }\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn answer_string(s: String, k: i32) -> String {\\n if k == 1 {\\n return s;\\n }\\n let n = s.len();\\n let mut ans = \\"\\";\\n for i in 0..n {\\n let end = n.min(i + n - k as usize + 1);\\n ans = ans.max(&s[i..end]);\\n }\\n ans.to_string()\\n }\\n}\\n
\\n也可以先把字典序最大的后缀算出来,然后取它的长度至多为 $n-k+1$ 的前缀,作为答案。
\\n正确性证明。在比较字典序大小的过程中,如果当前的后缀比前面的某个后缀的字典序更大,那么:
\\n如何计算字典序最大的后缀,见 1163. 按字典序排在最后的子串。
\\n###py
\\nclass Solution:\\n def answerString(self, s: str, numFriends: int) -> str:\\n if numFriends == 1:\\n return s\\n n = len(s)\\n i, j = 0, 1\\n while j < n:\\n k = 0\\n while j + k < n and s[i + k] == s[j + k]:\\n k += 1\\n if j + k < n and s[i + k] < s[j + k]:\\n i, j = j, max(j + 1, i + k + 1)\\n else:\\n j += k + 1\\n return s[i: i + n - numFriends + 1]\\n
\\n###java
\\nclass Solution {\\n public String answerString(String s, int numFriends) {\\n if (numFriends == 1) {\\n return s;\\n }\\n int n = s.length();\\n int i = 0;\\n int j = 1;\\n while (j < n) {\\n int k = 0;\\n while (j + k < n && s.charAt(i + k) == s.charAt(j + k)) {\\n k++;\\n }\\n if (j + k < n && s.charAt(i + k) < s.charAt(j + k)) {\\n int t = i;\\n i = j;\\n j = Math.max(j + 1, t + k + 1);\\n } else {\\n j += k + 1;\\n }\\n }\\n return s.substring(i, Math.min(i + n - numFriends + 1, n));\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string answerString(string s, int k) {\\n if (k == 1) {\\n return s;\\n }\\n int n = s.size();\\n int i = 0, j = 1;\\n while (j < n) {\\n int k = 0;\\n while (j + k < n && s[i + k] == s[j + k]) {\\n k++;\\n }\\n if (j + k < n && s[i + k] < s[j + k]) {\\n int t = i;\\n i = j;\\n j = max(j + 1, t + k + 1);\\n } else {\\n j += k + 1;\\n }\\n }\\n return s.substr(i, n - max(k - 1, i));\\n }\\n};\\n
\\n###go
\\nfunc answerString(s string, k int) string {\\nif k == 1 {\\nreturn s\\n}\\nn := len(s)\\ni, j := 0, 1\\nfor j < n {\\nk := 0\\nfor j+k < n && s[i+k] == s[j+k] {\\nk++\\n}\\nif j+k < n && s[i+k] < s[j+k] {\\ni, j = j, max(j+1, i+k+1)\\n} else {\\nj += k + 1\\n}\\n}\\nreturn s[i:min(i+n-k+1, n)]\\n}\\n
\\n###go
\\nfunc answerString(s string, k int) string {\\nif k == 1 {\\nreturn s\\n}\\nsa := (*struct{_[]byte;sa[]int32})(unsafe.Pointer(suffixarray.New([]byte(s)))).sa\\nn := len(s)\\ni := int(sa[n-1])\\nreturn s[i:min(i+n-k+1, n)]\\n}\\n
\\n###js
\\nvar answerString = function(s, numFriends) {\\n if (numFriends === 1) {\\n return s;\\n }\\n const n = s.length;\\n let i = 0, j = 1;\\n while (j < n) {\\n let k = 0;\\n while (j + k < n && s[i + k] === s[j + k]) {\\n k++;\\n }\\n if (j + k < n && s[i + k] < s[j + k]) {\\n const t = i;\\n i = j;\\n j = Math.max(j + 1, t + k + 1);\\n } else {\\n j += k + 1;\\n }\\n }\\n return s.substring(i, Math.min(i + n - numFriends + 1, n));\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn answer_string(word: String, k: i32) -> String {\\n if k == 1 {\\n return word;\\n }\\n let n = word.len();\\n let s = word.as_bytes();\\n let mut i = 0;\\n let mut j = 1;\\n while j < n {\\n let mut k = 0;\\n while j + k < n && s[i + k] == s[j + k] {\\n k += 1;\\n }\\n if j + k < n && s[i + k] < s[j + k] {\\n let t = i;\\n i = j;\\n j = (j + 1).max(t + k + 1);\\n } else {\\n j += k + 1;\\n }\\n }\\n let end = n.min(i + n - k as usize + 1);\\n word[i..end].to_string()\\n }\\n}\\n
\\n更多相似题目,见下面贪心题单中的「§3.1 字典序最小/最大」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"为方便描述,下文把 $\\\\textit{word}$ 简称为 $s$,把 $\\\\textit{numFriends}$ 简称为 $k$。 题意\\n\\n把 $s$ 分割为 $k$ 个非空子串,返回其中字典序最大的子串。\\n\\n方法一:枚举子串左端点\\n\\n如果固定子串的左端点,那么子串越长,字典序越大。\\n\\n所以核心思路是:枚举子串的左端点,计算最大子串。\\n\\n单个子串的长度不能超过多少?\\n\\n由于其余 $k-1$ 个子串必须是非空的,取长度为 $1$,其余子串的长度之和至少为 $k-1$。\\n\\n所以我们枚举的子串,长度至多为 $n-(k-1)$。\\n\\n注意特判 $k=1$ 的情况,此时无法分割…","guid":"https://leetcode.cn/problems/find-the-lexicographically-largest-string-from-the-box-i//solution/mei-ju-zuo-duan-dian-tan-xin-pythonjavac-y2em","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-29T04:04:43.610Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-通过投票对团队排名🟡","url":"https://leetcode.cn/problems/rank-teams-by-votes/","content":"现在有一个特殊的排名系统,依据参赛团队在投票人心中的次序进行排名,每个投票者都需要按从高到低的顺序对参与排名的所有团队进行排位。
\\n\\n排名规则如下:
\\n\\n给你一个字符串数组 votes
代表全体投票者给出的排位情况,请你根据上述排名规则对所有参赛团队进行排名。
请你返回能表示按排名系统 排序后 的所有团队排名的字符串。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:votes = [\\"ABC\\",\\"ACB\\",\\"ABC\\",\\"ACB\\",\\"ACB\\"]\\n输出:\\"ACB\\"\\n解释:\\nA 队获得五票「排位第一」,没有其他队获得「排位第一」,所以 A 队排名第一。\\nB 队获得两票「排位第二」,三票「排位第三」。\\nC 队获得三票「排位第二」,两票「排位第三」。\\n由于 C 队「排位第二」的票数较多,所以 C 队排第二,B 队排第三。\\n\\n\\n
示例 2:
\\n\\n输入:votes = [\\"WXYZ\\",\\"XYZW\\"]\\n输出:\\"XWYZ\\"\\n解释:\\nX 队在并列僵局打破后成为排名第一的团队。X 队和 W 队的「排位第一」票数一样,但是 X 队有一票「排位第二」,而 W 没有获得「排位第二」。 \\n\\n\\n
示例 3:
\\n\\n输入:votes = [\\"ZMNAGUEDSJYLBOPHRQICWFXTVK\\"]\\n输出:\\"ZMNAGUEDSJYLBOPHRQICWFXTVK\\"\\n解释:只有一个投票者,所以排名完全按照他的意愿。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= votes.length <= 1000
1 <= votes[i].length <= 26
votes[i].length == votes[j].length
for 0 <= i, j < votes.length
votes[i][j]
是英文 大写 字母votes[i]
中的所有字母都是唯一的votes[0]
中出现的所有字母 同样也 出现在 votes[j]
中,其中 1 <= j < votes.length
根据题意,我们需要将数组分成两部分,每部分的元素都是互不相同的。因此,我们可以统计数组中每个元素的出现次数,如果某个元素出现的次数大于等于 $3$ 次,那么就无法满足题意。否则,我们可以将数组分成两部分。
\\n###python
\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return max(Counter(nums).values()) < 3\\n
\\n###java
\\nclass Solution {\\n public boolean isPossibleToSplit(int[] nums) {\\n int[] cnt = new int[101];\\n for (int x : nums) {\\n if (++cnt[x] >= 3) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isPossibleToSplit(vector<int>& nums) {\\n int cnt[101]{};\\n for (int x : nums) {\\n if (++cnt[x] >= 3) {\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n
\\n###go
\\nfunc isPossibleToSplit(nums []int) bool {\\ncnt := [101]int{}\\nfor _, x := range nums {\\ncnt[x]++\\nif cnt[x] >= 3 {\\nreturn false\\n}\\n}\\nreturn true\\n}\\n
\\n###ts
\\nfunction isPossibleToSplit(nums: number[]): boolean {\\n const cnt: number[] = Array(101).fill(0);\\n for (const x of nums) {\\n if (++cnt[x] >= 3) {\\n return false;\\n }\\n }\\n return true;\\n}\\n
\\n###rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn is_possible_to_split(nums: Vec<i32>) -> bool {\\n let mut cnt = HashMap::new();\\n for &x in &nums {\\n *cnt.entry(x).or_insert(0) += 1;\\n }\\n *cnt.values().max().unwrap_or(&0) < 3\\n }\\n}\\n
\\n###cs
\\npublic class Solution {\\n public bool IsPossibleToSplit(int[] nums) {\\n int[] cnt = new int[101];\\n foreach (int x in nums) {\\n if (++cnt[x] >= 3) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 是数组的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 根据题意,我们需要将数组分成两部分,每部分的元素都是互不相同的。因此,我们可以统计数组中每个元素的出现次数,如果某个元素出现的次数大于等于 $3$ 次,那么就无法满足题意。否则,我们可以将数组分成两部分。\\n\\n###python\\n\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return max(Counter(nums).values()) < 3\\n\\n\\n###java\\n\\nclass Solution {\\n public…","guid":"https://leetcode.cn/problems/split-the-array//solution/python3javacgotypescript-yi-ti-yi-jie-ji-sg0o","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-28T01:04:46.674Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-分割数组🟢","url":"https://leetcode.cn/problems/split-the-array/","content":"给你一个长度为 偶数 的整数数组 nums
。你需要将这个数组分割成 nums1
和 nums2
两部分,要求:
nums1.length == nums2.length == nums.length / 2
。nums1
应包含 互不相同 的元素。nums2
也应包含 互不相同 的元素。如果能够分割数组就返回 true
,否则返回 false
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,1,2,2,3,4]\\n输出:true\\n解释:分割 nums 的可行方案之一是 nums1 = [1,2,3] 和 nums2 = [1,2,4] 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [1,1,1,1]\\n输出:false\\n解释:分割 nums 的唯一可行方案是 nums1 = [1,1] 和 nums2 = [1,1] 。但 nums1 和 nums2 都不是由互不相同的元素构成。因此,返回 false 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 100
nums.length % 2 == 0
1 <= nums[i] <= 100
根据题目描述,我们可以先遍历一遍数组 $\\\\textit{nums}$,找出所有值为 $x$ 的元素的下标,记录在数组 $\\\\textit{ids}$ 中。
\\n接着遍历数组 $\\\\textit{queries}$,对于每个查询 $i$,如果 $i - 1$ 小于 $\\\\textit{ids}$ 的长度,那么答案就是 $\\\\textit{ids}[i - 1]$,否则答案就是 $-1$。
\\n###python
\\nclass Solution:\\n def occurrencesOfElement(\\n self, nums: List[int], queries: List[int], x: int\\n ) -> List[int]:\\n ids = [i for i, v in enumerate(nums) if v == x]\\n return [ids[i - 1] if i - 1 < len(ids) else -1 for i in queries]\\n
\\n###java
\\nclass Solution {\\n public int[] occurrencesOfElement(int[] nums, int[] queries, int x) {\\n List<Integer> ids = new ArrayList<>();\\n for (int i = 0; i < nums.length; ++i) {\\n if (nums[i] == x) {\\n ids.add(i);\\n }\\n }\\n int m = queries.length;\\n int[] ans = new int[m];\\n for (int i = 0; i < m; ++i) {\\n int j = queries[i] - 1;\\n ans[i] = j < ids.size() ? ids.get(j) : -1;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> occurrencesOfElement(vector<int>& nums, vector<int>& queries, int x) {\\n vector<int> ids;\\n for (int i = 0; i < nums.size(); ++i) {\\n if (nums[i] == x) {\\n ids.push_back(i);\\n }\\n }\\n vector<int> ans;\\n for (int& i : queries) {\\n ans.push_back(i - 1 < ids.size() ? ids[i - 1] : -1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc occurrencesOfElement(nums []int, queries []int, x int) (ans []int) {\\nids := []int{}\\nfor i, v := range nums {\\nif v == x {\\nids = append(ids, i)\\n}\\n}\\nfor _, i := range queries {\\nif i-1 < len(ids) {\\nans = append(ans, ids[i-1])\\n} else {\\nans = append(ans, -1)\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction occurrencesOfElement(nums: number[], queries: number[], x: number): number[] {\\n const ids: number[] = nums.map((v, i) => (v === x ? i : -1)).filter(v => v !== -1);\\n return queries.map(i => ids[i - 1] ?? -1);\\n}\\n
\\n时间复杂度 $O(n + m)$,空间复杂度 $O(n + m)$。其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums}$ 和数组 $\\\\textit{queries}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 根据题目描述,我们可以先遍历一遍数组 $\\\\textit{nums}$,找出所有值为 $x$ 的元素的下标,记录在数组 $\\\\textit{ids}$ 中。\\n\\n接着遍历数组 $\\\\textit{queries}$,对于每个查询 $i$,如果 $i - 1$ 小于 $\\\\textit{ids}$ 的长度,那么答案就是 $\\\\textit{ids}[i - 1]$,否则答案就是 $-1$。\\n\\n###python\\n\\nclass Solution:\\n def occurrencesOfElement(\\n self, nums: List[int…","guid":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array//solution/python3javacgotypescript-yi-ti-yi-jie-mo-eigu","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-27T00:17:53.566Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-查询数组中元素的出现位置🟡","url":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array/","content":"给你一个整数数组 nums
,一个整数数组 queries
和一个整数 x
。
对于每个查询 queries[i]
,你需要找到 nums
中第 queries[i]
个 x
的位置,并返回它的下标。如果数组中 x
的出现次数少于 queries[i]
,该查询的答案为 -1 。
请你返回一个整数数组 answer
,包含所有查询的答案。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,3,1,7], queries = [1,3,2,4], x = 1
\\n\\n输出:[0,-1,2,-1]
\\n\\n解释:
\\n\\nnums
中只有两个 1 ,所以答案为 -1 。nums
中只有两个 1 ,所以答案为 -1 。示例 2:
\\n\\n输入:nums = [1,2,3], queries = [10], x = 5
\\n\\n输出:[-1]
\\n\\n解释:
\\n\\nnums
中没有 5 ,所以答案为 -1 。\\n\\n
提示:
\\n\\n1 <= nums.length, queries.length <= 105
1 <= queries[i] <= 105
1 <= nums[i], x <= 104
我们可以用一个哈希表或者二维数组 $st$ 来存储字符串 $s$ 反转后的所有长度为 $2$ 的子字符串。
\\n然后我们遍历字符串 $s$,对于每一个长度为 $2$ 的子字符串,我们判断它是否在 $st$ 中出现过,是则返回 true
。否则,遍历结束后返回 false
。
###python
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n st = {(a, b) for a, b in pairwise(s[::-1])}\\n return any((a, b) in st for a, b in pairwise(s))\\n
\\n###java
\\nclass Solution {\\n public boolean isSubstringPresent(String s) {\\n boolean[][] st = new boolean[26][26];\\n int n = s.length();\\n for (int i = 0; i < n - 1; ++i) {\\n st[s.charAt(i + 1) - \'a\'][s.charAt(i) - \'a\'] = true;\\n }\\n for (int i = 0; i < n - 1; ++i) {\\n if (st[s.charAt(i) - \'a\'][s.charAt(i + 1) - \'a\']) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isSubstringPresent(string s) {\\n bool st[26][26]{};\\n int n = s.size();\\n for (int i = 0; i < n - 1; ++i) {\\n st[s[i + 1] - \'a\'][s[i] - \'a\'] = true;\\n }\\n for (int i = 0; i < n - 1; ++i) {\\n if (st[s[i] - \'a\'][s[i + 1] - \'a\']) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###go
\\nfunc isSubstringPresent(s string) bool {\\nst := [26][26]bool{}\\nfor i := 0; i < len(s)-1; i++ {\\nst[s[i+1]-\'a\'][s[i]-\'a\'] = true\\n}\\nfor i := 0; i < len(s)-1; i++ {\\nif st[s[i]-\'a\'][s[i+1]-\'a\'] {\\nreturn true\\n}\\n}\\nreturn false\\n}\\n
\\n###ts
\\nfunction isSubstringPresent(s: string): boolean {\\n const st: boolean[][] = Array.from({ length: 26 }, () => Array(26).fill(false));\\n for (let i = 0; i < s.length - 1; ++i) {\\n st[s.charCodeAt(i + 1) - 97][s.charCodeAt(i) - 97] = true;\\n }\\n for (let i = 0; i < s.length - 1; ++i) {\\n if (st[s.charCodeAt(i) - 97][s.charCodeAt(i + 1) - 97]) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(|\\\\Sigma|^2)$。其中 $n$ 为字符串 $s$ 的长度;而 $\\\\Sigma$ 为字符串 $s$ 的字符集,本题中 $\\\\Sigma$ 为小写英文字母,所以 $|\\\\Sigma| = 26$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:哈希表或数组 我们可以用一个哈希表或者二维数组 $st$ 来存储字符串 $s$ 反转后的所有长度为 $2$ 的子字符串。\\n\\n然后我们遍历字符串 $s$,对于每一个长度为 $2$ 的子字符串,我们判断它是否在 $st$ 中出现过,是则返回 true。否则,遍历结束后返回 false。\\n\\n###python\\n\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n st = {(a, b) for a, b in pairwise(s[::-1])}…","guid":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/python3javacgotypescript-yi-ti-yi-jie-ha-mc3d","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-26T00:11:45.998Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-字符串及其反转中是否存在同一子字符串🟢","url":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse/","content":"给你一个字符串 s
,请你判断字符串 s
是否存在一个长度为 2
的子字符串,在其反转后的字符串中也出现。
如果存在这样的子字符串,返回 true
;如果不存在,返回 false
。
\\n\\n
示例 1:
\\n\\n输入:s = \\"leetcode\\"
\\n\\n输出:true
\\n\\n解释:子字符串 \\"ee\\"
的长度为 2
,它也出现在 reverse(s) == \\"edocteel\\"
中。
示例 2:
\\n\\n输入:s = \\"abcba\\"
\\n\\n输出:true
\\n\\n解释:所有长度为 2
的子字符串 \\"ab\\"
、\\"bc\\"
、\\"cb\\"
、\\"ba\\"
也都出现在 reverse(s) == \\"abcba\\"
中。
示例 3:
\\n\\n输入:s = \\"abcd\\"
\\n\\n输出:false
\\n\\n解释:字符串 s
中不存在满足「在其反转后的字符串中也出现」且长度为 2
的子字符串。
\\n\\n
提示:
\\n\\n1 <= s.length <= 100
s
仅由小写英文字母组成。对于一个位置,越早切,所需要切的次数越少,因此,显然是开销越大的位置越早切。
\\n所以,我们可以对数组 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 按照从大到小的顺序排序,然后使用两个指针 $i$ 和 $j$ 分别指向 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的开销,每次选择开销较大的位置进行切割,同时更新对应的行数和列数。
\\n每次在水平方向上切割时,如果此前列数为 $v$,那么此次的开销为 $\\\\textit{horizontalCut}[i] \\\\times v$,然后行数 $h$ 加一;同理,每次在垂直方向上切割时,如果此前行数为 $h$,那么此次的开销为 $\\\\textit{verticalCut}[j] \\\\times h$,然后列数 $v$ 加一。
\\n最后,当 $i$ 和 $j$ 都到达末尾时,返回总开销即可。
\\n###python
\\nclass Solution:\\n def minimumCost(\\n self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]\\n ) -> int:\\n horizontalCut.sort(reverse=True)\\n verticalCut.sort(reverse=True)\\n ans = i = j = 0\\n h = v = 1\\n while i < m - 1 or j < n - 1:\\n if j == n - 1 or (i < m - 1 and horizontalCut[i] > verticalCut[j]):\\n ans += horizontalCut[i] * v\\n h, i = h + 1, i + 1\\n else:\\n ans += verticalCut[j] * h\\n v, j = v + 1, j + 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n Arrays.sort(horizontalCut);\\n Arrays.sort(verticalCut);\\n int ans = 0;\\n int i = m - 2, j = n - 2;\\n int h = 1, v = 1;\\n while (i >= 0 || j >= 0) {\\n if (j < 0 || (i >= 0 && horizontalCut[i] > verticalCut[j])) {\\n ans += horizontalCut[i--] * v;\\n ++h;\\n } else {\\n ans += verticalCut[j--] * h;\\n ++v;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {\\n sort(horizontalCut.rbegin(), horizontalCut.rend());\\n sort(verticalCut.rbegin(), verticalCut.rend());\\n int ans = 0;\\n int i = 0, j = 0;\\n int h = 1, v = 1;\\n while (i < m - 1 || j < n - 1) {\\n if (j == n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {\\n ans += horizontalCut[i++] * v;\\n h++;\\n } else {\\n ans += verticalCut[j++] * h;\\n v++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minimumCost(m int, n int, horizontalCut []int, verticalCut []int) (ans int) {\\nsort.Sort(sort.Reverse(sort.IntSlice(horizontalCut)))\\nsort.Sort(sort.Reverse(sort.IntSlice(verticalCut)))\\ni, j := 0, 0\\nh, v := 1, 1\\nfor i < m-1 || j < n-1 {\\nif j == n-1 || (i < m-1 && horizontalCut[i] > verticalCut[j]) {\\nans += horizontalCut[i] * v\\nh++\\ni++\\n} else {\\nans += verticalCut[j] * h\\nv++\\nj++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {\\n horizontalCut.sort((a, b) => b - a);\\n verticalCut.sort((a, b) => b - a);\\n let ans = 0;\\n let [i, j] = [0, 0];\\n let [h, v] = [1, 1];\\n while (i < m - 1 || j < n - 1) {\\n if (j === n - 1 || (i < m - 1 && horizontalCut[i] > verticalCut[j])) {\\n ans += horizontalCut[i] * v;\\n h++;\\n i++;\\n } else {\\n ans += verticalCut[j] * h;\\n v++;\\n j++;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(m \\\\times \\\\log m + n \\\\times \\\\log n)$,空间复杂度 $O(\\\\log m + \\\\log n)$。其中 $m$ 和 $n$ 分别为 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:贪心 + 双指针 对于一个位置,越早切,所需要切的次数越少,因此,显然是开销越大的位置越早切。\\n\\n所以,我们可以对数组 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 按照从大到小的顺序排序,然后使用两个指针 $i$ 和 $j$ 分别指向 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 的开销,每次选择开销较大的位置进行切割,同时更新对应的行数和列数。\\n\\n每次在水平方向上切割时,如果此前列数为 $v$,那么此次的开销为 $\\\\textit…","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/python3javacgotypescript-yi-ti-yi-jie-ta-vxtu","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-25T00:16:31.160Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-切蛋糕的最小总开销 I🟡","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i/","content":"有一个 m x n
大小的矩形蛋糕,需要切成 1 x 1
的小块。
给你整数 m
,n
和两个数组:
horizontalCut
的大小为 m - 1
,其中 horizontalCut[i]
表示沿着水平线 i
切蛋糕的开销。verticalCut
的大小为 n - 1
,其中 verticalCut[j]
表示沿着垂直线 j
切蛋糕的开销。一次操作中,你可以选择任意不是 1 x 1
大小的矩形蛋糕并执行以下操作之一:
i
切开蛋糕,开销为 horizontalCut[i]
。j
切开蛋糕,开销为 verticalCut[j]
。每次操作后,这块蛋糕都被切成两个独立的小蛋糕。
\\n\\n每次操作的开销都为最开始对应切割线的开销,并且不会改变。
\\n\\n请你返回将蛋糕全部切成 1 x 1
的蛋糕块的 最小 总开销。
\\n\\n
示例 1:
\\n\\n输入:m = 3, n = 2, horizontalCut = [1,3], verticalCut = [5]
\\n\\n输出:13
\\n\\n解释:
\\n\\n3 x 1
的蛋糕块,开销为 1 。3 x 1
的蛋糕块,开销为 1 。2 x 1
的蛋糕块,开销为 3 。2 x 1
的蛋糕块,开销为 3 。总开销为 5 + 1 + 1 + 3 + 3 = 13
。
示例 2:
\\n\\n输入:m = 2, n = 2, horizontalCut = [7], verticalCut = [4]
\\n\\n输出:15
\\n\\n解释:
\\n\\n1 x 2
的蛋糕块,开销为 4 。1 x 2
的蛋糕块,开销为 4 。总开销为 7 + 4 + 4 = 15
。
\\n\\n
提示:
\\n\\n1 <= m, n <= 20
horizontalCut.length == m - 1
verticalCut.length == n - 1
1 <= horizontalCut[i], verticalCut[i] <= 103
有一棵特殊的苹果树,一连 n
天,每天都可以长出若干个苹果。在第 i
天,树上会长出 apples[i]
个苹果,这些苹果将会在 days[i]
天后(也就是说,第 i + days[i]
天时)腐烂,变得无法食用。也可能有那么几天,树上不会长出新的苹果,此时用 apples[i] == 0
且 days[i] == 0
表示。
你打算每天 最多 吃一个苹果来保证营养均衡。注意,你可以在这 n
天之后继续吃苹果。
给你两个长度为 n
的整数数组 days
和 apples
,返回你可以吃掉的苹果的最大数目。
\\n\\n
示例 1:
\\n\\n输入:apples = [1,2,3,5,2], days = [3,2,1,4,2]\\n输出:7\\n解释:你可以吃掉 7 个苹果:\\n- 第一天,你吃掉第一天长出来的苹果。\\n- 第二天,你吃掉一个第二天长出来的苹果。\\n- 第三天,你吃掉一个第二天长出来的苹果。过了这一天,第三天长出来的苹果就已经腐烂了。\\n- 第四天到第七天,你吃的都是第四天长出来的苹果。\\n\\n\\n
示例 2:
\\n\\n输入:apples = [3,0,0,0,0,2], days = [3,0,0,0,0,2]\\n输出:5\\n解释:你可以吃掉 5 个苹果:\\n- 第一天到第三天,你吃的都是第一天长出来的苹果。\\n- 第四天和第五天不吃苹果。\\n- 第六天和第七天,你吃的都是第六天长出来的苹果。\\n\\n\\n
\\n\\n
提示:
\\n\\napples.length == n
days.length == n
1 <= n <= 2 * 104
0 <= apples[i], days[i] <= 2 * 104
apples[i] = 0
时,days[i] = 0
才成立在考场里,一排有 N
个座位,分别编号为 0, 1, 2, ..., N-1
。
当学生进入考场后,他必须坐在能够使他与离他最近的人之间的距离达到最大化的座位上。如果有多个这样的座位,他会坐在编号最小的座位上。(另外,如果考场里没有人,那么学生就坐在 0 号座位上。)
\\n\\n返回 ExamRoom(int N)
类,它有两个公开的函数:其中,函数 ExamRoom.seat()
会返回一个 int
(整型数据),代表学生坐的位置;函数 ExamRoom.leave(int p)
代表坐在座位 p
上的学生现在离开了考场。每次调用 ExamRoom.leave(p)
时都保证有学生坐在座位 p
上。
\\n\\n
示例:
\\n\\n输入:[\\"ExamRoom\\",\\"seat\\",\\"seat\\",\\"seat\\",\\"seat\\",\\"leave\\",\\"seat\\"], [[10],[],[],[],[],[4],[]]\\n输出:[null,0,9,4,2,null,5]\\n解释:\\nExamRoom(10) -> null\\nseat() -> 0,没有人在考场里,那么学生坐在 0 号座位上。\\nseat() -> 9,学生最后坐在 9 号座位上。\\nseat() -> 4,学生最后坐在 4 号座位上。\\nseat() -> 2,学生最后坐在 2 号座位上。\\nleave(4) -> null\\nseat() -> 5,学生最后坐在 5 号座位上。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= N <= 10^9
ExamRoom.seat()
和 ExamRoom.leave()
最多被调用 10^4
次。ExamRoom.leave(p)
时有学生正坐在座位 p
上。最直观的做法是从小到大遍历每一种可能的操作次数,判断数组中的剩余元素是否互不相同。
\\n对于 $i \\\\ge 0$,当执行 $i$ 次操作之后,数组中的剩余元素为下标大于等于 $3i$ 的所有元素。使用哈希集合存储数组中的所有剩余元素,判断是否满足所有剩余元素互不相同。满足所有剩余元素互不相同的最少操作次数即为答案。
\\n###Java
\\nclass Solution {\\n public int minimumOperations(int[] nums) {\\n int length = nums.length;\\n int maxOperations = (length - 1) / 3 + 1;\\n for (int i = 0; i < maxOperations; i++) {\\n if (allUnique(nums, 3 * i, length - 1)) {\\n return i;\\n }\\n }\\n return maxOperations;\\n }\\n\\n public boolean allUnique(int[] nums, int start, int end) {\\n Set<Integer> numsSet = new HashSet<Integer>();\\n for (int i = start; i <= end; i++) {\\n if (!numsSet.add(nums[i])) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumOperations(int[] nums) {\\n int length = nums.Length;\\n int maxOperations = (length - 1) / 3 + 1;\\n for (int i = 0; i < maxOperations; i++) {\\n if (AllUnique(nums, 3 * i, length - 1)) {\\n return i;\\n }\\n }\\n return maxOperations;\\n }\\n\\n public bool AllUnique(int[] nums, int start, int end) {\\n ISet<int> numsSet = new HashSet<int>();\\n for (int i = start; i <= end; i++) {\\n if (!numsSet.Add(nums[i])) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历的操作次数是 $O(n)$,对于每个操作次数需要遍历数组 $\\\\textit{nums}$ 中的所有剩余元素一次判断是否满足所有剩余元素互不相同,因此时间复杂度是 $O(n^2)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。哈希集合的空间是 $O(n)$。
\\n使操作次数最少等价于使剩余元素最多,因此问题转换成计算数组 $\\\\textit{nums}$ 的满足所有元素互不相同的最长后缀。
\\n用 $\\\\textit{minStart}$ 表示数组 $\\\\textit{nums}$ 的满足所有元素互不相同的最长后缀的起始下标,即最小起始下标,初始时 $\\\\textit{minStart}$ 等于数组 $\\\\textit{nums}$ 的长度。反向遍历数组 $\\\\textit{nums}$ 并使用哈希集合存储遍历过的所有元素,当遍历到下标 $i$ 时,执行如下操作。
\\n如果 $\\\\textit{nums}[i]$ 不在哈希集合中,则将 $\\\\textit{nums}[i]$ 加入哈希集合,将 $\\\\textit{minStart}$ 的值更新为 $i$。
\\n如果 $\\\\textit{nums}[i]$ 已经在哈希集合中,则任何起始下标小于等于 $i$ 的后缀都存在重复元素,因此结束遍历。
\\n得到最小起始下标 $\\\\textit{minStart}$ 之后,将最少操作次数记为 $\\\\textit{operations}$,则 $\\\\textit{operations}$ 为满足 $3 \\\\times \\\\textit{operations} \\\\ge \\\\textit{minStart}$ 的最小整数,$\\\\textit{operations} = \\\\Big\\\\lceil \\\\dfrac{\\\\textit{minStart}}{3} \\\\Big\\\\rceil$。
\\n实现方面,计算 $\\\\textit{operations}$ 的方法如下。
\\n当 $\\\\textit{minStart} = 0$ 时,$\\\\textit{operations} = 0$。
\\n当 $\\\\textit{minStart} > 0$ 时,$\\\\textit{operations} = \\\\Big\\\\lfloor \\\\dfrac{\\\\textit{minStart} - 1}{3} \\\\Big\\\\rfloor + 1$。
\\n###Java
\\nclass Solution {\\n public int minimumOperations(int[] nums) {\\n Set<Integer> numsSet = new HashSet<Integer>();\\n int length = nums.length;\\n int minStart = length;\\n boolean allUnique = true;\\n for (int i = length - 1; i >= 0 && allUnique; i--) {\\n if (numsSet.add(nums[i])) {\\n minStart = i;\\n } else {\\n allUnique = false;\\n }\\n }\\n return minStart == 0 ? 0 : (minStart - 1) / 3 + 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumOperations(int[] nums) {\\n ISet<int> numsSet = new HashSet<int>();\\n int length = nums.Length;\\n int minStart = length;\\n bool allUnique = true;\\n for (int i = length - 1; i >= 0 && allUnique; i--) {\\n if (numsSet.Add(nums[i])) {\\n minStart = i;\\n } else {\\n allUnique = false;\\n }\\n }\\n return minStart == 0 ? 0 : (minStart - 1) / 3 + 1;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要反向遍历数组 $\\\\textit{nums}$ 一次。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。哈希集合的空间是 $O(n)$。
\\n每次操作,移除的都是 $\\\\textit{nums}$ 的开头的元素,或者说移除的是 $\\\\textit{nums}$ 的前缀。
\\n所以移除后,剩余的元素一定是 $\\\\textit{nums}$ 的后缀。
\\n所以本质上,我们要找的是 $\\\\textit{nums}$ 的一个后缀,其中没有重复元素。
\\n为了最小化操作次数,这个后缀越长越好。
\\n所以问题相当于:
\\n示例 1 的 $\\\\textit{nums} = [1,2,3,4,2,3,3,5,7]$,我们可以倒着遍历 $\\\\textit{nums}$,遍历到 $\\\\textit{nums}[5]=3$ 时,发现之前遍历过相同的数 $\\\\textit{nums}[6]=3$,这意味着 $\\\\textit{nums}[0..5]=[1,2,3,4,2,3]$ 都要移除,操作 $2$ 次。
\\n一般地,倒着遍历 $\\\\textit{nums}$,如果 $\\\\textit{nums}[i]$ 之前遍历过,意味着下标在 $[0,i]$ 中的元素都要移除,这一共有 $i+1$ 个数。每次操作移除 $3$ 个数,全部移除完,需要操作
\\n$$
\\n\\\\left\\\\lceil\\\\dfrac{i+1}{3}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{i}{3}\\\\right\\\\rfloor + 1
\\n$$
次。证明见 上取整下取整转换公式的证明。
\\n如果 $\\\\textit{nums}$ 没有重复元素,返回 $0$。
\\n###py
\\nclass Solution:\\n def minimumOperations(self, nums: List[int]) -> int:\\n seen = set()\\n for i in range(len(nums) - 1, -1, -1):\\n x = nums[i]\\n if x in seen:\\n return i // 3 + 1\\n seen.add(x)\\n return 0\\n
\\n###java
\\nclass Solution {\\n public int minimumOperations(int[] nums) {\\n Set<Integer> seen = new HashSet<>();\\n for (int i = nums.length - 1; i >= 0; i--) {\\n if (!seen.add(nums[i])) { // nums[i] 在 seen 中\\n return i / 3 + 1;\\n }\\n }\\n return 0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumOperations(vector<int>& nums) {\\n unordered_set<int> seen;\\n for (int i = nums.size() - 1; i >= 0; i--) {\\n if (!seen.insert(nums[i]).second) { // nums[i] 在 seen 中\\n return i / 3 + 1;\\n }\\n }\\n return 0;\\n }\\n};\\n
\\n###go
\\nfunc minimumOperations(nums []int) int {\\n seen := map[int]struct{}{}\\n for i, x := range slices.Backward(nums) {\\n if _, ok := seen[x]; ok {\\n return i/3 + 1\\n }\\n seen[x] = struct{}{}\\n }\\n return 0\\n}\\n
\\n###js
\\nvar minimumOperations = function(nums) {\\n const seen = new Set();\\n for (let i = nums.length - 1; i >= 0; i--) {\\n if (seen.has(nums[i])) {\\n return Math.floor(i / 3) + 1;\\n }\\n seen.add(nums[i]);\\n }\\n return 0;\\n};\\n
\\n###rust
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn minimum_operations(nums: Vec<i32>) -> i32 {\\n let mut seen = HashSet::new();\\n for (i, x) in nums.into_iter().enumerate().rev() {\\n if !seen.insert(x) { // x 在 seen 中\\n return i as i32 / 3 + 1;\\n }\\n }\\n 0\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 每次操作,移除的都是 $\\\\textit{nums}$ 的开头的元素,或者说移除的是 $\\\\textit{nums}$ 的前缀。\\n\\n所以移除后,剩余的元素一定是 $\\\\textit{nums}$ 的后缀。\\n\\n所以本质上,我们要找的是 $\\\\textit{nums}$ 的一个后缀,其中没有重复元素。\\n\\n为了最小化操作次数,这个后缀越长越好。\\n\\n所以问题相当于:\\n\\n$\\\\textit{nums}$ 的最长无重复元素后缀。\\n思路\\n\\n示例 1 的 $\\\\textit{nums} = [1,2,3,4,2,3,3,5,7]$,我们可以倒着遍历 $\\\\textit{nums…","guid":"https://leetcode.cn/problems/minimum-number-of-operations-to-make-elements-in-array-distinct//solution/on-yi-ci-bian-li-jian-ji-xie-fa-pythonja-jgox","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-22T04:05:58.754Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3392. 统计符合条件长度为 3 的子数组数目","url":"https://leetcode.cn/problems/count-subarrays-of-length-three-with-a-condition//solution/3392-tong-ji-fu-he-tiao-jian-chang-du-we-zrye","content":"遍历数组 $\\\\textit{nums}$ 的每个长度为 $3$ 的子数组,判断是否满足首尾两个元素之和的两倍等于中间元素。遍历结束之后即可得到符合条件的长度为 $3$ 的子数组数目。
\\n###Java
\\nclass Solution {\\n public int countSubarrays(int[] nums) {\\n int subarrays = 0;\\n int length = nums.length;\\n for (int i = 2; i < length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountSubarrays(int[] nums) {\\n int subarrays = 0;\\n int length = nums.Length;\\n for (int i = 2; i < length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int countSubarrays(vector<int>& nums) {\\n int subarrays = 0;\\n int length = nums.size();\\n for (int i = 2; i < length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def countSubarrays(self, nums: List[int]) -> int:\\n subarrays = 0\\n length = len(nums)\\n for i in range(2, length):\\n if (nums[i - 2] + nums[i]) * 2 == nums[i - 1]:\\n subarrays += 1\\n return subarrays\\n
\\n###C
\\nint countSubarrays(int* nums, int numsSize) {\\n int subarrays = 0;\\n for (int i = 2; i < numsSize; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n}\\n
\\n###Go
\\nfunc countSubarrays(nums []int) int {\\n subarrays := 0\\n length := len(nums)\\n for i := 2; i < length; i++ {\\n if (nums[i - 2] + nums[i]) * 2 == nums[i - 1] {\\n subarrays++\\n }\\n }\\n return subarrays\\n}\\n
\\n###JavaScript
\\nvar countSubarrays = function(nums) {\\n let subarrays = 0;\\n let length = nums.length;\\n for (let i = 2; i < length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 === nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n};\\n
\\n###TypeScript
\\nfunction countSubarrays(nums: number[]): number {\\n let subarrays = 0;\\n let length = nums.length;\\n for (let i = 2; i < length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 === nums[i - 1]) {\\n subarrays++;\\n }\\n }\\n return subarrays;\\n};\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(1)$。
\\n遍历 $\\\\textit{nums}$,统计满足 $2\\\\le i < n$ 且 $(\\\\textit{nums}[i - 2] + \\\\textit{nums}[i]) \\\\cdot 2 = \\\\textit{nums}[i - 1]$ 的 $i$ 的个数,即为答案。
\\n###py
\\nclass Solution:\\n def countSubarrays(self, nums: List[int]) -> int:\\n return sum((nums[i - 2] + nums[i]) * 2 == nums[i - 1]\\n for i in range(2, len(nums)))\\n
\\n###java
\\nclass Solution {\\n public int countSubarrays(int[] nums) {\\n int ans = 0;\\n for (int i = 2; i < nums.length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countSubarrays(vector<int>& nums) {\\n int ans = 0;\\n for (int i = 2; i < nums.size(); i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint countSubarrays(int* nums, int numsSize) {\\n int ans = 0;\\n for (int i = 2; i < numsSize; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 == nums[i - 1]) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc countSubarrays(nums []int) (ans int) {\\nfor i := 2; i < len(nums); i++ {\\nif (nums[i-2]+nums[i])*2 == nums[i-1] {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar countSubarrays = function(nums) {\\n let ans = 0;\\n for (let i = 2; i < nums.length; i++) {\\n if ((nums[i - 2] + nums[i]) * 2 === nums[i - 1]) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn count_subarrays(nums: Vec<i32>) -> i32 {\\n (2..nums.len())\\n .filter(|&i| (nums[i - 2] + nums[i]) * 2 == nums[i - 1])\\n .count() as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历 $\\\\textit{nums}$,统计满足 $2\\\\le i < n$ 且 $(\\\\textit{nums}[i - 2] + \\\\textit{nums}[i]) \\\\cdot 2 = \\\\textit{nums}[i - 1]$ 的 $i$ 的个数,即为答案。 ###py\\n\\nclass Solution:\\n def countSubarrays(self, nums: List[int]) -> int:\\n return sum((nums[i - 2] + nums[i]) * 2 == nums[i - 1]…","guid":"https://leetcode.cn/problems/count-subarrays-of-length-three-with-a-condition//solution/bian-li-pythonjavacgo-by-endlesscheng-emtn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-22T01:40:07.375Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/count-subarrays-of-length-three-with-a-condition//solution/mei-ju-by-tsreaper-f5zt","content":"中文题面有点问题,实际上是“返回长度为 $3$ 的子数组的数量”。
\\n枚举所有长度为 $3$ 的子数组并检查即可,复杂度 $\\\\mathcal{O}(n)$。
\\nclass Solution {\\npublic:\\n int countSubarrays(vector<int>& nums) {\\n int n = nums.size(), ans = 0;\\n for (int i = 2; i < n; i++) {\\n int x = nums[i - 2], y = nums[i - 1], z = nums[i];\\n if ((x + z) * 2 == y) ans++;\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 中文题面有点问题,实际上是“返回长度为 $3$ 的子数组的数量”。\\n\\n枚举所有长度为 $3$ 的子数组并检查即可,复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\nclass Solution {\\npublic:\\n int countSubarrays(vector我们将整数 x
的 权重 定义为按照下述规则将 x
变成 1
所需要的步数:
x
是偶数,那么 x = x / 2
x
是奇数,那么 x = 3 * x + 1
比方说,x=3 的权重为 7 。因为 3 需要 7 步变成 1 (3 --\x3e 10 --\x3e 5 --\x3e 16 --\x3e 8 --\x3e 4 --\x3e 2 --\x3e 1)。
\\n\\n给你三个整数 lo
, hi
和 k
。你的任务是将区间 [lo, hi]
之间的整数按照它们的权重 升序排序 ,如果大于等于 2 个整数有 相同 的权重,那么按照数字自身的数值 升序排序 。
请你返回区间 [lo, hi]
之间的整数按权重排序后的第 k
个数。
注意,题目保证对于任意整数 x
(lo <= x <= hi)
,它变成 1
所需要的步数是一个 32 位有符号整数。
\\n\\n
示例 1:
\\n\\n输入:lo = 12, hi = 15, k = 2\\n输出:13\\n解释:12 的权重为 9(12 --\x3e 6 --\x3e 3 --\x3e 10 --\x3e 5 --\x3e 16 --\x3e 8 --\x3e 4 --\x3e 2 --\x3e 1)\\n13 的权重为 9\\n14 的权重为 17\\n15 的权重为 17\\n区间内的数按权重排序以后的结果为 [12,13,14,15] 。对于 k = 2 ,答案是第二个整数也就是 13 。\\n注意,12 和 13 有相同的权重,所以我们按照它们本身升序排序。14 和 15 同理。\\n\\n\\n
示例 2:
\\n\\n输入:lo = 7, hi = 11, k = 4\\n输出:7\\n解释:区间内整数 [7, 8, 9, 10, 11] 对应的权重为 [16, 3, 19, 6, 14] 。\\n按权重排序后得到的结果为 [8, 10, 11, 7, 9] 。\\n排序后数组中第 4 个数字为 7 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= lo <= hi <= 1000
1 <= k <= hi - lo + 1
根据题目,我们可以直接枚举字符串的每一个长度为 $2$ 的连续子串,并检查其反转后是否存在于字符串中。若存在任意一个子串满足上述要求,返回 $true$ 即可,否则返回 $false$。
\\n###C#
\\npublic class Solution {\\n public bool IsSubstringPresent(string s) {\\n return Enumerable.Range(0, s.Length - 1).Any(i => s.Contains($\\"{s[i+1]}{s[i]}\\"));\\n }\\n}\\n
\\n###C++
\\n#include <ranges>\\n\\nclass Solution {\\npublic:\\n static auto isSubstringPresent(const std::string& s) -> bool {\\n return std::ranges::any_of(std::views::iota(0ULL, s.length() - 1), [&s](const auto &i) {\\n return s.find(std::string(1, s[i + 1]) + s[i]) != std::string::npos;\\n });\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public boolean isSubstringPresent(String s) {\\n return IntStream.range(0, s.length() - 1).anyMatch(i -> s.contains(s.charAt(i + 1) + \\"\\" + s.charAt(i)));\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n return any(s.find(f\\"{s[i+1]}{s[i]}\\") != -1 for i in range(len(s) - 1))\\n \\n
\\n###JavaScript
\\nvar isSubstringPresent = function(s) {\\n return [...Array(s.length - 1).keys()].some(i => s.includes(`${s[i + 1]}${s[i]}`));\\n};\\n
\\n###Kotlin
\\nclass Solution {\\n fun isSubstringPresent(s: String): Boolean {\\n return (0 until s.length - 1).any { i -> s.contains(\\"${s[i + 1]}${s[i]}\\") }\\n }\\n}\\n
\\n显然,每一次我们遍历整个字符串寻找翻转后的子串是否存在未免有些效率低下。因此我们此时可以考虑将字符串中每一个长度为 $2$ 的连续子串存入哈希表中,这样便可以将查找效率降低至 $O(1)$。
\\n###C#
\\npublic class Solution {\\n public bool IsSubstringPresent(string s) {\\n var hash = new HashSet<string>(Enumerable.Range(0, s.Length - 1).Select(i => s[i..(i + 2)]));\\n return Enumerable.Range(0, s.Length - 1).Any(i => hash.Contains($\\"{s[i + 1]}{s[i]}\\"));\\n }\\n}\\n
\\n###C++
\\n#include <ranges>\\n\\nclass Solution {\\npublic:\\n static auto isSubstringPresent(const std::string &s) -> bool {\\n auto hash = std::unordered_set<std::string>();\\n for (auto i = 0; i < s.length() - 1; i++) {\\n hash.insert(s.substr(i, 2));\\n }\\n\\n return std::ranges::any_of(std::views::iota(1ULL, s.length() - 1), [&](const auto &i) {\\n return hash.contains(std::string(1, s[i]) + s[i - 1]);\\n });\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public boolean isSubstringPresent(String s) {\\n var hash = new HashSet<String>();\\n IntStream.range(0, s.length() - 1).mapToObj(i -> s.substring(i, i + 2)).forEach(hash::add);\\n return IntStream.range(1, s.length()).anyMatch(i -> hash.contains(\\"\\" + s.charAt(i) + s.charAt(i - 1)));\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n hash_set = {s[i:i+2] for i in range(len(s) - 1)}\\n return any(hash_set.__contains__(f\\"{s[i+1]}{s[i]}\\") for i in range(len(s) - 1))\\n \\n
\\n###JavaScript
\\nvar isSubstringPresent = function (s) {\\n const hash = new Set();\\n for (let i = 0; i < s.length - 1; i++) {\\n hash.add(s.slice(i, i + 2));\\n }\\n\\n return [...Array(s.length - 1).keys()].some(i =>hash.has(`${s[i + 1]}${s[i]}`));\\n};\\n
\\n###Kotlin
\\nclass Solution {\\n fun isSubstringPresent(s: String): Boolean {\\n val hash = mutableSetOf<String>()\\n for (i in 0 until s.length - 1) {\\n hash.add(s.substring(i, i + 2))\\n }\\n\\n return (0 until s.length - 1).any { i -> hash.contains(\\"${s[i + 1]}${s[i]}\\")}\\n }\\n}\\n
\\n我们直接将 $\\\\textit{score}$ 按照第 $k$ 列的分数从大到小排序,然后返回即可。
\\n###python
\\nclass Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n return sorted(score, key=lambda x: -x[k])\\n
\\n###java
\\nclass Solution {\\n public int[][] sortTheStudents(int[][] score, int k) {\\n Arrays.sort(score, (a, b) -> b[k] - a[k]);\\n return score;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<vector<int>> sortTheStudents(vector<vector<int>>& score, int k) {\\n ranges::sort(score, [k](const auto& a, const auto& b) { return a[k] > b[k]; });\\n return score;\\n }\\n};\\n
\\n###go
\\nfunc sortTheStudents(score [][]int, k int) [][]int {\\nsort.Slice(score, func(i, j int) bool { return score[i][k] > score[j][k] })\\nreturn score\\n}\\n
\\n###ts
\\nfunction sortTheStudents(score: number[][], k: number): number[][] {\\n return score.sort((a, b) => b[k] - a[k]);\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn sort_the_students(mut score: Vec<Vec<i32>>, k: i32) -> Vec<Vec<i32>> {\\n let k = k as usize;\\n score.sort_by(|a, b| b[k].cmp(&a[k]));\\n score\\n }\\n}\\n
\\n时间复杂度 $O(m \\\\times \\\\log m)$,空间复杂度 $O(\\\\log m)$。其中 $m$ 为 $\\\\textit{score}$ 的行数。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:排序 我们直接将 $\\\\textit{score}$ 按照第 $k$ 列的分数从大到小排序,然后返回即可。\\n\\n###python\\n\\nclass Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n return sorted(score, key=lambda x: -x[k])\\n\\n\\n###java\\n\\nclass Solution {\\n public int[][] sortTheStudents(int[…","guid":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/python3javacgotypescript-yi-ti-yi-jie-pa-phon","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-21T00:37:42.694Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-根据第 K 场考试的分数排序🟡","url":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score/","content":"班里有 m
位学生,共计划组织 n
场考试。给你一个下标从 0 开始、大小为 m x n
的整数矩阵 score
,其中每一行对应一位学生,而 score[i][j]
表示第 i
位学生在第 j
场考试取得的分数。矩阵 score
包含的整数 互不相同 。
另给你一个整数 k
。请你按第 k
场考试分数从高到低完成对这些学生(矩阵中的行)的排序。
返回排序后的矩阵。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:score = [[10,6,9,1],[7,5,11,2],[4,8,3,15]], k = 2\\n输出:[[7,5,11,2],[10,6,9,1],[4,8,3,15]]\\n解释:在上图中,S 表示学生,E 表示考试。\\n- 下标为 1 的学生在第 2 场考试取得的分数为 11 ,这是考试的最高分,所以 TA 需要排在第一。\\n- 下标为 0 的学生在第 2 场考试取得的分数为 9 ,这是考试的第二高分,所以 TA 需要排在第二。\\n- 下标为 2 的学生在第 2 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第三。\\n\\n\\n
示例 2:
\\n\\n输入:score = [[3,4],[5,6]], k = 0\\n输出:[[5,6],[3,4]]\\n解释:在上图中,S 表示学生,E 表示考试。\\n- 下标为 1 的学生在第 0 场考试取得的分数为 5 ,这是考试的最高分,所以 TA 需要排在第一。\\n- 下标为 0 的学生在第 0 场考试取得的分数为 3 ,这是考试的最低分,所以 TA 需要排在第二。\\n\\n\\n
\\n\\n
提示:
\\n\\nm == score.length
n == score[i].length
1 <= m, n <= 250
1 <= score[i][j] <= 105
score
由 不同 的整数组成0 <= k < n
根据题目描述,字符串 $\\\\textit{t}$ 的长度一定是 $\\\\textit{s}$ 的长度的因子,我们可以从小到大枚举字符串 $\\\\textit{t}$ 的长度 $k$,然后检查是否满足题目要求,如果满足则返回。因此,问题转化为如何检查字符串 $\\\\textit{t}$ 的长度 $k$ 是否满足题目要求。
\\n我们首先统计字符串 $\\\\textit{s}$ 中每个字符出现的次数,记录在数组或哈希表 $\\\\textit{cnt}$ 中。
\\n然后,我们定义一个函数 $\\\\textit{check}(k)$,用来检查字符串 $\\\\textit{t}$ 的长度 $k$ 是否满足题目要求。我们可以遍历字符串 $\\\\textit{s}$,每次取长度为 $k$ 的子串,然后统计每个字符出现的次数,如果每个字符出现的次数乘以 $\\\\frac{n}{k}$ 不等于 $\\\\textit{cnt}$ 中的值,则返回 $\\\\textit{false}$。遍历结束,如果都满足,则返回 $\\\\textit{true}$。
\\n###python
\\nclass Solution:\\n def minAnagramLength(self, s: str) -> int:\\n def check(k: int) -> bool:\\n for i in range(0, n, k):\\n cnt1 = Counter(s[i : i + k])\\n for c, v in cnt.items():\\n if cnt1[c] * (n // k) != v:\\n return False\\n return True\\n\\n cnt = Counter(s)\\n n = len(s)\\n for i in range(1, n + 1):\\n if n % i == 0 and check(i):\\n return i\\n
\\n###java
\\nclass Solution {\\n private int n;\\n private char[] s;\\n private int[] cnt = new int[26];\\n\\n public int minAnagramLength(String s) {\\n n = s.length();\\n this.s = s.toCharArray();\\n for (int i = 0; i < n; ++i) {\\n ++cnt[this.s[i] - \'a\'];\\n }\\n for (int i = 1;; ++i) {\\n if (n % i == 0 && check(i)) {\\n return i;\\n }\\n }\\n }\\n\\n private boolean check(int k) {\\n for (int i = 0; i < n; i += k) {\\n int[] cnt1 = new int[26];\\n for (int j = i; j < i + k; ++j) {\\n ++cnt1[s[j] - \'a\'];\\n }\\n for (int j = 0; j < 26; ++j) {\\n if (cnt1[j] * (n / k) != cnt[j]) {\\n return false;\\n }\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minAnagramLength(string s) {\\n int n = s.size();\\n int cnt[26]{};\\n for (char c : s) {\\n cnt[c - \'a\']++;\\n }\\n auto check = [&](int k) {\\n for (int i = 0; i < n; i += k) {\\n int cnt1[26]{};\\n for (int j = i; j < i + k; ++j) {\\n cnt1[s[j] - \'a\']++;\\n }\\n for (int j = 0; j < 26; ++j) {\\n if (cnt1[j] * (n / k) != cnt[j]) {\\n return false;\\n }\\n }\\n }\\n return true;\\n };\\n for (int i = 1;; ++i) {\\n if (n % i == 0 && check(i)) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc minAnagramLength(s string) int {\\nn := len(s)\\ncnt := [26]int{}\\nfor _, c := range s {\\ncnt[c-\'a\']++\\n}\\ncheck := func(k int) bool {\\nfor i := 0; i < n; i += k {\\ncnt1 := [26]int{}\\nfor j := i; j < i+k; j++ {\\ncnt1[s[j]-\'a\']++\\n}\\nfor j, v := range cnt {\\nif cnt1[j]*(n/k) != v {\\nreturn false\\n}\\n}\\n}\\nreturn true\\n}\\nfor i := 1; ; i++ {\\nif n%i == 0 && check(i) {\\nreturn i\\n}\\n}\\n}\\n
\\n###ts
\\nfunction minAnagramLength(s: string): number {\\n const n = s.length;\\n const cnt: Record<string, number> = {};\\n for (let i = 0; i < n; i++) {\\n cnt[s[i]] = (cnt[s[i]] || 0) + 1;\\n }\\n const check = (k: number): boolean => {\\n for (let i = 0; i < n; i += k) {\\n const cnt1: Record<string, number> = {};\\n for (let j = i; j < i + k; j++) {\\n cnt1[s[j]] = (cnt1[s[j]] || 0) + 1;\\n }\\n for (const [c, v] of Object.entries(cnt)) {\\n if (cnt1[c] * ((n / k) | 0) !== v) {\\n return false;\\n }\\n }\\n }\\n return true;\\n };\\n for (let i = 1; ; ++i) {\\n if (n % i === 0 && check(i)) {\\n return i;\\n }\\n }\\n}\\n
\\n时间复杂度 $O(n \\\\times A)$,其中 $n$ 是字符串 $\\\\textit{s}$ 的长度,而 $A$ 是 $n$ 的因子个数。空间复杂度 $O(|\\\\Sigma|)$,其中 $\\\\Sigma$ 是字符集,本题中为小写字母集合。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:枚举 根据题目描述,字符串 $\\\\textit{t}$ 的长度一定是 $\\\\textit{s}$ 的长度的因子,我们可以从小到大枚举字符串 $\\\\textit{t}$ 的长度 $k$,然后检查是否满足题目要求,如果满足则返回。因此,问题转化为如何检查字符串 $\\\\textit{t}$ 的长度 $k$ 是否满足题目要求。\\n\\n我们首先统计字符串 $\\\\textit{s}$ 中每个字符出现的次数,记录在数组或哈希表 $\\\\textit{cnt}$ 中。\\n\\n然后,我们定义一个函数 $\\\\textit{check}(k)$,用来检查字符串 $\\\\textit{t}$ 的长度…","guid":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/python3javacgotypescript-yi-ti-yi-jie-me-5wmb","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-20T00:54:37.123Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-同位字符串连接的最小长度🟡","url":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation/","content":"给你一个字符串 s
,它由某个字符串 t
和若干 t
的 同位字符串 连接而成。
请你返回字符串 t
的 最小 可能长度。
同位字符串 指的是重新排列一个单词得到的另外一个字符串,原来字符串中的每个字符在新字符串中都恰好只使用一次。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:s = \\"abba\\"
\\n\\n输出:2
\\n\\n解释:
\\n\\n一个可能的字符串 t
为 \\"ba\\"
。
示例 2:
\\n\\n输入:s = \\"cdef\\"
\\n\\n输出:4
\\n\\n解释:
\\n\\n一个可能的字符串 t
为 \\"cdef\\"
,注意 t
可能等于 s
。
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s
只包含小写英文字母。思路
\\n我们先尝试按照两种顺序切,第一种顺序是横着切一次,竖着沿着一条线连续切两次,开销是 $\\\\textit{horizontalCut}[i] + 2 \\\\times \\\\textit{verticalCut}[j]$;第二种顺序是竖着切一次,横着沿着一条线连续切两次,开销是 $\\\\textit{verticalCut}[j] + 2 \\\\times \\\\textit{horizontalCut}[j]$。这么一比较,这两种顺序的切法,切出来的一样的,但是开销却有不同。贪心地看,第一刀应该选择开销最大的线来切。因此,我们就提出一个贪心的猜想,将 $\\\\textit{horizontalCut}$ 和 $\\\\textit{verticalCut}$ 分别排序,每次切的时候都挑选最大的开销,并切到底。
\\n接下来证明,按照这样的顺序,交换相邻两刀,不会使得开销更小。假设当前蛋糕已经沿着水平线切了 $p$ 刀,沿着垂直线切了 $q$ 刀。我们尝试交换接下来两刀:
\\n因此,按照这样的顺序,交换相邻两刀,不会使得开销更小。而交换任意两刀的顺序,可以通过多次交换相邻两刀的顺序得到。因此,交换任意两刀,不会使得开销更小。
\\n代码
\\n###Python
\\nclass Solution:\\n def minimumCost(self, m: int, n: int, H: List[int], V: List[int]) -> int: \\n H.sort(), V.sort()\\n h, v = 1, 1\\n res = 0\\n while H or V:\\n if not V or (H and H[-1] > V[-1]):\\n res += H.pop() * h\\n v += 1\\n else:\\n res += V.pop() * v\\n h += 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public long minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n Arrays.sort(horizontalCut);\\n Arrays.sort(verticalCut);\\n int h = 1, v = 1;\\n long res = 0;\\n int horizontalIndex = horizontalCut.length - 1, verticalIndex = verticalCut.length - 1;\\n while (horizontalIndex >= 0 || verticalIndex >= 0) {\\n if (verticalIndex < 0 || (horizontalIndex >= 0 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {\\n res += horizontalCut[horizontalIndex--] * h;\\n v++;\\n } else {\\n res += verticalCut[verticalIndex--] * v;\\n h++;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MinimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n Array.Sort(horizontalCut);\\n Array.Sort(verticalCut);\\n int h = 1, v = 1;\\n long res = 0;\\n int horizontalIndex = horizontalCut.Length - 1, verticalIndex = verticalCut.Length - 1;\\n while (horizontalIndex >= 0 || verticalIndex >= 0) {\\n if (verticalIndex < 0 || (horizontalIndex >= 0 && horizontalCut[horizontalIndex] > verticalCut[verticalIndex])) {\\n res += horizontalCut[horizontalIndex--] * h;\\n v++;\\n } else {\\n res += verticalCut[verticalIndex--] * v;\\n h++;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {\\n sort(horizontalCut.begin(), horizontalCut.end());\\n sort(verticalCut.begin(), verticalCut.end());\\n long long h = 1, v = 1;\\n long long res = 0;\\n while (!horizontalCut.empty() || !verticalCut.empty()) {\\n if (verticalCut.empty() || !horizontalCut.empty() && horizontalCut.back() > verticalCut.back()) {\\n res += horizontalCut.back() * h;\\n horizontalCut.pop_back();\\n v++;\\n } else {\\n res += verticalCut.back() * v;\\n verticalCut.pop_back();\\n h++;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc minimumCost(m int, n int, horizontalCut []int, verticalCut []int) int64 {\\n sort.Ints(horizontalCut)\\n sort.Ints(verticalCut)\\n h, v := 1, 1\\n var res int64\\n for len(horizontalCut) > 0 || len(verticalCut) > 0 {\\n if len(verticalCut) == 0 || len(horizontalCut) > 0 && horizontalCut[len(horizontalCut) - 1] > verticalCut[len(verticalCut) - 1] {\\n res += int64(horizontalCut[len(horizontalCut) - 1] * h)\\n horizontalCut = horizontalCut[:len(horizontalCut) - 1]\\n v++\\n } else {\\n res += int64(verticalCut[len(verticalCut) - 1] * v)\\n verticalCut = verticalCut[:len(verticalCut) - 1]\\n h++\\n }\\n }\\n return res\\n}\\n
\\n###C
\\nint compare(const void* a, const void* b) {\\n return (*(int*)b - *(int*)a);\\n}\\n\\nlong long minimumCost(int m, int n, int* horizontalCut, int horizontalCutSize, int* verticalCut, int verticalCutSize) {\\n qsort(horizontalCut, horizontalCutSize, sizeof(int), compare);\\n qsort(verticalCut, verticalCutSize, sizeof(int), compare);\\n long long h = 1, v = 1;\\n long long res = 0;\\n int hIndex = 0, vIndex = 0;\\n while (hIndex < horizontalCutSize || vIndex < verticalCutSize) {\\n if (vIndex == verticalCutSize || (hIndex < horizontalCutSize && horizontalCut[hIndex] > verticalCut[vIndex])) {\\n res += horizontalCut[hIndex++] * h;\\n v++;\\n } else {\\n res += verticalCut[vIndex++] * v;\\n h++;\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar minimumCost = function(m, n, horizontalCut, verticalCut) {\\n horizontalCut.sort((a, b) => a - b);\\n verticalCut.sort((a, b) => a - b);\\n let h = 1, v = 1;\\n let res = 0;\\n while (horizontalCut.length || verticalCut.length) {\\n if (!verticalCut.length || (horizontalCut.length && horizontalCut[horizontalCut.length - 1] > verticalCut[verticalCut.length - 1])) {\\n res += horizontalCut[horizontalCut.length - 1] * h;\\n horizontalCut.pop();\\n v++;\\n } else {\\n res += verticalCut[verticalCut.length - 1] * v;\\n verticalCut.pop();\\n h++;\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {\\n horizontalCut.sort((a, b) => a - b);\\n verticalCut.sort((a, b) => a - b);\\n let h = 1, v = 1;\\n let res = 0;\\n while (horizontalCut.length || verticalCut.length) {\\n if (!verticalCut.length || (horizontalCut.length && horizontalCut[horizontalCut.length - 1] > verticalCut[verticalCut.length - 1])) {\\n res += horizontalCut[horizontalCut.length - 1] * h;\\n horizontalCut.pop();\\n v++;\\n } else {\\n res += verticalCut[verticalCut.length - 1] * v;\\n verticalCut.pop();\\n h++;\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_cost(m: i32, n: i32, horizontal_cut: Vec<i32>, vertical_cut: Vec<i32>) -> i64 {\\n let mut horizontal_cut = horizontal_cut.into_iter().collect::<Vec<_>>();\\n let mut vertical_cut = vertical_cut.into_iter().collect::<Vec<_>>();\\n horizontal_cut.sort_unstable();\\n vertical_cut.sort_unstable();\\n let mut h = 1;\\n let mut v = 1;\\n let mut res = 0i64;\\n while !horizontal_cut.is_empty() || !vertical_cut.is_empty() {\\n if vertical_cut.is_empty() || (!horizontal_cut.is_empty() && horizontal_cut.last().unwrap() > vertical_cut.last().unwrap()) {\\n res += (*horizontal_cut.last().unwrap() as i64) * h as i64;\\n horizontal_cut.pop();\\n v += 1;\\n } else {\\n res += (*vertical_cut.last().unwrap() as i64) * v as i64;\\n vertical_cut.pop();\\n h += 1;\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m\\\\times\\\\log m + n\\\\times\\\\log n)$。
\\n空间复杂度:$O(\\\\log m + \\\\log n)$。
\\n思路与算法
\\n用数组 $\\\\textit{indices}$ 记录 $\\\\textit{nums}$ 中所有等于 $x$ 的下标 $i$,此时给定的查询 $\\\\textit{queries}[i]$,如果 $\\\\textit{queries}[i]$ 大于 $\\\\textit{indices}$ 的长度,则查询答案为 $-1$,否则答案为 $\\\\textit{indices}[\\\\textit{queries}[i] - 1]$,返回查询结果即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> occurrencesOfElement(vector<int>& nums, vector<int>& queries, int x) {\\n vector<int> indices;\\n for (int i = 0; i < nums.size(); i++) {\\n if (nums[i] == x) {\\n indices.emplace_back(i);\\n }\\n }\\n vector<int> res;\\n for (int q : queries) {\\n if (indices.size() < q) {\\n res.emplace_back(-1);\\n } else {\\n res.emplace_back(indices[q - 1]);\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int[] occurrencesOfElement(int[] nums, int[] queries, int x) {\\n List<Integer> indices = new ArrayList<>();\\n for (int i = 0; i < nums.length; i++) {\\n if (nums[i] == x) {\\n indices.add(i);\\n }\\n }\\n \\n int[] res = new int[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n if (indices.size() < queries[i]) {\\n res[i] = -1;\\n } else {\\n res[i] = indices.get(queries[i] - 1);\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] OccurrencesOfElement(int[] nums, int[] queries, int x) {\\n List<int> indices = new List<int>();\\n for (int i = 0; i < nums.Length; i++) {\\n if (nums[i] == x) {\\n indices.Add(i);\\n }\\n }\\n\\n int[] res = new int[queries.Length];\\n for (int i = 0; i < queries.Length; i++) {\\n if (indices.Count < queries[i]) {\\n res[i] = -1;\\n } else {\\n res[i] = indices[queries[i] - 1];\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Go
\\nfunc occurrencesOfElement(nums []int, queries []int, x int) []int {\\n indices := []int{}\\n for i, num := range nums {\\n if num == x {\\n indices = append(indices, i)\\n }\\n }\\n res := []int{}\\n for _, q := range queries {\\n if len(indices) < q {\\n res = append(res, -1)\\n } else {\\n res = append(res, indices[q-1])\\n }\\n }\\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def occurrencesOfElement(self, nums: List[int], queries: List[int], x: int) -> List[int]:\\n indices = [i for i, num in enumerate(nums) if num == x]\\n return [-1 if len(indices) < q else indices[q - 1] for q in queries]\\n
\\n###C
\\nint* occurrencesOfElement(int* nums, int numsSize, int* queries, int queriesSize, int x, int* returnSize) {\\n int* indices = (int*)malloc(numsSize * sizeof(int));\\n int indicesSize = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == x) {\\n indices[indicesSize++] = i;\\n }\\n }\\n int* res = (int*)malloc(queriesSize * sizeof(int));\\n *returnSize = queriesSize;\\n for (int i = 0; i < queriesSize; i++) {\\n if (indicesSize < queries[i]) {\\n res[i] = -1;\\n } else {\\n res[i] = indices[queries[i] - 1];\\n }\\n }\\n free(indices);\\n return res;\\n}\\n
\\n###JavaScript
\\nvar occurrencesOfElement = function(nums, queries, x) {\\n const indices = [];\\n for (let i = 0; i < nums.length; i++) {\\n if (nums[i] === x) {\\n indices.push(i);\\n }\\n }\\n const res = [];\\n for (const q of queries) {\\n if (indices.length < q) {\\n res.push(-1);\\n } else {\\n res.push(indices[q - 1]);\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction occurrencesOfElement(nums: number[], queries: number[], x: number): number[] {\\n const indices: number[] = [];\\n for (let i = 0; i < nums.length; i++) {\\n if (nums[i] === x) {\\n indices.push(i);\\n }\\n }\\n const res: number[] = [];\\n for (const q of queries) {\\n if (indices.length < q) {\\n res.push(-1);\\n } else {\\n res.push(indices[q - 1]);\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn occurrences_of_element(nums: Vec<i32>, queries: Vec<i32>, x: i32) -> Vec<i32> {\\n let indices: Vec<usize> = nums.iter()\\n .enumerate()\\n .filter_map(|(i, &num)| if num == x { Some(i) } else { None })\\n .collect();\\n \\n queries\\n .iter()\\n .map(|&q| {\\n if (q as usize) > indices.len() {\\n -1\\n } else {\\n indices[(q - 1) as usize] as i32\\n }\\n })\\n .collect()\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + q)$,其中 $n$ 表示给定数组 $\\\\textit{nums}$ 的长度, $q$ 表示给定的查询数组 $\\\\textit{queries}$ 的长度。只需遍历数组 $\\\\textit{nums}$ 与 $\\\\textit{queries}$ 一次即可。
\\n空间复杂度:$O(n)$,其中 $n$ 表示给定数组 $\\\\textit{nums}$ 的长度。记录 $x$ 出现的小标,需要的空间最多为 $O(n)$。
\\n我们直接从下标为 $1$ 的山开始遍历,如果它左侧相邻的山的高度大于 $threshold$,那么我们就将它的下标加入到结果数组中。
\\n遍历结束后,返回结果数组即可。
\\n###python
\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i for i in range(1, len(height)) if height[i - 1] > threshold]\\n
\\n###java
\\nclass Solution {\\n public List<Integer> stableMountains(int[] height, int threshold) {\\n List<Integer> ans = new ArrayList<>();\\n for (int i = 1; i < height.length; ++i) {\\n if (height[i - 1] > threshold) {\\n ans.add(i);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> stableMountains(vector<int>& height, int threshold) {\\n vector<int> ans;\\n for (int i = 1; i < height.size(); ++i) {\\n if (height[i - 1] > threshold) {\\n ans.push_back(i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc stableMountains(height []int, threshold int) (ans []int) {\\nfor i := 1; i < len(height); i++ {\\nif height[i-1] > threshold {\\nans = append(ans, i)\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction stableMountains(height: number[], threshold: number): number[] {\\n const ans: number[] = [];\\n for (let i = 1; i < height.length; ++i) {\\n if (height[i - 1] > threshold) {\\n ans.push(i);\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组 $\\\\textit{height}$ 的长度。忽略答案数组的空间消耗,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:遍历 我们直接从下标为 $1$ 的山开始遍历,如果它左侧相邻的山的高度大于 $threshold$,那么我们就将它的下标加入到结果数组中。\\n\\n遍历结束后,返回结果数组即可。\\n\\n###python\\n\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i for i in range(1, len(height)) if height[i - 1] > threshold]\\n\\n\\n###ja…","guid":"https://leetcode.cn/problems/find-indices-of-stable-mountains//solution/python3javacgotypescript-yi-ti-yi-jie-bi-j9th","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-19T00:24:01.424Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-找到稳定山的下标🟢","url":"https://leetcode.cn/problems/find-indices-of-stable-mountains/","content":"有 n
座山排成一列,每座山都有一个高度。给你一个整数数组 height
,其中 height[i]
表示第 i
座山的高度,再给你一个整数 threshold
。
对于下标不为 0
的一座山,如果它左侧相邻的山的高度 严格大于 threshold
,那么我们称它是 稳定 的。我们定义下标为 0
的山 不是 稳定的。
请你返回一个数组,包含所有 稳定 山的下标,你可以以 任意 顺序返回下标数组。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:height = [1,2,3,4,5], threshold = 2
\\n\\n输出:[3,4]
\\n\\n解释:
\\n\\nheight[2] == 3
大于 threshold == 2
。height[3] == 4
大于 threshold == 2
.示例 2:
\\n\\n输入:height = [10,1,10,1,10], threshold = 3
\\n\\n输出:[1,3]
\\n示例 3:
\\n\\n输入:height = [10,1,10,1,10], threshold = 10
\\n\\n输出:[]
\\n\\n\\n
提示:
\\n\\n2 <= n == height.length <= 100
1 <= height[i] <= 100
1 <= threshold <= 100
由于本题数据规模较大,使用“字典树 + 记忆化搜索”的方法将会超时,我们需要寻找一种更高效的解法。
\\n考虑从字符串 $\\\\textit{target}$ 的第 $i$ 个字符开始,最远能够匹配的字符串长度,假设为 $\\\\textit{dist}$,那么对于任意 $j \\\\in [i, i + \\\\textit{dist}-1]$,我们都能够在 $\\\\textit{words}$ 中找到一个字符串,使得 $\\\\textit{target}[i..j]$ 是这个字符串的前缀。这存在着单调性,我们可以使用二分查找来确定 $\\\\textit{dist}$。
\\n具体地,我们首先预处理出 $\\\\textit{words}$ 中所有字符串的每个前缀的哈希值,按照前缀长度分组存储在 $\\\\textit{s}$ 数组中。另外,将 $\\\\textit{target}$ 的哈希值也预处理出来,存储在 $\\\\textit{hashing}$ 中,便于我们查询任意 $\\\\textit{target}[l..r]$ 的哈希值。
\\n接下来,我们设计一个函数 $\\\\textit{f}(i)$,表示从字符串 $\\\\textit{target}$ 的第 $i$ 个字符开始,最远能够匹配的字符串长度。我们可以通过二分查找的方式确定 $\\\\textit{f}(i)$。
\\n定义二分查找的左边界 $l = 0$,右边界 $r = \\\\min(n - i, m)$,其中 $n$ 是字符串 $\\\\textit{target}$ 的长度,而 $m$ 是 $\\\\textit{words}$ 中字符串的最大长度。在二分查找的过程中,我们需要判断 $\\\\textit{target}[i..i+\\\\textit{mid}-1]$ 是否是 $\\\\textit{s}[\\\\textit{mid}]$ 中的某个哈希值,如果是,则将左边界 $l$ 更新为 $\\\\textit{mid}$,否则将右边界 $r$ 更新为 $\\\\textit{mid}-1$。二分结束后,返回 $l$ 即可。
\\n算出 $\\\\textit{f}(i)$ 后,问题就转化为了一个经典的贪心问题,我们从 $i = 0$ 开始,对于每个位置 $i$,最远可以移动到的位置为 $i + \\\\textit{f}(i)$,求最少需要多少次移动即可到达终点。
\\n我们定义 $\\\\textit{last}$ 表示上一次移动的位置,变量 $\\\\textit{mx}$ 表示当前位置能够移动到的最远位置,初始时 $\\\\textit{last} = \\\\textit{mx} = 0$。我们从 $i = 0$ 开始遍历,如果 $i$ 等于 $\\\\textit{last}$,说明我们需要再次移动,此时如果 $\\\\textit{last} = \\\\textit{mx}$,说明我们无法再移动,返回 $-1$;否则,我们将 $\\\\textit{last}$ 更新为 $\\\\textit{mx}$,并将答案加一。
\\n遍历结束后,返回答案即可。
\\n###python
\\nclass Hashing:\\n __slots__ = [\\"mod\\", \\"h\\", \\"p\\"]\\n\\n def __init__(self, s: List[str], base: int, mod: int):\\n self.mod = mod\\n self.h = [0] * (len(s) + 1)\\n self.p = [1] * (len(s) + 1)\\n for i in range(1, len(s) + 1):\\n self.h[i] = (self.h[i - 1] * base + ord(s[i - 1])) % mod\\n self.p[i] = (self.p[i - 1] * base) % mod\\n\\n def query(self, l: int, r: int) -> int:\\n return (self.h[r] - self.h[l - 1] * self.p[r - l + 1]) % self.mod\\n\\n\\nclass Solution:\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n def f(i: int) -> int:\\n l, r = 0, min(n - i, m)\\n while l < r:\\n mid = (l + r + 1) >> 1\\n sub = hashing.query(i + 1, i + mid)\\n if sub in s[mid]:\\n l = mid\\n else:\\n r = mid - 1\\n return l\\n\\n base, mod = 13331, 998244353\\n hashing = Hashing(target, base, mod)\\n m = max(len(w) for w in words)\\n s = [set() for _ in range(m + 1)]\\n for w in words:\\n h = 0\\n for j, c in enumerate(w, 1):\\n h = (h * base + ord(c)) % mod\\n s[j].add(h)\\n ans = last = mx = 0\\n n = len(target)\\n for i in range(n):\\n dist = f(i)\\n mx = max(mx, i + dist)\\n if i == last:\\n if i == mx:\\n return -1\\n last = mx\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Hashing {\\n private final long[] p;\\n private final long[] h;\\n private final long mod;\\n\\n public Hashing(String word, long base, int mod) {\\n int n = word.length();\\n p = new long[n + 1];\\n h = new long[n + 1];\\n p[0] = 1;\\n this.mod = mod;\\n for (int i = 1; i <= n; i++) {\\n p[i] = p[i - 1] * base % mod;\\n h[i] = (h[i - 1] * base + word.charAt(i - 1)) % mod;\\n }\\n }\\n\\n public long query(int l, int r) {\\n return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;\\n }\\n}\\n\\nclass Solution {\\n private Hashing hashing;\\n private Set<Long>[] s;\\n\\n public int minValidStrings(String[] words, String target) {\\n int base = 13331, mod = 998244353;\\n hashing = new Hashing(target, base, mod);\\n int m = Arrays.stream(words).mapToInt(String::length).max().orElse(0);\\n s = new Set[m + 1];\\n Arrays.setAll(s, k -> new HashSet<>());\\n for (String w : words) {\\n long h = 0;\\n for (int j = 0; j < w.length(); j++) {\\n h = (h * base + w.charAt(j)) % mod;\\n s[j + 1].add(h);\\n }\\n }\\n\\n int ans = 0;\\n int last = 0;\\n int mx = 0;\\n int n = target.length();\\n for (int i = 0; i < n; i++) {\\n int dist = f(i, n, m);\\n mx = Math.max(mx, i + dist);\\n if (i == last) {\\n if (i == mx) {\\n return -1;\\n }\\n last = mx;\\n ans++;\\n }\\n }\\n return ans;\\n }\\n\\n private int f(int i, int n, int m) {\\n int l = 0, r = Math.min(n - i, m);\\n while (l < r) {\\n int mid = (l + r + 1) >> 1;\\n long sub = hashing.query(i + 1, i + mid);\\n if (s[mid].contains(sub)) {\\n l = mid;\\n } else {\\n r = mid - 1;\\n }\\n }\\n return l;\\n }\\n}\\n
\\n###cpp
\\nclass Hashing {\\nprivate:\\n vector<long long> p;\\n vector<long long> h;\\n long long mod;\\n\\npublic:\\n Hashing(const string& word, long long base, int mod) {\\n int n = word.size();\\n p.resize(n + 1);\\n h.resize(n + 1);\\n p[0] = 1;\\n this->mod = mod;\\n for (int i = 1; i <= n; i++) {\\n p[i] = (p[i - 1] * base) % mod;\\n h[i] = (h[i - 1] * base + word[i - 1]) % mod;\\n }\\n }\\n\\n long long query(int l, int r) {\\n return (h[r] - h[l - 1] * p[r - l + 1] % mod + mod) % mod;\\n }\\n};\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n int base = 13331, mod = 998244353;\\n Hashing hashing(target, base, mod);\\n int m = 0, n = target.size();\\n for (const string& word : words) {\\n m = max(m, (int) word.size());\\n }\\n\\n vector<unordered_set<long long>> s(m + 1);\\n for (const string& w : words) {\\n long long h = 0;\\n for (int j = 0; j < w.size(); j++) {\\n h = (h * base + w[j]) % mod;\\n s[j + 1].insert(h);\\n }\\n }\\n\\n auto f = [&](int i) -> int {\\n int l = 0, r = min(n - i, m);\\n while (l < r) {\\n int mid = (l + r + 1) >> 1;\\n long long sub = hashing.query(i + 1, i + mid);\\n if (s[mid].count(sub)) {\\n l = mid;\\n } else {\\n r = mid - 1;\\n }\\n }\\n return l;\\n };\\n\\n int ans = 0, last = 0, mx = 0;\\n for (int i = 0; i < n; i++) {\\n int dist = f(i);\\n mx = max(mx, i + dist);\\n if (i == last) {\\n if (i == mx) {\\n return -1;\\n }\\n last = mx;\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\ntype Hashing struct {\\np []int64\\nh []int64\\nmod int64\\n}\\n\\nfunc NewHashing(word string, base int64, mod int64) *Hashing {\\nn := len(word)\\np := make([]int64, n+1)\\nh := make([]int64, n+1)\\np[0] = 1\\nfor i := 1; i <= n; i++ {\\np[i] = (p[i-1] * base) % mod\\nh[i] = (h[i-1]*base + int64(word[i-1])) % mod\\n}\\nreturn &Hashing{p, h, mod}\\n}\\n\\nfunc (hashing *Hashing) Query(l, r int) int64 {\\nreturn (hashing.h[r] - hashing.h[l-1]*hashing.p[r-l+1]%hashing.mod + hashing.mod) % hashing.mod\\n}\\n\\nfunc minValidStrings(words []string, target string) (ans int) {\\nbase, mod := int64(13331), int64(998244353)\\nhashing := NewHashing(target, base, mod)\\n\\nm, n := 0, len(target)\\nfor _, w := range words {\\nm = max(m, len(w))\\n}\\n\\ns := make([]map[int64]bool, m+1)\\n\\nf := func(i int) int {\\nl, r := 0, int(math.Min(float64(n-i), float64(m)))\\nfor l < r {\\nmid := (l + r + 1) >> 1\\nsub := hashing.Query(i+1, i+mid)\\nif s[mid][sub] {\\nl = mid\\n} else {\\nr = mid - 1\\n}\\n}\\nreturn l\\n}\\n\\nfor _, w := range words {\\nh := int64(0)\\nfor j := 0; j < len(w); j++ {\\nh = (h*base + int64(w[j])) % mod\\nif s[j+1] == nil {\\ns[j+1] = make(map[int64]bool)\\n}\\ns[j+1][h] = true\\n}\\n}\\n\\nvar last, mx int\\n\\nfor i := 0; i < n; i++ {\\ndist := f(i)\\nmx = max(mx, i+dist)\\nif i == last {\\nif i == mx {\\nreturn -1\\n}\\nlast = mx\\nans++\\n}\\n}\\nreturn ans\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n + L)$,空间复杂度 $O(n + L)$。其中 $n$ 是字符串 $\\\\textit{target}$ 的长度,而 $L$ 是所有有效字符串的总长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:字符串哈希 + 二分查找 + 贪心 由于本题数据规模较大,使用“字典树 + 记忆化搜索”的方法将会超时,我们需要寻找一种更高效的解法。\\n\\n考虑从字符串 $\\\\textit{target}$ 的第 $i$ 个字符开始,最远能够匹配的字符串长度,假设为 $\\\\textit{dist}$,那么对于任意 $j \\\\in [i, i + \\\\textit{dist}-1]$,我们都能够在 $\\\\textit{words}$ 中找到一个字符串,使得 $\\\\textit{target}[i..j]$ 是这个字符串的前缀。这存在着单调性,我们可以使用二分查找来确定…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/python3javacgo-yi-ti-yi-jie-zi-fu-chuan-v4lhy","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-18T00:43:30.282Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-形成目标字符串需要的最少字符串数 II🔴","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii/","content":"给你一个字符串数组 words
和一个字符串 target
。
如果字符串 x
是 words
中 任意 字符串的 前缀,则认为 x
是一个 有效 字符串。
现计划通过 连接 有效字符串形成 target
,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target
,则返回 -1
。
\\n\\n
示例 1:
\\n\\n输入: words = [\\"abc\\",\\"aaaaa\\",\\"bcdef\\"], target = \\"aabcdabc\\"
\\n\\n输出: 3
\\n\\n解释:
\\n\\ntarget 字符串可以通过连接以下有效字符串形成:
\\n\\nwords[1]
的长度为 2 的前缀,即 \\"aa\\"
。words[2]
的长度为 3 的前缀,即 \\"bcd\\"
。words[0]
的长度为 3 的前缀,即 \\"abc\\"
。示例 2:
\\n\\n输入: words = [\\"abababab\\",\\"ab\\"], target = \\"ababaababa\\"
\\n\\n输出: 2
\\n\\n解释:
\\n\\ntarget 字符串可以通过连接以下有效字符串形成:
\\n\\nwords[0]
的长度为 5 的前缀,即 \\"ababa\\"
。words[0]
的长度为 5 的前缀,即 \\"ababa\\"
。示例 3:
\\n\\n输入: words = [\\"abcdef\\"], target = \\"xyz\\"
\\n\\n输出: -1
\\n\\n\\n
提示:
\\n\\n1 <= words.length <= 100
1 <= words[i].length <= 5 * 104
sum(words[i].length) <= 105
.words[i]
只包含小写英文字母。1 <= target.length <= 5 * 104
target
只包含小写英文字母。我们可以使用字典树存储所有有效字符串,然后使用记忆化搜索计算答案。
\\n我们设计一个函数 $\\\\textit{dfs}(i)$,表示从字符串 $\\\\textit{target}$ 的第 $i$ 个字符开始,需要连接的最少字符串数量。那么答案就是 $\\\\textit{dfs}(0)$。
\\n函数 $\\\\textit{dfs}(i)$ 的计算方式如下:
\\n为了避免重复计算,我们使用记忆化搜索。
\\n###python
\\ndef min(a: int, b: int) -> int:\\n return a if a < b else b\\n\\n\\nclass Trie:\\n def __init__(self):\\n self.children: List[Optional[Trie]] = [None] * 26\\n\\n def insert(self, w: str):\\n node = self\\n for i in map(lambda c: ord(c) - 97, w):\\n if node.children[i] is None:\\n node.children[i] = Trie()\\n node = node.children[i]\\n\\n\\nclass Solution:\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n @cache\\n def dfs(i: int) -> int:\\n if i >= n:\\n return 0\\n node = trie\\n ans = inf\\n for j in range(i, n):\\n k = ord(target[j]) - 97\\n if node.children[k] is None:\\n break\\n node = node.children[k]\\n ans = min(ans, 1 + dfs(j + 1))\\n return ans\\n\\n trie = Trie()\\n for w in words:\\n trie.insert(w)\\n n = len(target)\\n ans = dfs(0)\\n return ans if ans < inf else -1\\n
\\n###java
\\nclass Trie {\\n Trie[] children = new Trie[26];\\n\\n void insert(String w) {\\n Trie node = this;\\n for (int i = 0; i < w.length(); ++i) {\\n int j = w.charAt(i) - \'a\';\\n if (node.children[j] == null) {\\n node.children[j] = new Trie();\\n }\\n node = node.children[j];\\n }\\n }\\n}\\n\\nclass Solution {\\n private Integer[] f;\\n private char[] s;\\n private Trie trie;\\n private final int inf = 1 << 30;\\n\\n public int minValidStrings(String[] words, String target) {\\n trie = new Trie();\\n for (String w : words) {\\n trie.insert(w);\\n }\\n s = target.toCharArray();\\n f = new Integer[s.length];\\n int ans = dfs(0);\\n return ans < inf ? ans : -1;\\n }\\n\\n private int dfs(int i) {\\n if (i >= s.length) {\\n return 0;\\n }\\n if (f[i] != null) {\\n return f[i];\\n }\\n Trie node = trie;\\n f[i] = inf;\\n for (int j = i; j < s.length; ++j) {\\n int k = s[j] - \'a\';\\n if (node.children[k] == null) {\\n break;\\n }\\n f[i] = Math.min(f[i], 1 + dfs(j + 1));\\n node = node.children[k];\\n }\\n return f[i];\\n }\\n}\\n
\\n###cpp
\\nclass Trie {\\npublic:\\n Trie* children[26]{};\\n\\n void insert(string& word) {\\n Trie* node = this;\\n for (char& c : word) {\\n int i = c - \'a\';\\n if (!node->children[i]) {\\n node->children[i] = new Trie();\\n }\\n node = node->children[i];\\n }\\n }\\n};\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n int n = target.size();\\n Trie* trie = new Trie();\\n for (auto& w : words) {\\n trie->insert(w);\\n }\\n const int inf = 1 << 30;\\n int f[n];\\n memset(f, -1, sizeof(f));\\n auto dfs = [&](auto&& dfs, int i) -> int {\\n if (i >= n) {\\n return 0;\\n }\\n if (f[i] != -1) {\\n return f[i];\\n }\\n f[i] = inf;\\n Trie* node = trie;\\n for (int j = i; j < n; ++j) {\\n int k = target[j] - \'a\';\\n if (!node->children[k]) {\\n break;\\n }\\n node = node->children[k];\\n f[i] = min(f[i], 1 + dfs(dfs, j + 1));\\n }\\n return f[i];\\n };\\n int ans = dfs(dfs, 0);\\n return ans < inf ? ans : -1;\\n }\\n};\\n
\\n###go
\\ntype Trie struct {\\nchildren [26]*Trie\\n}\\n\\nfunc (t *Trie) insert(word string) {\\nnode := t\\nfor _, c := range word {\\nidx := c - \'a\'\\nif node.children[idx] == nil {\\nnode.children[idx] = &Trie{}\\n}\\nnode = node.children[idx]\\n}\\n}\\n\\nfunc minValidStrings(words []string, target string) int {\\nn := len(target)\\ntrie := &Trie{}\\nfor _, w := range words {\\ntrie.insert(w)\\n}\\nconst inf int = 1 << 30\\nf := make([]int, n)\\nvar dfs func(int) int\\ndfs = func(i int) int {\\nif i >= n {\\nreturn 0\\n}\\nif f[i] != 0 {\\nreturn f[i]\\n}\\nnode := trie\\nf[i] = inf\\nfor j := i; j < n; j++ {\\nk := int(target[j] - \'a\')\\nif node.children[k] == nil {\\nbreak\\n}\\nf[i] = min(f[i], 1+dfs(j+1))\\nnode = node.children[k]\\n}\\nreturn f[i]\\n}\\nif ans := dfs(0); ans < inf {\\nreturn ans\\n}\\nreturn -1\\n}\\n
\\n###ts
\\nclass Trie {\\n children: (Trie | null)[] = Array(26).fill(null);\\n\\n insert(word: string): void {\\n let node: Trie = this;\\n for (const c of word) {\\n const i = c.charCodeAt(0) - \'a\'.charCodeAt(0);\\n if (!node.children[i]) {\\n node.children[i] = new Trie();\\n }\\n node = node.children[i];\\n }\\n }\\n}\\n\\nfunction minValidStrings(words: string[], target: string): number {\\n const n = target.length;\\n const trie = new Trie();\\n for (const w of words) {\\n trie.insert(w);\\n }\\n const inf = 1 << 30;\\n const f = Array(n).fill(0);\\n\\n const dfs = (i: number): number => {\\n if (i >= n) {\\n return 0;\\n }\\n if (f[i]) {\\n return f[i];\\n }\\n f[i] = inf;\\n let node: Trie | null = trie;\\n for (let j = i; j < n; ++j) {\\n const k = target[j].charCodeAt(0) - \'a\'.charCodeAt(0);\\n if (!node?.children[k]) {\\n break;\\n }\\n node = node.children[k];\\n f[i] = Math.min(f[i], 1 + dfs(j + 1));\\n }\\n return f[i];\\n };\\n\\n const ans = dfs(0);\\n return ans < inf ? ans : -1;\\n}\\n
\\n时间复杂度 $O(n^2 + L)$,空间复杂度 $O(n + L)$。其中 $n$ 是字符串 $\\\\textit{target}$ 的长度,而 $L$ 是所有有效字符串的总长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:字典树 + 记忆化搜索 我们可以使用字典树存储所有有效字符串,然后使用记忆化搜索计算答案。\\n\\n我们设计一个函数 $\\\\textit{dfs}(i)$,表示从字符串 $\\\\textit{target}$ 的第 $i$ 个字符开始,需要连接的最少字符串数量。那么答案就是 $\\\\textit{dfs}(0)$。\\n\\n函数 $\\\\textit{dfs}(i)$ 的计算方式如下:\\n\\n如果 $i \\\\geq n$,表示字符串 $\\\\textit{target}$ 已经遍历完了,返回 $0$;\\n否则,我们可以从字典树中找到以 $\\\\textit{target}[i…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/python3javacgotypescript-yi-ti-yi-jie-zi-q8u5","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-17T00:10:07.071Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-形成目标字符串需要的最少字符串数 I🟡","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i/","content":"给你一个字符串数组 words
和一个字符串 target
。
如果字符串 x
是 words
中 任意 字符串的 前缀,则认为 x
是一个 有效 字符串。
现计划通过 连接 有效字符串形成 target
,请你计算并返回需要连接的 最少 字符串数量。如果无法通过这种方式形成 target
,则返回 -1
。
\\n\\n
示例 1:
\\n\\n输入: words = [\\"abc\\",\\"aaaaa\\",\\"bcdef\\"], target = \\"aabcdabc\\"
\\n\\n输出: 3
\\n\\n解释:
\\n\\ntarget 字符串可以通过连接以下有效字符串形成:
\\n\\nwords[1]
的长度为 2 的前缀,即 \\"aa\\"
。words[2]
的长度为 3 的前缀,即 \\"bcd\\"
。words[0]
的长度为 3 的前缀,即 \\"abc\\"
。示例 2:
\\n\\n输入: words = [\\"abababab\\",\\"ab\\"], target = \\"ababaababa\\"
\\n\\n输出: 2
\\n\\n解释:
\\n\\ntarget 字符串可以通过连接以下有效字符串形成:
\\n\\nwords[0]
的长度为 5 的前缀,即 \\"ababa\\"
。words[0]
的长度为 5 的前缀,即 \\"ababa\\"
。示例 3:
\\n\\n输入: words = [\\"abcdef\\"], target = \\"xyz\\"
\\n\\n输出: -1
\\n\\n\\n
提示:
\\n\\n1 <= words.length <= 100
1 <= words[i].length <= 5 * 103
sum(words[i].length) <= 105
。words[i]
只包含小写英文字母。1 <= target.length <= 5 * 103
target
只包含小写英文字母。思路与算法
\\n用哈希表统计每个元素的出现次数,如果出现次数大于两次,则不能分割数组。
\\n如果没有出现两次以上的元素,则可以分割数组。
代码
\\n###C++
\\nclass Solution {\\npublic:\\n bool isPossibleToSplit(vector<int>& nums) {\\n unordered_map<int, int> count;\\n for (int num : nums) {\\n if (++count[num] > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean isPossibleToSplit(int[] nums) {\\n Map<Integer, Integer> count = new HashMap<>();\\n for (int num : nums) {\\n count.put(num, count.getOrDefault(num, 0) + 1);\\n if (count.get(num) > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n count = {}\\n for num in nums:\\n count[num] = count.get(num, 0) + 1\\n if count[num] > 2:\\n return False\\n return True\\n
\\n###JavaScript
\\nvar isPossibleToSplit = function(nums) {\\n const count = {};\\n for (const num of nums) {\\n count[num] = (count[num] || 0) + 1;\\n if (count[num] > 2) {\\n return false;\\n }\\n }\\n return true;\\n};\\n
\\n###TypeScript
\\nfunction isPossibleToSplit(nums: number[]): boolean {\\n const count = {};\\n for (const num of nums) {\\n count[num] = (count[num] || 0) + 1;\\n if (count[num] > 2) {\\n return false;\\n }\\n }\\n return true;\\n};\\n
\\n###Go
\\nfunc isPossibleToSplit(nums []int) bool {\\n count := make(map[int]int)\\n for _, num := range nums {\\n count[num]++\\n if count[num] > 2 {\\n return false\\n }\\n }\\n return true\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool IsPossibleToSplit(int[] nums) {\\n Dictionary<int, int> count = new Dictionary<int, int>();\\n foreach (int num in nums) {\\n if (count.ContainsKey(num)) {\\n count[num]++;\\n } else {\\n count[num] = 1;\\n }\\n if (count[num] > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###C
\\nbool isPossibleToSplit(int* nums, int numsSize) {\\n int count[101] = {0};\\n for (int i = 0; i < numsSize; i++) {\\n if (++count[nums[i]] > 2) {\\n return false;\\n }\\n }\\n return true;\\n}\\n
\\n###Rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn is_possible_to_split(nums: Vec<i32>) -> bool {\\n let mut count = HashMap::new();\\n for num in nums {\\n let counter = count.entry(num).or_insert(0);\\n *counter += 1;\\n if *counter > 2 {\\n return false;\\n }\\n }\\n true\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 是数组的长度。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组的长度。
\\n一个酒店里有 n
个房间,这些房间用二维整数数组 rooms
表示,其中 rooms[i] = [roomIdi, sizei]
表示有一个房间号为 roomIdi
的房间且它的面积为 sizei
。每一个房间号 roomIdi
保证是 独一无二 的。
同时给你 k
个查询,用二维数组 queries
表示,其中 queries[j] = [preferredj, minSizej]
。第 j
个查询的答案是满足如下条件的房间 id
:
minSizej
,且abs(id - preferredj)
的值 最小 ,其中 abs(x)
是 x
的绝对值。如果差的绝对值有 相等 的,选择 最小 的 id
。如果 没有满足条件的房间 ,答案为 -1
。
请你返回长度为 k
的数组 answer
,其中 answer[j]
为第 j
个查询的结果。
\\n\\n
示例 1:
\\n\\n输入:rooms = [[2,2],[1,2],[3,2]], queries = [[3,1],[3,3],[5,2]]\\n输出:[3,-1,3]\\n解释:查询的答案如下:\\n查询 [3,1] :房间 3 的面积为 2 ,大于等于 1 ,且号码是最接近 3 的,为 abs(3 - 3) = 0 ,所以答案为 3 。\\n查询 [3,3] :没有房间的面积至少为 3 ,所以答案为 -1 。\\n查询 [5,2] :房间 3 的面积为 2 ,大于等于 2 ,且号码是最接近 5 的,为 abs(3 - 5) = 2 ,所以答案为 3 。\\n\\n
示例 2:
\\n\\n输入:rooms = [[1,4],[2,3],[3,5],[4,1],[5,2]], queries = [[2,3],[2,4],[2,5]]\\n输出:[2,1,3]\\n解释:查询的答案如下:\\n查询 [2,3] :房间 2 的面积为 3 ,大于等于 3 ,且号码是最接近的,为 abs(2 - 2) = 0 ,所以答案为 2 。\\n查询 [2,4] :房间 1 和 3 的面积都至少为 4 ,答案为 1 因为它房间编号更小。\\n查询 [2,5] :房间 3 是唯一面积大于等于 5 的,所以答案为 3 。\\n\\n
\\n\\n
提示:
\\n\\nn == rooms.length
1 <= n <= 105
k == queries.length
1 <= k <= 104
1 <= roomIdi, preferredj <= 107
1 <= sizei, minSizej <= 107
我们可以用哈希表或数组 $cnt$ 统计数组 $\\\\textit{arr}$ 中每个数字出现的次数,然后将 $cnt$ 中的数字从大到小排序,从大到小遍历 $\\\\textit{cnt}$,每次遍历将当前数字 $x$ 加入答案,并将 $m$ 加上 $x$,如果 $m \\\\geq \\\\frac{n}{2}$,则返回答案。
\\n###python
\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n cnt = Counter(arr)\\n ans = m = 0\\n for _, v in cnt.most_common():\\n m += v\\n ans += 1\\n if m * 2 >= len(arr):\\n break\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minSetSize(int[] arr) {\\n int mx = 0;\\n for (int x : arr) {\\n mx = Math.max(mx, x);\\n }\\n int[] cnt = new int[mx + 1];\\n for (int x : arr) {\\n ++cnt[x];\\n }\\n Arrays.sort(cnt);\\n int ans = 0;\\n int m = 0;\\n for (int i = mx;; --i) {\\n if (cnt[i] > 0) {\\n m += cnt[i];\\n ++ans;\\n if (m * 2 >= arr.length) {\\n return ans;\\n }\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minSetSize(vector<int>& arr) {\\n int mx = ranges::max(arr);\\n int cnt[mx + 1];\\n memset(cnt, 0, sizeof(cnt));\\n for (int& x : arr) {\\n ++cnt[x];\\n }\\n sort(cnt, cnt + mx + 1, greater<int>());\\n int ans = 0;\\n int m = 0;\\n for (int& x : cnt) {\\n if (x) {\\n m += x;\\n ++ans;\\n if (m * 2 >= arr.size()) {\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minSetSize(arr []int) (ans int) {\\nmx := slices.Max(arr)\\ncnt := make([]int, mx+1)\\nfor _, x := range arr {\\ncnt[x]++\\n}\\nsort.Ints(cnt)\\nfor i, m := mx, 0; ; i-- {\\nif cnt[i] > 0 {\\nm += cnt[i]\\nans++\\nif m >= len(arr)/2 {\\nreturn\\n}\\n}\\n}\\n}\\n
\\n###ts
\\nfunction minSetSize(arr: number[]): number {\\n const cnt = new Map<number, number>();\\n for (const v of arr) {\\n cnt.set(v, (cnt.get(v) ?? 0) + 1);\\n }\\n let [ans, m] = [0, 0];\\n for (const v of Array.from(cnt.values()).sort((a, b) => b - a)) {\\n m += v;\\n ++ans;\\n if (m * 2 >= arr.length) {\\n break;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\\\\textit{arr}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 + 排序 我们可以用哈希表或数组 $cnt$ 统计数组 $\\\\textit{arr}$ 中每个数字出现的次数,然后将 $cnt$ 中的数字从大到小排序,从大到小遍历 $\\\\textit{cnt}$,每次遍历将当前数字 $x$ 加入答案,并将 $m$ 加上 $x$,如果 $m \\\\geq \\\\frac{n}{2}$,则返回答案。\\n\\n###python\\n\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n cnt = Counter(arr…","guid":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/python3javacgotypescript-yi-ti-yi-jie-ji-wmlb","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-15T00:46:25.804Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-数组大小减半🟡","url":"https://leetcode.cn/problems/reduce-array-size-to-the-half/","content":"给你一个整数数组 arr
。你可以从中选出一个整数集合,并删除这些整数在数组中的每次出现。
返回 至少 能删除数组中的一半整数的整数集合的最小大小。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:arr = [3,3,3,3,5,5,5,2,2,7]\\n输出:2\\n解释:选择 {3,7} 使得结果数组为 [5,5,5,2,2]、长度为 5(原数组长度的一半)。\\n大小为 2 的可行集合有 {3,5},{3,2},{5,2}。\\n选择 {2,7} 是不可行的,它的结果数组为 [3,3,3,3,5,5,5],新数组长度大于原数组的二分之一。\\n\\n\\n
示例 2:
\\n\\n输入:arr = [7,7,7,7,7,7]\\n输出:1\\n解释:我们只能选择集合 {7},结果数组为空。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= arr.length <= 105
arr.length
为偶数1 <= arr[i] <= 105
在示例 1 中,我们遍历 $\\\\textit{votes}$,统计得到如下结果:
\\n把每种字母及其出现次数列表,记到一个哈希表(或数组)中。在上面的例子中,记录如下信息:
\\n{\\n \'A\': [5,0,0],\\n \'B\': [0,2,3],\\n \'C\': [0,3,2],\\n}\\n
\\n然后把字母 $\\\\text{A},\\\\text{B},\\\\text{C}$ 排序:
\\n代码实现时,可以把出现次数取相反数,从而方便比大小。
\\n###py
\\nclass Solution:\\n def rankTeams(self, votes: List[str]) -> str:\\n m = len(votes[0])\\n cnts = defaultdict(lambda: [0] * m)\\n for vote in votes:\\n for i, ch in enumerate(vote):\\n cnts[ch][i] -= 1 # 改成负数(相反数),方便比大小\\n return \'\'.join(sorted(cnts, key=lambda ch: (cnts[ch], ch)))\\n
\\n###java
\\nclass Solution {\\n public String rankTeams(String[] votes) {\\n int m = votes[0].length();\\n int[][] cnts = new int[26][m];\\n for (String vote : votes) {\\n for (int i = 0; i < m; i++) {\\n cnts[vote.charAt(i) - \'A\'][i]++;\\n }\\n }\\n\\n return votes[0].chars()\\n .mapToObj(c -> (char) c)\\n .sorted((a, b) -> {\\n int[] cntA = cnts[a - \'A\'];\\n int[] cntB = cnts[b - \'A\'];\\n for (int i = 0; i < cntA.length; i++) {\\n if (cntA[i] != cntB[i]) {\\n return cntB[i] - cntA[i];\\n }\\n }\\n return a - b;\\n })\\n .map(String::valueOf)\\n .collect(Collectors.joining());\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string rankTeams(vector<string>& votes) {\\n int m = votes[0].length();\\n vector cnts(26, vector<int>(m));\\n for (string& vote : votes) {\\n for (int i = 0; i < m; i++) {\\n cnts[vote[i] - \'A\'][i]--; // 改成负数(相反数),方便比大小\\n }\\n }\\n\\n string ans = votes[0];\\n ranges::sort(ans, {}, [&](char a) { return make_pair(cnts[a - \'A\'], a); });\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc rankTeams(votes []string) string {\\n cnts := map[rune][]int{}\\n for _, ch := range votes[0] {\\n cnts[ch] = make([]int, len(votes[0]))\\n }\\n for _, vote := range votes {\\n for i, ch := range vote {\\n cnts[ch][i]++\\n }\\n }\\n\\n ans := slices.SortedFunc(maps.Keys(cnts), func(a rune, b rune) int {\\n return cmp.Or(slices.Compare(cnts[b], cnts[a]), cmp.Compare(a, b))\\n })\\n return string(ans)\\n}\\n
\\n###js
\\nvar rankTeams = function(votes) {\\n const m = votes[0].length;\\n const cnts = {};\\n for (const ch of votes[0]) {\\n cnts[ch] = Array(m).fill(0);\\n }\\n for (const vote of votes) {\\n for (let i = 0; i < m; i++) {\\n cnts[vote[i]][i]++;\\n }\\n }\\n\\n const ans = votes[0].split(\'\');\\n ans.sort((a, b) => {\\n for (let i = 0; i < m; i++) {\\n if (cnts[a][i] !== cnts[b][i]) {\\n return cnts[b][i] - cnts[a][i];\\n }\\n }\\n return a.localeCompare(b);\\n });\\n return ans.join(\'\');\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn rank_teams(mut votes: Vec<String>) -> String {\\n let m = votes[0].len();\\n let mut cnts = vec![vec![0; m]; 26];\\n for vote in &votes {\\n for (i, ch) in vote.bytes().enumerate() {\\n cnts[(ch - b\'A\') as usize][i] -= 1; // 改成负数(相反数),方便比大小\\n }\\n }\\n\\n let mut ans = unsafe { votes[0].as_bytes_mut() };\\n ans.sort_unstable_by_key(|&a| (&cnts[(a - b\'A\') as usize], a));\\n votes[0].clone()\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"在示例 1 中,我们遍历 $\\\\textit{votes}$,统计得到如下结果: 字母 $\\\\text{A}$ 在下标 $0$ 的位置上出现了 $5$ 次,在下标 $1$ 的位置上出现了 $0$ 次,在下标 $2$ 的位置上出现了 $0$ 次。记作 $[5,0,0]$。\\n字母 $\\\\text{B}$ 在下标 $0$ 的位置上出现了 $0$ 次,在下标 $1$ 的位置上出现了 $2$ 次,在下标 $2$ 的位置上出现了 $3$ 次。记作 $[0,2,3]$。\\n字母 $\\\\text{C}$ 在下标 $0$ 的位置上出现了 $0$ 次,在下标 $1$ 的位置上出现了…","guid":"https://leetcode.cn/problems/rank-teams-by-votes//solution/zi-ding-yi-pai-xu-jian-ji-xie-fa-pythonj-de1p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-14T12:55:08.527Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-K 次乘运算后的最终数组 II🔴","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii/","content":"给你一个整数数组 nums
,一个整数 k
和一个整数 multiplier
。
你需要对 nums
执行 k
次操作,每次操作中:
nums
中的 最小 值 x
,如果存在多个最小值,选择最 前面 的一个。x
替换为 x * multiplier
。k
次操作以后,你需要将 nums
中每一个数值对 109 + 7
取余。
请你返回执行完 k
次乘运算以及取余运算之后,最终的 nums
数组。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,1,3,5,6], k = 5, multiplier = 2
\\n\\n输出:[8,4,6,5,6]
\\n\\n解释:
\\n\\n操作 | \\n结果 | \\n
---|---|
1 次操作后 | \\n[2, 2, 3, 5, 6] | \\n
2 次操作后 | \\n[4, 2, 3, 5, 6] | \\n
3 次操作后 | \\n[4, 4, 3, 5, 6] | \\n
4 次操作后 | \\n[4, 4, 6, 5, 6] | \\n
5 次操作后 | \\n[8, 4, 6, 5, 6] | \\n
取余操作后 | \\n[8, 4, 6, 5, 6] | \\n
示例 2:
\\n\\n输入:nums = [100000,2000], k = 2, multiplier = 1000000
\\n\\n输出:[999999307,999999993]
\\n\\n解释:
\\n\\n操作 | \\n结果 | \\n
---|---|
1 次操作后 | \\n[100000, 2000000000] | \\n
2 次操作后 | \\n[100000000000, 2000000000] | \\n
取余操作后 | \\n[999999307, 999999993] | \\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 104
1 <= nums[i] <= 109
1 <= k <= 109
1 <= multiplier <= 106
我们可以用一个小根堆来维护数组 $\\\\textit{nums}$ 中的元素,每次从小根堆中取出最小值,将其乘以 $\\\\textit{multiplier}$ 后再放回小根堆中。在实现过程中,我们往小根堆插入的是元素的下标,然后自定义比较函数,使得小根堆按照 $\\\\textit{nums}$ 中元素的大小作为第一关键字,下标作为第二关键字进行排序。
\\n最后,我们返回数组 $\\\\textit{nums}$ 即可。
\\n###python
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n pq = [(x, i) for i, x in enumerate(nums)]\\n heapify(pq)\\n for _ in range(k):\\n _, i = heappop(pq)\\n nums[i] *= multiplier\\n heappush(pq, (nums[i], i))\\n return nums\\n
\\n###java
\\nclass Solution {\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n PriorityQueue<Integer> pq\\n = new PriorityQueue<>((i, j) -> nums[i] - nums[j] == 0 ? i - j : nums[i] - nums[j]);\\n for (int i = 0; i < nums.length; i++) {\\n pq.offer(i);\\n }\\n while (k-- > 0) {\\n int i = pq.poll();\\n nums[i] *= multiplier;\\n pq.offer(i);\\n }\\n return nums;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n auto cmp = [&nums](int i, int j) {\\n return nums[i] == nums[j] ? i > j : nums[i] > nums[j];\\n };\\n priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);\\n\\n for (int i = 0; i < nums.size(); ++i) {\\n pq.push(i);\\n }\\n\\n while (k--) {\\n int i = pq.top();\\n pq.pop();\\n nums[i] *= multiplier;\\n pq.push(i);\\n }\\n\\n return nums;\\n }\\n};\\n
\\n###go
\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\nh := &hp{nums: nums}\\nfor i := range nums {\\nheap.Push(h, i)\\n}\\n\\nfor k > 0 {\\ni := heap.Pop(h).(int)\\nnums[i] *= multiplier\\nheap.Push(h, i)\\nk--\\n}\\n\\nreturn nums\\n}\\n\\ntype hp struct {\\nsort.IntSlice\\nnums []int\\n}\\n\\nfunc (h *hp) Less(i, j int) bool {\\nif h.nums[h.IntSlice[i]] == h.nums[h.IntSlice[j]] {\\nreturn h.IntSlice[i] < h.IntSlice[j]\\n}\\nreturn h.nums[h.IntSlice[i]] < h.nums[h.IntSlice[j]]\\n}\\n\\nfunc (h *hp) Pop() any {\\nold := h.IntSlice\\nn := len(old)\\nx := old[n-1]\\nh.IntSlice = old[:n-1]\\nreturn x\\n}\\n\\nfunc (h *hp) Push(x any) {\\nh.IntSlice = append(h.IntSlice, x.(int))\\n}\\n
\\n###ts
\\nfunction getFinalState(nums: number[], k: number, multiplier: number): number[] {\\n const pq = new PriorityQueue({\\n compare: (i, j) => (nums[i] === nums[j] ? i - j : nums[i] - nums[j]),\\n });\\n for (let i = 0; i < nums.length; ++i) {\\n pq.enqueue(i);\\n }\\n while (k--) {\\n const i = pq.dequeue()!;\\n nums[i] *= multiplier;\\n pq.enqueue(i);\\n }\\n return nums;\\n}\\n
\\n时间复杂度 $O((n + k) \\\\times \\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:优先队列(小根堆)+ 模拟 我们可以用一个小根堆来维护数组 $\\\\textit{nums}$ 中的元素,每次从小根堆中取出最小值,将其乘以 $\\\\textit{multiplier}$ 后再放回小根堆中。在实现过程中,我们往小根堆插入的是元素的下标,然后自定义比较函数,使得小根堆按照 $\\\\textit{nums}$ 中元素的大小作为第一关键字,下标作为第二关键字进行排序。\\n\\n最后,我们返回数组 $\\\\textit{nums}$ 即可。\\n\\n###python\\n\\nclass Solution:\\n def getFinalState(self, nums…","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-i//solution/python3javacgotypescript-yi-ti-yi-jie-yo-p6cl","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-13T01:05:10.151Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-K 次乘运算后的最终数组 I🟢","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-i/","content":"给你一个整数数组 nums
,一个整数 k
和一个整数 multiplier
。
你需要对 nums
执行 k
次操作,每次操作中:
nums
中的 最小 值 x
,如果存在多个最小值,选择最 前面 的一个。x
替换为 x * multiplier
。请你返回执行完 k
次乘运算之后,最终的 nums
数组。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,1,3,5,6], k = 5, multiplier = 2
\\n\\n输出:[8,4,6,5,6]
\\n\\n解释:
\\n\\n操作 | \\n结果 | \\n
---|---|
1 次操作后 | \\n[2, 2, 3, 5, 6] | \\n
2 次操作后 | \\n[4, 2, 3, 5, 6] | \\n
3 次操作后 | \\n[4, 4, 3, 5, 6] | \\n
4 次操作后 | \\n[4, 4, 6, 5, 6] | \\n
5 次操作后 | \\n[8, 4, 6, 5, 6] | \\n
示例 2:
\\n\\n输入:nums = [1,2], k = 3, multiplier = 4
\\n\\n输出:[16,8]
\\n\\n解释:
\\n\\n操作 | \\n结果 | \\n
---|---|
1 次操作后 | \\n[4, 2] | \\n
2 次操作后 | \\n[4, 8] | \\n
3 次操作后 | \\n[16, 8] | \\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 100
1 <= nums[i] <= 100
1 <= k <= 10
1 <= multiplier <= 5
根据题目描述,我们应该优先选择价值越小的物品,把价值越大的物品留到后面购买,这样才能使得总开销最大。因此,我们使用优先队列(小根堆)存储每个商店中还未购买的最小价值的物品。初始时,我们将每个商店中最右边的物品加入优先队列。
\\n在每一天,我们从优先队列中取出价值最小的物品,将其加入答案,并将该物品所在商店中的上一个物品加入优先队列。我们重复上述操作,直到优先队列为空。
\\n###python
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n n = len(values[0])\\n pq = [(row[-1], i, n - 1) for i, row in enumerate(values)]\\n heapify(pq)\\n ans = d = 0\\n while pq:\\n d += 1\\n v, i, j = heappop(pq)\\n ans += v * d\\n if j:\\n heappush(pq, (values[i][j - 1], i, j - 1))\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n int m = values.length, n = values[0].length;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n for (int i = 0; i < m; ++i) {\\n pq.offer(new int[] {values[i][n - 1], i, n - 1});\\n }\\n long ans = 0;\\n for (int d = 1; !pq.isEmpty(); ++d) {\\n var p = pq.poll();\\n int v = p[0], i = p[1], j = p[2];\\n ans += (long) v * d;\\n if (j > 0) {\\n pq.offer(new int[] {values[i][j - 1], i, j - 1});\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxSpending(vector<vector<int>>& values) {\\n priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;\\n int m = values.size(), n = values[0].size();\\n for (int i = 0; i < m; ++i) {\\n pq.emplace(values[i][n - 1], i, n - 1);\\n }\\n long long ans = 0;\\n for (int d = 1; pq.size(); ++d) {\\n auto [v, i, j] = pq.top();\\n pq.pop();\\n ans += 1LL * v * d;\\n if (j) {\\n pq.emplace(values[i][j - 1], i, j - 1);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxSpending(values [][]int) (ans int64) {\\npq := hp{}\\nn := len(values[0])\\nfor i, row := range values {\\nheap.Push(&pq, tuple{row[n-1], i, n - 1})\\n}\\nfor d := 1; len(pq) > 0; d++ {\\np := heap.Pop(&pq).(tuple)\\nans += int64(p.v * d)\\nif p.j > 0 {\\nheap.Push(&pq, tuple{values[p.i][p.j-1], p.i, p.j - 1})\\n}\\n}\\nreturn\\n}\\n\\ntype tuple struct{ v, i, j int }\\ntype hp []tuple\\n\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].v < h[j].v }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(tuple)) }\\nfunc (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }\\n
\\n###ts
\\nfunction maxSpending(values: number[][]): number {\\n const m = values.length;\\n const n = values[0].length;\\n const pq = new PriorityQueue({ compare: (a, b) => a[0] - b[0] });\\n for (let i = 0; i < m; ++i) {\\n pq.enqueue([values[i][n - 1], i, n - 1]);\\n }\\n\\n let ans = 0;\\n for (let d = 1; !pq.isEmpty(); ++d) {\\n const [v, i, j] = pq.dequeue()!;\\n ans += v * d;\\n if (j > 0) {\\n pq.enqueue([values[i][j - 1], i, j - 1]);\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(m \\\\times n \\\\times \\\\log m)$,空间复杂度 $O(m)$。其中 $m$ 和 $n$ 分别是数组 $values$ 的行数和列数。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:贪心 + 优先队列 根据题目描述,我们应该优先选择价值越小的物品,把价值越大的物品留到后面购买,这样才能使得总开销最大。因此,我们使用优先队列(小根堆)存储每个商店中还未购买的最小价值的物品。初始时,我们将每个商店中最右边的物品加入优先队列。\\n\\n在每一天,我们从优先队列中取出价值最小的物品,将其加入答案,并将该物品所在商店中的上一个物品加入优先队列。我们重复上述操作,直到优先队列为空。\\n\\n###python\\n\\nclass Solution:\\n def maxSpending(self, values: List[List[int…","guid":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/python3javacgotypescript-yi-ti-yi-jie-ta-va33","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-12T00:47:29.118Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-购买物品的最大开销🔴","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items/","content":"给你一个下标从 0 开始大小为 m * n
的整数矩阵 values
,表示 m
个不同商店里 m * n
件不同的物品。每个商店有 n
件物品,第 i
个商店的第 j
件物品的价值为 values[i][j]
。除此以外,第 i
个商店的物品已经按照价值非递增排好序了,也就是说对于所有 0 <= j < n - 1
都有 values[i][j] >= values[i][j + 1]
。
每一天,你可以在一个商店里购买一件物品。具体来说,在第 d
天,你可以:
i
。j
,开销为 values[i][j] * d
。换句话说,选择该商店中还没购买过的物品中最大的下标 j
,并且花费 values[i][j] * d
去购买。注意,所有物品都视为不同的物品。比方说如果你已经从商店 1
购买了物品 0
,你还可以在别的商店里购买其他商店的物品 0
。
请你返回购买所有 m * n
件物品需要的 最大开销 。
\\n\\n
示例 1:
\\n\\n输入:values = [[8,5,2],[6,4,1],[9,7,3]]\\n输出:285\\n解释:第一天,从商店 1 购买物品 2 ,开销为 values[1][2] * 1 = 1 。\\n第二天,从商店 0 购买物品 2 ,开销为 values[0][2] * 2 = 4 。\\n第三天,从商店 2 购买物品 2 ,开销为 values[2][2] * 3 = 9 。\\n第四天,从商店 1 购买物品 1 ,开销为 values[1][1] * 4 = 16 。\\n第五天,从商店 0 购买物品 1 ,开销为 values[0][1] * 5 = 25 。\\n第六天,从商店 1 购买物品 0 ,开销为 values[1][0] * 6 = 36 。\\n第七天,从商店 2 购买物品 1 ,开销为 values[2][1] * 7 = 49 。\\n第八天,从商店 0 购买物品 0 ,开销为 values[0][0] * 8 = 64 。\\n第九天,从商店 2 购买物品 0 ,开销为 values[2][0] * 9 = 81 。\\n所以总开销为 285 。\\n285 是购买所有 m * n 件物品的最大总开销。\\n\\n\\n
示例 2:
\\n\\n输入:values = [[10,8,6,4,2],[9,7,5,3,2]]\\n输出:386\\n解释:第一天,从商店 0 购买物品 4 ,开销为 values[0][4] * 1 = 2 。\\n第二天,从商店 1 购买物品 4 ,开销为 values[1][4] * 2 = 4 。\\n第三天,从商店 1 购买物品 3 ,开销为 values[1][3] * 3 = 9 。\\n第四天,从商店 0 购买物品 3 ,开销为 values[0][3] * 4 = 16 。\\n第五天,从商店 1 购买物品 2 ,开销为 values[1][2] * 5 = 25 。\\n第六天,从商店 0 购买物品 2 ,开销为 values[0][2] * 6 = 36 。\\n第七天,从商店 1 购买物品 1 ,开销为 values[1][1] * 7 = 49 。\\n第八天,从商店 0 购买物品 1 ,开销为 values[0][1] * 8 = 64 。\\n第九天,从商店 1 购买物品 0 ,开销为 values[1][0] * 9 = 81 。\\n第十天,从商店 0 购买物品 0 ,开销为 values[0][0] * 10 = 100 。\\n所以总开销为 386 。\\n386 是购买所有 m * n 件物品的最大总开销。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= m == values.length <= 10
1 <= n == values[i].length <= 104
1 <= values[i][j] <= 106
values[i]
按照非递增顺序排序。思路与算法
\\n遍历字符串中每个长度为 $2$ 的子串,将其翻转后判断是否在原串中出现即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n bool isSubstringPresent(string s) {\\n for (int i = 0; i + 1 < s.size(); i++) {\\n string substr = s.substr(i, 2);\\n reverse(substr.begin(), substr.end());\\n if (s.find(substr) != string::npos) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean isSubstringPresent(String s) {\\n for (int i = 0; i + 1 < s.length(); i++) {\\n String substr = new StringBuilder(s.substring(i, i + 2)).reverse().toString();\\n if (s.contains(substr)) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool IsSubstringPresent(string s) {\\n for (int i = 0; i + 1 < s.Length; i++) {\\n StringBuilder sb = new StringBuilder();\\n sb.Append(s[i + 1]);\\n sb.Append(s[i]);\\n string substr = sb.ToString();\\n if (s.Contains(substr)) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n for i in range(len(s) - 1):\\n if s[i:i+2][::-1] in s:\\n return True\\n return False\\n
\\n###Rust
\\nimpl Solution {\\n pub fn is_substring_present(s: String) -> bool {\\n let n = s.len();\\n for i in 0..(n - 1) {\\n let substr = &s[i..i+2].chars().rev().collect::<String>();\\n if s.contains(substr) {\\n return true\\n }\\n }\\n false\\n }\\n}\\n
\\n###Go
\\nfunc isSubstringPresent(s string) bool {\\n for i := 0; i < len(s) - 1; i++ {\\nsubstr := string([]byte{s[i + 1], s[i]})\\nif strings.Contains(s, substr) {\\nreturn true\\n}\\n}\\nreturn false\\n}\\n
\\n###C
\\nbool isSubstringPresent(char* s) {\\n int len = strlen(s);\\n for (int i = 0; i < len - 1; i++) {\\n char substr[3] = {s[i + 1], s[i], \'\\\\0\'};\\n if (strstr(s, substr)) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
\\n###JavaScript
\\nvar isSubstringPresent = function(s) {\\n for (let i = 0; i < s.length - 1; i++) {\\n let substr = s[i + 1] + s[i];\\n if (s.includes(substr)) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\n###TypeScript
\\nfunction isSubstringPresent(s: string): boolean {\\n for (let i = 0; i < s.length - 1; i++) {\\n let substr = s[i + 1] + s[i];\\n if (s.includes(substr)) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n^2)$,其中 $n$ 是字符串的长度。总共有 $O(n)$ 个长度为 $2$ 的子串,每次查找其翻转后的字符串是否在原字符串中出现的时间复杂度为 $O(n)$,因此总共的时间复杂度为 $O(n^2)$。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n我们可以用哈希表提前存储字符串中的每个长度为 $2$ 的子串,这样在判断翻转后的字符串是否出现时就避免了花费 $O(n)$ 的时间查找。
\\n由于字符仅包含小写字母,该哈希表可以用一个整数类型的二维数组实现,形如 $\\\\textit{hash}[26][26]$。如果要进一步优化,还可以考虑将第二维使用二进制表示,例如 $\\\\textit{hash}[2]$ 二进制形式中,如果从低到高第 $1$ 位为 $1$,则表示子串 $``cb\\"$ 出现在字符串中($2$ 表示字符 $c$,$1$ 表示字符 $b$)。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n bool isSubstringPresent(string s) {\\n vector<int> h(26);\\n for (int i = 0; i + 1 < s.size(); i++) {\\n int x = s[i] - \'a\';\\n int y = s[i + 1] - \'a\';\\n h[x] |= 1 << y;\\n if (h[y] >> x & 1) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean isSubstringPresent(String s) {\\n int[] h = new int[26];\\n for (int i = 0; i + 1 < s.length(); i++) {\\n int x = s.charAt(i) - \'a\';\\n int y = s.charAt(i + 1) - \'a\';\\n h[x] |= 1 << y;\\n if ((h[y] >> x & 1) != 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool IsSubstringPresent(string s) {\\n int[] h = new int[26];\\n for (int i = 0; i + 1 < s.Length; i++) {\\n int x = s[i] - \'a\';\\n int y = s[i + 1] - \'a\';\\n h[x] |= 1 << y;\\n if ((h[y] >> x & 1) != 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n h = [0] * 26\\n for i in range(len(s) - 1):\\n x = ord(s[i]) - ord(\'a\')\\n y = ord(s[i + 1]) - ord(\'a\')\\n h[x] |= 1 << y\\n if h[y] >> x & 1:\\n return True\\n return False\\n
\\n###Rust
\\nimpl Solution {\\n pub fn is_substring_present(s: String) -> bool {\\n let mut h = vec![0; 26];\\n let s = s.as_bytes();\\n for i in 0..s.len() - 1 {\\n let x = (s[i] - b\'a\') as usize;\\n let y = (s[i + 1] - b\'a\') as usize;\\n h[x] |= 1 << y;\\n if h[y] >> x & 1 == 1 {\\n return true;\\n }\\n } \\n false\\n }\\n}\\n
\\n###Go
\\nfunc isSubstringPresent(s string) bool {\\n h := make([]int, 26)\\nfor i := 0; i + 1 < len(s); i++ {\\nx, y := s[i] - \'a\', s[i + 1] - \'a\'\\nh[x] |= (1 << y)\\nif (h[y] >> x) & 1 != 0 {\\nreturn true\\n}\\n}\\nreturn false\\n}\\n
\\n###C
\\nbool isSubstringPresent(char* s) {\\n int h[26] = {0};\\n int len = strlen(s);\\n for (int i = 0; i + 1 < len; i++) {\\n int x = s[i] - \'a\';\\n int y = s[i + 1] - \'a\';\\n h[x] |= (1 << y);\\n if ((h[y] >> x) & 1) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
\\n###JavaScript
\\nvar isSubstringPresent = function(s) {\\n let h = new Array(26).fill(0);\\n for (let i = 0; i + 1 < s.length; i++) {\\n let x = s.charCodeAt(i) - \'a\'.charCodeAt(0);\\n let y = s.charCodeAt(i + 1) - \'a\'.charCodeAt(0);\\n h[x] |= (1 << y);\\n if ((h[y] >> x) & 1) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\n###TypeScript
\\nfunction isSubstringPresent(s: string): boolean {\\n let h: number[] = new Array(26).fill(0);\\n for (let i = 0; i + 1 < s.length; i++) {\\n let x = s.charCodeAt(i) - \'a\'.charCodeAt(0);\\n let y = s.charCodeAt(i + 1) - \'a\'.charCodeAt(0);\\n h[x] |= (1 << y);\\n if ((h[y] >> x) & 1) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n + C)$,其中 $n$ 是字符串的长度,$C$ 为字符集大小,本题中等于 $26$。
\\n空间复杂度:$O(C)$。
\\n思路
\\n将原问题分解成子问题,设计函数 $\\\\textit{dp}[\\\\textit{row}_1, \\\\textit{col}_1,\\\\textit{row}_2,\\\\textit{col}_2]$ 表示切割完整的子矩形至最小单元的代价,其中子矩形的两个对角顶点的坐标分别为 $(\\\\textit{row}_1,\\\\textit{col}_1)$ 和 $(\\\\textit{row}_2,\\\\textit{col}_2)$。我们可以任意水平或者垂直切一刀,然后将问题分解成更小的子问题,直到分到最小单元。遍历所有可能的切法,取出最小值作为子问题的返回值。因为递归过程中有许多重复状态,我们利用记忆话搜索的方式来降低时间复杂度。最后返回 $\\\\textit{dp}(0, 0, m - 1, n - 1)$ 即可。
\\n代码
\\n###Python
\\nclass Solution:\\n def minimumCost(self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]) -> int:\\n \\n @cache\\n def dp(row1: int, col1: int, row2: int, col2: int) -> int:\\n if row1 == row2 and col1 == col2:\\n return 0\\n res = inf\\n for i in range(row1, row2):\\n res = min(res, dp(row1, col1, i, col2) + dp(i + 1, col1, row2, col2) + horizontalCut[i])\\n for i in range(col1, col2):\\n res = min(res, dp(row1, col1, row2, i) + dp(row1, i + 1, row2, col2) + verticalCut[i])\\n return res\\n\\n return dp(0, 0, m - 1, n - 1)\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {\\n vector<int> cache(m * m * n * n, -1);\\n auto index = [&](int row1, int col1, int row2, int col2) -> int {\\n return (row1 * n + col1) * m * n + row2 * n + col2;\\n };\\n function<int(int, int, int, int)> dp;\\n dp = [&](int row1, int col1, int row2, int col2) -> int {\\n if (row1 == row2 && col1 == col2) {\\n return 0;\\n }\\n int ind = index(row1, col1, row2, col2);\\n if (cache[ind] >= 0) {\\n return cache[ind];\\n }\\n cache[ind] = INT_MAX;\\n for (int i = row1; i < row2; i++) {\\n cache[ind] = min(cache[ind], dp(row1, col1, i, col2) + dp(i + 1, col1, row2, col2) + horizontalCut[i]);\\n }\\n for (int i = col1; i < col2; i++) {\\n cache[ind] = min(cache[ind], dp(row1, col1, row2, i) + dp(row1, i + 1, row2, col2) + verticalCut[i]);\\n }\\n return cache[ind];\\n };\\n return dp(0, 0, m - 1, n - 1);\\n }\\n};\\n
\\n###Go
\\nfunc minimumCost(m int, n int, horizontalCut []int, verticalCut []int) int {\\n cache := make([]int, m*m*n*n)\\n for i := range cache {\\n cache[i] = -1\\n }\\n\\n index := func(row1, col1, row2, col2 int) int {\\n return (row1*n + col1)*m*n + row2*n + col2\\n }\\n\\n var dp func(row1, col1, row2, col2 int) int\\n dp = func(row1, col1, row2, col2 int) int {\\n if row1 == row2 && col1 == col2 {\\n return 0\\n }\\n ind := index(row1, col1, row2, col2)\\n if cache[ind] >= 0 {\\n return cache[ind]\\n }\\n cache[ind] = math.MaxInt32\\n for i := row1; i < row2; i++ {\\n cache[ind] = min(cache[ind], dp(row1, col1, i, col2)+dp(i+1, col1, row2, col2)+horizontalCut[i])\\n }\\n for i := col1; i < col2; i++ {\\n cache[ind] = min(cache[ind], dp(row1, col1, row2, i)+dp(row1, i+1, row2, col2)+verticalCut[i])\\n }\\n return cache[ind]\\n }\\n\\n return dp(0, 0, m-1, n-1)\\n}\\n
\\n###C
\\nint idx(int m, int n, int row1, int col1, int row2, int col2) {\\n return (row1 * n + col1) * m * n + row2 * n + col2;\\n}\\n\\nint dp(int m, int n, int row1, int col1, int row2, int col2, int *horizontalCut, int *verticalCut, int *cache) {\\n if (row1 == row2 && col1 == col2) {\\n return 0;\\n }\\n int ind = idx(m, n, row1, col1, row2, col2);\\n if (cache[ind] >= 0) {\\n return cache[ind];\\n }\\n cache[ind] = INT_MAX;\\n for (int i = row1; i < row2; i++) {\\n cache[ind] = fmin(cache[ind], dp(m, n, row1, col1, i, col2, horizontalCut, verticalCut, cache) + dp(m, n, i + 1, col1, row2, col2, horizontalCut, verticalCut, cache) + horizontalCut[i]);\\n }\\n for (int i = col1; i < col2; i++) {\\n cache[ind] = fmin(cache[ind], dp(m, n, row1, col1, row2, i, horizontalCut, verticalCut, cache) + dp(m, n, row1, i + 1, row2, col2, horizontalCut, verticalCut, cache) + verticalCut[i]);\\n }\\n return cache[ind];\\n}\\n\\nint minimumCost(int m, int n, int *horizontalCut, int horizontalCutSize, int *verticalCut, int verticalCutSize) {\\n int *cache = (int *)malloc(m * m * n * n * sizeof(int));\\n memset(cache, 0xff, m * m * n * n * sizeof(int));\\n int res = dp(m, n, 0, 0, m - 1, n - 1, horizontalCut, verticalCut, cache);\\n free(cache);\\n return res;\\n}\\n
\\n###Java
\\nclass Solution {\\n int[][][][] memo;\\n int[] horizontalCut;\\n int[] verticalCut;\\n\\n public int minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n this.memo = new int[m][n][m][n];\\n this.horizontalCut = horizontalCut;\\n this.verticalCut = verticalCut;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n for (int k = 0; k < m; k++) {\\n Arrays.fill(memo[i][j][k], -1);\\n }\\n }\\n }\\n return dp(0, 0, m - 1, n - 1);\\n }\\n\\n public int dp(int row1, int col1, int row2, int col2) {\\n if (row1 == row2 && col1 == col2) {\\n return 0;\\n }\\n if (memo[row1][col1][row2][col2] < 0) {\\n int res = Integer.MAX_VALUE;\\n for (int i = row1; i < row2; i++) {\\n res = Math.min(res, dp(row1, col1, i, col2) + dp(i + 1, col1, row2, col2) + horizontalCut[i]);\\n }\\n for (int i = col1; i < col2; i++) {\\n res = Math.min(res, dp(row1, col1, row2, i) + dp(row1, i + 1, row2, col2) + verticalCut[i]);\\n }\\n memo[row1][col1][row2][col2] = res;\\n }\\n return memo[row1][col1][row2][col2];\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n int[][][][] memo;\\n int[] horizontalCut;\\n int[] verticalCut;\\n\\n public int MinimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n this.memo = new int[m][][][];\\n this.horizontalCut = horizontalCut;\\n this.verticalCut = verticalCut;\\n for (int i = 0; i < m; i++) {\\n memo[i] = new int[n][][];\\n for (int j = 0; j < n; j++) {\\n memo[i][j] = new int[m][];\\n for (int k = 0; k < m; k++) {\\n memo[i][j][k] = new int[n];\\n Array.Fill(memo[i][j][k], -1);\\n }\\n }\\n }\\n return DP(0, 0, m - 1, n - 1);\\n }\\n\\n public int DP(int row1, int col1, int row2, int col2) {\\n if (row1 == row2 && col1 == col2) {\\n return 0;\\n }\\n if (memo[row1][col1][row2][col2] < 0) {\\n int res = int.MaxValue;\\n for (int i = row1; i < row2; i++) {\\n res = Math.Min(res, DP(row1, col1, i, col2) + DP(i + 1, col1, row2, col2) + horizontalCut[i]);\\n }\\n for (int i = col1; i < col2; i++) {\\n res = Math.Min(res, DP(row1, col1, row2, i) + DP(row1, i + 1, row2, col2) + verticalCut[i]);\\n }\\n memo[row1][col1][row2][col2] = res;\\n }\\n return memo[row1][col1][row2][col2];\\n }\\n}\\n
\\n###JavaScript
\\nvar minimumCost = function(m, n, horizontalCut, verticalCut) {\\n const memo = new Array(m * m * n * n).fill(-1);\\n const index = (row1, col1, row2, col2) => {\\n return (row1 * n + col1) * m * n + row2 * n + col2;\\n };\\n\\n const dp = (row1, col1, row2, col2) => {\\n if (row1 === row2 && col1 === col2) {\\n return 0;\\n }\\n const ind = index(row1, col1, row2, col2);\\n if (memo[ind] >= 0) {\\n return memo[ind];\\n }\\n\\n memo[ind] = Number.MAX_SAFE_INTEGER;\\n for (let i = row1; i < row2; i++) {\\n memo[ind] = Math.min(memo[ind], dp(row1, col1, i, col2) + dp(i + 1, col1, row2, col2) + horizontalCut[i]);\\n }\\n for (let i = col1; i < col2; i++) {\\n memo[ind] = Math.min(memo[ind], dp(row1, col1, row2, i) + dp(row1, i + 1, row2, col2) + verticalCut[i]);\\n }\\n return memo[ind];\\n };\\n\\n return dp(0, 0, m - 1, n - 1);\\n};\\n
\\n###TypeScript
\\nfunction minimumCost(m: number, n: number, horizontalCut: number[], verticalCut: number[]): number {\\n const memo: number[] = new Array(m * m * n * n).fill(-1);\\n const index = (row1: number, col1: number, row2: number, col2: number): number => {\\n return (row1 * n + col1) * m * n + row2 * n + col2;\\n };\\n const dp = (row1: number, col1: number, row2: number, col2: number): number => {\\n if (row1 === row2 && col1 === col2) {\\n return 0;\\n }\\n const ind = index(row1, col1, row2, col2);\\n if (memo[ind] >= 0) {\\n return memo[ind];\\n }\\n memo[ind] = Number.MAX_SAFE_INTEGER;\\n for (let i = row1; i < row2; i++) {\\n memo[ind] = Math.min(memo[ind], dp(row1, col1, i, col2) + dp(i + 1, col1, row2, col2) + horizontalCut[i]);\\n }\\n for (let i = col1; i < col2; i++) {\\n memo[ind] = Math.min(memo[ind], dp(row1, col1, row2, i) + dp(row1, i + 1, row2, col2) + verticalCut[i]);\\n }\\n return memo[ind];\\n };\\n\\n return dp(0, 0, m - 1, n - 1);\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_cost(m: i32, n: i32, horizontal_cut: Vec<i32>, vertical_cut: Vec<i32>) -> i32 {\\n let mut memo = vec![-1; (m * m * n * n) as usize];\\n\\n fn dp(\\n row1: i32,\\n col1: i32,\\n row2: i32,\\n col2: i32,\\n m: i32,\\n n: i32,\\n horizontal_cut: &Vec<i32>,\\n vertical_cut: &Vec<i32>,\\n memo: &mut Vec<i32>\\n ) -> i32 {\\n let index = |row1: i32, col1: i32, row2: i32, col2: i32| -> usize {\\n ((row1 * n + col1) * m * n + row2 * n + col2) as usize\\n };\\n if row1 == row2 && col1 == col2 {\\n return 0;\\n }\\n let ind = index(row1, col1, row2, col2);\\n if memo[ind] >= 0 {\\n return memo[ind];\\n }\\n memo[ind] = i32::MAX;\\n for i in row1..row2 {\\n memo[ind] = memo[ind].min(dp(row1, col1, i, col2, m, n, horizontal_cut, vertical_cut, memo)\\n + dp(i + 1, col1, row2, col2, m, n, horizontal_cut, vertical_cut, memo)\\n + horizontal_cut[i as usize]);\\n }\\n for i in col1..col2 {\\n memo[ind] = memo[ind].min(dp(row1, col1, row2, i, m, n, horizontal_cut, vertical_cut, memo)\\n + dp(row1, i + 1, row2, col2, m, n, horizontal_cut, vertical_cut, memo)\\n + vertical_cut[i as usize]);\\n }\\n memo[ind]\\n }\\n\\n dp(0, 0, m - 1, n - 1, m, n, &horizontal_cut, &vertical_cut, &mut memo)\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m^2\\\\times n^2 \\\\times (m+n))$,一共有 $O(m^2\\\\times n^2)$ 个状态,每个状态消耗 $O(m+n)$ 的时间计算。
\\n空间复杂度:$O(m^2\\\\times n^2)$。
\\n我们可以先找到 $1$ 和 $n$ 的下标 $i$ 和 $j$,然后根据 $i$ 和 $j$ 的相对位置,判断需要交换的次数。
\\n如果 $i \\\\lt j$,那么需要交换的次数为 $i + n - j - 1$;如果 $i \\\\gt j$,那么需要交换的次数为 $i + n - j - 2$。
\\n###python
\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n n = len(nums)\\n i = nums.index(1)\\n j = nums.index(n)\\n k = 1 if i < j else 2\\n return i + n - j - k\\n
\\n###java
\\nclass Solution {\\n public int semiOrderedPermutation(int[] nums) {\\n int n = nums.length;\\n int i = 0, j = 0;\\n for (int k = 0; k < n; ++k) {\\n if (nums[k] == 1) {\\n i = k;\\n }\\n if (nums[k] == n) {\\n j = k;\\n }\\n }\\n int k = i < j ? 1 : 2;\\n return i + n - j - k;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int semiOrderedPermutation(vector<int>& nums) {\\n int n = nums.size();\\n int i = find(nums.begin(), nums.end(), 1) - nums.begin();\\n int j = find(nums.begin(), nums.end(), n) - nums.begin();\\n int k = i < j ? 1 : 2;\\n return i + n - j - k;\\n }\\n};\\n
\\n###go
\\nfunc semiOrderedPermutation(nums []int) int {\\nn := len(nums)\\nvar i, j int\\nfor k, x := range nums {\\nif x == 1 {\\ni = k\\n}\\nif x == n {\\nj = k\\n}\\n}\\nk := 1\\nif i > j {\\nk = 2\\n}\\nreturn i + n - j - k\\n}\\n
\\n###ts
\\nfunction semiOrderedPermutation(nums: number[]): number {\\n const n = nums.length;\\n const i = nums.indexOf(1);\\n const j = nums.indexOf(n);\\n const k = i < j ? 1 : 2;\\n return i + n - j - k;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn semi_ordered_permutation(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let (mut i, mut j) = (0, 0);\\n\\n for k in 0..n {\\n if nums[k] == 1 {\\n i = k;\\n }\\n if nums[k] == (n as i32) {\\n j = k;\\n }\\n }\\n\\n let k = if i < j { 1 } else { 2 };\\n (i + n - j - k) as i32\\n }\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:寻找 1 和 n 的位置 我们可以先找到 $1$ 和 $n$ 的下标 $i$ 和 $j$,然后根据 $i$ 和 $j$ 的相对位置,判断需要交换的次数。\\n\\n如果 $i \\\\lt j$,那么需要交换的次数为 $i + n - j - 1$;如果 $i \\\\gt j$,那么需要交换的次数为 $i + n - j - 2$。\\n\\n###python\\n\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n n = len(nums…","guid":"https://leetcode.cn/problems/semi-ordered-permutation//solution/python3javacgotypescript-yi-ti-yi-jie-xu-2hmm","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-11T01:03:38.543Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-半有序排列🟢","url":"https://leetcode.cn/problems/semi-ordered-permutation/","content":"给你一个下标从 0 开始、长度为 n
的整数排列 nums
。
如果排列的第一个数字等于 1
且最后一个数字等于 n
,则称其为 半有序排列 。你可以执行多次下述操作,直到将 nums
变成一个 半有序排列 :
nums
中相邻的两个元素,然后交换它们。返回使 nums
变成 半有序排列 所需的最小操作次数。
排列 是一个长度为 n
的整数序列,其中包含从 1
到 n
的每个数字恰好一次。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,1,4,3]\\n输出:2\\n解释:可以依次执行下述操作得到半有序排列:\\n1 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3] 。\\n2 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。\\n可以证明,要让 nums 成为半有序排列,不存在执行操作少于 2 次的方案。\\n\\n
示例 2:
\\n\\n输入:nums = [2,4,1,3]\\n输出:3\\n解释:\\n可以依次执行下述操作得到半有序排列:\\n1 - 交换下标 1 和下标 2 对应元素。排列变为 [2,1,4,3] 。\\n2 - 交换下标 0 和下标 1 对应元素。排列变为 [1,2,4,3] 。\\n3 - 交换下标 2 和下标 3 对应元素。排列变为 [1,2,3,4] 。\\n可以证明,要让 nums 成为半有序排列,不存在执行操作少于 3 次的方案。\\n\\n\\n
示例 3:
\\n\\n输入:nums = [1,3,4,2,5]\\n输出:0\\n解释:这个排列已经是一个半有序排列,无需执行任何操作。\\n\\n\\n
\\n\\n
提示:
\\n\\n2 <= nums.length == n <= 50
1 <= nums[i] <= 50
nums
是一个 排列象棋骑士有一个独特的移动方式,它可以垂直移动两个方格,水平移动一个方格,或者水平移动两个方格,垂直移动一个方格(两者都形成一个 L 的形状)。
\\n\\n象棋骑士可能的移动方式如下图所示:
\\n\\n我们有一个象棋骑士和一个电话垫,如下所示,骑士只能站在一个数字单元格上(即蓝色单元格)。
\\n\\n给定一个整数 n,返回我们可以拨多少个长度为 n 的不同电话号码。
\\n\\n你可以将骑士放置在任何数字单元格上,然后你应该执行 n - 1 次移动来获得长度为 n 的号码。所有的跳跃应该是有效的骑士跳跃。
\\n\\n因为答案可能很大,所以输出答案模 109 + 7
.
\\n\\n
示例 1:
\\n\\n输入:n = 1\\n输出:10\\n解释:我们需要拨一个长度为1的数字,所以把骑士放在10个单元格中的任何一个数字单元格上都能满足条件。\\n\\n\\n
示例 2:
\\n\\n输入:n = 2\\n输出:20\\n解释:我们可以拨打的所有有效号码为[04, 06, 16, 18, 27, 29, 34, 38, 40, 43, 49, 60, 61, 67, 72, 76, 81, 83, 92, 94]\\n\\n\\n
示例 3:
\\n\\n输入:n = 3131\\n输出:136006598\\n解释:注意取模\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 5000
由题意可知,字符串 $t$ 的长度一定为字符串 $s$ 的长度 $n$ 的因数,因此我们可以从小到大枚举 $n$ 的因数作为 $t$ 的长度。令当前枚举的因数为 $i$,我们将字符串 $s$ 切分为若干个长度为 $i$ 的子字符串,用 $\\\\textit{count}_0$ 统计前一子字符串的字符出现次数,用 $\\\\textit{count}_1$ 统计后一子字符串的出现次数,如果 $\\\\textit{count}_0$ 不等于 $\\\\textit{count}_1$,那么说明 $i$ 不符合题意;否则说明所有子字符串的字符出现次数都相等,那么返回 $i$ 作为 $t$ 的最小可能长度。
\\n###C++
\\nclass Solution {\\npublic:\\n int minAnagramLength(string s) {\\n int n = s.size();\\n auto check = [&](int m) -> bool {\\n vector<int> count0(26);\\n for (int j = 0; j < n; j += m) {\\n vector<int> count1(26);\\n for (int k = j; k < j + m; k++) {\\n count1[s[k] - \'a\']++;\\n }\\n if (j > 0 && count0 != count1) {\\n return false;\\n }\\n count0.swap(count1);\\n }\\n return true;\\n };\\n for (int i = 1; i < n; i++) {\\n if (n % i != 0) {\\n continue;\\n }\\n if (check(i)) {\\n return i;\\n }\\n }\\n return n;\\n }\\n};\\n
\\n###C
\\nbool check(char *s, int m) {\\n int n = strlen(s), count0[26] = {0};\\n for (int j = 0; j < n; j += m) {\\n int count1[26] = {0};\\n for (int k = j; k < j + m; k++) {\\n count1[s[k] - \'a\']++;\\n }\\n if (j > 0 && memcmp(count0, count1, sizeof(int) * 26) != 0) {\\n return false;\\n }\\n memcpy(count0, count1, sizeof(int) * 26);\\n }\\n return true;\\n}\\n\\nint minAnagramLength(char *s) {\\n int n = strlen(s);\\n for (int i = 1; i < n; i++) {\\n if (n % i != 0) {\\n continue;\\n }\\n if(check(s, i)) {\\n return i;\\n }\\n }\\n return n;\\n}\\n
\\n###Go
\\nfunc minAnagramLength(s string) int {\\n n := len(s)\\n check := func(m int) bool {\\n var count0 [26]int\\n for j := 0; j < n; j += m {\\n var count1 [26]int\\n for k := j; k < j + m; k++ {\\n count1[s[k] - \'a\']++\\n }\\n if j > 0 && count0 != count1 {\\n return false\\n }\\n count0 = count1\\n }\\n return true\\n }\\n for i := 1; i < n; i++ {\\n if n % i != 0 {\\n continue\\n }\\n if check(i) {\\n return i\\n }\\n }\\n return n\\n}\\n
\\n###Python
\\nclass Solution:\\n def minAnagramLength(self, s: str) -> int:\\n n = len(s)\\n def check(m: int) -> bool:\\n for j in range(m, n, m):\\n if Counter(s[:m]) != Counter(s[j:j+m]):\\n return False\\n return True\\n for i in range(1, n):\\n if n % i != 0:\\n continue\\n if check(i):\\n return i\\n return n\\n
\\n###Java
\\nclass Solution {\\n public int minAnagramLength(String s) {\\n int n = s.length();\\n for (int i = 1; i < n; i++) {\\n if (n % i != 0) {\\n continue;\\n }\\n if (check(s, i)) {\\n return i;\\n }\\n }\\n return n;\\n }\\n\\n public boolean check(String s, int m) {\\n int[] count0 = new int[26];\\n for (int j = 0; j < s.length(); j += m) {\\n int[] count1 = new int[26];\\n for (int k = j; k < j + m; k++) {\\n count1[s.charAt(k) - \'a\']++;\\n }\\n if (j > 0 && !Arrays.equals(count0, count1)) {\\n return false;\\n }\\n count0 = count1;\\n }\\n return true;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinAnagramLength(string s) {\\n int n = s.Length;\\n for (int i = 1; i < n; i++) {\\n if (n % i != 0) {\\n continue;\\n }\\n if (check(s, i)) {\\n return i;\\n }\\n }\\n return n;\\n }\\n\\n public bool check(string s, int m) {\\n int[] count0 = new int[26];\\n for (int j = 0; j < s.Length; j += m) {\\n int[] count1 = new int[26];\\n for (int k = j; k < j + m; k++) {\\n count1[s[k] - \'a\']++;\\n }\\n if (j > 0 && !count0.SequenceEqual(count1)) {\\n return false;\\n }\\n count0 = count1;\\n }\\n return true;\\n }\\n}\\n
\\n###JavaScript
\\nvar check = function(s, m) {\\n let count0 = new Array(26).fill(0);\\n for (let j = 0; j < s.length; j += m) {\\n let count1 = new Array(26).fill(0);\\n for (let k = j; k < j + m; k++) {\\n count1[s.charCodeAt(k) - \'a\'.charCodeAt(0)]++;\\n }\\n if (j > 0 && !count0.every((val, index) => val === count1[index])) {\\n return false;\\n }\\n count0 = count1.slice();\\n }\\n return true;\\n}\\n\\nvar minAnagramLength = function(s) {\\n let n = s.length;\\n for (let i = 1; i < n; i++) {\\n if (n % i !== 0) {\\n continue;\\n }\\n if (check(s, i)) {\\n return i;\\n }\\n }\\n return n;\\n}\\n
\\n###TypeScript
\\nfunction minAnagramLength(s: string): number {\\n let n = s.length;\\n for (let i = 1; i < n; i++) {\\n if (n % i !== 0) {\\n continue;\\n }\\n if (check(s, i)) {\\n return i;\\n }\\n }\\n return n;\\n};\\n\\nfunction check(s: string, m: number): boolean {\\n let count0 = new Array(26).fill(0);\\n for (let j = 0; j < s.length; j += m) {\\n let count1 = new Array(26).fill(0);\\n for (let k = j; k < j + m; k++) {\\n count1[s.charCodeAt(k) - \'a\'.charCodeAt(0)]++;\\n }\\n if (j > 0 && !count0.every((val, index) => val === count1[index])) {\\n return false;\\n }\\n count0 = count1.slice();\\n }\\n return true;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_anagram_length(s: String) -> i32 {\\n let n = s.len();\\n let check = |m: usize| -> bool {\\n let mut count0 = vec![0; 26];\\n for j in (0..n).step_by(m) {\\n let mut count1 = vec![0; 26];\\n for k in j..j + m {\\n count1[s.as_bytes()[k] as usize - b\'a\' as usize] += 1;\\n }\\n if j > 0 && count0 != count1 {\\n return false;\\n }\\n count0 = count1;\\n }\\n true\\n };\\n for i in 1..n {\\n if n % i != 0 {\\n continue;\\n }\\n if check(i) {\\n return i as i32;\\n }\\n }\\n n as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times T)$,其中 $n$ 是字符串 $s$ 的长度,$T$ 是 $n$ 的因数数目。
\\n空间复杂度:$O(|\\\\Sigma|)$,其中 $|\\\\Sigma| = 26$ 表示字符集的大小。
\\n思路
\\n按照题意,从下标为 $1$ 的元素开始遍历,如果前一个下标的元素严格大于 $\\\\textit{threshold}$,则将这一个元素的下标加入结果数组中。遍历完成后,返回结果数组。
\\n代码
\\n###Python
\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i for i in range(1, len(height)) if height[i - 1] > threshold]\\n
\\n###Java
\\nclass Solution {\\n public List<Integer> stableMountains(int[] height, int threshold) {\\n List<Integer> result = new ArrayList<>();\\n for (int i = 1; i < height.length; i++) {\\n if (height[i - 1] > threshold) {\\n result.add(i);\\n }\\n }\\n return result;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public IList<int> StableMountains(int[] height, int threshold) {\\n List<int> result = new List<int>();\\n for (int i = 1; i < height.Length; i++) {\\n if (height[i - 1] > threshold) {\\n result.Add(i);\\n }\\n }\\n return result;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> stableMountains(vector<int>& height, int threshold) {\\n vector<int> result;\\n for (int i = 1; i < height.size(); i++) {\\n if (height[i - 1] > threshold) {\\n result.push_back(i);\\n }\\n }\\n return result;\\n }\\n};\\n
\\n###Go
\\nfunc stableMountains(height []int, threshold int) []int {\\n var result []int\\n for i := 1; i < len(height); i++ {\\n if height[i - 1] > threshold {\\n result = append(result, i)\\n }\\n }\\n return result\\n}\\n
\\n###C
\\nint* stableMountains(int* height, int heightSize, int threshold, int* returnSize) {\\n int* result = (int*)malloc((heightSize - 1) * sizeof(int));\\n *returnSize = 0;\\n for (int i = 1; i < heightSize; i++) {\\n if (height[i - 1] > threshold) {\\n result[*returnSize] = i;\\n (*returnSize)++;\\n }\\n }\\n return result;\\n}\\n
\\n###JavaScript
\\nvar stableMountains = function(height, threshold) {\\n const result = [];\\n for (let i = 1; i < height.length; i++) {\\n if (height[i - 1] > threshold) {\\n result.push(i);\\n }\\n }\\n return result;\\n};\\n
\\n###TypeScript
\\nfunction stableMountains(height: number[], threshold: number): number[] {\\n const result: number[] = [];\\n for (let i = 1; i < height.length; i++) {\\n if (height[i - 1] > threshold) {\\n result.push(i);\\n }\\n }\\n return result;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn stable_mountains(height: Vec<i32>, threshold: i32) -> Vec<i32> {\\n let mut result = Vec::new();\\n for i in 1..height.len() {\\n if height[i - 1] > threshold {\\n result.push(i as i32);\\n }\\n }\\n result\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$。
\\n空间复杂度:$O(1)$,结果不计入空间复杂度。
\\n根据题意,对 $\\\\textit{nums}$ 进行模拟操作,每次操作先找到 $\\\\textit{nums}$ 的最前面的最小值,然后将该元素替换成乘以 $\\\\textit{multiplier}$ 后的值,最后返回 $k$ 次模拟操作后的数组 $\\\\textit{nums}$。
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n while (k--) {\\n auto iter = min_element(nums.begin(), nums.end());\\n *iter *= multiplier;\\n }\\n return nums;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n for _ in range(k):\\n i = nums.index(min(nums))\\n nums[i] *= multiplier\\n return nums\\n
\\n###C
\\nint* getFinalState(int* nums, int numsSize, int k, int multiplier, int* returnSize) {\\n int *ret = (int *)malloc(sizeof(int) * numsSize);\\n memcpy(ret, nums, sizeof(int) * numsSize);\\n while (k--) {\\n int m = 0;\\n for (int j = 0; j < numsSize; j++) {\\n if (ret[j] < ret[m]) {\\n m = j;\\n }\\n }\\n ret[m] *= multiplier;\\n }\\n *returnSize = numsSize;\\n return ret;\\n}\\n
\\n###Go
\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\n for i := 0; i < k; i++ {\\n m := 0\\n for j := range nums {\\n if nums[j] < nums[m] {\\n m = j\\n }\\n }\\n nums[m] *= multiplier\\n }\\n return nums\\n}\\n
\\n###Java
\\nclass Solution {\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n for (int i = 0; i < k; i++) {\\n int m = 0;\\n for (int j = 0; j < nums.length; j++) {\\n if (nums[j] < nums[m]) {\\n m = j;\\n }\\n }\\n nums[m] *= multiplier;\\n }\\n return nums;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] GetFinalState(int[] nums, int k, int multiplier) {\\n for (int i = 0; i < k; i++) {\\n int m = 0;\\n for (int j = 0; j < nums.Length; j++) {\\n if (nums[j] < nums[m]) {\\n m = j;\\n }\\n }\\n nums[m] *= multiplier;\\n }\\n return nums;\\n }\\n}\\n
\\n###JavaScript
\\nvar getFinalState = function(nums, k, multiplier) {\\n for (let i = 0; i < k; i++) {\\n let m = 0;\\n for (let j = 0; j < nums.length; j++) {\\n if (nums[j] < nums[m]) {\\n m = j;\\n }\\n }\\n nums[m] *= multiplier;\\n }\\n return nums;\\n};\\n
\\n###TypeScript
\\nfunction getFinalState(nums: number[], k: number, multiplier: number): number[] {\\n for (let i = 0; i < k; i++) {\\n let m = 0;\\n for (let j = 0; j < nums.length; j++) {\\n if (nums[j] < nums[m]) {\\n m = j;\\n }\\n }\\n nums[m] *= multiplier;\\n }\\n return nums;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn get_final_state(mut nums: Vec<i32>, k: i32, multiplier: i32) -> Vec<i32> {\\n for _ in 0..k {\\n let mut m = 0;\\n for j in 1..nums.len() {\\n if nums[j] < nums[m] {\\n m = j;\\n }\\n }\\n nums[m] *= multiplier;\\n }\\n nums\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(nk)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$k$ 是操作次数。
\\n空间复杂度:$O(1)$。返回值不计入空间复杂度。
\\n假设初始时,数组 $\\\\textit{nums}$ 的最大值为 $\\\\textit{mx}$。首先将数组的所有元素及对应的下标都放到优先队列(最小堆,排序规则按照元素值、下标升序)中,然后不断从优先队列中取出元素,执行乘 $\\\\textit{multiplier}$ 操作后,再放回优先队列中。那么有以下两种情况:
\\n情况一
\\n每次取出的元素 $x$,都有 $x \\\\times \\\\textit{multiplier} \\\\lt \\\\textit{mx}$,直到 $k = 0$ 成立。对于这类情况,数组后续无需执行任何操作,我们直接返回操作后的数组。
\\n情况二
\\n如果对于堆顶元素 $x$,有 $x \\\\ge \\\\textit{mx}$,我们直接终止以上操作。那么对于优先队列中的任一元素值 $y$,都有 $\\\\textit{mx} \\\\times \\\\textit{multiplier} \\\\gt y \\\\ge \\\\textit{mx}$(假设 $\\\\textit{mx} \\\\times \\\\textit{multiplier} \\\\le y$,那么执行操作前 $y$ 对应的元素值为 $\\\\frac{y}{\\\\textit{multiplier}} \\\\ge \\\\textit{mx}$,矛盾)。
\\n此时按照规则,优先队列中值最小同时下标最小的堆顶元素是下一次操作要选择的元素,我们对它执行操作后,后续需要对其余所有元素都恰好执行一次操作后,才会再次对它执行操作。
\\n这里简单论证一下,当我们对堆顶元素执行操作后,它的值变成 $\\\\textit{multiplier} \\\\times \\\\textit{mx}$,是大于任一其余元素的,所以必须对其余所有元素执行至少一次操作后,才会再次对它执行操作;然后对于任一其余元素 $y$,执行操作后,有 $y \\\\times \\\\textit{multiplier} \\\\ge \\\\textit{multiplier} \\\\times \\\\textit{mx}$,当且仅当 $y$ 的下标大于堆顶元素的下标时,等号成立,所以元素 $y$ 最多执行一次操作,得证。
\\n根据以上推导,后续的操作可以先批量对数组整体执行 $\\\\lfloor \\\\frac{k}{n} \\\\rfloor$ 次操作,然后再按照规则单个执行 $k \\\\bmod n$ 次操作,具体实现为快速幂算法 + 排序。
\\n###C++
\\nclass Solution {\\npublic:\\n long long quickMul(long long x, long long y, long long m) {\\n long long res = 1;\\n while (y) {\\n if (y & 1) {\\n res = (res * x) % m;\\n }\\n y >>= 1;\\n x = (x * x) % m;\\n }\\n return res;\\n }\\n\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n if (multiplier == 1) {\\n return nums;\\n }\\n long long n = nums.size(), m = 1e9 + 7;\\n long long mx = *max_element(nums.begin(), nums.end());\\n vector<pair<long long, int>> v(n);\\n for (int i = 0; i < n; i++) {\\n v[i] = {nums[i], i};\\n }\\n make_heap(v.begin(), v.end(), greater<>());\\n for (; v[0].first < mx && k; k--) {\\n pop_heap(v.begin(), v.end(), greater<>());\\n v.back().first *= multiplier;\\n push_heap(v.begin(), v.end(), greater<>());\\n }\\n sort(v.begin(), v.end());\\n for (int i = 0; i < n; i++) {\\n int t = k / n + (i < k % n);\\n nums[v[i].second] = ((v[i].first % m) * quickMul(multiplier, t, m)) % m;\\n }\\n return nums;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n if multiplier == 1:\\n return nums\\n n, m = len(nums), int(1e9 + 7)\\n mx = max(nums)\\n v = [(num, i) for i, num in enumerate(nums)]\\n heapify(v)\\n while v[0][0] < mx and k:\\n k -= 1\\n num, i = heappop(v)\\n heappush(v, (num * multiplier, i))\\n v.sort()\\n for i in range(n):\\n t = k // n + (i < k % n)\\n nums[v[i][1]] = ((v[i][0] % m) * pow(multiplier, t, m)) % m\\n return nums\\n
\\n###Go
\\nfunc quickMul(x, y, m int64) int64 {\\n res := int64(1)\\n for y > 0 {\\n if (y & 1) == 1 {\\n res = (res * x) % m\\n }\\n y >>= 1\\n x = (x * x) % m\\n }\\n return res\\n}\\n\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\n if multiplier == 1 {\\n return nums\\n }\\n n, m := len(nums), int64(1e9+7)\\n mx := 0\\n var v minHeap\\n for i, num := range nums {\\n mx = max(mx, num)\\n v = append(v, pair{int64(num), i})\\n }\\n heap.Init(&v)\\n for ; v[0].first < int64(mx) && k > 0; k-- {\\n x := heap.Pop(&v).(pair)\\n x.first *= int64(multiplier)\\n heap.Push(&v, x)\\n }\\n sort.Slice(v, func(i, j int) bool {\\n return v[i].first < v[j].first || v[i].first == v[j].first && v[i].second < v[j].second\\n })\\n for i := 0; i < n; i++ {\\n t := k / n\\n if i < k % n {\\n t++\\n }\\n nums[v[i].second] = int((v[i].first % m) * quickMul(int64(multiplier), int64(t), m) % m)\\n }\\n return nums\\n}\\n\\ntype pair struct {\\n first int64\\n second int\\n}\\n\\ntype minHeap []pair\\n\\nfunc (h minHeap) Len() int{\\n return len(h)\\n}\\nfunc (h minHeap) Less(i, j int) bool {\\n return h[i].first < h[j].first || h[i].first == h[j].first && h[i].second < h[j].second\\n}\\nfunc (h minHeap) Swap(i, j int) {\\n h[i], h[j] = h[j], h[i]\\n}\\n\\nfunc (h *minHeap) Push(x interface{}) {\\n *h = append(*h, x.(pair))\\n}\\n\\nfunc (h *minHeap) Pop() interface{} {\\n n := len(*h)\\n res := (*h)[n - 1]\\n *h = (*h)[0 : n - 1]\\n return res\\n}\\n
\\n###Java
\\nclass Solution {\\n private long quickMul(long x, long y, long m) {\\n long res = 1;\\n while (y > 0) {\\n if ((y & 1) == 1) {\\n res = (res * x) % m;\\n }\\n y >>= 1;\\n x = (x * x) % m;\\n }\\n return res;\\n }\\n\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) {\\n return nums;\\n }\\n int n = nums.length, mx = 0;\\n long m = 1000000007L;\\n PriorityQueue<long[]> v = new PriorityQueue<>((x, y) -> {\\n if (x[0] != y[0]) {\\n return Long.compare(x[0], y[0]);\\n } else {\\n return Long.compare(x[1], y[1]);\\n }\\n });\\n for (int i = 0; i < n; i++) {\\n mx = Math.max(mx, nums[i]);\\n v.offer(new long[]{nums[i], i});\\n }\\n for (; v.peek()[0] < mx && k > 0; k--) {\\n long[] x = v.poll();\\n x[0] *= multiplier;\\n v.offer(x);\\n }\\n for (int i = 0; i < n; i++) {\\n long[] x = v.poll();\\n int t = k / n + (i < k % n ? 1 : 0);\\n nums[(int)x[1]] = (int)((x[0] % m) * quickMul(multiplier, t, m) % m);\\n }\\n return nums;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n private long QuickMul(long x, long y, long m) {\\n long res = 1;\\n while (y > 0) {\\n if ((y & 1) == 1) {\\n res = (res * x) % m;\\n }\\n y >>= 1;\\n x = (x * x) % m;\\n }\\n return res;\\n }\\n\\n public int[] GetFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) {\\n return nums;\\n }\\n int n = nums.Length, mx = 0;\\n long m = 1000000007L;\\n var pq = new PriorityQueue<long[], long[]>(Comparer<long[]>.Create((x, y) => {\\n if (x[0] != y[0]) {\\n return x[0].CompareTo(y[0]);\\n } else {\\n return x[1].CompareTo(y[1]);\\n }\\n }));\\n for (int i = 0; i < n; i++) {\\n mx = Math.Max(mx, nums[i]);\\n pq.Enqueue(new long[]{nums[i], i}, new long[]{nums[i], i});\\n }\\n for (; pq.Peek()[0] < mx && k > 0; k--) {\\n var x = pq.Dequeue();\\n x[0] *= multiplier;\\n pq.Enqueue(x, x);\\n }\\n for (int i = 0; i < n; i++) {\\n var x = pq.Dequeue();\\n int t = k / n + (i < k % n ? 1 : 0);\\n nums[(int)x[1]] = (int)((x[0] % m) * QuickMul(multiplier, t, m) % m);\\n }\\n return nums;\\n }\\n}\\n
\\n###JavaScript
\\nvar getFinalState = function(nums, k, multiplier) {\\n const quickMul = (x, y, m) => {\\n let res = BigInt(1);\\n while (y > 0n) {\\n if (y % 2n === 1n) {\\n res = (res * x) % m;\\n }\\n y >>= 1n;\\n x = (x * x) % m;\\n }\\n return res;\\n };\\n\\n if (multiplier === 1) {\\n return nums;\\n }\\n const n = nums.length;\\n const m = 1000000007;\\n let mx = 0;\\n const pq = new MinPriorityQueue({\\n priority: (a) => a.first * 100000 + a.second\\n });\\n for (let i = 0; i < n; i++) {\\n mx = Math.max(mx, nums[i]);\\n pq.enqueue({\\n first: nums[i],\\n second: i\\n });\\n }\\n for (; pq.front().element.first < mx && k > 0; k--) {\\n let x = pq.dequeue().element;\\n x.first *= multiplier;\\n pq.enqueue(x);\\n }\\n for (let i = 0; i < n; i++) {\\n let x = pq.dequeue().element;\\n const t = Math.floor(k / n) + (i < k % n ? 1 : 0);\\n nums[x.second] = Number(((BigInt(x.first) % BigInt(m)) * quickMul(BigInt(multiplier), BigInt(t), BigInt(m))) % BigInt(m));\\n }\\n return nums;\\n};\\n
\\n###TypeScript
\\nfunction getFinalState(nums: number[], k: number, multiplier: number): number[] {\\n const quickMul = (x: bigint, y: bigint, m: bigint): bigint => {\\n let res = 1n;\\n while (y > 0n) {\\n if (y % 2n === 1n) {\\n res = (res * x) % m;\\n }\\n y >>= 1n;\\n x = (x * x) % m;\\n }\\n return res;\\n };\\n\\n if (multiplier === 1) {\\n return nums;\\n }\\n const n = nums.length;\\n const m = 1000000007;\\n let mx = 0;\\n const pq = new MinPriorityQueue({\\n priority: (a) => a.first * 100000 + a.second\\n });\\n for (let i = 0; i < n; i++) {\\n mx = Math.max(mx, nums[i]);\\n pq.enqueue({\\n first: nums[i],\\n second: i\\n });\\n }\\n for (; pq.front().element.first < mx && k > 0; k--) {\\n let x = pq.dequeue().element;\\n x.first *= multiplier;\\n pq.enqueue(x);\\n }\\n for (let i = 0; i < n; i++) {\\n let x = pq.dequeue().element;\\n const t = Math.floor(k / n) + (i < k % n ? 1 : 0);\\n nums[x.second] = Number(((BigInt(x.first) % BigInt(m)) * quickMul(BigInt(multiplier), BigInt(t), BigInt(m))) % BigInt(m));\\n }\\n return nums;\\n}\\n
\\n###Rust
\\nuse std::cmp::Reverse;\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n fn quick_mul(x: i64, y: i64, m: i64) -> i64 {\\n let mut res = 1;\\n let mut x = x;\\n let mut y = y;\\n while y > 0 {\\n if y % 2 == 1 {\\n res = (res * x) % m;\\n }\\n x = (x * x) % m;\\n y /= 2;\\n }\\n res\\n }\\n\\n pub fn get_final_state(nums: Vec<i32>, k: i32, multiplier: i32) -> Vec<i32> {\\n if multiplier == 1 {\\n return nums;\\n }\\n let n = nums.len();\\n let m = 1_000_000_007;\\n let mx = *nums.iter().max().unwrap() as i64;\\n let mut v: BinaryHeap<Reverse<(i64, usize)>> = nums.into_iter()\\n .enumerate()\\n .map(|(i, num)| Reverse((num as i64, i)))\\n .collect();\\n\\n let mut k = k as i64;\\n while let Some(Reverse((val, _))) = v.peek() {\\n if *val >= mx || k == 0 {\\n break;\\n }\\n let Reverse((mut min_val, idx)) = v.pop().unwrap();\\n min_val *= multiplier as i64;\\n v.push(Reverse((min_val, idx)));\\n k -= 1;\\n }\\n\\n let mut result = vec![0; n];\\n let mut vec_v = v.into_vec();\\n vec_v.sort_unstable_by_key(|Reverse((val, idx))| (*val, *idx));\\n\\n for (i, Reverse((val, idx))) in vec_v.iter().enumerate() {\\n let t = k / n as i64 + if (i as i64) < k % n as i64 { 1 } else { 0 };\\n result[*idx] = ((val % m) * Solution::quick_mul(multiplier as i64, t, m) % m) as i32;\\n }\\n result\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times (\\\\log n \\\\log \\\\textit{mx} + \\\\log \\\\frac{k}{n}))$。其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$k$ 是操作次数,$\\\\textit{mx}$ 是数组 $\\\\textit{nums}$ 的最大值。优先队列的出队和入队每个元素最多执行的次数为 $O(\\\\log \\\\textit{mx})$,所以时间复杂度为 $O(n \\\\log n \\\\log \\\\textit{mx})$;批量执行时,每个元素执行一次的时间复杂度为 $O(\\\\log \\\\frac{k}{n})$,所以时间复杂度为 $O(n \\\\log \\\\frac{k}{n})$。
\\n空间复杂度:$O(n)$。主要为优先队列需要的空间。
\\n把 $\\\\textit{coordinates}$ 简记为 $s$。
\\n根据题目中的图片,如果 $s[0]$ 和 $s[1]$ 的 ASCII 值的奇偶性不同,那么格子是白格,否则是黑格。
\\n###py
\\nclass Solution:\\n def squareIsWhite(self, s: str) -> bool:\\n return ord(s[0]) % 2 != ord(s[1]) % 2\\n
\\n###java
\\nclass Solution {\\n public boolean squareIsWhite(String s) {\\n return s.charAt(0) % 2 != s.charAt(1) % 2;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool squareIsWhite(string s) {\\n return s[0] % 2 != s[1] % 2;\\n }\\n};\\n
\\n###c
\\nbool squareIsWhite(char* s) {\\n return s[0] % 2 != s[1] % 2;\\n}\\n
\\n###go
\\nfunc squareIsWhite(s string) bool {\\n return s[0]%2 != s[1]%2\\n}\\n
\\n###js
\\nvar squareIsWhite = function(s) {\\n return s.charCodeAt(0) % 2 !== s.charCodeAt(1) % 2;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn square_is_white(coordinates: String) -> bool {\\n let s = coordinates.as_bytes();\\n s[0] % 2 != s[1] % 2\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"把 $\\\\textit{coordinates}$ 简记为 $s$。 根据题目中的图片,如果 $s[0]$ 和 $s[1]$ 的 ASCII 值的奇偶性不同,那么格子是白格,否则是黑格。\\n\\n###py\\n\\nclass Solution:\\n def squareIsWhite(self, s: str) -> bool:\\n return ord(s[0]) % 2 != ord(s[1]) % 2\\n\\n\\n###java\\n\\nclass Solution {\\n public boolean squareIsWhite(String s) {…","guid":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/jian-dan-ti-jian-dan-zuo-pythonjavaccgoj-jwdv","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-09T00:26:42.741Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-判断国际象棋棋盘中一个格子的颜色🟢","url":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square/","content":"给你一个坐标 coordinates
,它是一个字符串,表示国际象棋棋盘中一个格子的坐标。下图是国际象棋棋盘示意图。
如果所给格子的颜色是白色,请你返回 true
,如果是黑色,请返回 false
。
给定坐标一定代表国际象棋棋盘上一个存在的格子。坐标第一个字符是字母,第二个字符是数字。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:coordinates = \\"a1\\"\\n输出:false\\n解释:如上图棋盘所示,\\"a1\\" 坐标的格子是黑色的,所以返回 false 。\\n\\n\\n
示例 2:
\\n\\n输入:coordinates = \\"h3\\"\\n输出:true\\n解释:如上图棋盘所示,\\"h3\\" 坐标的格子是白色的,所以返回 true 。\\n\\n\\n
示例 3:
\\n\\n输入:coordinates = \\"c7\\"\\n输出:false\\n\\n\\n
\\n\\n
提示:
\\n\\ncoordinates.length == 2
\'a\' <= coordinates[0] <= \'h\'
\'1\' <= coordinates[1] <= \'8\'
\\n\\nProblem: 3375. 使数组的值全部为 K 的最少操作次数
\\n
[TOC]
\\n按题意去重计数计算
\\n执行用时分布68ms击败100.00%;消耗内存分布16.89MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n if min(nums) < k: return -1\\n return len(set(nums + [k])) - 1\\n
\\n","description":"Problem: 3375. 使数组的值全部为 K 的最少操作次数 [TOC]\\n\\n按题意去重计数计算\\n\\n执行用时分布68ms击败100.00%;消耗内存分布16.89MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n if min(nums) < k: return -1\\n return len(set(nums + [k])) - 1","guid":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/qu-zhong-ji-suan-by-admiring-meninskyuli-wiqn","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-08T02:46:38.643Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"本质是计算不同元素个数(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/ben-zhi-shi-ji-suan-bu-tong-yuan-su-ge-s-o0uc","content":"题目说「选择一个整数 $h$」,哪些 $h$ 是可以选的?
\\n根据「合法」的定义,$h$ 不能低于 $\\\\textit{nums}$ 的次大值。比如 $\\\\textit{nums}=[5,2,5,4,5]$,$h$ 不能小于次大值 $4$,否则大于 $h$ 的数不都相等。
\\n所以操作只能改变大于次大值的数,也就是最大值。
\\n要让所有数都变成 $k$,前提条件是所有数都变成一样的。那么,能变成哪些数呢?
\\n仍然以 $\\\\textit{nums}=[5,2,5,4,5]$ 为例。选择 $h=4$,可以把最大值 $5$ 改成次大值 $4$。修改后 $\\\\textit{nums}=[4,2,4,4,4]$,有更多的数都相同。并且修改后,原来的次大值 $4$ 就变成最大值了。下一次操作,我们就可以选择更小的 $h$,把更多的数都变成一样的。
\\n选择 $h=2$,可以把最大值 $4$ 改成次大值 $2$。修改后 $\\\\textit{nums}=[2,2,2,2,2]$,所有数都相同。
\\n如果想继续改成比 $2$ 小的数,比如 $0$,选择 $h=0$ 即可。
\\n所以,$\\\\textit{nums}$ 中的数可以都变成任意 $\\\\le \\\\min(\\\\textit{nums})$ 的数。
\\n为了最小化操作次数,每次选 $h$ 为次大值是最优的。贪心地想,能一步到达次大值,没必要分好几步。
\\n分类讨论:
\\n###py
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n mn = min(nums)\\n if k > mn:\\n return -1\\n return len(set(nums)) - (k == mn)\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int min = Arrays.stream(nums).min().getAsInt();\\n if (k > min) {\\n return -1;\\n }\\n int distinctCount = (int) Arrays.stream(nums).distinct().count();\\n return distinctCount - (k == min ? 1 : 0);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n int mn = ranges::min(nums);\\n if (k > mn) {\\n return -1;\\n }\\n unordered_set<int> st(nums.begin(), nums.end());\\n return st.size() - (k == mn);\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int, k int) int {\\n mn := slices.Min(nums)\\n if k > mn {\\n return -1\\n }\\n\\n set := map[int]struct{}{}\\n for _, x := range nums {\\n set[x] = struct{}{}\\n }\\n if k == mn {\\n return len(set) - 1\\n }\\n return len(set)\\n}\\n
\\n###js
\\nvar minOperations = function(nums, k) {\\n const min = Math.min(...nums);\\n if (k > min) {\\n return -1;\\n }\\n return new Set(nums).size - (k === min ? 1 : 0);\\n};\\n
\\n###rust
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {\\n let min = *nums.iter().min().unwrap();\\n if k > min {\\n return -1;\\n }\\n let set = nums.into_iter().collect::<HashSet<_>>();\\n set.len() as i32 - if k == min { 1 } else { 0 }\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思考框架 理解操作在做什么。\\n把 $\\\\textit{nums}$ 中的数都变成一样的,能变成哪些数?\\n如何最小化操作次数?\\n操作在做什么\\n\\n题目说「选择一个整数 $h$」,哪些 $h$ 是可以选的?\\n\\n根据「合法」的定义,$h$ 不能低于 $\\\\textit{nums}$ 的次大值。比如 $\\\\textit{nums}=[5,2,5,4,5]$,$h$ 不能小于次大值 $4$,否则大于 $h$ 的数不都相等。\\n\\n所以操作只能改变大于次大值的数,也就是最大值。\\n\\n能变成哪些数\\n\\n要让所有数都变成 $k$,前提条件是所有数都变成一样的。那么,能变成哪些数呢?\\n\\n仍然以…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/ben-zhi-shi-ji-suan-bu-tong-yuan-su-ge-s-o0uc","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-08T01:11:30.647Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心","url":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/tan-xin-by-tsreaper-ggv1","content":"首先,由于操作只能把数变小,而不能把数变大,因此如果数组里有小于 $k$ 的数则无解。
\\n一次合法的操作,其实就是把数组里最大的数变成任意的更小的数。为了减少操作总数,我们需要利用每次操作尽量让数组里的元素种数变少,这样一次操作才能处理掉更多的数。也就是说,最优的操作,其实就是每次把数组里最大的数变成次大的数,直到所有的数都变成 $k$。
\\n那么具体需要操作几次呢?每次把最大的数变成次大的数,其实就是让数组里元素的种数减少了 $1$。因此我们至少操作元素种数减一次。如果最小的那种元素不是 $k$,那么还需要额外的一次操作把所有数都变成 $k$。
\\n用哈希表计算元素种数,复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n // 如果数组里有小于 k 的数则无解\\n int mn = 1e9;\\n for (int x : nums) mn = min(mn, x);\\n if (mn < k) return -1;\\n\\n unordered_set<int> st;\\n for (int x : nums) st.insert(x);\\n // 答案就是元素种数减一,此时最小的元素必须是 k,所以我们往哈希表里加入一个 k\\n st.insert(k);\\n return st.size() - 1;\\n }\\n};\\n
\\n","description":"解法:贪心 首先,由于操作只能把数变小,而不能把数变大,因此如果数组里有小于 $k$ 的数则无解。\\n\\n一次合法的操作,其实就是把数组里最大的数变成任意的更小的数。为了减少操作总数,我们需要利用每次操作尽量让数组里的元素种数变少,这样一次操作才能处理掉更多的数。也就是说,最优的操作,其实就是每次把数组里最大的数变成次大的数,直到所有的数都变成 $k$。\\n\\n那么具体需要操作几次呢?每次把最大的数变成次大的数,其实就是让数组里元素的种数减少了 $1$。因此我们至少操作元素种数减一次。如果最小的那种元素不是 $k$,那么还需要额外的一次操作把所有数都变成 $k$。…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/tan-xin-by-tsreaper-ggv1","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-07T16:44:46.676Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python 模拟法","url":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/python-mo-ni-fa-by-nrib8zib57-rj9f","content":"python 模拟法","description":"python 模拟法","guid":"https://leetcode.cn/problems/minimum-operations-to-make-array-values-equal-to-k//solution/python-mo-ni-fa-by-nrib8zib57-rj9f","author":"nriB8ZIB57","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-07T16:15:34.112Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-变为棋盘🔴","url":"https://leetcode.cn/problems/transform-to-chessboard/","content":"一个 n x n
的二维网络 board
仅由 0
和 1
组成 。每次移动,你能交换任意两列或是两行的位置。
返回 将这个矩阵变为 “棋盘” 所需的最小移动次数 。如果不存在可行的变换,输出 -1
。
“棋盘” 是指任意一格的上下左右四个方向的值均与本身不同的矩阵。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: board = [[0,1,1,0],[0,1,1,0],[1,0,0,1],[1,0,0,1]]\\n输出: 2\\n解释:一种可行的变换方式如下,从左到右:\\n第一次移动交换了第一列和第二列。\\n第二次移动交换了第二行和第三行。\\n\\n\\n
示例 2:
\\n\\n输入: board = [[0, 1], [1, 0]]\\n输出: 0\\n解释: 注意左上角的格值为0时也是合法的棋盘,也是合法的棋盘.\\n\\n\\n
示例 3:
\\n\\n输入: board = [[1, 0], [1, 0]]\\n输出: -1\\n解释: 任意的变换都不能使这个输入变为合法的棋盘。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == board.length
n == board[i].length
2 <= n <= 30
board[i][j]
将只包含 0
或 1
在一个 n x n
的国际象棋棋盘上,一个骑士从单元格 (row, column)
开始,并尝试进行 k
次移动。行和列是 从 0 开始 的,所以左上单元格是 (0,0)
,右下单元格是 (n - 1, n - 1)
。
象棋骑士有8种可能的走法,如下图所示。每次移动在基本方向上是两个单元格,然后在正交方向上是一个单元格。
\\n\\n每次骑士要移动时,它都会随机从8种可能的移动中选择一种(即使棋子会离开棋盘),然后移动到那里。
\\n\\n骑士继续移动,直到它走了 k
步或离开了棋盘。
返回 骑士在棋盘停止移动后仍留在棋盘上的概率 。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: n = 3, k = 2, row = 0, column = 0\\n输出: 0.0625\\n解释: 有两步(到(1,2),(2,1))可以让骑士留在棋盘上。\\n在每一个位置上,也有两种移动可以让骑士留在棋盘上。\\n骑士留在棋盘上的总概率是0.0625。\\n\\n\\n
示例 2:
\\n\\n输入: n = 1, k = 0, row = 0, column = 0\\n输出: 1.00000\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 25
0 <= k <= 100
0 <= row, column <= n - 1
给定一个 8 x 8
的棋盘,只有一个 白色的车,用字符 \'R\'
表示。棋盘上还可能存在白色的象 \'B\'
以及黑色的卒 \'p\'
。空方块用字符 \'.\'
表示。
车可以按水平或竖直方向(上,下,左,右)移动任意个方格直到它遇到另一个棋子或棋盘的边界。如果它能够在一次移动中移动到棋子的方格,则能够 吃掉 棋子。
\\n\\n注意:车不能穿过其它棋子,比如象和卒。这意味着如果有其它棋子挡住了路径,车就不能够吃掉棋子。
\\n\\n返回白车将能 吃掉 的 卒的数量。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:[[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"p\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"R\\",\\".\\",\\".\\",\\".\\",\\"p\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"p\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"]]\\n输出:3\\n解释:\\n在本例中,车能够吃掉所有的卒。\\n\\n\\n
示例 2:
\\n\\n输入:[[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\"p\\",\\"p\\",\\"p\\",\\"p\\",\\"p\\",\\".\\",\\".\\"],[\\".\\",\\"p\\",\\"p\\",\\"B\\",\\"p\\",\\"p\\",\\".\\",\\".\\"],[\\".\\",\\"p\\",\\"B\\",\\"R\\",\\"B\\",\\"p\\",\\".\\",\\".\\"],[\\".\\",\\"p\\",\\"p\\",\\"B\\",\\"p\\",\\"p\\",\\".\\",\\".\\"],[\\".\\",\\"p\\",\\"p\\",\\"p\\",\\"p\\",\\"p\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"]]\\n输出:0\\n解释:\\n象阻止了车吃掉任何卒。\\n\\n\\n
示例 3:
\\n\\n输入:[[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"p\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"p\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\"p\\",\\"p\\",\\".\\",\\"R\\",\\".\\",\\"p\\",\\"B\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"B\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\"p\\",\\".\\",\\".\\",\\".\\",\\".\\"],[\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\",\\".\\"]]\\n输出:3\\n解释: \\n车可以吃掉位置 b5,d6 和 f5 的卒。\\n\\n\\n
\\n\\n
提示:
\\n\\nboard.length == 8
board[i].length == 8
board[i][j]
可以是 \'R\'
,\'.\'
,\'B\'
或 \'p\'
board[i][j] == \'R\'
核心思路:对于两个苹果 $A$ 和 $B$,设 $A$ 更早腐烂,那么应该先吃 $A$。如果先吃 $B$,可能下一天 $A$ 就烂了。
\\n用最小堆维护苹果的腐烂日期和个数,模拟一天吃一个苹果。
\\n注意第 $n$ 天之后还可以继续吃苹果,所以要一直模拟到堆为空为止。
\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n ans = i = 0\\n h = []\\n while i < len(apples) or h:\\n while h and h[0][0] == i: # 已腐烂\\n heappop(h)\\n if i < len(apples) and apples[i]:\\n heappush(h, [i + days[i], apples[i]])\\n if h:\\n ans += 1\\n h[0][1] -= 1 # 吃一个最早腐烂的苹果\\n if h[0][1] == 0:\\n heappop(h)\\n i += 1\\n return ans\\n
\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n int ans = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n for (int i = 0; i < apples.length || !pq.isEmpty(); i++) {\\n while (!pq.isEmpty() && pq.peek()[0] == i) { // 已腐烂\\n pq.poll();\\n }\\n if (i < apples.length && apples[i] > 0) {\\n pq.offer(new int[]{i + days[i], apples[i]});\\n }\\n if (!pq.isEmpty()) {\\n // 吃一个最早腐烂的苹果\\n ans++;\\n if (--pq.peek()[1] == 0) {\\n pq.poll();\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int eatenApples(vector<int>& apples, vector<int>& days) {\\n int ans = 0;\\n priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;\\n for (int i = 0; i < apples.size() || !pq.empty(); i++) {\\n while (!pq.empty() && pq.top().first == i) { // 已腐烂\\n pq.pop();\\n }\\n if (i < apples.size() && apples[i]) {\\n pq.emplace(i + days[i], apples[i]);\\n }\\n if (!pq.empty()) {\\n // 吃一个最早腐烂的苹果\\n ans++;\\n auto [rotten_day, num] = pq.top();\\n pq.pop();\\n if (num > 1) {\\n pq.emplace(rotten_day, num - 1);\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc eatenApples(apples, days []int) (ans int) {\\n h := hp{}\\n for i := 0; i < len(apples) || h.Len() > 0; i++ {\\n for len(h) > 0 && h[0].rottenDay == i { // 已腐烂\\n heap.Pop(&h)\\n }\\n if i < len(apples) && apples[i] > 0 {\\n heap.Push(&h, pair{i + days[i], apples[i]})\\n }\\n if len(h) > 0 {\\n // 吃一个最早腐烂的苹果\\n ans++\\n h[0].num--\\n if h[0].num == 0 {\\n heap.Pop(&h)\\n }\\n }\\n }\\n return\\n}\\n\\ntype pair struct{ rottenDay, num int }\\ntype hp []pair\\n\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].rottenDay < h[j].rottenDay }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(pair)) }\\nfunc (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }\\n
\\nvar eatenApples = function(apples, days) {\\n const pq = new MinPriorityQueue({priority: x => x[0]});\\n let ans = 0;\\n for (let i = 0; i < apples.length || !pq.isEmpty(); i++) {\\n while (!pq.isEmpty() && pq.front().element[0] === i) { // 已腐烂\\n pq.dequeue();\\n }\\n if (i < apples.length && apples[i] > 0) {\\n pq.enqueue([i + days[i], apples[i]]);\\n }\\n if (!pq.isEmpty()) {\\n ans++;\\n if (--pq.front().element[1] === 0) {\\n pq.dequeue();\\n }\\n }\\n }\\n return ans;\\n};\\n
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn eaten_apples(apples: Vec<i32>, days: Vec<i32>) -> i32 {\\n let mut h: BinaryHeap<(i32, i32)> = BinaryHeap::new();\\n let mut ans = 0;\\n let mut i = 0;\\n while i < apples.len() || !h.is_empty() {\\n while let Some(&(rotten_day, _)) = h.peek() {\\n if -rotten_day > i as i32 {\\n break;\\n }\\n h.pop(); // 已腐烂\\n }\\n if i < apples.len() && apples[i] > 0 {\\n h.push((-(i as i32 + days[i]), apples[i])); // 取反变最小堆\\n }\\n if let Some(mut p) = h.peek_mut() {\\n // 吃一个最早腐烂的苹果\\n ans += 1;\\n p.1 -= 1;\\n }\\n if let Some(&(_, num)) = h.peek() {\\n if num == 0 {\\n h.pop();\\n }\\n }\\n i += 1;\\n }\\n ans\\n }\\n}\\n
\\n当 $i\\\\ge n$ 时,我们浪费太多时间在「一个一个地」吃苹果上了。事实上,可以直接把最早腐烂的苹果(在腐烂前)一次性解决掉,也就是直接把 $i$ 跳到吃完(或者腐烂)的那一天。
\\n设当前是第 $i$ 天,最早腐烂的苹果有 $\\\\textit{num}$ 个,在第 $\\\\textit{rottenDay}$ 天腐烂。
\\n那么我们可以在腐烂前吃掉
\\n$$
\\nk = \\\\min(\\\\textit{num}, \\\\textit{rottenDay} - i)
\\n$$
个苹果。
\\n吃完后,把答案和 $i$ 都增加 $k$,继续循环,直到堆为空为止。
\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n ans = 0\\n h = []\\n for i, (num, day) in enumerate(zip(apples, days)):\\n while h and h[0][0] == i: # 已腐烂\\n heappop(h)\\n if num:\\n heappush(h, [i + day, num])\\n if h:\\n ans += 1\\n h[0][1] -= 1 # 吃一个最早腐烂的苹果\\n if h[0][1] == 0:\\n heappop(h)\\n\\n i = len(apples)\\n while True:\\n while h and h[0][0] <= i: # 已腐烂\\n heappop(h)\\n if not h:\\n return ans\\n rotten_day, num = heappop(h)\\n k = min(num, rotten_day - i)\\n ans += k\\n i += k\\n
\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n int ans = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n int i = 0;\\n for (; i < apples.length; i++) {\\n while (!pq.isEmpty() && pq.peek()[0] == i) { // 已腐烂\\n pq.poll();\\n }\\n if (apples[i] > 0) {\\n pq.offer(new int[]{i + days[i], apples[i]});\\n }\\n if (!pq.isEmpty()) {\\n // 吃一个最早腐烂的苹果\\n ans++;\\n if (--pq.peek()[1] == 0) {\\n pq.poll();\\n }\\n }\\n }\\n\\n while (true) {\\n while (!pq.isEmpty() && pq.peek()[0] <= i) { // 已腐烂\\n pq.poll();\\n }\\n if (pq.isEmpty()) {\\n return ans;\\n }\\n int[] top = pq.poll();\\n int k = Math.min(top[1], top[0] - i);\\n ans += k;\\n i += k;\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int eatenApples(vector<int>& apples, vector<int>& days) {\\n int ans = 0, i = 0;\\n priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;\\n for (; i < apples.size(); i++) {\\n while (!pq.empty() && pq.top().first == i) { // 已腐烂\\n pq.pop();\\n }\\n if (apples[i]) {\\n pq.emplace(i + days[i], apples[i]);\\n }\\n if (!pq.empty()) {\\n // 吃一个最早腐烂的苹果\\n ans++;\\n auto [rotten_day, num] = pq.top();\\n pq.pop();\\n if (num > 1) {\\n pq.emplace(rotten_day, num - 1);\\n }\\n }\\n }\\n\\n while (true) {\\n while (!pq.empty() && pq.top().first <= i) { // 已腐烂\\n pq.pop();\\n }\\n if (pq.empty()) {\\n return ans;\\n }\\n auto [rotten_day, num] = pq.top();\\n pq.pop();\\n int k = min(num, rotten_day - i);\\n ans += k;\\n i += k;\\n }\\n }\\n};\\n
\\nfunc eatenApples(apples, days []int) (ans int) {\\n h := hp{}\\n for i, num := range apples {\\n for len(h) > 0 && h[0].rottenDay == i { // 已腐烂\\n heap.Pop(&h)\\n }\\n if num > 0 {\\n heap.Push(&h, pair{i + days[i], num})\\n }\\n if len(h) > 0 {\\n // 吃一个最早腐烂的苹果\\n ans++\\n h[0].num--\\n if h[0].num == 0 {\\n heap.Pop(&h)\\n }\\n }\\n }\\n\\n i := len(apples)\\n for {\\n for len(h) > 0 && h[0].rottenDay <= i { // 已腐烂\\n heap.Pop(&h)\\n }\\n if len(h) == 0 {\\n return\\n }\\n p := heap.Pop(&h).(pair)\\n k := min(p.num, p.rottenDay-i)\\n ans += k\\n i += k\\n }\\n}\\n\\ntype pair struct{ rottenDay, num int }\\ntype hp []pair\\n\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].rottenDay < h[j].rottenDay }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(pair)) }\\nfunc (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }\\n
\\nvar eatenApples = function(apples, days) {\\n const pq = new MinPriorityQueue({priority: x => x[0]});\\n let ans = 0, i = 0;\\n for (; i < apples.length; i++) {\\n while (!pq.isEmpty() && pq.front().element[0] === i) { // 已腐烂\\n pq.dequeue();\\n }\\n if (apples[i] > 0) {\\n pq.enqueue([i + days[i], apples[i]]);\\n }\\n if (!pq.isEmpty()) {\\n ans++;\\n if (--pq.front().element[1] === 0) {\\n pq.dequeue();\\n }\\n }\\n }\\n\\n while (true) {\\n while (!pq.isEmpty() && pq.front().element[0] <= i) { // 已腐烂\\n pq.dequeue();\\n }\\n if (pq.isEmpty()) {\\n return ans;\\n }\\n const [rottenDay, num] = pq.dequeue().element;\\n const k = Math.min(num, rottenDay - i);\\n ans += k;\\n i += k;\\n }\\n};\\n
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn eaten_apples(apples: Vec<i32>, days: Vec<i32>) -> i32 {\\n let n = apples.len();\\n let mut h: BinaryHeap<(i32, i32)> = BinaryHeap::new();\\n let mut ans = 0;\\n for (i, (num, day)) in apples.into_iter().zip(days).enumerate() {\\n while let Some(&(rotten_day, _)) = h.peek() {\\n if -rotten_day > i as i32 {\\n break;\\n }\\n h.pop(); // 已腐烂\\n }\\n if num > 0 {\\n h.push((-(i as i32 + day), num)); // 取反变最小堆\\n }\\n if let Some(mut p) = h.peek_mut() {\\n // 吃一个最早腐烂的苹果\\n ans += 1;\\n p.1 -= 1;\\n }\\n if let Some(&(_, num)) = h.peek() {\\n if num == 0 {\\n h.pop();\\n }\\n }\\n }\\n\\n let mut i = n as i32;\\n loop {\\n while let Some(&(rotten_day, _)) = h.peek() {\\n if -rotten_day > i {\\n break;\\n }\\n h.pop(); // 已腐烂\\n }\\n if let Some((rotten_day, num)) = h.pop() {\\n let k = num.min(-rotten_day - i);\\n ans += k;\\n i += k;\\n } else {\\n return ans;\\n }\\n }\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单中的「五、堆(优先队列)」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:对于两个苹果 $A$ 和 $B$,设 $A$ 更早腐烂,那么应该先吃 $A$。如果先吃 $B$,可能下一天 $A$ 就烂了。 用最小堆维护苹果的腐烂日期和个数,模拟一天吃一个苹果。\\n\\n注意第 $n$ 天之后还可以继续吃苹果,所以要一直模拟到堆为空为止。\\n\\n优化前\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n ans = i = 0\\n h = []\\n while i < len…","guid":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/chi-zui-zao-fu-lan-de-ping-guo-pythonjav-hx5d","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-05T11:10:12.076Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"冰雹猜想,记忆化搜索(Python/Java/C++/Go/JS)","url":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/bing-bao-cai-xiang-ji-yi-hua-sou-suo-pyt-q5iz","content":"\\n\\n数学还没准备好应对这样的问题。
\\n—— 埃尔德什·帕尔
\\n
按照题意写一个递归函数,$\\\\textit{dfs}(i)$ 表示从 $i$ 到 $1$ 的权重。
\\n递归边界:$\\\\textit{dfs}(1) = 0$。
\\n代码实现时,由于会递归到重复的状态,用记忆化搜索优化。进一步地,可以把 $\\\\textit{dfs}$ 写到外面,这样多个测试数据之间可以共享记忆化搜索的结果。
\\n###py
\\n@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\ndef dfs(i: int) -> int:\\n if i == 1:\\n return 0\\n if i % 2:\\n return dfs((i * 3 + 1) // 2) + 2\\n return dfs(i // 2) + 1\\n\\nclass Solution:\\n def getKth(self, lo: int, hi: int, k: int) -> int:\\n return sorted(range(lo, hi + 1), key=lambda x: (dfs(x), x))[k - 1]\\n
\\n###java
\\nclass Solution {\\n private static final Map<Integer, Integer> memo = new HashMap<>();\\n\\n public int getKth(int lo, int hi, int k) {\\n Integer[] nums = new Integer[hi - lo + 1];\\n Arrays.setAll(nums, i -> i + lo);\\n Arrays.sort(nums, (x, y) -> {\\n int fx = dfs(x), fy = dfs(y);\\n return fx != fy ? fx - fy : x - y;\\n });\\n return nums[k - 1];\\n }\\n\\n private int dfs(int i) {\\n if (i == 1) {\\n return 0;\\n }\\n if (memo.containsKey(i)) { // 之前计算过\\n return memo.get(i);\\n }\\n if (i % 2 == 1) {\\n memo.put(i, dfs((i * 3 + 1) / 2) + 2);\\n } else {\\n memo.put(i, dfs(i / 2) + 1);\\n }\\n return memo.get(i);\\n }\\n}\\n
\\n###cpp
\\nunordered_map<int, int> memo;\\n\\nint dfs(int i) {\\n if (i == 1) {\\n return 0;\\n }\\n auto it = memo.find(i);\\n if (it != memo.end()) { // 之前计算过\\n return it->second;\\n }\\n if (i % 2) {\\n return memo[i] = dfs((i * 3 + 1) / 2) + 2;\\n }\\n return memo[i] = dfs(i / 2) + 1;\\n}\\n\\nclass Solution {\\npublic:\\n int getKth(int lo, int hi, int k) {\\n vector<int> nums(hi - lo + 1);\\n iota(nums.begin(), nums.end(), lo);\\n ranges::sort(nums, {}, [](int x) { return pair{dfs(x), x}; });\\n return nums[k - 1];\\n }\\n};\\n
\\n###go
\\nvar memo = map[int]int{}\\n\\nfunc dfs(i int) int {\\n if i == 1 {\\n return 0\\n }\\n if res, ok := memo[i]; ok { // 之前计算过\\n return res\\n }\\n if i%2 == 1 {\\n memo[i] = dfs((i*3+1)/2) + 2\\n } else {\\n memo[i] = dfs(i/2) + 1\\n }\\n return memo[i]\\n}\\n\\nfunc getKth(lo, hi, k int) int {\\n nums := make([]int, hi-lo+1)\\n for i := range nums {\\n nums[i] = i + lo\\n }\\n slices.SortFunc(nums, func(x, y int) int {\\n return cmp.Or(dfs(x)-dfs(y), x-y)\\n })\\n return nums[k-1]\\n}\\n
\\n###js
\\nconst memo = {};\\n\\nfunction dfs(i) {\\n if (i === 1) {\\n return 0;\\n }\\n if (memo[i] !== undefined) { // 之前计算过\\n return memo[i];\\n }\\n if (i % 2 === 1) {\\n memo[i] = dfs((i * 3 + 1) / 2) + 2;\\n } else {\\n memo[i] = dfs(i / 2) + 1;\\n }\\n return memo[i];\\n}\\n\\nvar getKth = function(lo, hi, k) {\\n const nums = Array.from({ length: hi - lo + 1 }, (_, i) => i + lo);\\n nums.sort((x, y) => {\\n const dx = dfs(x), dy = dfs(y);\\n return dx !== dy ? dx - dy : x - y;\\n });\\n return nums[k - 1];\\n};\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"数学还没准备好应对这样的问题。 —— 埃尔德什·帕尔\\n\\n按照题意写一个递归函数,$\\\\textit{dfs}(i)$ 表示从 $i$ 到 $1$ 的权重。\\n\\n如果 $i$ 是偶数,那么 $\\\\textit{dfs}(i) = \\\\textit{dfs}(i/2)+1$。\\n如果 $i$ 是奇数,那么 $\\\\textit{dfs}(i) = \\\\textit{dfs}(3i+1)+1$。进一步地,由于 $3i+1$ 一定是偶数,所以也可以直接走两步,写成 $\\\\textit{dfs}(i) = \\\\textit{dfs}((3i+1)/2)+2$。\\n\\n递归边界…","guid":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/bing-bao-cai-xiang-ji-yi-hua-sou-suo-pyt-q5iz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-05T08:57:45.955Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/Rust/Cangjie] 一题一解:分类讨论(清晰题解)","url":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/python3javacgorustcangjie-yi-ti-yi-jie-f-xxa5","content":"根据题意,我们可以将捕获黑皇后的情况分为以下几种:
\\n\\\\
上,且中间没有其他棋子,此时只需要移动白色象 $1$ 一次;/
上,且中间没有其他棋子,此时只需要移动白色象 $1$ 一次;###python
\\nclass Solution:\\n def minMovesToCaptureTheQueen(\\n self, a: int, b: int, c: int, d: int, e: int, f: int\\n ) -> int:\\n if a == e and (c != a or (d - b) * (d - f) > 0):\\n return 1\\n if b == f and (d != b or (c - a) * (c - e) > 0):\\n return 1\\n if c - e == d - f and (a - e != b - f or (a - c) * (a - e) > 0):\\n return 1\\n if c - e == f - d and (a - e != f - b or (a - c) * (a - e) > 0):\\n return 1\\n return 2\\n
\\n###java
\\nclass Solution {\\n public int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n if (a == e && (c != a || (d - b) * (d - f) > 0)) {\\n return 1;\\n }\\n if (b == f && (d != b || (c - a) * (c - e) > 0)) {\\n return 1;\\n }\\n if (c - e == d - f && (a - e != b - f || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n if (c - e == f - d && (a - e != f - b || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n return 2;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n if (a == e && (c != a || (d - b) * (d - f) > 0)) {\\n return 1;\\n }\\n if (b == f && (d != b || (c - a) * (c - e) > 0)) {\\n return 1;\\n }\\n if (c - e == d - f && (a - e != b - f || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n if (c - e == f - d && (a - e != f - b || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n return 2;\\n }\\n};\\n
\\n###go
\\nfunc minMovesToCaptureTheQueen(a int, b int, c int, d int, e int, f int) int {\\nif a == e && (c != a || (d-b)*(d-f) > 0) {\\nreturn 1\\n}\\nif b == f && (d != b || (c-a)*(c-e) > 0) {\\nreturn 1\\n}\\nif c-e == d-f && (a-e != b-f || (a-c)*(a-e) > 0) {\\nreturn 1\\n}\\nif c-e == f-d && (a-e != f-b || (a-c)*(a-e) > 0) {\\nreturn 1\\n}\\nreturn 2\\n}\\n
\\n###ts
\\nfunction minMovesToCaptureTheQueen(\\n a: number,\\n b: number,\\n c: number,\\n d: number,\\n e: number,\\n f: number,\\n): number {\\n if (a === e && (c !== a || (d - b) * (d - f) > 0)) {\\n return 1;\\n }\\n if (b === f && (d !== b || (c - a) * (c - e) > 0)) {\\n return 1;\\n }\\n if (c - e === d - f && (a - e !== b - f || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n if (c - e === f - d && (a - e !== f - b || (a - c) * (a - e) > 0)) {\\n return 1;\\n }\\n return 2;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_moves_to_capture_the_queen(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {\\n if a == e && (c != a || (d - b) * (d - f) > 0) {\\n return 1;\\n }\\n if b == f && (d != b || (c - a) * (c - e) > 0) {\\n return 1;\\n }\\n if c - e == d - f && (a - e != b - f || (a - c) * (a - e) > 0) {\\n return 1;\\n }\\n if c - e == f - d && (a - e != f - b || (a - c) * (a - e) > 0) {\\n return 1;\\n }\\n return 2;\\n }\\n}\\n
\\n###cj
\\nclass Solution {\\n func minMovesToCaptureTheQueen(a: Int64, b: Int64, c: Int64, d: Int64, e: Int64, f: Int64): Int64 {\\n if (a == e && (c != a || (d - b) * (d - f) > 0)) {\\n return 1\\n }\\n if (b == f && (d != b || (c - a) * (c - e) > 0)) {\\n return 1\\n }\\n if (c - e == d - f && (a - e != b - f || (a - c) * (a - e) > 0)) {\\n return 1\\n }\\n if (c - e == f - d && (a - e != f - b || (a - c) * (a - e) > 0)) {\\n return 1\\n }\\n 2\\n }\\n}\\n
\\n时间复杂度 $O(1)$,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:分类讨论 根据题意,我们可以将捕获黑皇后的情况分为以下几种:\\n\\n白色车和黑皇后在同一行,且中间没有其他棋子,此时只需要移动白色车 $1$ 一次;\\n白色车和黑皇后在同一列,且中间没有其他棋子,此时只需要移动白色车 $1$ 一次;\\n白色象和黑皇后在对角线 \\\\ 上,且中间没有其他棋子,此时只需要移动白色象 $1$ 一次;\\n白色象和黑皇后在对角线 / 上,且中间没有其他棋子,此时只需要移动白色象 $1$ 一次;\\n其他情况,只需要移动两次。\\n\\n###python\\n\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/python3javacgorustcangjie-yi-ti-yi-jie-f-xxa5","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-05T01:00:30.504Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-捕获黑皇后需要的最少移动次数🟡","url":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen/","content":"现有一个下标从 1 开始的 8 x 8
棋盘,上面有 3
枚棋子。
给你 6
个整数 a
、b
、c
、d
、e
和 f
,其中:
(a, b)
表示白色车的位置。(c, d)
表示白色象的位置。(e, f)
表示黑皇后的位置。假定你只能移动白色棋子,返回捕获黑皇后所需的最少移动次数。
\\n\\n请注意:
\\n\\n\\n\\n
示例 1:
\\n输入:a = 1, b = 1, c = 8, d = 8, e = 2, f = 3\\n输出:2\\n解释:将白色车先移动到 (1, 3) ,然后移动到 (2, 3) 来捕获黑皇后,共需移动 2 次。\\n由于起始时没有任何棋子正在攻击黑皇后,要想捕获黑皇后,移动次数不可能少于 2 次。\\n\\n\\n
示例 2:
\\n输入:a = 5, b = 3, c = 3, d = 4, e = 5, f = 2\\n输出:1\\n解释:可以通过以下任一方式移动 1 次捕获黑皇后:\\n- 将白色车移动到 (5, 2) 。\\n- 将白色象移动到 (5, 2) 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= a, b, c, d, e, f <= 8
题目最多只有 $4$ 个棋子,每个棋子的移动方向最多有 $8$ 种,我们可以考虑使用 DFS 搜索所有的移动组合。
\\n我们按照顺序依次枚举每个棋子,对于每个棋子,我们可以选择不移动,或者按照规则移动,用数组 $\\\\textit{dist}[i]$ 记录第 $i$ 个棋子的移动情况,其中 $\\\\textit{dist}[i][x][y]$ 表示第 $i$ 个棋子经过坐标 $(x, y)$ 时的时间,用数组 $\\\\textit{end}[i]$ 记录第 $i$ 个棋子的终点坐标和时间。在搜索时,我们需要分别判断当前棋子是否可以停止移动,以及当前棋子是否可以继续在当前方向移动。
\\n我们定义方法 $\\\\text{checkStop}(i, x, y, t)$ 判断第 $i$ 个棋子是否在时间 $t$ 时停在坐标 $(x, y)$,如果对于此前的所有棋子 $j$,都有 $\\\\textit{dist}[j][x][y] < t$,那么第 $i$ 个棋子可以停止移动。
\\n另外,我们定义方法 $\\\\text{checkPass}(i, x, y, t)$ 判断第 $i$ 个棋子是否可以在时间 $t$ 时经过坐标 $(x, y)$。如果此前有其它棋子 $j$ 也在时间 $t$ 经过坐标 $(x, y)$,或者有其它棋子 $j$ 停在 $(x, y)$,且时间不超过 $t$,那么第 $i$ 个棋子不能在时间 $t$ 时经过坐标 $(x, y)$。
\\n###python
\\nrook_dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)]\\nbishop_dirs = [(1, 1), (1, -1), (-1, 1), (-1, -1)]\\nqueue_dirs = rook_dirs + bishop_dirs\\n\\n\\ndef get_dirs(piece: str) -> List[Tuple[int, int]]:\\n match piece[0]:\\n case \\"r\\":\\n return rook_dirs\\n case \\"b\\":\\n return bishop_dirs\\n case _:\\n return queue_dirs\\n\\n\\nclass Solution:\\n def countCombinations(self, pieces: List[str], positions: List[List[int]]) -> int:\\n def check_stop(i: int, x: int, y: int, t: int) -> bool:\\n return all(dist[j][x][y] < t for j in range(i))\\n\\n def check_pass(i: int, x: int, y: int, t: int) -> bool:\\n for j in range(i):\\n if dist[j][x][y] == t:\\n return False\\n if end[j][0] == x and end[j][1] == y and end[j][2] <= t:\\n return False\\n return True\\n\\n def dfs(i: int) -> None:\\n if i >= n:\\n nonlocal ans\\n ans += 1\\n return\\n x, y = positions[i]\\n dist[i][:] = [[-1] * m for _ in range(m)]\\n dist[i][x][y] = 0\\n end[i] = (x, y, 0)\\n if check_stop(i, x, y, 0):\\n dfs(i + 1)\\n dirs = get_dirs(pieces[i])\\n for dx, dy in dirs:\\n dist[i][:] = [[-1] * m for _ in range(m)]\\n dist[i][x][y] = 0\\n nx, ny, nt = x + dx, y + dy, 1\\n while 1 <= nx < m and 1 <= ny < m and check_pass(i, nx, ny, nt):\\n dist[i][nx][ny] = nt\\n end[i] = (nx, ny, nt)\\n if check_stop(i, nx, ny, nt):\\n dfs(i + 1)\\n nx += dx\\n ny += dy\\n nt += 1\\n\\n n = len(pieces)\\n m = 9\\n dist = [[[-1] * m for _ in range(m)] for _ in range(n)]\\n end = [(0, 0, 0) for _ in range(n)]\\n ans = 0\\n dfs(0)\\n return ans\\n
\\n###java
\\nclass Solution {\\n int n, m = 9, ans;\\n int[][][] dist;\\n int[][] end;\\n String[] pieces;\\n int[][] positions;\\n int[][] rookDirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};\\n int[][] bishopDirs = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};\\n int[][] queenDirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};\\n\\n public int countCombinations(String[] pieces, int[][] positions) {\\n n = pieces.length;\\n dist = new int[n][m][m];\\n end = new int[n][3];\\n ans = 0;\\n this.pieces = pieces;\\n this.positions = positions;\\n\\n dfs(0);\\n return ans;\\n }\\n\\n private void dfs(int i) {\\n if (i >= n) {\\n ans++;\\n return;\\n }\\n\\n int x = positions[i][0], y = positions[i][1];\\n resetDist(i);\\n dist[i][x][y] = 0;\\n end[i] = new int[] {x, y, 0};\\n\\n if (checkStop(i, x, y, 0)) {\\n dfs(i + 1);\\n }\\n\\n int[][] dirs = getDirs(pieces[i]);\\n for (int[] dir : dirs) {\\n resetDist(i);\\n dist[i][x][y] = 0;\\n int nx = x + dir[0], ny = y + dir[1], nt = 1;\\n\\n while (isValid(nx, ny) && checkPass(i, nx, ny, nt)) {\\n dist[i][nx][ny] = nt;\\n end[i] = new int[] {nx, ny, nt};\\n if (checkStop(i, nx, ny, nt)) {\\n dfs(i + 1);\\n }\\n nx += dir[0];\\n ny += dir[1];\\n nt++;\\n }\\n }\\n }\\n\\n private void resetDist(int i) {\\n for (int j = 0; j < m; j++) {\\n for (int k = 0; k < m; k++) {\\n dist[i][j][k] = -1;\\n }\\n }\\n }\\n\\n private boolean checkStop(int i, int x, int y, int t) {\\n for (int j = 0; j < i; j++) {\\n if (dist[j][x][y] >= t) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n private boolean checkPass(int i, int x, int y, int t) {\\n for (int j = 0; j < i; j++) {\\n if (dist[j][x][y] == t) {\\n return false;\\n }\\n if (end[j][0] == x && end[j][1] == y && end[j][2] <= t) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n private boolean isValid(int x, int y) {\\n return x >= 1 && x < m && y >= 1 && y < m;\\n }\\n\\n private int[][] getDirs(String piece) {\\n char c = piece.charAt(0);\\n return switch (c) {\\n case \'r\' -> rookDirs;\\n case \'b\' -> bishopDirs;\\n default -> queenDirs;\\n };\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countCombinations(vector<string>& pieces, vector<vector<int>>& positions) {\\n int n = pieces.size();\\n const int m = 9;\\n int ans = 0;\\n\\n vector<vector<vector<int>>> dist(n, vector<vector<int>>(m, vector<int>(m, -1)));\\n vector<vector<int>> end(n, vector<int>(3));\\n\\n const int rookDirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};\\n const int bishopDirs[4][2] = {{1, 1}, {1, -1}, {-1, 1}, {-1, -1}};\\n const int queenDirs[8][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}};\\n\\n auto resetDist = [&](int i) {\\n for (int j = 0; j < m; j++) {\\n for (int k = 0; k < m; k++) {\\n dist[i][j][k] = -1;\\n }\\n }\\n };\\n\\n auto checkStop = [&](int i, int x, int y, int t) -> bool {\\n for (int j = 0; j < i; j++) {\\n if (dist[j][x][y] >= t) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n auto checkPass = [&](int i, int x, int y, int t) -> bool {\\n for (int j = 0; j < i; j++) {\\n if (dist[j][x][y] == t) {\\n return false;\\n }\\n if (end[j][0] == x && end[j][1] == y && end[j][2] <= t) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n auto isValid = [&](int x, int y) -> bool {\\n return x >= 1 && x < m && y >= 1 && y < m;\\n };\\n\\n auto getDirs = [&](const string& piece) -> const int(*)[2] {\\n char c = piece[0];\\n if (c == \'r\') {\\n return rookDirs;\\n }\\n if (c == \'b\') {\\n return bishopDirs;\\n }\\n return queenDirs;\\n };\\n\\n auto dfs = [&](auto&& dfs, int i) -> void {\\n if (i >= n) {\\n ans++;\\n return;\\n }\\n\\n int x = positions[i][0], y = positions[i][1];\\n resetDist(i);\\n dist[i][x][y] = 0;\\n end[i] = {x, y, 0};\\n\\n if (checkStop(i, x, y, 0)) {\\n dfs(dfs, i + 1);\\n }\\n\\n const int(*dirs)[2] = getDirs(pieces[i]);\\n int dirsSize = (pieces[i][0] == \'q\') ? 8 : 4;\\n\\n for (int d = 0; d < dirsSize; d++) {\\n resetDist(i);\\n dist[i][x][y] = 0;\\n int nx = x + dirs[d][0], ny = y + dirs[d][1], nt = 1;\\n\\n while (isValid(nx, ny) && checkPass(i, nx, ny, nt)) {\\n dist[i][nx][ny] = nt;\\n end[i] = {nx, ny, nt};\\n if (checkStop(i, nx, ny, nt)) {\\n dfs(dfs, i + 1);\\n }\\n nx += dirs[d][0];\\n ny += dirs[d][1];\\n nt++;\\n }\\n }\\n };\\n\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countCombinations(pieces []string, positions [][]int) (ans int) {\\nn := len(pieces)\\nm := 9\\ndist := make([][][]int, n)\\nfor i := range dist {\\ndist[i] = make([][]int, m)\\nfor j := range dist[i] {\\ndist[i][j] = make([]int, m)\\n}\\n}\\n\\nend := make([][3]int, n)\\n\\nrookDirs := [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}}\\nbishopDirs := [][2]int{{1, 1}, {1, -1}, {-1, 1}, {-1, -1}}\\nqueenDirs := [][2]int{{1, 0}, {-1, 0}, {0, 1}, {0, -1}, {1, 1}, {1, -1}, {-1, 1}, {-1, -1}}\\n\\nresetDist := func(i int) {\\nfor j := 0; j < m; j++ {\\nfor k := 0; k < m; k++ {\\ndist[i][j][k] = -1\\n}\\n}\\n}\\n\\ncheckStop := func(i, x, y, t int) bool {\\nfor j := 0; j < i; j++ {\\nif dist[j][x][y] >= t {\\nreturn false\\n}\\n}\\nreturn true\\n}\\n\\ncheckPass := func(i, x, y, t int) bool {\\nfor j := 0; j < i; j++ {\\nif dist[j][x][y] == t {\\nreturn false\\n}\\nif end[j][0] == x && end[j][1] == y && end[j][2] <= t {\\nreturn false\\n}\\n}\\nreturn true\\n}\\n\\nisValid := func(x, y int) bool {\\nreturn x >= 1 && x < m && y >= 1 && y < m\\n}\\n\\ngetDirs := func(piece string) [][2]int {\\nswitch piece[0] {\\ncase \'r\':\\nreturn rookDirs\\ncase \'b\':\\nreturn bishopDirs\\ndefault:\\nreturn queenDirs\\n}\\n}\\n\\nvar dfs func(i int)\\ndfs = func(i int) {\\nif i >= n {\\nans++\\nreturn\\n}\\n\\nx, y := positions[i][0], positions[i][1]\\nresetDist(i)\\ndist[i][x][y] = 0\\nend[i] = [3]int{x, y, 0}\\n\\nif checkStop(i, x, y, 0) {\\ndfs(i + 1)\\n}\\n\\ndirs := getDirs(pieces[i])\\nfor _, dir := range dirs {\\nresetDist(i)\\ndist[i][x][y] = 0\\nnx, ny, nt := x+dir[0], y+dir[1], 1\\n\\nfor isValid(nx, ny) && checkPass(i, nx, ny, nt) {\\ndist[i][nx][ny] = nt\\nend[i] = [3]int{nx, ny, nt}\\nif checkStop(i, nx, ny, nt) {\\ndfs(i + 1)\\n}\\nnx += dir[0]\\nny += dir[1]\\nnt++\\n}\\n}\\n}\\n\\ndfs(0)\\nreturn\\n}\\n
\\n###ts
\\nconst rookDirs: [number, number][] = [\\n [1, 0],\\n [-1, 0],\\n [0, 1],\\n [0, -1],\\n];\\nconst bishopDirs: [number, number][] = [\\n [1, 1],\\n [1, -1],\\n [-1, 1],\\n [-1, -1],\\n];\\nconst queenDirs = [...rookDirs, ...bishopDirs];\\n\\nfunction countCombinations(pieces: string[], positions: number[][]): number {\\n const n = pieces.length;\\n const m = 9;\\n let ans = 0;\\n\\n const dist = Array.from({ length: n }, () =>\\n Array.from({ length: m }, () => Array(m).fill(-1)),\\n );\\n\\n const end: [number, number, number][] = Array(n).fill([0, 0, 0]);\\n\\n const resetDist = (i: number) => {\\n for (let j = 0; j < m; j++) {\\n for (let k = 0; k < m; k++) {\\n dist[i][j][k] = -1;\\n }\\n }\\n };\\n\\n const checkStop = (i: number, x: number, y: number, t: number): boolean => {\\n for (let j = 0; j < i; j++) {\\n if (dist[j][x][y] >= t) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n const checkPass = (i: number, x: number, y: number, t: number): boolean => {\\n for (let j = 0; j < i; j++) {\\n if (dist[j][x][y] === t) {\\n return false;\\n }\\n if (end[j][0] === x && end[j][1] === y && end[j][2] <= t) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n const isValid = (x: number, y: number): boolean => {\\n return x >= 1 && x < m && y >= 1 && y < m;\\n };\\n\\n const getDirs = (piece: string): [number, number][] => {\\n switch (piece[0]) {\\n case \'r\':\\n return rookDirs;\\n case \'b\':\\n return bishopDirs;\\n default:\\n return queenDirs;\\n }\\n };\\n\\n const dfs = (i: number) => {\\n if (i >= n) {\\n ans++;\\n return;\\n }\\n\\n const [x, y] = positions[i];\\n resetDist(i);\\n dist[i][x][y] = 0;\\n end[i] = [x, y, 0];\\n\\n if (checkStop(i, x, y, 0)) {\\n dfs(i + 1);\\n }\\n\\n const dirs = getDirs(pieces[i]);\\n for (const [dx, dy] of dirs) {\\n resetDist(i);\\n dist[i][x][y] = 0;\\n let nx = x + dx,\\n ny = y + dy,\\n nt = 1;\\n\\n while (isValid(nx, ny) && checkPass(i, nx, ny, nt)) {\\n dist[i][nx][ny] = nt;\\n end[i] = [nx, ny, nt];\\n if (checkStop(i, nx, ny, nt)) {\\n dfs(i + 1);\\n }\\n nx += dx;\\n ny += dy;\\n nt++;\\n }\\n }\\n };\\n\\n dfs(0);\\n return ans;\\n}\\n
\\n时间复杂度 $O((n \\\\times M)^n)$,空间复杂度 $O(n \\\\times M)$。其中 $n$ 是棋子的数量,而 $M$ 是每个棋子的移动范围。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:DFS 题目最多只有 $4$ 个棋子,每个棋子的移动方向最多有 $8$ 种,我们可以考虑使用 DFS 搜索所有的移动组合。\\n\\n我们按照顺序依次枚举每个棋子,对于每个棋子,我们可以选择不移动,或者按照规则移动,用数组 $\\\\textit{dist}[i]$ 记录第 $i$ 个棋子的移动情况,其中 $\\\\textit{dist}[i][x][y]$ 表示第 $i$ 个棋子经过坐标 $(x, y)$ 时的时间,用数组 $\\\\textit{end}[i]$ 记录第 $i$ 个棋子的终点坐标和时间。在搜索时,我们需要分别判断当前棋子是否可以停止移动…","guid":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/python3javacgotypescript-yi-ti-yi-jie-df-ybwz","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-04T00:04:45.720Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-棋盘上有效移动组合的数目🔴","url":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard/","content":"有一个 8 x 8
的棋盘,它包含 n
个棋子(棋子包括车,后和象三种)。给你一个长度为 n
的字符串数组 pieces
,其中 pieces[i]
表示第 i
个棋子的类型(车,后或象)。除此以外,还给你一个长度为 n
的二维整数数组 positions
,其中 positions[i] = [ri, ci]
表示第 i
个棋子现在在棋盘上的位置为 (ri, ci)
,棋盘下标从 1 开始。
棋盘上每个棋子都可以移动 至多一次 。每个棋子的移动中,首先选择移动的 方向 ,然后选择 移动的步数 ,同时你要确保移动过程中棋子不能移到棋盘以外的地方。棋子需按照以下规则移动:
\\n\\n(r, c)
沿着方向 (r+1, c)
,(r-1, c)
,(r, c+1)
或者 (r, c-1)
移动。(r, c)
沿着方向 (r+1, c)
,(r-1, c)
,(r, c+1)
,(r, c-1)
,(r+1, c+1)
,(r+1, c-1)
,(r-1, c+1)
,(r-1, c-1)
移动。(r, c)
沿着方向 (r+1, c+1)
,(r+1, c-1)
,(r-1, c+1)
,(r-1, c-1)
移动。移动组合 包含所有棋子的 移动 。每一秒,每个棋子都沿着它们选择的方向往前移动 一步 ,直到它们到达目标位置。所有棋子从时刻 0
开始移动。如果在某个时刻,两个或者更多棋子占据了同一个格子,那么这个移动组合 不有效 。
请你返回 有效 移动组合的数目。
\\n\\n注意:
\\n\\n\\n\\n
示例 1:
\\n\\n输入:pieces = [\\"rook\\"], positions = [[1,1]]\\n输出:15\\n解释:上图展示了棋子所有可能的移动。\\n\\n\\n
示例 2:
\\n\\n输入:pieces = [\\"queen\\"], positions = [[1,1]]\\n输出:22\\n解释:上图展示了棋子所有可能的移动。\\n\\n\\n
示例 3:
\\n\\n输入:pieces = [\\"bishop\\"], positions = [[4,3]]\\n输出:12\\n解释:上图展示了棋子所有可能的移动。\\n\\n\\n
示例 4:
\\n\\n输入:pieces = [\\"rook\\",\\"rook\\"], positions = [[1,1],[8,8]]\\n输出:223\\n解释:每个车有 15 种移动,所以总共有 15 * 15 = 225 种移动组合。\\n但是,有两个是不有效的移动组合:\\n- 将两个车都移动到 (8, 1) ,会导致它们在同一个格子相遇。\\n- 将两个车都移动到 (1, 8) ,会导致它们在同一个格子相遇。\\n所以,总共有 225 - 2 = 223 种有效移动组合。\\n注意,有两种有效的移动组合,分别是一个车在 (1, 8) ,另一个车在 (8, 1) 。\\n即使棋盘状态是相同的,这两个移动组合被视为不同的,因为每个棋子移动操作是不相同的。\\n\\n\\n
示例 5:
\\n\\n输入:pieces = [\\"queen\\",\\"bishop\\"], positions = [[5,7],[3,4]]\\n输出:281\\n解释:总共有 12 * 24 = 288 种移动组合。\\n但是,有一些不有效的移动组合:\\n- 如果后停在 (6, 7) ,它会阻挡象到达 (6, 7) 或者 (7, 8) 。\\n- 如果后停在 (5, 6) ,它会阻挡象到达 (5, 6) ,(6, 7) 或者 (7, 8) 。\\n- 如果象停在 (5, 2) ,它会阻挡后到达 (5, 2) 或者 (5, 1) 。\\n在 288 个移动组合当中,281 个是有效的。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == pieces.length
n == positions.length
1 <= n <= 4
pieces
只包含字符串 \\"rook\\"
,\\"queen\\"
和 \\"bishop\\"
。1 <= ri, ci <= 8
positions[i]
互不相同。我们计算两个坐标的横纵坐标的差值,如果两个坐标的横纵坐标的差值之和为偶数,那么这两个坐标的方格颜色相同,否则不同。
\\n###python
\\nclass Solution:\\n def checkTwoChessboards(self, coordinate1: str, coordinate2: str) -> bool:\\n x = ord(coordinate1[0]) - ord(coordinate2[0])\\n y = int(coordinate1[1]) - int(coordinate2[1])\\n return (x + y) % 2 == 0\\n
\\n###java
\\nclass Solution {\\n public boolean checkTwoChessboards(String coordinate1, String coordinate2) {\\n int x = coordinate1.charAt(0) - coordinate2.charAt(0);\\n int y = coordinate1.charAt(1) - coordinate2.charAt(1);\\n return (x + y) % 2 == 0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool checkTwoChessboards(string coordinate1, string coordinate2) {\\n int x = coordinate1[0] - coordinate2[0];\\n int y = coordinate1[1] - coordinate2[1];\\n return (x + y) % 2 == 0;\\n }\\n};\\n
\\n###go
\\nfunc checkTwoChessboards(coordinate1 string, coordinate2 string) bool {\\nx := coordinate1[0] - coordinate2[0]\\ny := coordinate1[1] - coordinate2[1]\\nreturn (x+y)%2 == 0\\n}\\n
\\n###ts
\\nfunction checkTwoChessboards(coordinate1: string, coordinate2: string): boolean {\\n const x = coordinate1.charCodeAt(0) - coordinate2.charCodeAt(0);\\n const y = coordinate1.charCodeAt(1) - coordinate2.charCodeAt(1);\\n return (x + y) % 2 === 0;\\n}\\n
\\n时间复杂度 $O(1)$,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:数学 我们计算两个坐标的横纵坐标的差值,如果两个坐标的横纵坐标的差值之和为偶数,那么这两个坐标的方格颜色相同,否则不同。\\n\\n###python\\n\\nclass Solution:\\n def checkTwoChessboards(self, coordinate1: str, coordinate2: str) -> bool:\\n x = ord(coordinate1[0]) - ord(coordinate2[0])\\n y = int(coordinate1[1]) - int(coordinate2[1])…","guid":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color//solution/python3javacgotypescript-yi-ti-yi-jie-sh-t2io","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-03T00:07:42.246Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-检查棋盘方格颜色是否相同🟢","url":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color/","content":"给你两个字符串 coordinate1
和 coordinate2
,代表 8 x 8
国际象棋棋盘上的两个方格的坐标。
以下是棋盘的参考图。
\\n\\n如果这两个方格颜色相同,返回 true
,否则返回 false
。
坐标总是表示有效的棋盘方格。坐标的格式总是先字母(表示列),再数字(表示行)。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: coordinate1 = \\"a1\\", coordinate2 = \\"c3\\"
\\n\\n输出: true
\\n\\n解释:
\\n\\n两个方格均为黑色。
\\n示例 2:
\\n\\n输入: coordinate1 = \\"a1\\", coordinate2 = \\"h3\\"
\\n\\n输出: false
\\n\\n解释:
\\n\\n方格 \\"a1\\"
是黑色,而 \\"h3\\"
是白色。
\\n\\n
提示:
\\n\\ncoordinate1.length == coordinate2.length == 2
\'a\' <= coordinate1[0], coordinate2[0] <= \'h\'
\'1\' <= coordinate1[1], coordinate2[1] <= \'8\'
n 皇后问题 研究的是如何将 n
个皇后放置在 n × n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回 n 皇后问题 不同的解决方案的数量。
\\n\\n
示例 1:
\\n输入:n = 4\\n输出:2\\n解释:如上图所示,4 皇后问题存在两个不同的解法。\\n\\n\\n
示例 2:
\\n\\n输入:n = 1\\n输出:1\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 9
由于目标结点的定义是距离为偶数的结点,因此可以根据奇偶性划分同一棵树中的结点。
\\n由于同一棵树中的任意两个结点之间都连通且只存在一条路径,因此可以规定相邻结点的奇偶性不同,只要确定一个结点的奇偶性,即可确定树中所有结点的奇偶性。规定两棵树的结点 $0$ 分别是两棵树中的偶数结点,分别以两棵树的结点 $0$ 作为起点执行广度优先搜索,即可得到两棵树中的每个结点的奇偶性。以下使用 $0$ 和 $1$ 分别表示偶数结点和奇数结点。
\\n用 $\\\\textit{counts}_1[0]$ 和 $\\\\textit{counts}_1[1]$ 分别表示第一棵树中的偶数结点数目和奇数结点数目,用 $\\\\textit{counts}_2[0]$ 和 $\\\\textit{counts}_2[1]$ 分别表示第二棵树中的偶数结点数目和奇数结点数目。对于第一棵树中的每个结点 $i$,计算其目标结点数目的最大值的方法如下。
\\n得到第一棵树中的结点 $i$ 的奇偶性 $\\\\textit{parity}$,则结点 $i$ 在第一棵树中的目标结点数目等于第一棵树中的奇偶性是 $\\\\textit{parity}$ 的结点数目,即 $\\\\textit{counts}_1[\\\\textit{parity}]$。
\\n在添加一条边将第一棵树与第二棵树连接之后,结点 $i$ 在第二棵树中的目标结点数目计算如下。
\\n如果连接的两个结点的奇偶性相同,则第二棵树中的奇偶性是 $\\\\textit{parity} \\\\oplus 1$ 的结点都是结点 $i$ 的目标结点,因此结点 $i$ 在第二棵树中的目标结点数目是 $\\\\textit{counts}_2[\\\\textit{parity} \\\\oplus 1]$。
\\n如果连接的两个结点的奇偶性不同,则第二棵树中的奇偶性是 $\\\\textit{parity}$ 的结点都是结点 $i$ 的目标结点,因此结点 $i$ 在第二棵树中的目标结点数目是 $\\\\textit{counts}_2[\\\\textit{parity}]$。
\\n第一棵树中的结点 $i$ 的其目标结点数目的最大值是 $\\\\textit{counts}_1[\\\\textit{parity}] + \\\\max(\\\\textit{counts}_2[\\\\textit{parity} \\\\oplus 1], \\\\textit{counts}_2[\\\\textit{parity}])$。
\\n由于 $\\\\textit{parity} \\\\oplus 1$ 和 $\\\\textit{parity}$ 的取值一定分别是 $0$ 和 $1$,因此 $\\\\max(\\\\textit{counts}_2[\\\\textit{parity} \\\\oplus 1], \\\\textit{counts}_2[\\\\textit{parity}]) = \\\\max(\\\\textit{counts}_2[0], \\\\textit{counts}_2[1])$。记 $\\\\textit{maxIncrease} = \\\\max(\\\\textit{counts}_2[0], \\\\textit{counts}_2[1])$,则对于第一棵树中的每个结点 $i$,将其奇偶性记为 $\\\\textit{parity}$,其目标结点数目的最大值是 $\\\\textit{counts}_1[\\\\textit{parity}] + \\\\textit{maxIncrease}$。
\\n###Java
\\nclass Solution {\\n public int[] maxTargetNodes(int[][] edges1, int[][] edges2) {\\n int[] parities1 = getParities(edges1);\\n int[] parities2 = getParities(edges2);\\n int[] counts1 = new int[2];\\n int[] counts2 = new int[2];\\n for (int parity : parities1) {\\n counts1[parity]++;\\n }\\n for (int parity : parities2) {\\n counts2[parity]++;\\n }\\n int maxIncrease = Math.max(counts2[0], counts2[1]);\\n int n = edges1.length + 1;\\n int[] answer = new int[n];\\n for (int i = 0; i < n; i++) {\\n int parity = parities1[i];\\n answer[i] = counts1[parity] + maxIncrease;\\n }\\n return answer;\\n }\\n\\n public int[] getParities(int[][] edges) {\\n int length = edges.length + 1;\\n List<Integer>[] adjacentArr = new List[length];\\n for (int i = 0; i < length; i++) {\\n adjacentArr[i] = new ArrayList<Integer>();\\n }\\n for (int[] edge : edges) {\\n adjacentArr[edge[0]].add(edge[1]);\\n adjacentArr[edge[1]].add(edge[0]);\\n }\\n int[] parities = new int[length];\\n Arrays.fill(parities, -1);\\n parities[0] = 0;\\n Queue<Integer> queue = new ArrayDeque<Integer>();\\n queue.offer(0);\\n int parity = 0;\\n while (!queue.isEmpty()) {\\n parity ^= 1;\\n int size = queue.size();\\n for (int i = 0; i < size; i++) {\\n int node = queue.poll();\\n List<Integer> adjacent = adjacentArr[node];\\n for (int next : adjacent) {\\n if (parities[next] < 0) {\\n parities[next] = parity;\\n queue.offer(next);\\n }\\n }\\n }\\n }\\n return parities;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] MaxTargetNodes(int[][] edges1, int[][] edges2) {\\n int[] parities1 = GetParities(edges1);\\n int[] parities2 = GetParities(edges2);\\n int[] counts1 = new int[2];\\n int[] counts2 = new int[2];\\n foreach (int parity in parities1) {\\n counts1[parity]++;\\n }\\n foreach (int parity in parities2) {\\n counts2[parity]++;\\n }\\n int maxIncrease = Math.Max(counts2[0], counts2[1]);\\n int n = edges1.Length + 1;\\n int[] answer = new int[n];\\n for (int i = 0; i < n; i++) {\\n int parity = parities1[i];\\n answer[i] = counts1[parity] + maxIncrease;\\n }\\n return answer;\\n }\\n\\n public int[] GetParities(int[][] edges) {\\n int length = edges.Length + 1;\\n IList<int>[] adjacentArr = new IList<int>[length];\\n for (int i = 0; i < length; i++) {\\n adjacentArr[i] = new List<int>();\\n }\\n foreach (int[] edge in edges) {\\n adjacentArr[edge[0]].Add(edge[1]);\\n adjacentArr[edge[1]].Add(edge[0]);\\n }\\n int[] parities = new int[length];\\n Array.Fill(parities, -1);\\n parities[0] = 0;\\n Queue<int> queue = new Queue<int>();\\n queue.Enqueue(0);\\n int parity = 0;\\n while (queue.Count > 0) {\\n parity ^= 1;\\n int size = queue.Count;\\n for (int i = 0; i < size; i++) {\\n int node = queue.Dequeue();\\n IList<int> adjacent = adjacentArr[node];\\n foreach (int next in adjacent) {\\n if (parities[next] < 0) {\\n parities[next] = parity;\\n queue.Enqueue(next);\\n }\\n }\\n }\\n }\\n return parities;\\n }\\n}\\n
\\n时间复杂度:$O(n + m)$,其中 $n$ 和 $m$ 分别是第一棵树和第二棵树的结点数。两棵树的广度优先搜索的时间分别是 $O(n)$ 和 $O(m)$,广度优先搜索之后计算最大目标结点数目的时间是 $O(n + m)$,因此时间复杂度是 $O(n + m)$。
\\n空间复杂度:$O(n + m)$,其中 $n$ 和 $m$ 分别是第一棵树和第二棵树的结点数。广度优先搜索空间和记录目标结点数目的空间是 $O(n + m)$。
\\n核心思路:对于 $\\\\textit{answer}[i]$ 来说,新添加的边的一个端点必然是 $i$。为什么?因为用其他节点当作端点,只会让第二棵树的节点到 $i$ 距离变得更远,答案更小。
\\n新添加的边,连到第二棵树的哪个节点上呢?
\\n暴力枚举第二棵树的节点 $j$,用 DFS 计算距离 $j$ 不超过 $k-1$ 的节点个数 $\\\\textit{cnt}_j$。这里 $k-1$ 是因为新添加的边也算在距离中。所有 $\\\\textit{cnt}_j$ 取最大值,记作 $\\\\textit{max}_2$。新添加的边就连到 $\\\\textit{max}_2$ 对应的节点上。
\\n同样地,暴力枚举第一棵树的节点 $i$,用 DFS 计算距离 $i$ 不超过 $k$ 的节点个数 $\\\\textit{cnt}_i$。那么 $\\\\textit{answer}[i] = \\\\textit{cnt}_i + \\\\textit{max}_2$。
\\n小结:贪心思考后,把问题拆分成第一棵树与第二棵树独立计算,从而可以暴力通过。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def build_tree(self, edges: List[List[int]], k: int) -> Callable[[int, int, int], int]:\\n g = [[] for _ in range(len(edges) + 1)]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n def dfs(x: int, fa: int, d: int) -> int:\\n if d > k:\\n return 0\\n cnt = 1\\n for y in g[x]:\\n if y != fa:\\n cnt += dfs(y, x, d + 1)\\n return cnt\\n return dfs\\n\\n def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]], k: int) -> List[int]:\\n max2 = 0\\n if k:\\n dfs = self.build_tree(edges2, k - 1) # 注意这里传的是 k-1\\n max2 = max(dfs(i, -1, 0) for i in range(len(edges2) + 1))\\n\\n dfs = self.build_tree(edges1, k)\\n return [dfs(i, -1, 0) + max2 for i in range(len(edges1) + 1)]\\n
\\n###java
\\nclass Solution {\\n public int[] maxTargetNodes(int[][] edges1, int[][] edges2, int k) {\\n int max2 = 0;\\n if (k > 0) {\\n List<Integer>[] g = buildTree(edges2);\\n for (int i = 0; i < edges2.length + 1; i++) {\\n max2 = Math.max(max2, dfs(i, -1, 0, g, k - 1)); // 注意这里传的是 k-1\\n }\\n }\\n\\n List<Integer>[] g = buildTree(edges1);\\n int[] ans = new int[edges1.length + 1];\\n for (int i = 0; i < ans.length; i++) {\\n ans[i] = dfs(i, -1, 0, g, k) + max2;\\n }\\n return ans;\\n }\\n\\n private List<Integer>[] buildTree(int[][] edges) {\\n List<Integer>[] g = new ArrayList[edges.length + 1];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n return g;\\n }\\n\\n private int dfs(int x, int fa, int d, List<Integer>[] g, int k) {\\n if (d > k) {\\n return 0;\\n }\\n int cnt = 1;\\n for (int y : g[x]) {\\n if (y != fa) {\\n cnt += dfs(y, x, d + 1, g, k);\\n }\\n }\\n return cnt;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n vector<vector<int>> buildTree(vector<vector<int>>& edges) {\\n vector<vector<int>> g(edges.size() + 1);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n return g;\\n }\\n\\n int dfs(int x, int fa, int d, vector<vector<int>>& g, int k) {\\n if (d > k) {\\n return 0;\\n }\\n int cnt = 1;\\n for (int y : g[x]) {\\n if (y != fa) {\\n cnt += dfs(y, x, d + 1, g, k);\\n }\\n }\\n return cnt;\\n }\\n\\npublic:\\n vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2, int k) {\\n int max2 = 0;\\n if (k > 0) {\\n auto g = buildTree(edges2);\\n for (int i = 0; i < edges2.size() + 1; i++) {\\n max2 = max(max2, dfs(i, -1, 0, g, k - 1)); // 注意这里传的是 k-1\\n }\\n }\\n\\n auto g = buildTree(edges1);\\n vector<int> ans(edges1.size() + 1);\\n for (int i = 0; i < ans.size(); i++) {\\n ans[i] = dfs(i, -1, 0, g, k) + max2;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc buildTree(edges [][]int, k int) func(int, int, int) int {\\ng := make([][]int, len(edges)+1)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\n\\nvar dfs func(int, int, int) int\\ndfs = func(x, fa, d int) int {\\nif d > k {\\nreturn 0\\n}\\ncnt := 1\\nfor _, y := range g[x] {\\nif y != fa {\\ncnt += dfs(y, x, d+1)\\n}\\n}\\nreturn cnt\\n}\\nreturn dfs\\n}\\n\\nfunc maxTargetNodes(edges1, edges2 [][]int, k int) []int {\\nmax2 := 0\\nif k > 0 {\\ndfs := buildTree(edges2, k-1) // 注意这里传的是 k-1\\nfor i := range len(edges2) + 1 {\\nmax2 = max(max2, dfs(i, -1, 0))\\n}\\n}\\n\\ndfs := buildTree(edges1, k)\\nans := make([]int, len(edges1)+1)\\nfor i := range ans {\\nans[i] = dfs(i, -1, 0) + max2\\n}\\nreturn ans\\n}\\n
\\n对于第一棵树来说,如果 树的直径【基础算法精讲 23】小于等于 $k$,那么就不需要暴力算了,第一棵树的每个节点都是目标节点。
\\n同理,对于对于第二棵树来说,如果其直径小于 $k$,那么也不需要暴力算了,第二棵树的每个节点都是目标节点。
\\n###py
\\nclass Solution:\\n def calc_tree(self, edges: List[List[int]], k: int) -> Tuple[int, Callable[[int, int, int], int]]:\\n g = [[] for _ in range(len(edges) + 1)]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n diameter = 0\\n def dfs_diameter(x: int, fa: int) -> int:\\n nonlocal diameter\\n max_len = 0\\n for y in g[x]:\\n if y != fa:\\n sub_len = dfs_diameter(y, x) + 1\\n diameter = max(diameter, max_len + sub_len)\\n max_len = max(max_len, sub_len)\\n return max_len\\n dfs_diameter(0, -1)\\n\\n def dfs(x: int, fa: int, d: int) -> int:\\n if d > k:\\n return 0\\n cnt = 1\\n for y in g[x]:\\n if y != fa:\\n cnt += dfs(y, x, d + 1)\\n return cnt\\n\\n return diameter, dfs\\n\\n def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]], k: int) -> List[int]:\\n n, m = len(edges1) + 1, len(edges2) + 1\\n\\n max2 = 0\\n if k:\\n diameter, dfs = self.calc_tree(edges2, k - 1)\\n if diameter < k:\\n max2 = m # 第二棵树的每个节点都是目标节点\\n else:\\n max2 = max(dfs(i, -1, 0) for i in range(m))\\n\\n diameter, dfs = self.calc_tree(edges1, k)\\n if diameter <= k:\\n return [n + max2] * n # 第一棵树的每个节点都是目标节点\\n return [dfs(i, -1, 0) + max2 for i in range(n)]\\n
\\n###java
\\nclass Solution {\\n public int[] maxTargetNodes(int[][] edges1, int[][] edges2, int k) {\\n int n = edges1.length + 1;\\n int m = edges2.length + 1;\\n \\n int max2 = 0;\\n if (k > 0) {\\n List<Integer>[] g = buildTree(edges2);\\n diameter = 0;\\n dfs(0, -1, g);\\n\\n if (diameter < k) {\\n max2 = m; // 第二棵树的每个节点都是目标节点\\n } else {\\n for (int i = 0; i < m; i++) {\\n max2 = Math.max(max2, dfs(i, -1, 0, g, k - 1));\\n }\\n }\\n }\\n\\n List<Integer>[] g = buildTree(edges1);\\n diameter = 0;\\n dfs(0, -1, g); \\n\\n int[] ans = new int[n];\\n if (diameter <= k) {\\n Arrays.fill(ans, n + max2); // 第一棵树的每个节点都是目标节点\\n } else {\\n for (int i = 0; i < ans.length; i++) {\\n ans[i] = dfs(i, -1, 0, g, k) + max2;\\n }\\n }\\n return ans;\\n }\\n\\n private List<Integer>[] buildTree(int[][] edges) {\\n List<Integer>[] g = new ArrayList[edges.length + 1];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n return g;\\n }\\n\\n private int diameter;\\n\\n // 求树的直径\\n private int dfs(int x, int fa, List<Integer>[] g) {\\n int maxLen = 0;\\n for (int y : g[x]) {\\n if (y != fa) {\\n int subLen = dfs(y, x, g) + 1;\\n diameter = Math.max(diameter, maxLen + subLen);\\n maxLen = Math.max(maxLen, subLen);\\n }\\n }\\n return maxLen;\\n }\\n\\n private int dfs(int x, int fa, int d, List<Integer>[] g, int k) {\\n if (d > k) {\\n return 0;\\n }\\n int cnt = 1;\\n for (int y : g[x]) {\\n if (y != fa) {\\n cnt += dfs(y, x, d + 1, g, k);\\n }\\n }\\n return cnt;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n vector<vector<int>> buildTree(vector<vector<int>>& edges) {\\n vector<vector<int>> g(edges.size() + 1);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n return g;\\n }\\n\\n int dfs_diameter(int x, int fa, const vector<vector<int>>& g, int& diameter) {\\n int max_len = 0;\\n for (int y : g[x]) {\\n if (y != fa) {\\n int sub_len = dfs_diameter(y, x, g, diameter) + 1;\\n diameter = max(diameter, max_len + sub_len);\\n max_len = max(max_len, sub_len);\\n }\\n }\\n return max_len;\\n }\\n\\n int dfs(int x, int fa, int d, vector<vector<int>>& g, int k) {\\n if (d > k) {\\n return 0;\\n }\\n int cnt = 1;\\n for (int y : g[x]) {\\n if (y != fa) {\\n cnt += dfs(y, x, d + 1, g, k);\\n }\\n }\\n return cnt;\\n }\\n\\npublic:\\n vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2, int k) {\\n int n = edges1.size() + 1;\\n int m = edges2.size() + 1;\\n \\n int max2 = 0;\\n if (k > 0) {\\n auto g = buildTree(edges2);\\n int diameter = 0;\\n dfs_diameter(0, -1, g, diameter);\\n\\n if (diameter < k) {\\n max2 = m; // 第二棵树的每个节点都是目标节点\\n } else {\\n for (int i = 0; i < m; i++) {\\n max2 = max(max2, dfs(i, -1, 0, g, k - 1));\\n }\\n }\\n }\\n\\n auto g = buildTree(edges1);\\n int diameter = 0;\\n dfs_diameter(0, -1, g, diameter);\\n\\n vector<int> ans(n);\\n if (diameter <= k) {\\n ranges::fill(ans, n + max2); // 第一棵树的每个节点都是目标节点\\n } else {\\n for (int i = 0; i < n; i++) {\\n ans[i] = dfs(i, -1, 0, g, k) + max2;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc calcTree(edges [][]int, k int) (diameter int, dfs func(int, int, int) int) {\\ng := make([][]int, len(edges)+1)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\n\\nvar dfsDiameter func(int, int) int\\ndfsDiameter = func(x, fa int) (maxLen int) {\\nfor _, y := range g[x] {\\nif y != fa {\\nsubLen := dfsDiameter(y, x) + 1\\ndiameter = max(diameter, maxLen+subLen)\\nmaxLen = max(maxLen, subLen)\\n}\\n}\\nreturn\\n}\\ndfsDiameter(0, -1)\\n\\ndfs = func(x, fa, d int) int {\\nif d > k {\\nreturn 0\\n}\\ncnt := 1\\nfor _, y := range g[x] {\\nif y != fa {\\ncnt += dfs(y, x, d+1)\\n}\\n}\\nreturn cnt\\n}\\n\\nreturn\\n}\\n\\nfunc maxTargetNodes(edges1, edges2 [][]int, k int) []int {\\nn := len(edges1) + 1\\nm := len(edges2) + 1\\n\\nmax2 := 0\\nif k > 0 {\\ndiameter, dfs := calcTree(edges2, k-1)\\nif diameter < k {\\nmax2 = m // 第二棵树的每个节点都是目标节点\\n} else {\\nfor i := range m {\\nmax2 = max(max2, dfs(i, -1, 0))\\n}\\n}\\n}\\n\\ndiameter, dfs := calcTree(edges1, k)\\nans := make([]int, n)\\nif diameter <= k {\\nfor i := range ans {\\nans[i] = n + max2 // 第一棵树的每个节点都是目标节点\\n}\\n} else {\\nfor i := range ans {\\nans[i] = dfs(i, -1, 0) + max2\\n}\\n}\\nreturn ans\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:对于 $\\\\textit{answer}[i]$ 来说,新添加的边的一个端点必然是 $i$。为什么?因为用其他节点当作端点,只会让第二棵树的节点到 $i$ 距离变得更远,答案更小。 新添加的边,连到第二棵树的哪个节点上呢?\\n\\n暴力枚举第二棵树的节点 $j$,用 DFS 计算距离 $j$ 不超过 $k-1$ 的节点个数 $\\\\textit{cnt}_j$。这里 $k-1$ 是因为新添加的边也算在距离中。所有 $\\\\textit{cnt}_j$ 取最大值,记作 $\\\\textit{max}_2$。新添加的边就连到 $\\\\textit{max}_2…","guid":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-i//solution/nao-jin-ji-zhuan-wan-bao-li-mei-ju-pytho-ua6k","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-01T04:17:40.983Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"bfs","url":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-i//solution/bfs-by-mipha-2022-aa8s","content":"\\n\\nProblem: 100475. 连接两棵树后最大目标节点数目 I
\\n
[TOC]
\\n假设:
\\nedges1
对应的树为 A
edges2
对应的树为 B
根据题意,假设要获取answer[nodeA]
A
树中,以nodeA
为根节点,路径长度 ≤ k
的数目有多少B
树中,枚举其所有节点nodeB
,路径长度 ≤ k - 1
的数目有多少,并找出最大数目以及对应的节点maxB
,这样A
中所有节点都连接maxB
即可对于第2步,可以发现时间复杂度是$$O(m^2)$$,m
是B
树节点数目,其实可以发现maxB
其实是树的中心,即:
\\n\\n这个节点到树中所有其他节点的最大距离达到最小化
\\n
时间复杂度可以降到$$O(m)$$,有兴趣的同学可以试试
\\n更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]], k: int) -> List[int]:\\n # 返回树所有节点的离该节点长度为 k 的数目\\n def getNodeCnt(edges,k):\\n road = defaultdict(list)\\n for x,y in edges:\\n road[x].append(y)\\n road[y].append(x)\\n n = len(edges) + 1\\n res = [0] * n\\n # bfs\\n for start in range(n):\\n que = deque()\\n que.append((start,-1,0))\\n total = 0\\n while que:\\n for _ in range(len(que)):\\n node,last,t = que.pop()\\n if t <= k:\\n total += 1\\n\\n if t == k:\\n continue\\n\\n for nxt in road[node]:\\n if nxt == last:\\n continue\\n\\n que.appendleft((nxt,node,t+1))\\n res[start] = total\\n\\n return res\\n\\n res = getNodeCnt(edges1,k)\\n \\n tmp = max(getNodeCnt(edges2,k-1))\\n\\n n = len(edges1) + 1\\n for node in range(n):\\n res[node] += tmp\\n\\n return res\\n
\\n","description":"Problem: 100475. 连接两棵树后最大目标节点数目 I [TOC]\\n\\n假设:\\n\\nedges1 对应的树为 A\\nedges2 对应的树为 B\\nbfs\\n\\n根据题意,假设要获取answer[nodeA]\\n\\n统计A树中,以nodeA为根节点,路径长度 ≤ k 的数目有多少\\n在B树中,枚举其所有节点nodeB,路径长度 ≤ k - 1 的数目有多少,并找出最大数目以及对应的节点maxB,这样A中所有节点都连接maxB即可\\n优化\\n\\n对于第2步,可以发现时间复杂度是$$O(m^2)$$,m是B树节点数目,其实可以发现maxB其实是树的中心,即:\\n\\n这个…","guid":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-i//solution/bfs-by-mipha-2022-aa8s","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-01T04:17:15.990Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"黑白染色(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-ii//solution/an-qi-ou-fen-lei-pythonjavacgo-by-endles-dweg","content":"对于一棵树,我们把这棵树的所有节点染成黑色或者白色,规则如下:
\\n\\n\\n这个想法来自国际象棋的棋盘:所有黑色格子的四方向邻居都是白色格子,所有白色格子的四方向邻居都是黑色格子。也可以从图论的角度理解,因为树一定是二分图。
\\n
染色后,从任意节点出发,每走一步,节点的颜色都会改变。所以:
\\n所以从任意黑色节点出发,所找到的目标节点,一定都是黑色;从任意白色节点出发,所找到的目标节点,一定都是白色。
\\n不妨从节点 $0$ 开始 DFS。(你想从其他节点开始 DFS 也可以。)
\\n对于第二棵树,我们把其中的节点分成两个集合:
\\n分类讨论:
\\n所以节点 $i$ 在第二棵树中,最多有
\\n$$
\\n\\\\textit{max}_2 = \\\\max(\\\\textit{cnt}_2[0],\\\\textit{cnt}_2[1])
\\n$$
个目标节点。
\\n\\n\\n注意本题保证 $n\\\\ge 2$ 且 $m\\\\ge 2$。如果 $n=1$ 且 $m=1$,则不能用上式计算,需要特判这种情况。
\\n
对于第一棵树,我们把其中的节点分成两个集合:
\\n分类讨论:
\\n所以 $\\\\textit{answer}[i]$ 等于节点 $i$ 所属集合的大小,加上 $\\\\textit{max}_2$。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def count(self, edges: List[List[int]]) -> Tuple[List[List[int]], List[int]]:\\n g = [[] for _ in range(len(edges) + 1)]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n cnt = [0, 0]\\n def dfs(x: int, fa: int, d: int) -> None:\\n cnt[d] += 1\\n for y in g[x]:\\n if y != fa:\\n dfs(y, x, d ^ 1)\\n dfs(0, -1, 0)\\n return g, cnt\\n\\n def maxTargetNodes(self, edges1: List[List[int]], edges2: List[List[int]]) -> List[int]:\\n _, cnt2 = self.count(edges2)\\n max2 = max(cnt2)\\n\\n g, cnt1 = self.count(edges1)\\n ans = [max2] * len(g)\\n def dfs(x: int, fa: int, d: int) -> None:\\n ans[x] += cnt1[d]\\n for y in g[x]:\\n if y != fa:\\n dfs(y, x, d ^ 1)\\n dfs(0, -1, 0)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] maxTargetNodes(int[][] edges1, int[][] edges2) {\\n List<Integer>[] g2 = buildTree(edges2);\\n int[] cnt2 = new int[2];\\n dfs(0, -1, 0, g2, cnt2);\\n int max2 = Math.max(cnt2[0], cnt2[1]);\\n\\n List<Integer>[] g1 = buildTree(edges1);\\n int[] cnt1 = new int[2];\\n dfs(0, -1, 0, g1, cnt1);\\n\\n int[] ans = new int[g1.length];\\n Arrays.fill(ans, max2);\\n dfs1(0, -1, 0, g1, cnt1, ans);\\n return ans;\\n }\\n\\n private List<Integer>[] buildTree(int[][] edges) {\\n List<Integer>[] g = new ArrayList[edges.length + 1];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n return g;\\n }\\n\\n private void dfs(int x, int fa, int d, List<Integer>[] g, int[] cnt) {\\n cnt[d]++;\\n for (int y : g[x]) {\\n if (y != fa) {\\n dfs(y, x, d ^ 1, g, cnt);\\n }\\n }\\n }\\n\\n private void dfs1(int x, int fa, int d, List<Integer>[] g, int[] cnt1, int[] ans) {\\n ans[x] += cnt1[d];\\n for (int y : g[x]) {\\n if (y != fa) {\\n dfs1(y, x, d ^ 1, g, cnt1, ans);\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {\\n auto count = [](vector<vector<int>>& edges) {\\n vector<vector<int>> g(edges.size() + 1);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n\\n array<int, 2> cnt{};\\n auto dfs = [&](this auto&& dfs, int x, int fa, int d) -> void {\\n cnt[d]++;\\n for (int y : g[x]) {\\n if (y != fa) {\\n dfs(y, x, d ^ 1);\\n }\\n }\\n };\\n dfs(0, -1, 0);\\n return pair(g, cnt);\\n };\\n\\n auto [_, cnt2] = count(edges2);\\n int max2 = max(cnt2[0], cnt2[1]);\\n\\n auto [g, cnt1] = count(edges1);\\n vector<int> ans(g.size(), max2);\\n auto dfs = [&](this auto&& dfs, int x, int fa, int d) -> void {\\n ans[x] += cnt1[d];\\n for (int y : g[x]) {\\n if (y != fa) {\\n dfs(y, x, d ^ 1);\\n }\\n }\\n };\\n dfs(0, -1, 0);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc count(edges [][]int) (g [][]int, cnt [2]int) {\\ng = make([][]int, len(edges)+1)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\n\\nvar dfs func(int, int, int)\\ndfs = func(x, fa, d int) {\\ncnt[d]++\\nfor _, y := range g[x] {\\nif y != fa {\\ndfs(y, x, d^1)\\n}\\n}\\n}\\ndfs(0, -1, 0)\\nreturn\\n}\\n\\nfunc maxTargetNodes(edges1, edges2 [][]int) []int {\\n_, cnt2 := count(edges2)\\nmax2 := max(cnt2[0], cnt2[1])\\n\\ng, cnt1 := count(edges1)\\nans := make([]int, len(g))\\nvar dfs func(int, int, int)\\ndfs = func(x, fa, d int) {\\nans[x] = cnt1[d] + max2\\nfor _, y := range g[x] {\\nif y != fa {\\ndfs(y, x, d^1)\\n}\\n}\\n}\\ndfs(0, -1, 0)\\nreturn ans\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 对于一棵树,我们把这棵树的所有节点染成黑色或者白色,规则如下:\\n\\n黑色节点的所有邻居都是白色。\\n白色节点的所有邻居都是黑色。\\n\\n这个想法来自国际象棋的棋盘:所有黑色格子的四方向邻居都是白色格子,所有白色格子的四方向邻居都是黑色格子。也可以从图论的角度理解,因为树一定是二分图。\\n\\n染色后,从任意节点出发,每走一步,节点的颜色都会改变。所以:\\n\\n从某个节点走奇数步之后,一定会走到异色节点上。\\n从某个节点走偶数步之后,一定会走到同色节点上。\\n\\n所以从任意黑色节点出发,所找到的目标节点,一定都是黑色;从任意白色节点出发,所找到的目标节点,一定都是白色。…","guid":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-ii//solution/an-qi-ou-fen-lei-pythonjavacgo-by-endles-dweg","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-01T04:16:36.827Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DFS","url":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-i//solution/dfs-by-tsreaper-7d3x","content":"answer[i]
可以分成两个部分:第一棵树里距离节点 $i$ 不超过 $k$ 的点数,以及第二棵树里距离满足要求的点数。
答案的第一部分直接从第一棵树的节点 $i$ 开始 DFS 即可。为了让答案的第二部分尽量大,连边的一个端点肯定是第一棵树的节点 $i$,这样才能减少距离的损耗。这样,第一棵树的节点 $i$ 只要走过一条边就能到达第二棵树,那我们要求的就是第二棵树里距离某个节点不超过 $(k - 1)$ 的点数最多是多少。因此我们可以预先枚举第二棵树里的所有节点,求出距离它不超过 $(k - 1)$ 的点数,取最大值加到所有答案上即可。
\\n复杂度 $\\\\mathcal{O}(n^2)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2, int K) {\\n // 建图\\n int n = edges1.size() + 1, N = edges2.size() + 1;\\n vector<int> e[n], E[N];\\n for (auto &edge : edges1) {\\n e[edge[0]].push_back(edge[1]);\\n e[edge[1]].push_back(edge[0]);\\n }\\n for (auto &edge : edges2) {\\n E[edge[0]].push_back(edge[1]);\\n E[edge[1]].push_back(edge[0]);\\n }\\n\\n // DFS 求距离不超过 rem 的点数\\n auto dfs = [&](auto &&self, int sn, int fa, int rem, vector<int> *e) -> int {\\n int ret = 1;\\n if (rem > 0) {\\n for (int fn : e[sn]) if (fn != fa)\\n ret += self(self, fn, sn, rem - 1, e);\\n }\\n return ret;\\n };\\n\\n vector<int> ans(n);\\n // 计算答案的第一部分\\n for (int i = 0; i < n; i++) ans[i] = dfs(dfs, i, -1, K, e);\\n if (K > 0) {\\n // 计算答案的第二部分\\n int best = 0;\\n for (int i = 0; i < N; i++) best = max(best, dfs(dfs, i, -1, K - 1, E));\\n for (int i = 0; i < n; i++) ans[i] += best;\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:DFS answer[i] 可以分成两个部分:第一棵树里距离节点 $i$ 不超过 $k$ 的点数,以及第二棵树里距离满足要求的点数。\\n\\n答案的第一部分直接从第一棵树的节点 $i$ 开始 DFS 即可。为了让答案的第二部分尽量大,连边的一个端点肯定是第一棵树的节点 $i$,这样才能减少距离的损耗。这样,第一棵树的节点 $i$ 只要走过一条边就能到达第二棵树,那我们要求的就是第二棵树里距离某个节点不超过 $(k - 1)$ 的点数最多是多少。因此我们可以预先枚举第二棵树里的所有节点,求出距离它不超过 $(k - 1)$ 的点数,取最大值加到所有答案上即可…","guid":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-i//solution/dfs-by-tsreaper-7d3x","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-12-01T04:14:31.578Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DFS","url":"https://leetcode.cn/problems/maximize-the-number-of-target-nodes-after-connecting-trees-ii//solution/dfs-by-tsreaper-q4c9","content":"与 连接两棵树后最大目标节点数目 I 相似,每个答案都可以分为两部分:第一棵树里距离节点 $i$ 为偶数的点数,以及第二棵树里距离满足要求的点数。
\\n对树进行二分图染色,答案的第一部分就是和节点 $i$ 相同颜色的点数。另外可以发现,答案的第二部分的节点颜色也都是相同的,因此取第二棵树里颜色较多的节点作为答案的第二部分即可。
\\n复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> maxTargetNodes(vector<vector<int>>& edges1, vector<vector<int>>& edges2) {\\n // 建图\\n int n = edges1.size() + 1, N = edges2.size() + 1;\\n vector<int> e[n], E[N];\\n for (auto &edge : edges1) {\\n e[edge[0]].push_back(edge[1]);\\n e[edge[1]].push_back(edge[0]);\\n }\\n for (auto &edge : edges2) {\\n E[edge[0]].push_back(edge[1]);\\n E[edge[1]].push_back(edge[0]);\\n }\\n\\n // a[u]:第一棵树节点 u 的颜色\\n // c[0/1]:第一棵树颜色为 0/1 的节点数量\\n int a[n], c[2] = {0};\\n // C[0/1]:第二棵树颜色为 0/1 的节点数量\\n int A[N], C[2] = {0};\\n // DFS 进行二分图染色\\n auto dfs = [&](auto &&self, int sn, int fa, int col, int *a, int *c, vector<int> *e) -> void {\\n a[sn] = col; c[col]++;\\n for (int fn : e[sn]) if (fn != fa)\\n self(self, fn, sn, col ^ 1, a, c, e);\\n };\\n dfs(dfs, 0, -1, 0, a, c, e);\\n dfs(dfs, 0, -1, 0, A, C, E);\\n\\n vector<int> ans(n);\\n // 答案的第一部分\\n for (int i = 0; i < n; i++) ans[i] = c[a[i]];\\n if (N == 1) return ans;\\n // 答案的第二部分\\n int mx = max(C[0], C[1]);\\n for (int i = 0; i < n; i++) ans[i] += mx;\\n return ans;\\n }\\n};\\n
\\n","description":"解法:DFS 与 连接两棵树后最大目标节点数目 I 相似,每个答案都可以分为两部分:第一棵树里距离节点 $i$ 为偶数的点数,以及第二棵树里距离满足要求的点数。\\n\\n对树进行二分图染色,答案的第一部分就是和节点 $i$ 相同颜色的点数。另外可以发现,答案的第二部分的节点颜色也都是相同的,因此取第二棵树里颜色较多的节点作为答案的第二部分即可。\\n\\n复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n vector具体来说,根据题目,可以有一个直接的贪心思路 $\\\\lfloor$将价值高的商品都尽可能地放在后续的天数中购买$\\\\rceil$ ,因此我们可以直接选择将二维数组 $values$ 展平为一维数组 $values\'$ 并对之进行排序。
\\n随后,对于数组中的每一个元素 $values\'[i]$ ,我们将其与他自身的下标加 $1$ 相乘,即 $values\'[i] \\\\times (i+1)$,并将相乘结果累加最终返回即可。
\\n没错直接这样就能过
###C#
\\npublic class Solution {\\n public long MaxSpending(int[][] values) {\\n var fixedValues = values.SelectMany(ints => ints).Order().ToArray();\\n return Enumerable.Range(0, fixedValues.Length).Sum(i => (i + 1L) * fixedValues[i]);\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static auto maxSpending(const auto &values) {\\n auto fixedValues = std::vector<int>();\\n for (const auto &vec: values) {\\n fixedValues.insert(fixedValues.end(), vec.begin(), vec.end());\\n }\\n\\n std::ranges::sort(fixedValues);\\n auto index = 0;\\n \\n return std::accumulate(fixedValues.begin(), fixedValues.end(), 0LL, \\n [&index](const auto sum, const auto value) {\\n return sum + value * ++index;\\n }\\n );\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public long maxSpending(int[][] values) {\\n var fixedValues = Arrays.stream(values).flatMapToInt(Arrays::stream).sorted().toArray();\\n return IntStream.range(0, fixedValues.length).mapToLong(i -> (i + 1L) * fixedValues[i]).sum();\\n }\\n}\\n
\\n###Javascript
\\nvar maxSpending = function(values) {\\n return values.flat().sort((a, b) => a - b).reduce(\\n (sum, value, index) => sum + (index + 1) * value, 0\\n );\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n return sum(\\n (i + 1) * value for i, value in enumerate(sorted(chain(*values)))\\n )\\n\\n
\\n###Kotlin
\\nclass Solution {\\n fun maxSpending(values: Array<IntArray>): Long {\\n return values.flatMap { it.toList() }.sorted().foldIndexed(0L) { \\n index, sum, value -> sum + (index + 1L) * value\\n }\\n }\\n}\\n
\\n时间复杂度: $O(mn\\\\cdot log(mn))$,其中 $m$ 为商店的数量,$n$ 为商品的数量,我们需要将二维数组展平为一维后进行排序。
\\n空间复杂度: $O(mn)$,其中 $m$ 为商店的数量,$n$ 为商品的数量,我们需要将二维数组展平为一维。
\\n注意到本题是困难题,因此不妨假定我们现在突然失忆忘记了上述方法。
我们可以切换一下思路,我们本质的贪心需求仍然是将价值高的商品都尽可能地放在后续的天数中购买,换而言之,便是 $\\\\lfloor$将价值低的商品都尽可能地放在前面的天数中购买$\\\\rceil$ 。
\\n那么这就需要我们在每次购买中选取当前所有商店中价格最低的商品,因此我们可以参照 23. 合并 K 个升序链表,以及 $18$ 天前的每日一题 632. 最小区间 等题的思路,具体来说如下:
\\n对于每一家商店,我们可以各自都分配一个指针指向商店当前的商品。我们将这些商品按照其价格储存在底层为最小堆的优先队列中,这样的话便可以保证我们每次都能通过堆顶获得当前当前所有商店中价格最低的商品。随后我们再将这一件商品的前一件商品入堆即可(如果有的话)。
\\n具体到代码上,首先我们需要解决的问题是:“如何才能将当前商品的前一件商品入堆?”那么我们需要知道的信息便有两个:当前商品所属的是哪一家店?以及当前商品在店中的下标是多少?
\\n因此我们可以考虑,使用数组或者元组来满足上述需求,此处我们以元组为例。
\\n我们不妨假设有元组 (int val, int shop, int idx)
,其中第 $0$,第 $1$ 以及第 $2$ 个成员分别表示当前商品的价格,所属商店以及店中的下标。
同时在部分语言中,我们还需要自定义最小堆的排列规则。我们以元组的第 $0$ 个成员,也就是商品的价格的大小作为排列规则。那么以上,我们便可以得到我们操作主体的优先队列(最小堆) heap
了。此外,我们可以额外定义一个变量 day = 1
来记录当前的天数。
在初始时,我们将每一个商店的最后一个商品放入堆中。对于每一天,我们通过堆顶获取当前所有商店中价格最低的商品,随后我们将 val * day
添加到结果中并使 day += 1
。
而对于当前商品的下一件商品,我们需要首先检查是否有 idx > 0
,若不是,则说明当前商店的商品已经全部买完了,我们也就无需进行入堆操作了。
否则,根据我们定义的元组,我们便可以通过 values[shop][idx - 1]
将当前商品的下一件商品入堆了。
最终我们返回我们的求和即可。
\\n###C#
\\npublic class Solution {\\n public long MaxSpending(int[][] values) {\\n var result = 0L;\\n var heap = new PriorityQueue<(long, int, int), int>(\\n values.Select((ints, i) => (((long)ints[^1], i, ints.Length - 1), ints[^1]))\\n );\\n\\n for (var day = 1; day <= values.Length * values[0].Length; day++) {\\n var (val, shop, idx) = heap.Dequeue();\\n result += val * day;\\n \\n if (idx > 0) {\\n heap.Enqueue((values[shop][idx - 1], shop, idx - 1), values[shop][idx - 1]);\\n }\\n }\\n\\n return result;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\n using int64 = long long;\\n\\npublic:\\n static auto maxSpending(const auto &values) {\\n auto comparison = [](const auto &t1, const auto &t2) {\\n return std::get<0>(t1) > std::get<0>(t2);\\n };\\n\\n auto heap = std::priority_queue<\\n std::tuple<int64, int, int>, \\n std::vector<std::tuple<int64, int, int>>, \\n decltype(comparison)\\n >(comparison);\\n\\n for (auto i = 0; i < values.size(); i++) {\\n heap.emplace(static_cast<int64>(values[i].back()), i, values[0].size() - 1);\\n }\\n\\n auto result = 0LL;\\n for (auto day = 1; day <= values.size() * values[0].size(); day++) {\\n auto [val, shop, idx] = heap.top();\\n heap.pop();\\n \\n result += val * day;\\n if (idx > 0) {\\n heap.emplace(static_cast<int64>(values[shop][idx - 1]), shop, idx - 1);\\n }\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public long maxSpending(int[][] values) {\\n var heap = new PriorityQueue<int[]>(Comparator.comparingInt(a -> a[0]));\\n for (var i = 0; i < values.length; i++) {\\n heap.offer(new int[] { values[i][values[0].length - 1], i, values[0].length - 1 });\\n }\\n\\n var result = 0L;\\n for (var day = 1; day <= values.length * values[0].length; day++) {\\n var top = Objects.requireNonNull(heap.poll());\\n int val = top[0], shop = top[1], idx = top[2];\\n\\n result += (long) val * day;\\n if (idx > 0) {\\n heap.offer(new int[] { values[shop][idx - 1], shop, idx - 1 });\\n }\\n }\\n\\n return result;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n result = 0\\n heap = []\\n \\n for i in range(len(values)):\\n heapq.heappush(heap, (values[i][-1], i, len(values[i]) - 1))\\n \\n for day in range(1, len(values) * len(values[0]) + 1):\\n val, shop, idx = heapq.heappop(heap)\\n result += val * day\\n \\n if idx > 0:\\n heapq.heappush(heap, (values[shop][idx - 1], shop, idx - 1))\\n \\n return result\\n \\n
\\n###Kotlin
\\nclass Solution {\\n fun maxSpending(values: Array<IntArray>): Long {\\n var result = 0L\\n val heap = PriorityQueue<Triple<Long, Int, Int>> { a, b ->\\n a.first.compareTo(b.first)\\n }\\n\\n for (i in values.indices) {\\n heap.add(Triple(values[i][values[0].size - 1].toLong(), i, values[0].size - 1))\\n }\\n\\n for (day in 1..values.size * values[0].size) {\\n val (value, shop, idx) = heap.poll()\\n result += value * day\\n\\n if (idx > 0) {\\n heap.add(Triple(values[shop][idx - 1].toLong(), shop, idx - 1))\\n }\\n }\\n\\n return result\\n }\\n}\\n
\\n时间复杂度: $O(mn\\\\cdot log(m))$,其中 $m$ 为商店的数量,$n$ 为商品的数量。我们需要使用大小为 $m$ 的优先队列获取当前所有商店中价格最低的商品。
\\n空间复杂度: $O(m)$,其中 $m$ 为商店的数量。我们需要使用大小为 $m$ 的优先队列获取当前所有商店中价格最低的商品。
\\n按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
\\n\\nn 皇后问题 研究的是如何将 n
个皇后放置在 n×n
的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n
,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 \'Q\'
和 \'.\'
分别代表了皇后和空位。
\\n\\n
示例 1:
\\n输入:n = 4\\n输出:[[\\".Q..\\",\\"...Q\\",\\"Q...\\",\\"..Q.\\"],[\\"..Q.\\",\\"Q...\\",\\"...Q\\",\\".Q..\\"]]\\n解释:如上图所示,4 皇后问题存在两个不同的解法。\\n\\n\\n
示例 2:
\\n\\n输入:n = 1\\n输出:[[\\"Q\\"]]\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 9
根据题目描述,只要个位数之和不等于两位数之和,那么 Alice 一定可以选择一个较大的和来获胜。
\\n###python
\\nclass Solution:\\n def canAliceWin(self, nums: List[int]) -> bool:\\n a = sum(x for x in nums if x < 10)\\n b = sum(x for x in nums if x > 9)\\n return a != b\\n
\\n###java
\\nclass Solution {\\n public boolean canAliceWin(int[] nums) {\\n int a = 0, b = 0;\\n for (int x : nums) {\\n if (x < 10) {\\n a += x;\\n } else {\\n b += x;\\n }\\n }\\n return a != b;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canAliceWin(vector<int>& nums) {\\n int a = 0, b = 0;\\n for (int x : nums) {\\n if (x < 10) {\\n a += x;\\n } else {\\n b += x;\\n }\\n }\\n return a != b;\\n }\\n};\\n
\\n###go
\\nfunc canAliceWin(nums []int) bool {\\na, b := 0, 0\\nfor _, x := range nums {\\nif x < 10 {\\na += x\\n} else {\\nb += x\\n}\\n}\\nreturn a != b\\n}\\n
\\n###ts
\\nfunction canAliceWin(nums: number[]): boolean {\\n let [a, b] = [0, 0];\\n for (const x of nums) {\\n if (x < 10) {\\n a += x;\\n } else {\\n b += x;\\n }\\n }\\n return a !== b;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:求和 根据题目描述,只要个位数之和不等于两位数之和,那么 Alice 一定可以选择一个较大的和来获胜。\\n\\n###python\\n\\nclass Solution:\\n def canAliceWin(self, nums: List[int]) -> bool:\\n a = sum(x for x in nums if x < 10)\\n b = sum(x for x in nums if x > 9)\\n return a != b\\n\\n\\n###java\\n\\nclass Solution {\\n public…","guid":"https://leetcode.cn/problems/find-if-digit-game-can-be-won//solution/python3javacgotypescript-yi-ti-yi-jie-qi-2f8j","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-30T00:05:14.055Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-判断是否可以赢得数字游戏🟢","url":"https://leetcode.cn/problems/find-if-digit-game-can-be-won/","content":"给你一个 正整数 数组 nums
。
Alice 和 Bob 正在玩游戏。在游戏中,Alice 可以从 nums
中选择所有个位数 或 所有两位数,剩余的数字归 Bob 所有。如果 Alice 所选数字之和 严格大于 Bob 的数字之和,则 Alice 获胜。
如果 Alice 能赢得这场游戏,返回 true
;否则,返回 false
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3,4,10]
\\n\\n输出:false
\\n\\n解释:
\\n\\nAlice 不管选个位数还是两位数都无法赢得比赛。
\\n示例 2:
\\n\\n输入:nums = [1,2,3,4,5,14]
\\n\\n输出:true
\\n\\n解释:
\\n\\nAlice 选择个位数可以赢得比赛,所选数字之和为 15。
\\n示例 3:
\\n\\n输入:nums = [5,5,5,25]
\\n\\n输出:true
\\n\\n解释:
\\n\\nAlice 选择两位数可以赢得比赛,所选数字之和为 25。
\\n\\n\\n
提示:
\\n\\n1 <= nums.length <= 100
1 <= nums[i] <= 99
注意题目保证 $n$ 是偶数。
\\n###py
\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n cnt = sorted(Counter(arr).values(), reverse=True)\\n m = len(arr) // 2\\n for i, s in enumerate(accumulate(cnt)):\\n if s >= m:\\n return i + 1\\n
\\n###java
\\nclass Solution {\\n public int minSetSize(int[] arr) {\\n Map<Integer, Integer> freq = new HashMap<>();\\n for (int x : arr) {\\n freq.merge(x, 1, Integer::sum); // freq[x]++\\n }\\n\\n List<Integer> cnt = new ArrayList<>(freq.values());\\n cnt.sort((a, b) -> b - a);\\n\\n int s = 0;\\n for (int i = 0; ; i++) {\\n s += cnt.get(i);\\n if (s >= arr.length / 2) {\\n return i + 1;\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minSetSize(vector<int>& arr) {\\n unordered_map<int, int> freq;\\n for (int x : arr) {\\n freq[x]++;\\n }\\n\\n vector<int> cnt;\\n for (auto& [_, c] : freq) {\\n cnt.push_back(c);\\n }\\n ranges::sort(cnt, greater());\\n\\n int s = 0;\\n for (int i = 0; ; i++) {\\n s += cnt[i];\\n if (s >= arr.size() / 2) {\\n return i + 1;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc minSetSize(arr []int) int {\\nfreq := map[int]int{}\\nfor _, x := range arr {\\nfreq[x]++\\n}\\n\\ncnt := slices.SortedFunc(maps.Values(freq), func(a, b int) int { return b - a })\\n\\ns := 0\\nfor i, c := range cnt {\\ns += c\\nif s >= len(arr)/2 {\\nreturn i + 1\\n}\\n}\\npanic(\\"impossible\\")\\n}\\n
\\n###py
\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n cnt = [0] * (max(arr) + 1)\\n for x in arr:\\n cnt[x] += 1\\n cnt.sort(reverse=True)\\n\\n m = len(arr) // 2\\n for i, s in enumerate(accumulate(cnt)):\\n if s >= m:\\n return i + 1\\n
\\n###java
\\nclass Solution {\\n public int minSetSize(int[] arr) {\\n int mx = 0;\\n for (int x : arr) {\\n mx = Math.max(mx, x);\\n }\\n\\n int[] cnt = new int[mx + 1];\\n for (int x : arr) {\\n cnt[x]++;\\n }\\n Arrays.sort(cnt);\\n\\n int s = 0;\\n for (int i = mx; ; i--) {\\n s += cnt[i];\\n if (s >= arr.length / 2) {\\n return mx + 1 - i;\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minSetSize(vector<int>& arr) {\\n int mx = ranges::max(arr);\\n vector<int> cnt(mx + 1);\\n for (int x : arr) {\\n cnt[x]++;\\n }\\n ranges::sort(cnt, greater());\\n\\n int s = 0;\\n for (int i = 0; ; i++) {\\n s += cnt[i];\\n if (s >= arr.size() / 2) {\\n return i + 1;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc minSetSize(arr []int) int {\\ncnt := make([]int, slices.Max(arr)+1)\\nfor _, x := range arr {\\ncnt[x]++\\n}\\nslices.SortFunc(cnt, func(a, b int) int { return b - a })\\n\\ns := 0\\nfor i, c := range cnt {\\ns += c\\nif s >= len(arr)/2 {\\nreturn i + 1\\n}\\n}\\npanic(\\"impossible\\")\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用哈希表(或者数组)统计每个元素的出现次数。 把出现次数从大到小排序,得到 $\\\\textit{cnt}$ 数组。\\n遍历 $\\\\textit{cnt}$,计算前缀和 $s$,直到 $s \\\\ge \\\\dfrac{n}{2}$ 为止,返回此时的下标加一,即为答案。\\n\\n注意题目保证 $n$ 是偶数。\\n\\n哈希表写法\\n\\n###py\\n\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n cnt = sorted(Counter(arr).values(), reverse=True)…","guid":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/ji-shu-cong-da-dao-xiao-tan-xin-pythonja-9vth","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-29T05:43:26.386Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"购买物品的最大开销","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/gou-mai-wu-pin-de-zui-da-kai-xiao-by-lee-zwv3","content":"思路与算法
\\n由于每一个商店的物品都已经按照价值单调递减排好序了,那么当我们选择某个商店购买物品时,都可以买到该商店中价值最低的物品。由于我们可以任意选择商店,这就说,我们总是可以买到当前所有物品中价值最低的那个。
\\n在开销的计算公式中,物品的价值会乘上购买它的天数。根据排序不等式,在理想状态下我们应该将所有商品按照价值从低到高排序,分别在第 $1$ 到 $m \\\\times n$ 天去购买。根据上一段的结论,我们一定是可以达到这个理想状态的。
\\n因此,我们可以将 $m \\\\times n$ 个商品按照价值进行排序,就可以得到答案,但这样做的时间复杂度是 $O(mn \\\\log (mn))$,没有进一步用到「每一个商店的物品都已经按照价值单调递减排好序」这个性质。我们可以使用「23. 合并 K 个升序链表」中的方法,使用一个小根堆,存储每个商店当前价值最小的物品,那么小根堆的堆顶就是全局价值最小的物品。随后,我们将该物品在对应的商店中的下一个物品放入小根堆中,重复一共 $m \\\\times n$ 次操作即可,时间复杂度降低至 $O(mn \\\\log m)$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long maxSpending(vector<vector<int>>& values) {\\n int m = values.size(), n = values[0].size();\\n priority_queue<ti3, vector<ti3>, greater<ti3>> q;\\n for (int i = 0; i < m; ++i) {\\n q.emplace(values[i][n - 1], i, n - 1);\\n }\\n long long ans = 0;\\n for (int turn = 1; turn <= m * n; ++turn) {\\n auto [val, i, j] = q.top();\\n q.pop();\\n ans += static_cast<long long>(val) * turn;\\n if (j > 0) {\\n q.emplace(values[i][j - 1], i, j - 1);\\n }\\n }\\n return ans;\\n }\\n\\nprivate:\\n using ti3 = tuple<int, int, int>;\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n m, n = len(values), len(values[0])\\n q = [(values[i][-1], i, n - 1) for i in range(m)]\\n heapify(q)\\n ans = 0\\n for turn in range(1, m * n + 1):\\n val, i, j = heappop(q)\\n ans += val * turn\\n if j > 0:\\n heappush(q, (values[i][j - 1], i, j - 1))\\n return ans\\n
\\n###Java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n int m = values.length, n = values[0].length;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n for (int i = 0; i < m; i++) {\\n pq.offer(new int[]{values[i][n - 1], i, n - 1});\\n }\\n\\n long ans = 0;\\n for (int turn = 1; turn <= m * n; turn++) {\\n int[] top = pq.poll();\\n int val = top[0], i = top[1], j = top[2];\\n ans += (long)val * turn;\\n if (j > 0) {\\n pq.offer(new int[]{values[i][j - 1], i, j - 1});\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaxSpending(int[][] values) {\\n int m = values.Length, n = values[0].Length;\\n var pq = new PriorityQueue<int[], int>(\\n Comparer<int>.Create((a, b) => a.CompareTo(b))\\n );\\n for (int i = 0; i < m; i++) {\\n pq.Enqueue(new int[]{values[i][n - 1], i, n - 1}, values[i][n - 1]);\\n }\\n long ans = 0;\\n for (int turn = 1; turn <= m * n; turn++) {\\n var top = pq.Dequeue();\\n int val = top[0], i = top[1], j = top[2];\\n ans += (long)val * turn;\\n if (j > 0) {\\n pq.Enqueue(new int[]{values[i][j - 1], i, j - 1}, values[i][j - 1]);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc maxSpending(values [][]int) int64 {\\n m, n := len(values), len(values[0])\\n pq := &Heap{}\\n for i := 0; i < m; i++ {\\n heap.Push(pq, []int{values[i][n-1], i, n - 1})\\n }\\n ans := int64(0)\\n for turn := 1; turn <= m * n; turn++ {\\n top := heap.Pop(pq).([]int)\\n val, i, j := top[0], top[1], top[2]\\n ans += int64(val) * int64(turn)\\n if j > 0 {\\n heap.Push(pq, []int{values[i][j-1], i, j - 1})\\n }\\n }\\n return ans\\n}\\n\\ntype Heap [][]int\\n\\nfunc (h Heap) Len() int { \\n return len(h) \\n}\\n\\nfunc (h Heap) Less(i, j int) bool { \\n if (h[i][0] == h[j][0]) {\\n return h[i][1] < h[j][1]\\n }\\n return h[i][0] < h[j][0]\\n}\\n\\nfunc (h Heap) Swap(i, j int) { \\n h[i], h[j] = h[j], h[i] \\n}\\n\\nfunc (h *Heap) Push(x interface{}) { \\n *h = append(*h, x.([]int)) \\n}\\n\\nfunc (h *Heap) Pop() interface{} {\\nold := *h\\nn := len(old)\\nx := old[n - 1]\\n*h = old[0 : n - 1]\\nreturn x\\n}\\n
\\n###C
\\n#define MIN_QUEUE_SIZE 64\\n\\ntypedef struct Element {\\n int data[3];\\n} Element;\\n\\ntypedef bool (*compare)(const void *, const void *);\\n\\ntypedef struct PriorityQueue {\\n Element *arr;\\n int capacity;\\n int queueSize;\\n compare lessFunc;\\n} PriorityQueue;\\n\\nstatic bool less(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] > e2->data[0] || \\\\\\n (e1->data[0] == e2->data[0] && \\\\\\n e1->data[1] > e2->data[1]);\\n}\\n\\nstatic bool greater(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] < e2->data[0];\\n}\\n\\nstatic void memswap(void *m1, void *m2, size_t size){\\n unsigned char *a = (unsigned char*)m1;\\n unsigned char *b = (unsigned char*)m2;\\n while (size--) {\\n *b ^= *a ^= *b ^= *a;\\n a++;\\n b++;\\n }\\n}\\n\\nstatic void swap(Element *arr, int i, int j) {\\n memswap(&arr[i], &arr[j], sizeof(Element));\\n}\\n\\nstatic void down(Element *arr, int size, int i, compare cmpFunc) {\\n for (int k = 2 * i + 1; k < size; k = 2 * k + 1) {\\n if (k + 1 < size && cmpFunc(&arr[k], &arr[k + 1])) {\\n k++;\\n }\\n if (cmpFunc(&arr[k], &arr[(k - 1) / 2])) {\\n break;\\n }\\n swap(arr, k, (k - 1) / 2);\\n }\\n}\\n\\nPriorityQueue *createPriorityQueue(compare cmpFunc) {\\n PriorityQueue *obj = (PriorityQueue *)malloc(sizeof(PriorityQueue));\\n obj->capacity = MIN_QUEUE_SIZE;\\n obj->arr = (Element *)malloc(sizeof(Element) * obj->capacity);\\n obj->queueSize = 0;\\n obj->lessFunc = cmpFunc;\\n return obj;\\n}\\n\\nvoid heapfiy(PriorityQueue *obj) {\\n for (int i = obj->queueSize / 2 - 1; i >= 0; i--) {\\n down(obj->arr, obj->queueSize, i, obj->lessFunc);\\n }\\n}\\n\\nvoid enQueue(PriorityQueue *obj, Element *e) {\\n // we need to alloc more space, just twice space size\\n if (obj->queueSize == obj->capacity) {\\n obj->capacity *= 2;\\n obj->arr = realloc(obj->arr, sizeof(Element) * obj->capacity);\\n }\\n memcpy(&obj->arr[obj->queueSize], e, sizeof(Element));\\n for (int i = obj->queueSize; i > 0 && obj->lessFunc(&obj->arr[(i - 1) / 2], &obj->arr[i]); i = (i - 1) / 2) {\\n swap(obj->arr, i, (i - 1) / 2);\\n }\\n obj->queueSize++;\\n}\\n\\nElement* deQueue(PriorityQueue *obj) {\\n swap(obj->arr, 0, obj->queueSize - 1);\\n down(obj->arr, obj->queueSize - 1, 0, obj->lessFunc);\\n Element *e = &obj->arr[obj->queueSize - 1];\\n obj->queueSize--;\\n return e;\\n}\\n\\nbool isEmpty(const PriorityQueue *obj) {\\n return obj->queueSize == 0;\\n}\\n\\nElement* front(const PriorityQueue *obj) {\\n if (obj->queueSize == 0) {\\n return NULL;\\n } else {\\n return &obj->arr[0];\\n }\\n}\\n\\nvoid clear(PriorityQueue *obj) {\\n obj->queueSize = 0;\\n}\\n\\nint size(const PriorityQueue *obj) {\\n return obj->queueSize;\\n}\\n\\nvoid freeQueue(PriorityQueue *obj) {\\n free(obj->arr);\\n free(obj);\\n}\\n\\nlong long maxSpending(int** values, int valuesSize, int* valuesColSize) {\\n int m = valuesSize, n = valuesColSize[0];\\n PriorityQueue *pq = createPriorityQueue(less);\\n Element e;\\n for (int i = 0; i < m; ++i) {\\n e.data[0] = values[i][n - 1];\\n e.data[1] = i;\\n e.data[2] = n - 1;\\n enQueue(pq, &e);\\n }\\n long long ans = 0;\\n for (int turn = 1; turn <= m * n; ++turn) {\\n Element *p = front(pq);\\n int val = p->data[0];\\n int i = p->data[1];\\n int j = p->data[2];\\n deQueue(pq);\\n ans += (long long)val * turn;\\n if (j > 0) {\\n e.data[0] = values[i][j - 1];\\n e.data[1] = i;\\n e.data[2] = j - 1;\\n enQueue(pq, &e);\\n }\\n }\\n freeQueue(pq);\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar maxSpending = function(values) {\\n const m = values.length, n = values[0].length;\\n const pq = new MinPriorityQueue();\\n for (let i = 0; i < m; i++) {\\n pq.enqueue([values[i][n - 1], i, n - 1], values[i][n - 1]);\\n }\\n \\n let ans = 0;\\n for (let turn = 1; turn <= m * n; turn++) {\\n const [val, i, j] = pq.dequeue().element;\\n ans += val * turn;\\n if (j > 0) {\\n pq.enqueue([values[i][j - 1], i, j - 1], values[i][j - 1]);\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction maxSpending(values: number[][]): number {\\n const m = values.length, n = values[0].length;\\n const pq = new MinPriorityQueue();\\n for (let i = 0; i < m; i++) {\\n pq.enqueue([values[i][n - 1], i, n - 1], values[i][n - 1]);\\n }\\n \\n let ans = 0;\\n for (let turn = 1; turn <= m * n; turn++) {\\n const [val, i, j] = pq.dequeue().element;\\n ans += val * turn;\\n if (j > 0) {\\n pq.enqueue([values[i][j - 1], i, j - 1], values[i][j - 1]);\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nuse std::collections::BinaryHeap;\\n\\n#[derive(Eq, PartialEq)]\\nstruct Item {\\n value: i32,\\n i: usize,\\n j: usize,\\n}\\n\\nimpl Ord for Item {\\n fn cmp(&self, other: &Self) -> std::cmp::Ordering {\\n other.value.cmp(&self.value)\\n }\\n}\\n\\nimpl PartialOrd for Item {\\n fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\\n Some(self.cmp(other))\\n }\\n}\\n\\nimpl Solution {\\n pub fn max_spending(values: Vec<Vec<i32>>) -> i64 {\\n let m = values.len();\\n let n = values[0].len();\\n let mut pq = BinaryHeap::new();\\n for i in 0..m {\\n pq.push(Item { value: values[i][n - 1], i, j: n - 1 });\\n }\\n \\n let mut ans: i64 = 0;\\n for turn in 1..= m * n {\\n if let Some(top) = pq.pop() {\\n let val = top.value;\\n let i = top.i;\\n let j = top.j;\\n ans += (val as i64) * (turn as i64);\\n if j > 0 {\\n pq.push(Item { value: values[i][j - 1], i, j: j - 1 });\\n }\\n }\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(mn \\\\log m)$。
\\n空间复杂度:$O(m)$,即为小根堆(优先队列)需要使用的空间。
\\n我们定义 $f[i][j]$ 表示下标 $[0,..i]$ 的单调数组对的数目,且 $arr1[i] = j$。初始时 $[i][j] = 0$,答案为 $\\\\sum_{j=0}^{\\\\textit{nums}[n-1]} f[n-1][j]$。
\\n当 $i = 0$ 时,有 $[0][j] = 1$,其中 $0 \\\\leq j \\\\leq \\\\textit{nums}[0]$。
\\n当 $i > 0$ 时,我们可以根据 $f[i-1][j\']$ 计算 $f[i][j]$。由于 $\\\\textit{arr1}$ 是单调非递减的,因此 $j\' \\\\leq j$。又由于 $\\\\textit{arr2}$ 是单调非递增的,因此 $\\\\textit{nums}[i] - j \\\\leq \\\\textit{nums}[i - 1] - j\'$。即 $j\' \\\\leq \\\\min(j, j + \\\\textit{nums}[i - 1] - \\\\textit{nums}[i])$。
\\n答案为 $\\\\sum_{j=0}^{\\\\textit{nums}[n-1]} f[n-1][j]$。
\\n###python
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n mod = 10**9 + 7\\n n, m = len(nums), max(nums)\\n f = [[0] * (m + 1) for _ in range(n)]\\n for j in range(nums[0] + 1):\\n f[0][j] = 1\\n for i in range(1, n):\\n s = list(accumulate(f[i - 1]))\\n for j in range(nums[i] + 1):\\n k = min(j, j + nums[i - 1] - nums[i])\\n if k >= 0:\\n f[i][j] = s[k] % mod\\n return sum(f[-1][: nums[-1] + 1]) % mod\\n
\\n###java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int mod = (int) 1e9 + 7;\\n int n = nums.length;\\n int m = Arrays.stream(nums).max().getAsInt();\\n int[][] f = new int[n][m + 1];\\n for (int j = 0; j <= nums[0]; ++j) {\\n f[0][j] = 1;\\n }\\n int[] g = new int[m + 1];\\n for (int i = 1; i < n; ++i) {\\n g[0] = f[i - 1][0];\\n for (int j = 1; j <= m; ++j) {\\n g[j] = (g[j - 1] + f[i - 1][j]) % mod;\\n }\\n for (int j = 0; j <= nums[i]; ++j) {\\n int k = Math.min(j, j + nums[i - 1] - nums[i]);\\n if (k >= 0) {\\n f[i][j] = g[k];\\n }\\n }\\n }\\n int ans = 0;\\n for (int j = 0; j <= nums[n - 1]; ++j) {\\n ans = (ans + f[n - 1][j]) % mod;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int mod = 1e9 + 7;\\n int n = nums.size();\\n int m = *max_element(nums.begin(), nums.end());\\n vector<vector<int>> f(n, vector<int>(m + 1));\\n for (int j = 0; j <= nums[0]; ++j) {\\n f[0][j] = 1;\\n }\\n vector<int> g(m + 1);\\n for (int i = 1; i < n; ++i) {\\n g[0] = f[i - 1][0];\\n for (int j = 1; j <= m; ++j) {\\n g[j] = (g[j - 1] + f[i - 1][j]) % mod;\\n }\\n for (int j = 0; j <= nums[i]; ++j) {\\n int k = min(j, j + nums[i - 1] - nums[i]);\\n if (k >= 0) {\\n f[i][j] = g[k];\\n }\\n }\\n }\\n int ans = 0;\\n for (int j = 0; j <= nums[n - 1]; ++j) {\\n ans = (ans + f[n - 1][j]) % mod;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countOfPairs(nums []int) (ans int) {\\nconst mod int = 1e9 + 7\\nn := len(nums)\\nm := slices.Max(nums)\\nf := make([][]int, n)\\nfor i := range f {\\nf[i] = make([]int, m+1)\\n}\\nfor j := 0; j <= nums[0]; j++ {\\nf[0][j] = 1\\n}\\ng := make([]int, m+1)\\nfor i := 1; i < n; i++ {\\ng[0] = f[i-1][0]\\nfor j := 1; j <= m; j++ {\\ng[j] = (g[j-1] + f[i-1][j]) % mod\\n}\\nfor j := 0; j <= nums[i]; j++ {\\nk := min(j, j+nums[i-1]-nums[i])\\nif k >= 0 {\\nf[i][j] = g[k]\\n}\\n}\\n}\\nfor j := 0; j <= nums[n-1]; j++ {\\nans = (ans + f[n-1][j]) % mod\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countOfPairs(nums: number[]): number {\\n const mod = 1e9 + 7;\\n const n = nums.length;\\n const m = Math.max(...nums);\\n const f: number[][] = Array.from({ length: n }, () => Array(m + 1).fill(0));\\n for (let j = 0; j <= nums[0]; j++) {\\n f[0][j] = 1;\\n }\\n const g: number[] = Array(m + 1).fill(0);\\n for (let i = 1; i < n; i++) {\\n g[0] = f[i - 1][0];\\n for (let j = 1; j <= m; j++) {\\n g[j] = (g[j - 1] + f[i - 1][j]) % mod;\\n }\\n for (let j = 0; j <= nums[i]; j++) {\\n const k = Math.min(j, j + nums[i - 1] - nums[i]);\\n if (k >= 0) {\\n f[i][j] = g[k];\\n }\\n }\\n }\\n let ans = 0;\\n for (let j = 0; j <= nums[n - 1]; j++) {\\n ans = (ans + f[n - 1][j]) % mod;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times m)$,空间复杂度 $O(n \\\\times m)$。其中 $n$ 表示数组 $\\\\textit{nums}$ 的长度,而 $m$ 表示数组 $\\\\textit{nums}$ 中的最大值。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:动态规划 + 前缀和优化 我们定义 $f[i][j]$ 表示下标 $[0,..i]$ 的单调数组对的数目,且 $arr1[i] = j$。初始时 $[i][j] = 0$,答案为 $\\\\sum_{j=0}^{\\\\textit{nums}[n-1]} f[n-1][j]$。\\n\\n当 $i = 0$ 时,有 $[0][j] = 1$,其中 $0 \\\\leq j \\\\leq \\\\textit{nums}[0]$。\\n\\n当 $i > 0$ 时,我们可以根据 $f[i-1][j\']$ 计算 $f[i][j]$。由于 $\\\\textit{arr1}$ 是单调非递减的,因此 $j…","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/python3javacgotypescript-yi-ti-yi-jie-do-0orn","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-29T00:08:13.279Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-单调数组对的数目 II🔴","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii/","content":"给你一个长度为 n
的 正 整数数组 nums
。
如果两个 非负 整数数组 (arr1, arr2)
满足以下条件,我们称它们是 单调 数组对:
n
。arr1
是单调 非递减 的,换句话说 arr1[0] <= arr1[1] <= ... <= arr1[n - 1]
。arr2
是单调 非递增 的,换句话说 arr2[0] >= arr2[1] >= ... >= arr2[n - 1]
。0 <= i <= n - 1
都有 arr1[i] + arr2[i] == nums[i]
。请你返回所有 单调 数组对的数目。
\\n\\n由于答案可能很大,请你将它对 109 + 7
取余 后返回。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,3,2]
\\n\\n输出:4
\\n\\n解释:
\\n\\n单调数组对包括:
\\n\\n([0, 1, 1], [2, 2, 1])
([0, 1, 2], [2, 2, 0])
([0, 2, 2], [2, 1, 0])
([1, 2, 2], [1, 1, 0])
示例 2:
\\n\\n输入:nums = [5,5,5,5]
\\n\\n输出:126
\\n\\n\\n
提示:
\\n\\n1 <= n == nums.length <= 2000
1 <= nums[i] <= 1000
首先,由于无法从 $5$ 移动到其他单元格,所以当 $n\\\\ge 2$ 时,马的初始位置不能等于 $5$。
\\n特判 $n=1$ 的情况,直接返回 $10$。
\\n在下文中,$n\\\\ge 2$。
\\n比如 $n=3$,我们要解决的问题(原问题)是:
\\n枚举移动到的位置,比如一开始在 $1$,移动到 $6$,那么问题变成:
\\n这是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n根据上面的讨论,我们需要在递归过程中跟踪以下信息:
\\n因此,定义状态为 $\\\\textit{dfs}(i,j)$,表示把马放在单元格 $j$ 上,然后移动 $i$ 步,有多少种移动方案。
\\n枚举马能移动到的单元格 $k$,问题变成:把马放在单元格 $k$ 上,然后移动 $i-1$ 步,有多少种移动方案,即 $\\\\textit{dfs}(i-1,k)$。
\\n累加得
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\sum_{k} \\\\textit{dfs}(i-1,k)
\\n$$
递归边界:$\\\\textit{dfs}(0,j)=1$。无法移动,算作一种移动方案,对应着电话号码 $j$。
\\n递归入口:$\\\\sum\\\\limits_{j=0}^{9}\\\\textit{dfs}(n-1,j)$,这是原问题,也是答案。注意 $\\\\textit{dfs}(n-1,5)=0$。
\\n具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\nMOD = 1_000_000_007\\nNEXT = (4, 6), (6, 8), (7, 9), (4, 8), (0, 3, 9), (), (0, 1, 7), (2, 6), (1, 3), (2, 4)\\n\\n@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\ndef dfs(i: int, j: int) -> int:\\n if i == 0:\\n return 1\\n return sum(dfs(i - 1, k) for k in NEXT[j]) % MOD\\n\\nclass Solution:\\n def knightDialer(self, n: int) -> int:\\n if n == 1:\\n return 10\\n return (sum(dfs(n - 1, j) for j in range(10))) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int[][] NEXT = {\\n {4, 6}, {6, 8}, {7, 9}, {4, 8}, {0, 3, 9}, {}, {0, 1, 7}, {2, 6}, {1, 3}, {2, 4}\\n };\\n private static final int[][] memo = new int[5000][10];\\n\\n public int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n int ans = 0;\\n for (int j = 0; j < 10; j++) {\\n ans = (ans + dfs(n - 1, j)) % MOD;\\n }\\n return ans;\\n }\\n\\n private int dfs(int i, int j) {\\n if (i == 0) {\\n return 1;\\n }\\n if (memo[i][j] > 0) { // 之前计算过\\n return memo[i][j];\\n }\\n int res = 0;\\n for (int k : NEXT[j]) {\\n res = (res + dfs(i - 1, k)) % MOD;\\n }\\n return memo[i][j] = res; // 记忆化\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nconst vector<int> NEXT[10] = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {0, 3, 9}, {}, {0, 1, 7}, {2, 6}, {1, 3}, {2, 4}};\\nint memo[5000][10];\\n\\nint dfs(int i, int j) {\\n if (i == 0) {\\n return 1;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n for (int k : NEXT[j]) {\\n res = (res + dfs(i - 1, k)) % MOD;\\n }\\n return res;\\n}\\n\\nclass Solution {\\npublic:\\n int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n long long ans = 0;\\n for (int j = 0; j < 10; j++) {\\n ans += dfs(n - 1, j);\\n }\\n return ans % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nvar next = [10][]int{{4, 6}, {6, 8}, {7, 9}, {4, 8}, {0, 3, 9}, {}, {0, 1, 7}, {2, 6}, {1, 3}, {2, 4}}\\nvar memo [5000][10]int\\n\\nfunc dfs(i, j int) int {\\n if i == 0 {\\n return 1\\n }\\n p := &memo[i][j]\\n if *p > 0 { // 之前计算过\\n return *p\\n }\\n res := 0\\n for _, k := range next[j] {\\n res += dfs(i-1, k)\\n }\\n res %= mod\\n *p = res // 记忆化\\n return res\\n}\\n\\nfunc knightDialer(n int) int {\\n if n == 1 {\\n return 10\\n }\\n ans := 0\\n for j := range 10 {\\n ans += dfs(n-1, j)\\n }\\n return ans % mod\\n}\\n
\\n如下图所示,其实只有 $4$ 种本质不同的 $j$。
\\n令 $j=0,1,2,3$ 对应上面的 $A,B,C,D$ 类,那么有状态转移方程
\\n$$
\\n\\\\begin{aligned}
\\n\\\\textit{dfs}(i,0) &= \\\\textit{dfs}(i - 1, 1) + \\\\textit{dfs}(i - 1, 2) \\\\
\\n\\\\textit{dfs}(i,1) &= 2\\\\cdot \\\\textit{dfs}(i - 1, 0) \\\\
\\n\\\\textit{dfs}(i,2) &= 2\\\\cdot \\\\textit{dfs}(i - 1, 0) + \\\\textit{dfs}(i - 1, 3) \\\\
\\n\\\\textit{dfs}(i,3) &= 2\\\\cdot \\\\textit{dfs}(i - 1, 2) \\\\
\\n\\\\end{aligned}
\\n$$
答案为
\\n$$
\\n4\\\\cdot\\\\textit{dfs}(n - 1, 0) + 2\\\\cdot \\\\textit{dfs}(n - 1, 1) + 2\\\\cdot \\\\textit{dfs}(n - 1, 2) + \\\\textit{dfs}(n - 1, 3)
\\n$$
\\n\\n注:相当于把 $\\\\sum\\\\limits_{j=0}^{9}\\\\textit{dfs}(n-1,j)$ 中的 $j=1,3,7,9$ 合并成 $4\\\\cdot\\\\textit{dfs}(n - 1, 0)$,其他同理。
\\n
###py
\\nMOD = 1_000_000_007\\n\\n@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\ndef dfs(i: int, j: int) -> int:\\n if i == 0:\\n return 1\\n if j == 0:\\n return (dfs(i - 1, 1) + dfs(i - 1, 2)) % MOD\\n if j == 1:\\n return dfs(i - 1, 0) * 2 % MOD\\n if j == 2:\\n return (dfs(i - 1, 0) * 2 + dfs(i - 1, 3)) % MOD\\n return dfs(i - 1, 2) * 2 % MOD\\n\\nclass Solution:\\n def knightDialer(self, n: int) -> int:\\n if n == 1:\\n return 10\\n return (dfs(n - 1, 0) * 4 + dfs(n - 1, 1) * 2 +\\n dfs(n - 1, 2) * 2 + dfs(n - 1, 3)) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final long[][] memo = new long[5000][10];\\n\\n public int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n return (int) ((dfs(n - 1, 0) * 4 + dfs(n - 1, 1) * 2 +\\n dfs(n - 1, 2) * 2 + dfs(n - 1, 3)) % MOD);\\n }\\n\\n private long dfs(int i, int j) {\\n if (i == 0) {\\n return 1;\\n }\\n if (memo[i][j] > 0) { // 之前计算过\\n return memo[i][j];\\n }\\n if (j == 0) {\\n memo[i][j] = (dfs(i - 1, 1) + dfs(i - 1, 2)) % MOD;\\n } else if (j == 1) {\\n memo[i][j] = dfs(i - 1, 0) * 2 % MOD;\\n } else if (j == 2) {\\n memo[i][j] = (dfs(i - 1, 0) * 2 + dfs(i - 1, 3)) % MOD;\\n } else {\\n memo[i][j] = dfs(i - 1, 2) * 2 % MOD;\\n }\\n return memo[i][j];\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nlong long memo[5000][10];\\n\\nlong long dfs(int i, int j) {\\n if (i == 0) {\\n return 1;\\n }\\n long long& res = memo[i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n if (j == 0) {\\n res = (dfs(i - 1, 1) + dfs(i - 1, 2)) % MOD;\\n } else if (j == 1) {\\n res = dfs(i - 1, 0) * 2 % MOD;\\n } else if (j == 2) {\\n res = (dfs(i - 1, 0) * 2 + dfs(i - 1, 3)) % MOD;\\n } else {\\n res = dfs(i - 1, 2) * 2 % MOD;\\n }\\n return res;\\n}\\n\\nclass Solution {\\npublic:\\n int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n return (dfs(n - 1, 0) * 4 + dfs(n - 1, 1) * 2 +\\n dfs(n - 1, 2) * 2 + dfs(n - 1, 3)) % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nvar memo [5000][10]int\\n\\nfunc dfs(i, j int) int {\\n if i == 0 {\\n return 1\\n }\\n p := &memo[i][j]\\n if *p > 0 { // 之前计算过\\n return *p\\n }\\n if j == 0 {\\n *p = (dfs(i-1, 1) + dfs(i-1, 2)) % mod\\n } else if j == 1 {\\n *p = dfs(i-1, 0) * 2 % mod\\n } else if j == 2 {\\n *p = (dfs(i-1, 0)*2 + dfs(i-1, 3)) % mod\\n } else {\\n *p = dfs(i-1, 2) * 2 % mod\\n }\\n return *p\\n}\\n\\nfunc knightDialer(n int) int {\\n if n == 1 {\\n return 10\\n }\\n return (dfs(n-1, 0)*4 + dfs(n-1, 1)*2 + dfs(n-1, 2)*2 + dfs(n-1, 3)) % mod\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示把马放在 $j$ 类单元格上,然后移动 $i$ 步,有多少种移动方案。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\n\\\\begin{aligned}
\\nf[i][0] &= f[i-1][1] + f[i-1][2] \\\\
\\nf[i][1] &= 2\\\\cdot f[i-1][0] \\\\
\\nf[i][2] &= 2\\\\cdot f[i-1][0] + f[i-1][3] \\\\
\\nf[i][3] &= 2\\\\cdot f[i-1][2] \\\\
\\n\\\\end{aligned}
\\n$$
初始值 $f[0][j]=1$,翻译自递归边界 $\\\\textit{dfs}(0,j)=1$。
\\n答案为 $4\\\\cdot f[n-1][0] + 2\\\\cdot f[n-1][1] + 2\\\\cdot f[n-1][2] + f[n-1][3]$,翻译自递归入口 $4\\\\cdot\\\\textit{dfs}(n - 1, 0) + 2\\\\cdot \\\\textit{dfs}(n - 1, 1) + 2\\\\cdot \\\\textit{dfs}(n - 1, 2) + \\\\textit{dfs}(n - 1, 3)$。
\\n###py
\\nMOD = 1_000_000_007\\nMX = 5000\\n\\nf = [[1] * 4 for _ in range(MX)]\\nfor i in range(1, MX):\\n f[i][0] = (f[i - 1][1] + f[i - 1][2]) % MOD\\n f[i][1] = f[i - 1][0] * 2 % MOD\\n f[i][2] = (f[i - 1][0] * 2 + f[i - 1][3]) % MOD\\n f[i][3] = f[i - 1][2] * 2 % MOD\\n\\nclass Solution:\\n def knightDialer(self, n: int) -> int:\\n if n == 1:\\n return 10\\n return (f[n - 1][0] * 4 + f[n - 1][1] * 2 +\\n f[n - 1][2] * 2 + f[n - 1][3]) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MX = 5000;\\n private static final long[][] f = new long[MX][4];\\n\\n static {\\n f[0][0] = f[0][1] = f[0][2] = f[0][3] = 1;\\n for (int i = 1; i < MX; i++) {\\n f[i][0] = (f[i - 1][1] + f[i - 1][2]) % MOD;\\n f[i][1] = f[i - 1][0] * 2 % MOD;\\n f[i][2] = (f[i - 1][0] * 2 + f[i - 1][3]) % MOD;\\n f[i][3] = f[i - 1][2] * 2 % MOD;\\n }\\n }\\n\\n public int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n return (int) ((f[n - 1][0] * 4 + f[n - 1][1] * 2 +\\n f[n - 1][2] * 2 + f[n - 1][3]) % MOD);\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 5000;\\nlong long f[MX][4];\\n\\nint init = []() {\\n f[0][0] = f[0][1] = f[0][2] = f[0][3] = 1;\\n for (int i = 1; i < MX; i++) {\\n f[i][0] = (f[i - 1][1] + f[i - 1][2]) % MOD;\\n f[i][1] = f[i - 1][0] * 2 % MOD;\\n f[i][2] = (f[i - 1][0] * 2 + f[i - 1][3]) % MOD;\\n f[i][3] = f[i - 1][2] * 2 % MOD;\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n return (f[n - 1][0] * 4 + f[n - 1][1] * 2 +\\n f[n - 1][2] * 2 + f[n - 1][3]) % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nvar f = [5000][4]int{{1, 1, 1, 1}}\\n\\nfunc init() {\\n for i := 1; i < len(f); i++ {\\n f[i][0] = (f[i-1][1] + f[i-1][2]) % mod\\n f[i][1] = f[i-1][0] * 2 % mod\\n f[i][2] = (f[i-1][0]*2 + f[i-1][3]) % mod\\n f[i][3] = f[i-1][2] * 2 % mod\\n }\\n}\\n\\nfunc knightDialer(n int) int {\\n if n == 1 {\\n return 10\\n }\\n return (f[n-1][0]*4 + f[n-1][1]*2 + f[n-1][2]*2 + f[n-1][3]) % mod\\n}\\n
\\n把状态转移方程用矩阵乘法表示,即
\\n$$
\\n\\\\begin{bmatrix}
\\nf[i][0] \\\\
\\nf[i][1] \\\\
\\nf[i][2] \\\\
\\nf[i][3] \\\\
\\n\\\\end{bmatrix}
\\n= \\\\begin{bmatrix}
\\n0 & 1 & 1 & 0 \\\\
\\n2 & 0 & 0 & 0 \\\\
\\n2 & 0 & 0 & 1 \\\\
\\n0 & 0 & 2 & 0 \\\\
\\n\\\\end{bmatrix}
\\n\\\\begin{bmatrix}
\\nf[i-1][0] \\\\
\\nf[i-1][1] \\\\
\\nf[i-1][2] \\\\
\\nf[i-1][3] \\\\
\\n\\\\end{bmatrix}
\\n$$
把上式中的三个矩阵分别记作 $F[i],M,F[i-1]$,即
\\n$$
\\nF[i] = M\\\\times F[i-1]
\\n$$
那么有
\\n$$
\\n\\\\begin{aligned}
\\nF[n] ={} & M\\\\times F[n-1] \\\\
\\n={} & M\\\\times M\\\\times F[n-2] \\\\
\\n={} & M\\\\times M\\\\times M\\\\times F[n-3] \\\\
\\n\\\\vdots & \\\\
\\n={} & M^n\\\\times F[0]
\\n\\\\end{aligned}
\\n$$
其中
\\n$$
\\nF[0] =
\\n\\\\begin{bmatrix}
\\n1 \\\\
\\n1 \\\\
\\n1 \\\\
\\n1 \\\\
\\n\\\\end{bmatrix}
\\n$$
$M^n$ 可以用快速幂计算,原理请看【图解】一张图秒懂快速幂。
\\n\\n\\n注:由于力扣不计入初始化的时间,实际运行时间可能不如递推写法。
\\n
###py
\\nMOD = 1_000_000_007\\n\\n# a @ b,其中 @ 是矩阵乘法\\ndef mul(a: List[List[int]], b: List[List[int]]) -> List[List[int]]:\\n return [[sum(x * y for x, y in zip(row, col)) % MOD for col in zip(*b)]\\n for row in a]\\n\\n# a^n @ f0\\ndef pow_mul(a: List[List[int]], n: int, f0: List[List[int]]) -> List[List[int]]:\\n res = f0\\n while n:\\n if n & 1:\\n res = mul(a, res)\\n a = mul(a, a)\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def knightDialer(self, n: int) -> int:\\n if n == 1:\\n return 10\\n f0 = [[1], [1], [1], [1]]\\n m = [[0, 1, 1, 0], [2, 0, 0, 0], [2, 0, 0, 1], [0, 0, 2, 0]]\\n m = pow_mul(m, n - 1, f0)\\n return (m[0][0] * 4 + m[1][0] * 2 + m[2][0] * 2 + m[3][0]) % MOD\\n
\\n###py
\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\n# a^n @ f0\\ndef pow(a: np.ndarray, n: int, f0: np.ndarray) -> np.ndarray:\\n res = f0\\n while n:\\n if n & 1:\\n res = a @ res % MOD\\n a = a @ a % MOD\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def knightDialer(self, n: int) -> int:\\n if n == 1:\\n return 10\\n f0 = np.ones((4,), dtype=object)\\n m = np.array([[0, 1, 1, 0], [2, 0, 0, 0], [2, 0, 0, 1], [0, 0, 2, 0]], dtype=object)\\n m = pow(m, n - 1, f0)\\n return (m[0] * 4 + m[1] * 2 + m[2] * 2 + m[3]) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n long[][] f0 = {{1}, {1}, {1}, {1}};\\n long[][] m = {\\n {0, 1, 1, 0},\\n {2, 0, 0, 0},\\n {2, 0, 0, 1},\\n {0, 0, 2, 0}\\n };\\n m = powMul(m, n - 1, f0);\\n return (int) ((m[0][0] * 4 + m[1][0] * 2 +\\n m[2][0] * 2 + m[3][0]) % MOD);\\n }\\n\\n // a^n * f0\\n private long[][] powMul(long[][] a, int n, long[][] f0) {\\n long[][] res = f0;\\n while (n > 0) {\\n if ((n & 1) > 0) {\\n res = mul(a, res);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n }\\n\\n // 返回矩阵 a 和矩阵 b 相乘的结果\\n private long[][] mul(long[][] a, long[][] b) {\\n long[][] c = new long[a.length][b[0].length];\\n for (int i = 0; i < a.length; i++) {\\n for (int k = 0; k < a[i].length; k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < b[k].length; j++) {\\n c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;\\n }\\n }\\n }\\n return c;\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\n\\nusing matrix = vector<vector<long long>>;\\n\\n// 返回矩阵 a 和矩阵 b 相乘的结果\\nmatrix mul(matrix& a, matrix& b) {\\n int n = a.size(), m = b[0].size();\\n matrix c = matrix(n, vector<long long>(m));\\n for (int i = 0; i < n; i++) {\\n for (int k = 0; k < a[i].size(); k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < m; j++) {\\n c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;\\n }\\n }\\n }\\n return c;\\n}\\n\\n// a^n * f0\\nmatrix powMul(matrix a, int n, matrix& f0) {\\n matrix res = f0;\\n while (n) {\\n if (n & 1) {\\n res = mul(a, res);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n}\\n\\nclass Solution {\\npublic:\\n int knightDialer(int n) {\\n if (n == 1) {\\n return 10;\\n }\\n matrix f0 = {{1}, {1}, {1}, {1}};\\n matrix m = {\\n {0, 1, 1, 0},\\n {2, 0, 0, 0},\\n {2, 0, 0, 1},\\n {0, 0, 2, 0},\\n };\\n m = powMul(m, n - 1, f0);\\n return (m[0][0] * 4 + m[1][0] * 2 +\\n m[2][0] * 2 + m[3][0]) % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\ntype matrix [][]int\\n\\nfunc newMatrix(n, m int) matrix {\\n a := make(matrix, n)\\n for i := range a {\\n a[i] = make([]int, m)\\n }\\n return a\\n}\\n\\n// 返回矩阵 a 和矩阵 b 相乘的结果\\nfunc (a matrix) mul(b matrix) matrix {\\n c := newMatrix(len(a), len(b[0]))\\n for i, row := range a {\\n for k, x := range row {\\n if x == 0 {\\n continue\\n }\\n for j, y := range b[k] {\\n c[i][j] = (c[i][j] + x*y) % mod\\n }\\n }\\n }\\n return c\\n}\\n\\n// a^n * f0\\nfunc (a matrix) powMul(n int, f0 matrix) matrix {\\n res := f0\\n for ; n > 0; n /= 2 {\\n if n%2 > 0 {\\n res = a.mul(res)\\n }\\n a = a.mul(a)\\n }\\n return res\\n}\\n\\nfunc knightDialer(n int) int {\\n if n == 1 {\\n return 10\\n }\\n f0 := matrix{{1}, {1}, {1}, {1}}\\n m := matrix{\\n {0, 1, 1, 0},\\n {2, 0, 0, 0},\\n {2, 0, 0, 1},\\n {0, 0, 2, 0},\\n }\\n m = m.powMul(n-1, f0)\\n return (m[0][0]*4 + m[1][0]*2 + m[2][0]*2 + m[3][0]) % mod\\n}\\n
\\n更多相似题目,见下面动态规划题单中的「§7.3 矩阵快速幂优化 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"首先,由于无法从 $5$ 移动到其他单元格,所以当 $n\\\\ge 2$ 时,马的初始位置不能等于 $5$。 特判 $n=1$ 的情况,直接返回 $10$。\\n\\n在下文中,$n\\\\ge 2$。\\n\\n一、寻找子问题\\n\\n比如 $n=3$,我们要解决的问题(原问题)是:\\n\\n把马放在单元格 $0$ 到 $9$(除了 $5$)上,然后移动 $n-1=2$ 步,一共有多少种移动方案?\\n\\n枚举移动到的位置,比如一开始在 $1$,移动到 $6$,那么问题变成:\\n\\n把马放在单元格 $6$ 上,然后移动 $n-2=1$ 步,一共有多少种移动方案?\\n\\n这是和原问题相似的、规模更小的子问题,可…","guid":"https://leetcode.cn/problems/knight-dialer//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-x06l","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-28T14:01:15.485Z","media":[{"url":"https://pic.leetcode.cn/1732785081-HHnGDF-lc935-c.png","type":"photo","width":2614,"height":2375,"blurhash":"L7S6Pl_3~q_3%M%M-;WB9Fxuj[M{"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【常规三解】一步步推导,记忆化搜索 -> 动规 -> 前缀和优化","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i//solution/chang-gui-san-jie-yi-bu-bu-tui-dao-ji-yi-jego","content":"【常规三解】一步步推导,记忆化搜索 -> 动规 -> 前缀和优化","description":"【常规三解】一步步推导,记忆化搜索 -> 动规 -> 前缀和优化","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i//solution/chang-gui-san-jie-yi-bu-bu-tui-dao-ji-yi-jego","author":"priceless-poincaresxe","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-28T04:36:24.774Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-单调数组对的数目 I🔴","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i/","content":"给你一个长度为 n
的 正 整数数组 nums
。
如果两个 非负 整数数组 (arr1, arr2)
满足以下条件,我们称它们是 单调 数组对:
n
。arr1
是单调 非递减 的,换句话说 arr1[0] <= arr1[1] <= ... <= arr1[n - 1]
。arr2
是单调 非递增 的,换句话说 arr2[0] >= arr2[1] >= ... >= arr2[n - 1]
。0 <= i <= n - 1
都有 arr1[i] + arr2[i] == nums[i]
。请你返回所有 单调 数组对的数目。
\\n\\n由于答案可能很大,请你将它对 109 + 7
取余 后返回。
\\n\\n
示例 1:
\\n\\n输入:nums = [2,3,2]
\\n\\n输出:4
\\n\\n解释:
\\n\\n单调数组对包括:
\\n\\n([0, 1, 1], [2, 2, 1])
([0, 1, 2], [2, 2, 0])
([0, 2, 2], [2, 1, 0])
([1, 2, 2], [1, 1, 0])
示例 2:
\\n\\n输入:nums = [5,5,5,5]
\\n\\n输出:126
\\n\\n\\n
提示:
\\n\\n1 <= n == nums.length <= 2000
1 <= nums[i] <= 50
我们可以将环展开成一个长度为 $2n$ 的数组,然后从左到右遍历这个数组,用一个变量 $\\\\textit{cnt}$ 记录当前交替组的长度,如果遇到了相同的颜色,就将 $\\\\textit{cnt}$ 重置为 $1$,否则将 $\\\\textit{cnt}$ 加一。如果 $\\\\textit{cnt} \\\\ge k$,并且当前位置 $i$ 大于等于 $n$,那么就找到了一个交替组,答案加一。
\\n遍历结束后,返回答案即可。
\\n###python
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int], k: int) -> int:\\n n = len(colors)\\n ans = cnt = 0\\n for i in range(n << 1):\\n if i and colors[i % n] == colors[(i - 1) % n]:\\n cnt = 1\\n else:\\n cnt += 1\\n ans += i >= n and cnt >= k\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int numberOfAlternatingGroups(int[] colors, int k) {\\n int n = colors.length;\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n << 1; ++i) {\\n if (i > 0 && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors, int k) {\\n int n = colors.size();\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n << 1; ++i) {\\n if (i && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfAlternatingGroups(colors []int, k int) (ans int) {\\nn := len(colors)\\ncnt := 0\\nfor i := 0; i < n<<1; i++ {\\nif i > 0 && colors[i%n] == colors[(i-1)%n] {\\ncnt = 1\\n} else {\\ncnt++\\n}\\nif i >= n && cnt >= k {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction numberOfAlternatingGroups(colors: number[], k: number): number {\\n const n = colors.length;\\n let [ans, cnt] = [0, 0];\\n for (let i = 0; i < n << 1; ++i) {\\n if (i && colors[i % n] === colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{colors}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们可以将环展开成一个长度为 $2n$ 的数组,然后从左到右遍历这个数组,用一个变量 $\\\\textit{cnt}$ 记录当前交替组的长度,如果遇到了相同的颜色,就将 $\\\\textit{cnt}$ 重置为 $1$,否则将 $\\\\textit{cnt}$ 加一。如果 $\\\\textit{cnt} \\\\ge k$,并且当前位置 $i$ 大于等于 $n$,那么就找到了一个交替组,答案加一。\\n\\n遍历结束后,返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def numberOfAlternatingGroups(self…","guid":"https://leetcode.cn/problems/alternating-groups-ii//solution/python3javacgotypescript-yi-ti-yi-jie-yi-75u3","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-27T00:57:37.604Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-交替组 II🟡","url":"https://leetcode.cn/problems/alternating-groups-ii/","content":"给你一个整数数组 colors
和一个整数 k
,colors
表示一个由红色和蓝色瓷砖组成的环,第 i
块瓷砖的颜色为 colors[i]
:
colors[i] == 0
表示第 i
块瓷砖的颜色是 红色 。colors[i] == 1
表示第 i
块瓷砖的颜色是 蓝色 。环中连续 k
块瓷砖的颜色如果是 交替 颜色(也就是说除了第一块和最后一块瓷砖以外,中间瓷砖的颜色与它 左边 和 右边 的颜色都不同),那么它被称为一个 交替 组。
请你返回 交替 组的数目。
\\n\\n注意 ,由于 colors
表示一个 环 ,第一块 瓷砖和 最后一块 瓷砖是相邻的。
\\n\\n
示例 1:
\\n\\n输入:colors = [0,1,0,1,0], k = 3
\\n\\n输出:3
\\n\\n解释:
\\n\\n交替组包括:
\\n\\n示例 2:
\\n\\n输入:colors = [0,1,0,0,1,0,1], k = 6
\\n\\n输出:2
\\n\\n解释:
\\n\\n交替组包括:
\\n\\n示例 3:
\\n\\n输入:colors = [1,1,0,1], k = 4
\\n\\n输出:0
\\n\\n解释:
\\n\\n\\n\\n
提示:
\\n\\n3 <= colors.length <= 105
0 <= colors[i] <= 1
3 <= k <= colors.length
根据题目,我们需要在仅能够通过交换两个相邻元素的情况下,将长度为 $n$ 的数组中值为 $1$ 的数交换到数组首位,值为 $n$ 的数交换到数组末位,那么此时便有两种情况:
\\n###C++
\\nclass Solution {\\npublic:\\n static auto semiOrderedPermutation(const auto& nums) {\\n const auto [oneIndex, nIndex] = std::ranges::minmax_element(nums);\\n return oneIndex + nums.size() - 1 - nIndex - (nIndex < oneIndex ? 1 : 0);\\n }\\n};\\n
\\n###C#
\\npublic class Solution {\\n public int SemiOrderedPermutation(int[] nums) {\\n var (oneIndex, nIndex) = (Array.IndexOf(nums, 1), Array.IndexOf(nums, nums.Length));\\n return oneIndex + (nums.Length - 1 - nIndex) - (nIndex < oneIndex ? 1 : 0);\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n oneIndex, nIndex = nums.index(1), nums.index(len(nums))\\n return oneIndex + (len(nums) - 1 - nIndex) - (1 if nIndex < oneIndex else 0)\\n \\n
\\n###Java
\\nclass Solution {\\n public int semiOrderedPermutation(int[] nums) {\\n int oneIndex = 0, nIndex = 0;\\n\\n for (var i = 0; i < nums.length; i++) {\\n if (nums[i] == 1) {\\n oneIndex = i;\\n } else if (nums[i] == nums.length) {\\n nIndex = i;\\n }\\n }\\n\\n return oneIndex + (nums.length - 1 - nIndex) - (nIndex < oneIndex ? 1 : 0);\\n }\\n}\\n
\\n###JavaScript
\\nvar semiOrderedPermutation = function(nums) {\\n const [oneIndex, nIndex] = [nums.indexOf(1), nums.indexOf(nums.length)];\\n return oneIndex + (nums.length - 1 - nIndex) - (nIndex < oneIndex ? 1 : 0);\\n};\\n
\\n###Kotlin
\\nclass Solution {\\n fun semiOrderedPermutation(nums: IntArray): Int {\\n val oneIndex = nums.indexOf(1)\\n val nIndex = nums.indexOf(nums.size)\\n return oneIndex + nums.size - 1 + - nIndex - if (nIndex < oneIndex) 1 else 0\\n }\\n}\\n
\\n我们令 $k = 3$,表示交替组的长度为 $3$。
\\n为了方便处理,我们可以将环展开成一个长度为 $2n$ 的数组,然后从左到右遍历这个数组,用一个变量 $\\\\textit{cnt}$ 记录当前交替组的长度,如果遇到了相同的颜色,就将 $\\\\textit{cnt}$ 重置为 $1$,否则将 $\\\\textit{cnt}$ 加一。如果 $\\\\textit{cnt} \\\\ge k$,并且当前位置 $i$ 大于等于 $n$,那么就找到了一个交替组,答案加一。
\\n遍历结束后,返回答案即可。
\\n###python
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int]) -> int:\\n k = 3\\n n = len(colors)\\n ans = cnt = 0\\n for i in range(n << 1):\\n if i and colors[i % n] == colors[(i - 1) % n]:\\n cnt = 1\\n else:\\n cnt += 1\\n ans += i >= n and cnt >= k\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int numberOfAlternatingGroups(int[] colors) {\\n int k = 3;\\n int n = colors.length;\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n << 1; ++i) {\\n if (i > 0 && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors) {\\n int k = 3;\\n int n = colors.size();\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n << 1; ++i) {\\n if (i && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfAlternatingGroups(colors []int) (ans int) {\\nk := 3\\nn := len(colors)\\ncnt := 0\\nfor i := 0; i < n<<1; i++ {\\nif i > 0 && colors[i%n] == colors[(i-1)%n] {\\ncnt = 1\\n} else {\\ncnt++\\n}\\nif i >= n && cnt >= k {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction numberOfAlternatingGroups(colors: number[]): number {\\n const k = 3;\\n const n = colors.length;\\n let [ans, cnt] = [0, 0];\\n for (let i = 0; i < n << 1; ++i) {\\n if (i && colors[i % n] === colors[(i - 1) % n]) {\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n ans += i >= n && cnt >= k ? 1 : 0;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{colors}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们令 $k = 3$,表示交替组的长度为 $3$。\\n\\n为了方便处理,我们可以将环展开成一个长度为 $2n$ 的数组,然后从左到右遍历这个数组,用一个变量 $\\\\textit{cnt}$ 记录当前交替组的长度,如果遇到了相同的颜色,就将 $\\\\textit{cnt}$ 重置为 $1$,否则将 $\\\\textit{cnt}$ 加一。如果 $\\\\textit{cnt} \\\\ge k$,并且当前位置 $i$ 大于等于 $n$,那么就找到了一个交替组,答案加一。\\n\\n遍历结束后,返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/alternating-groups-i//solution/python3javacgotypescript-yi-ti-yi-jie-yi-gcuo","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-26T01:14:10.549Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-交替组 I🟢","url":"https://leetcode.cn/problems/alternating-groups-i/","content":"给你一个整数数组 colors
,它表示一个由红色和蓝色瓷砖组成的环,第 i
块瓷砖的颜色为 colors[i]
:
colors[i] == 0
表示第 i
块瓷砖的颜色是 红色 。colors[i] == 1
表示第 i
块瓷砖的颜色是 蓝色 。环中连续 3 块瓷砖的颜色如果是 交替 颜色(也就是说中间瓷砖的颜色与它 左边 和 右边 的颜色都不同),那么它被称为一个 交替 组。
\\n\\n请你返回 交替 组的数目。
\\n\\n注意 ,由于 colors
表示一个 环 ,第一块 瓷砖和 最后一块 瓷砖是相邻的。
\\n\\n
示例 1:
\\n\\n输入:colors = [1,1,1]
\\n\\n输出:0
\\n\\n解释:
\\n\\n示例 2:
\\n\\n输入:colors = [0,1,0,0,1]
\\n\\n输出:3
\\n\\n解释:
\\n\\n交替组包括:
\\n\\n\\n\\n
提示:
\\n\\n3 <= colors.length <= 100
0 <= colors[i] <= 1
思路与算法
\\n根据题意我们找到 $1$ 在数组中的索引 $\\\\textit{first}$,$n$ 在数组中的索引 $\\\\textit{last}$,此时分为两种情况讨论:
\\n如果 $\\\\textit{first} < \\\\textit{last}$,此时将 $1$ 通过交换相邻元素移到数组的首位需要的交换次数为 $\\\\textit{first}$,将 $n$ 通过交换相邻元素移到数组的末尾需要的交换次数为 $n - 1 - \\\\textit{last}$,总的交换次数即为 $\\\\textit{first} + n - 1 - \\\\textit{last}$;
\\n如果 $\\\\textit{first} > \\\\textit{last}$,此时将 $1$ 通过交换相邻元素移到数组的首位需要的交换次数为 $\\\\textit{first}$,将 $n$ 通过交换相邻元素移到数组的末尾需要的交换次数为 $n - 1 - \\\\textit{first}$,由于 $\\\\textit{first} > \\\\textit{last}$,实际交换过程时 $1$ 与 $n$ 会同时交换 $1$ 次,因此会减少 $1$ 次交换,总的交换次数即为 $\\\\textit{first} + n - 1 - \\\\textit{last} - 1$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int semiOrderedPermutation(vector<int>& nums) {\\n auto [first, last] = minmax_element(nums.begin(), nums.end());\\n return first + nums.size() - 1 - last - (last < first);\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int semiOrderedPermutation(int[] nums) {\\n int n = nums.length;\\n int first = 0, last = 0;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 1) {\\n first = i;\\n }\\n if (nums[i] == n) {\\n last = i;\\n }\\n }\\n return first + n - 1 - last - (last < first ? 1 : 0);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int SemiOrderedPermutation(int[] nums) {\\n int n = nums.Length;\\n int first = 0, last = 0;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 1) {\\n first = i;\\n }\\n if (nums[i] == n) {\\n last = i;\\n }\\n }\\n return first + n - 1 - last - (last < first ? 1 : 0);\\n }\\n}\\n
\\n###Go
\\nfunc semiOrderedPermutation(nums []int) int {\\n n := len(nums)\\n first, last := 0, 0\\n for i := 0; i < n; i++ {\\n if nums[i] == 1 {\\n first = i\\n }\\n if nums[i] == n {\\n last = i\\n }\\n }\\n if last < first {\\n return first + n - 1 - last - 1\\n }\\n return first + n - 1 - last\\n}\\n
\\n###Python
\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n n = len(nums)\\n first = nums.index(1)\\n last = nums.index(n)\\n return first + n - 1 - last - (first > last)\\n
\\n###C
\\nint semiOrderedPermutation(int* nums, int numsSize) {\\n int first = 0, last = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == 1) {\\n first = i;\\n }\\n if (nums[i] == numsSize) {\\n last = i;\\n }\\n }\\n return first + numsSize - 1 - last - (last < first ? 1 : 0);\\n}\\n
\\n###JavaScript
\\nvar semiOrderedPermutation = function(nums) {\\n const n = nums.length;\\n let first = 0, last = 0;\\n for (let i = 0; i < n; i++) {\\n if (nums[i] === 1) {\\n first = i;\\n }\\n if (nums[i] === n) {\\n last = i;\\n }\\n }\\n return first + n - 1 - last - (last < first ? 1 : 0);\\n};\\n
\\n###TypeScript
\\nfunction semiOrderedPermutation(nums: number[]): number {\\n const n = nums.length;\\n let first = 0, last = 0;\\n for (let i = 0; i < n; i++) {\\n if (nums[i] === 1) {\\n first = i;\\n }\\n if (nums[i] === n) {\\n last = i;\\n }\\n }\\n return first + n - 1 - last - (last < first ? 1 : 0);\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn semi_ordered_permutation(nums: Vec<i32>) -> i32 {\\n let n = nums.len() as i32;\\n let mut first = 0;\\n let mut last = 0;\\n for (i, &num) in nums.iter().enumerate() {\\n if num == 1 {\\n first = i as i32;\\n }\\n if num == n {\\n last = i as i32;\\n }\\n }\\n first + n - 1 - last - if last < first { 1 } else { 0 }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度: $O(n)$,其中 $n$ 表示给定数组的长度。遍历数组找到 $1$ 与 $n$ 的索引需要的时间为 $O(n)$。
\\n空间复杂度:$O(1)$。
\\n有 n
个网络节点,标记为 1
到 n
。
给你一个列表 times
,表示信号经过 有向 边的传递时间。 times[i] = (ui, vi, wi)
,其中 ui
是源节点,vi
是目标节点, wi
是一个信号从源节点传递到目标节点的时间。
现在,从某个节点 K
发出一个信号。需要多久才能使所有节点都收到信号?如果不能使所有节点收到信号,返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:times = [[2,1,1],[2,3,1],[3,4,1]], n = 4, k = 2\\n输出:2\\n\\n\\n
示例 2:
\\n\\n输入:times = [[1,2,1]], n = 2, k = 1\\n输出:1\\n\\n\\n
示例 3:
\\n\\n输入:times = [[1,2,1]], n = 2, k = 2\\n输出:-1\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= k <= n <= 100
1 <= times.length <= 6000
times[i].length == 3
1 <= ui, vi <= n
ui != vi
0 <= wi <= 100
(ui, vi)
对都 互不相同(即,不含重复边)题目的包装和 零数组变换 I 一样,问的其实是这样一个问题:
\\n\\n\\n在
\\nqueries
中给定若干个区间,从中删除尽量多的区间,使得nums
中的每个元素都能至少被nums[i]
个剩余区间覆盖。
删除尽量多的区间,其实就是保留尽量少的区间。我们可以利用贪心的思想解决问题。一开始先不保留任何区间,从左到右枚举 nums
中的每个元素,。如果当前元素的覆盖数不够,我们再不断加入能覆盖该元素的区间,直到覆盖数满足要求。
如果同时有很多区间都能覆盖当前元素,应该加入哪个呢?反正当前元素左边都已经符合条件了,我们只要为后面的元素考虑即可。因此我们应该加入右端点尽量大的区间,这样才能尽可能增加后续元素的覆盖数。
\\n怎么知道哪些区间可以覆盖当前元素,并取出右端点最大的区间?可以用一个单调指针和优先队列来维护。怎么维护当前元素的覆盖数?可以用差分数组来维护。详见参考代码。
\\n复杂度 $\\\\mathcal{O}(n + q\\\\log q)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxRemoval(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size(), q = queries.size();\\n // 所有区间按左端点从小到大排序,方便之后用单调指针维护\\n sort(queries.begin(), queries.end());\\n // now:当前元素的覆盖数\\n // ans:最少要选几个区间\\n int now = 0, ans = 0;\\n // pq:优先队列(大根堆),记录了所有左端点小等于当前下标的,且还未选择的区间的右端点\\n // 这样,如果堆顶元素大等于当前下标,那么它就是能覆盖当前元素且右端点尽量大的区间\\n priority_queue<int> pq;\\n // f:差分数组,f[i] 表示有几个区间在下标 i 结束\\n int f[n + 1];\\n memset(f, 0, sizeof(f));\\n // i:从左到右枚举每个元素,检查覆盖数\\n // j:单调指针,指向左端点大于当前下标的下一个区间\\n for (int i = 0, j = 0; i < n; i++) {\\n // 减去差分数组中记录的区间结束数量\\n now -= f[i];\\n // 移动单调指针,把左端点小等于当前下标的区间加入优先队列\\n while (j < q && queries[j][0] <= i) pq.push(queries[j][1]), j++;\\n // 如果覆盖数不够,则尝试从优先队列中取出区间\\n while (now < nums[i] && !pq.empty()) {\\n int t = pq.top(); pq.pop();\\n if (t >= i) {\\n // 堆顶区间能覆盖当前元素,那么加入该区间\\n now++;\\n ans++;\\n // 别忘了在差分数组里记录该区间的结束位置\\n f[t + 1]++;\\n }\\n }\\n // 优先队列掏空了,覆盖数还是不够,无解\\n if (now < nums[i]) return -1;\\n }\\n // 删除尽量多的区间,其实就是保留尽量少的区间\\n return q - ans;\\n }\\n};\\n
\\n","description":"解法:贪心 & 差分 & 单调指针 & 优先队列 题目的包装和 零数组变换 I 一样,问的其实是这样一个问题:\\n\\n在 queries 中给定若干个区间,从中删除尽量多的区间,使得 nums 中的每个元素都能至少被 nums[i] 个剩余区间覆盖。\\n\\n删除尽量多的区间,其实就是保留尽量少的区间。我们可以利用贪心的思想解决问题。一开始先不保留任何区间,从左到右枚举 nums 中的每个元素,。如果当前元素的覆盖数不够,我们再不断加入能覆盖该元素的区间,直到覆盖数满足要求。\\n\\n如果同时有很多区间都能覆盖当前元素,应该加入哪个呢?反正当前元素左边都已经符合条件了…","guid":"https://leetcode.cn/problems/zero-array-transformation-iii//solution/tan-xin-chai-fen-dan-diao-zhi-zhen-you-x-tfkf","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-24T02:28:02.193Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心+最大堆+差分数组(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/zero-array-transformation-iii//solution/tan-xin-zui-da-dui-chai-fen-shu-zu-pytho-35o6","content":"前置题目:3355. 零数组变换 I
\\n以 $\\\\textit{nums}=[2,0,2,0,2]$ 为例。
\\n从左到右遍历数组。对于 $\\\\textit{nums}[0]=2$ 来说,我们必须从 $\\\\textit{queries}$ 中选两个左端点为 $0$ 的区间。选哪两个呢?
\\n贪心地想,区间的右端点越大越好,后面的元素越小,后续操作就越少。所以选两个左端点为 $0$,且右端点最大的区间。
\\n继续遍历,由于 $\\\\textit{nums}[1]=0$,可以直接跳过。
\\n继续遍历,如果 $\\\\textit{nums}[2]$ 仍然大于 $0$,我们需要从左端点 $\\\\le 2$ 的未选区间中,选择右端点最大的区间。
\\n这启发我们用最大堆维护左端点 $\\\\le i$ 的未选区间的右端点。
\\n剩下的就是用差分数组去维护区间减一了。你需要先完成 3355. 零数组变换 I 这题。
\\n最终堆的大小(剩余没有使用的区间个数)就是答案。
\\n代码实现时,还需要把 $\\\\textit{queries}$ 按照左端点排序,这样我们可以用双指针遍历 $\\\\textit{nums}$ 和左端点 $\\\\le i$ 的区间。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def maxRemoval(self, nums: List[int], queries: List[List[int]]) -> int:\\n queries.sort(key=lambda q: q[0]) # 按照左端点从小到大排序\\n h = []\\n diff = [0] * (len(nums) + 1)\\n sum_d = j = 0\\n for i, x in enumerate(nums):\\n sum_d += diff[i]\\n # 维护左端点 <= i 的区间\\n while j < len(queries) and queries[j][0] <= i:\\n heappush(h, -queries[j][1]) # 取相反数表示最大堆\\n j += 1\\n # 选择右端点最大的区间\\n while sum_d < x and h and -h[0] >= i:\\n sum_d += 1\\n diff[-heappop(h) + 1] -= 1\\n if sum_d < x:\\n return -1\\n return len(h)\\n
\\n###java
\\nclass Solution {\\n public int maxRemoval(int[] nums, int[][] queries) {\\n Arrays.sort(queries, (a, b) -> a[0] - b[0]);\\n PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> b - a);\\n int n = nums.length;\\n int[] diff = new int[n + 1];\\n int sumD = 0;\\n int j = 0;\\n for (int i = 0; i < n; i++) {\\n sumD += diff[i];\\n // 维护左端点 <= i 的区间\\n while (j < queries.length && queries[j][0] <= i) {\\n pq.add(queries[j][1]);\\n j++;\\n }\\n // 选择右端点最大的区间\\n while (sumD < nums[i] && !pq.isEmpty() && pq.peek() >= i) {\\n sumD++;\\n diff[pq.poll() + 1]--;\\n }\\n if (sumD < nums[i]) {\\n return -1;\\n }\\n }\\n return pq.size();\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxRemoval(vector<int>& nums, vector<vector<int>>& queries) {\\n ranges::sort(queries, {}, [](auto& q) { return q[0]; }); // 按照左端点从小到大排序\\n int n = nums.size(), j = 0, sum_d = 0;\\n vector<int> diff(n + 1, 0);\\n priority_queue<int> pq;\\n for (int i = 0; i < n; i++) {\\n sum_d += diff[i];\\n // 维护左端点 <= i 的区间\\n while (j < queries.size() && queries[j][0] <= i) {\\n pq.push(queries[j][1]);\\n j++;\\n }\\n // 选择右端点最大的区间\\n while (sum_d < nums[i] && !pq.empty() && pq.top() >= i) {\\n sum_d++;\\n diff[pq.top() + 1]--;\\n pq.pop();\\n }\\n if (sum_d < nums[i]) {\\n return -1;\\n }\\n }\\n return pq.size();\\n }\\n};\\n
\\n###go
\\nfunc maxRemoval(nums []int, queries [][]int) int {\\nslices.SortFunc(queries, func(a, b []int) int { return a[0] - b[0] })\\nh := hp{}\\ndiff := make([]int, len(nums)+1)\\nsumD, j := 0, 0\\nfor i, x := range nums {\\nsumD += diff[i]\\n// 维护左端点 <= i 的区间\\nfor ; j < len(queries) && queries[j][0] <= i; j++ {\\nheap.Push(&h, queries[j][1])\\n}\\n// 选择右端点最大的区间\\nfor sumD < x && h.Len() > 0 && h.IntSlice[0] >= i {\\nsumD++\\ndiff[heap.Pop(&h).(int)+1]--\\n}\\nif sumD < x {\\nreturn -1\\n}\\n}\\nreturn h.Len()\\n}\\n\\ntype hp struct{ sort.IntSlice }\\nfunc (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] }\\nfunc (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }\\nfunc (h *hp) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }\\n
\\n另外,可以做做下面贪心题单中的「§1.9 反悔贪心」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:3355. 零数组变换 I 以 $\\\\textit{nums}=[2,0,2,0,2]$ 为例。\\n\\n从左到右遍历数组。对于 $\\\\textit{nums}[0]=2$ 来说,我们必须从 $\\\\textit{queries}$ 中选两个左端点为 $0$ 的区间。选哪两个呢?\\n\\n贪心地想,区间的右端点越大越好,后面的元素越小,后续操作就越少。所以选两个左端点为 $0$,且右端点最大的区间。\\n\\n继续遍历,由于 $\\\\textit{nums}[1]=0$,可以直接跳过。\\n\\n继续遍历,如果 $\\\\textit{nums}[2]$ 仍然大于 $0$,我们需要从左端点…","guid":"https://leetcode.cn/problems/zero-array-transformation-iii//solution/tan-xin-zui-da-dui-chai-fen-shu-zu-pytho-35o6","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-23T23:17:59.534Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序 + 双指针 + 堆 (理解 queries 中每一项的流程)","url":"https://leetcode.cn/problems/zero-array-transformation-iii//solution/pai-xu-shuang-zhi-zhen-dui-li-jie-querie-2ehn","content":"\\n\\nProblem: 3362. 零数组转化 III
\\n
[TOC]
\\n应该先做一下这题,理解下里面的双指针解法:
\\n3356. 零数组变换 II
题目转化为从queries
,选取部分项,使原数组转化成零数组。跟 3356. 零数组变换 II 这题的区别其实就是不考虑执行顺序了,因此先排个序:
# l 升序\\n queries.sort()\\n
\\n可以先做下会议室系列:
\\n2402. 会议室 III
i
指针指向nums
数组j
指针指向queries
数组diff
,若选上queries
中某一项,则追加到差分数组上pre
,i
指针移动过程中维护前缀和对于queries
中每一项,都有可能进入以下区域:
j
指针移动来遍历每一个待选值queries[j]
后,以 r = queries[j][1]
值塞入最大堆 heap
候选区
中heap
候选区中弹出最远的r
值,写入diff
,选中区
中 while i < n:\\n num = nums[i]\\n while j < m and queries[j][0] <= i:\\n # r最大堆\\n heappush(heap,-queries[j][1])\\n j += 1\\n # print(i,heap)\\n # 当前差分前缀和不够,从heap中取,要保持\\n while pre + diff[i] < num and heap:\\n r = -heappop(heap)\\n # 没用,扔掉\\n if r < i:\\n res += 1\\n # 有用,进入差分\\n else:\\n diff[i] += 1\\n diff[r+1] -= 1\\n # 满足不了题目\\n if pre + diff[i] < num:\\n return -1\\n\\n pre += diff[i]\\n i += 1\\n
\\n这种双指针 + 堆
,我都归纳为会议安排
类的题目,理解双指针
以及堆
在其中的作用才能更好地做这类题,一开始我也有点蒙,理解了就好了。。。。
更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def maxRemoval(self, nums: List[int], queries: List[List[int]]) -> int:\\n # l 升序\\n queries.sort()\\n n,m = len(nums),len(queries)\\n \\n # nums 指针\\n i = 0\\n # queries 指针,代表待选区\\n j = 0\\n # 候选区,选取 queries 后,以 r 值塞入最大堆 \\n heap = []\\n # 选中区,选中后维护差分数组\\n diff = [0] * (n + 1)\\n # 维护前缀和\\n pre = 0\\n # 结果\\n res = 0\\n \\n while i < n:\\n num = nums[i]\\n # 待选区 进入 候选区\\n while j < m and queries[j][0] <= i:\\n # r最大堆\\n heappush(heap,-queries[j][1])\\n j += 1\\n\\n # 候选区 进入 选中区\\n # 当前差分前缀和不够,从heap中取,要保持\\n while pre + diff[i] < num and heap:\\n r = -heappop(heap)\\n # 没用,扔掉\\n if r < i:\\n res += 1\\n # 有用,进入差分\\n else:\\n diff[i] += 1\\n diff[r+1] -= 1\\n # 满足不了题目\\n if pre + diff[i] < num:\\n return -1\\n\\n # 维护前缀和\\n pre += diff[i]\\n i += 1\\n\\n # 统计剩下的不需要的 queries\\n res += len(heap)\\n while j < m:\\n res += 1\\n j += 1\\n return res\\n
\\n","description":"Problem: 3362. 零数组转化 III [TOC]\\n\\n应该先做一下这题,理解下里面的双指针解法:\\n 3356. 零数组变换 II\\n\\n排序\\n\\n题目转化为从queries,选取部分项,使原数组转化成零数组。跟 3356. 零数组变换 II 这题的区别其实就是不考虑执行顺序了,因此先排个序:\\n\\n # l 升序\\n queries.sort()\\n\\n双指针 + 堆\\n\\n可以先做下会议室系列:\\n 2402. 会议室 III\\n\\ni指针指向nums数组\\nj指针指向queries数组\\ndiff,若选上queries中某一项,则追加到差分数组上\\np…","guid":"https://leetcode.cn/problems/zero-array-transformation-iii//solution/pai-xu-shuang-zhi-zhen-dui-li-jie-querie-2ehn","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-23T16:27:16.262Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最小区间🔴","url":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists/","content":"你有 k
个 非递减排列 的整数列表。找到一个 最小 区间,使得 k
个列表中的每个列表至少有一个数包含在其中。
我们定义如果 b-a < d-c
或者在 b-a == d-c
时 a < c
,则区间 [a,b]
比 [c,d]
小。
\\n\\n
示例 1:
\\n\\n输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]\\n输出:[20,24]\\n解释: \\n列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。\\n列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中。\\n列表 3:[5, 18, 22, 30],22 在区间 [20,24] 中。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [[1,2,3],[1,2,3],[1,2,3]]\\n输出:[1,1]\\n\\n\\n
\\n\\n
提示:
\\n\\nnums.length == k
1 <= k <= 3500
1 <= nums[i].length <= 50
-105 <= nums[i][j] <= 105
nums[i]
按非递减顺序排列\\n","description":"你有 k 个 非递减排列 的整数列表。找到一个 最小 区间,使得 k 个列表中的每个列表至少有一个数包含在其中。 我们定义如果 b-a < d-c 或者在 b-a == d-c 时 a < c,则区间 [a,b] 比 [c,d] 小。\\n\\n \\n\\n示例 1:\\n\\n输入:nums = [[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]\\n输出:[20,24]\\n解释: \\n列表 1:[4, 10, 15, 24, 26],24 在区间 [20,24] 中。\\n列表 2:[0, 9, 12, 20],20 在区间 [20,24] 中…","guid":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists/","author":null,"authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-23T16:00:00.516Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:计数(清晰题解)","url":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/python3javacgotypescript-yi-ti-yi-jie-ji-xbqv","content":"
我们可以用一个二维数组 $\\\\textit{cnt}$ 记录每个玩家获得的每种颜色球的数量,用一个哈希表 $\\\\textit{s}$ 记录胜利玩家的编号。
\\n遍历 $\\\\textit{pick}$ 数组,对于每个元素 $[x, y]$,我们将 $\\\\textit{cnt}[x][y]$ 加一,如果 $\\\\textit{cnt}[x][y]$ 大于 $x$,则将 $x$ 加入哈希表 $\\\\textit{s}$。
\\n最后返回哈希表 $\\\\textit{s}$ 的大小即可。
\\n###python
\\nclass Solution:\\n def winningPlayerCount(self, n: int, pick: List[List[int]]) -> int:\\n cnt = [[0] * 11 for _ in range(n)]\\n s = set()\\n for x, y in pick:\\n cnt[x][y] += 1\\n if cnt[x][y] > x:\\n s.add(x)\\n return len(s)\\n
\\n###java
\\nclass Solution {\\n public int winningPlayerCount(int n, int[][] pick) {\\n int[][] cnt = new int[n][11];\\n Set<Integer> s = new HashSet<>();\\n for (var p : pick) {\\n int x = p[0], y = p[1];\\n if (++cnt[x][y] > x) {\\n s.add(x);\\n }\\n }\\n return s.size();\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int winningPlayerCount(int n, vector<vector<int>>& pick) {\\n int cnt[10][11]{};\\n unordered_set<int> s;\\n for (const auto& p : pick) {\\n int x = p[0], y = p[1];\\n if (++cnt[x][y] > x) {\\n s.insert(x);\\n }\\n }\\n return s.size();\\n }\\n};\\n
\\n###go
\\nfunc winningPlayerCount(n int, pick [][]int) int {\\ncnt := make([][11]int, n)\\ns := map[int]struct{}{}\\nfor _, p := range pick {\\nx, y := p[0], p[1]\\ncnt[x][y]++\\nif cnt[x][y] > x {\\ns[x] = struct{}{}\\n}\\n}\\nreturn len(s)\\n}\\n
\\n###ts
\\nfunction winningPlayerCount(n: number, pick: number[][]): number {\\n const cnt: number[][] = Array.from({ length: n }, () => Array(11).fill(0));\\n const s = new Set<number>();\\n for (const [x, y] of pick) {\\n if (++cnt[x][y] > x) {\\n s.add(x);\\n }\\n }\\n return s.size;\\n}\\n
\\n时间复杂度 $O(m + n \\\\times M)$,空间复杂度 $O(n \\\\times M)$。其中 $m$ 为 $\\\\textit{pick}$ 数组的长度,而 $n$ 和 $M$ 分别为玩家数目和颜色数目。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 我们可以用一个二维数组 $\\\\textit{cnt}$ 记录每个玩家获得的每种颜色球的数量,用一个哈希表 $\\\\textit{s}$ 记录胜利玩家的编号。\\n\\n遍历 $\\\\textit{pick}$ 数组,对于每个元素 $[x, y]$,我们将 $\\\\textit{cnt}[x][y]$ 加一,如果 $\\\\textit{cnt}[x][y]$ 大于 $x$,则将 $x$ 加入哈希表 $\\\\textit{s}$。\\n\\n最后返回哈希表 $\\\\textit{s}$ 的大小即可。\\n\\n###python\\n\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/python3javacgotypescript-yi-ti-yi-jie-ji-xbqv","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-23T01:43:51.235Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-求出胜利玩家的数目🟢","url":"https://leetcode.cn/problems/find-the-number-of-winning-players/","content":"给你一个整数 n
,表示在一个游戏中的玩家数目。同时给你一个二维整数数组 pick
,其中 pick[i] = [xi, yi]
表示玩家 xi
获得了一个颜色为 yi
的球。
如果玩家 i
获得的球中任何一种颜色球的数目 严格大于 i
个,那么我们说玩家 i
是胜利玩家。换句话说:
i
获得了至少 i + 1
个相同颜色的球,那么玩家 i
是胜利玩家。请你返回游戏中 胜利玩家 的数目。
\\n\\n注意,可能有多个玩家是胜利玩家。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:n = 4, pick = [[0,0],[1,0],[1,0],[2,1],[2,1],[2,0]]
\\n\\n输出:2
\\n\\n解释:
\\n\\n玩家 0 和玩家 1 是胜利玩家,玩家 2 和玩家 3 不是胜利玩家。
\\n示例 2:
\\n\\n输入:n = 5, pick = [[1,1],[1,2],[1,3],[1,4]]
\\n\\n输出:0
\\n\\n解释:
\\n\\n没有胜利玩家。
\\n示例 3:
\\n\\n输入:n = 5, pick = [[1,1],[2,4],[2,4],[2,4]]
\\n\\n输出:1
\\n\\n解释:
\\n\\n玩家 2 是胜利玩家,因为玩家 2 获得了 3 个颜色为 4 的球。
\\n\\n\\n
提示:
\\n\\n2 <= n <= 10
1 <= pick.length <= 100
pick[i].length == 2
0 <= xi <= n - 1
0 <= yi <= 10
###py
\\nclass Solution:\\n def numRookCaptures(self, board: List[List[str]]) -> int:\\n SIZE = 8\\n for i, row in enumerate(board):\\n for j, c in enumerate(row):\\n if c == \'R\':\\n x0, y0 = i, j\\n ans = 0\\n for dx, dy in (0, -1), (0, 1), (-1, 0), (1, 0):\\n x, y = x0 + dx, y0 + dy\\n while 0 <= x < SIZE and 0 <= y < SIZE and board[x][y] == \'.\':\\n x += dx\\n y += dy\\n if 0 <= x < SIZE and 0 <= y < SIZE and board[x][y] == \'p\':\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n private static final int[][] DIRS = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};\\n\\n public int numRookCaptures(char[][] board) {\\n final int SIZE = 8;\\n int x0 = 0;\\n int y0 = 0;\\n for (int i = 0; i < SIZE; i++) {\\n for (int j = 0; j < SIZE; j++) {\\n if (board[i][j] == \'R\') {\\n x0 = i;\\n y0 = j;\\n }\\n }\\n }\\n int ans = 0;\\n for (int[] d : DIRS) {\\n int x = x0 + d[0];\\n int y = y0 + d[1];\\n while (0 <= x && x < SIZE && 0 <= y && y < SIZE && board[x][y] == \'.\') {\\n x += d[0];\\n y += d[1];\\n }\\n if (0 <= x && x < SIZE && 0 <= y && y < SIZE && board[x][y] == \'p\') {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n static constexpr int DIRS[4][2] = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};\\npublic:\\n int numRookCaptures(vector<vector<char>>& board) {\\n const int SIZE = 8;\\n int x0, y0;\\n for (int i = 0; i < SIZE; i++) {\\n for (int j = 0; j < SIZE; j++) {\\n if (board[i][j] == \'R\') {\\n x0 = i;\\n y0 = j;\\n }\\n }\\n }\\n int ans = 0;\\n for (auto& [dx, dy] : DIRS) {\\n int x = x0 + dx, y = y0 + dy;\\n while (0 <= x && x < SIZE && 0 <= y && y < SIZE && board[x][y] == \'.\') {\\n x += dx;\\n y += dy;\\n }\\n if (0 <= x && x < SIZE && 0 <= y && y < SIZE && board[x][y] == \'p\') {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nvar dirs = []struct{ x, y int }{{0, -1}, {0, 1}, {-1, 0}, {1, 0}}\\n\\nfunc numRookCaptures(board [][]byte) (ans int) {\\n const size = 8\\n var x0, y0 int\\n for i, row := range board {\\n for j, c := range row {\\n if c == \'R\' {\\n x0, y0 = i, j\\n }\\n }\\n }\\n for _, d := range dirs {\\n x, y := x0+d.x, y0+d.y\\n for 0 <= x && x < size && 0 <= y && y < size && board[x][y] == \'.\' {\\n x += d.x\\n y += d.y\\n }\\n if 0 <= x && x < size && 0 <= y && y < size && board[x][y] == \'p\' {\\n ans++\\n }\\n }\\n return\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历 $\\\\textit{board}$,找到车的位置 $(x_0,y_0)$。 枚举往左右上下四个方向移动。一步一步走,只要没有出界且是空方块,就继续走。\\n如果移动到卒(p)的位置,答案加一。\\n\\n###py\\n\\nclass Solution:\\n def numRookCaptures(self, board: List[List[str]]) -> int:\\n SIZE = 8\\n for i, row in enumerate(board):\\n for j, c in enumerate(row):…","guid":"https://leetcode.cn/problems/available-captures-for-rook//solution/jian-dan-ti-jian-dan-zuo-pythonjavacgo-b-3vhr","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-22T12:18:51.050Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"教你一步步思考 DP:从记忆化搜索到递推(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-dgt6","content":"在示例 1 中,我们要解决的问题(原问题)是:
\\n枚举马走的八个方向,假设走到了 $(1,2)$,问题变成:
\\n这是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n根据上面的讨论,我们需要在递归过程中跟踪以下信息:
\\n因此,定义状态为 $\\\\textit{dfs}(k,i,j)$,表示马从 $(i,j)$ 出发,走 $k$ 步后仍然在棋盘上的概率。
\\n枚举马走的八个方向,其中有 $\\\\dfrac{1}{8}$ 概率走到了 $(x,y)$,问题变成:
\\n八种情况累加,得
\\n$$
\\n\\\\textit{dfs}(k,i,j) = \\\\dfrac{1}{8}\\\\sum_{(x,y)} \\\\textit{dfs}(k-1,x,y)
\\n$$
递归边界:如果马出界,那么在棋盘上的概率为 $0$,即 $\\\\textit{dfs}(k,i,j)=0$。如果 $k=0$ 时马仍然在棋盘上,那么概率为 $1$,即 $\\\\textit{dfs}(0,i,j)=1$。
\\n递归入口:$\\\\textit{dfs}(k,\\\\textit{row},\\\\textit{column})$,也就是答案。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(k,i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。本题可以初始化成 $0$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\nDIRS = (2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1)\\n\\nclass Solution:\\n def knightProbability(self, n: int, k: int, row: int, column: int) -> float:\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(k: int, i: int, j: int) -> float:\\n if not (0 <= i < n and 0 <= j < n): # 出界\\n return 0\\n if k == 0: # 走完了,仍然在棋盘上\\n return 1\\n return sum(dfs(k - 1, i + dx, j + dy) for dx, dy in DIRS) / 8\\n return dfs(k, row, column)\\n
\\nclass Solution {\\n private static final int[][] DIRS = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n double[][][] memo = new double[k + 1][n][n];\\n return dfs(k, row, column, n, memo);\\n }\\n\\n private double dfs(int k, int i, int j, int n, double[][][] memo) {\\n if (i < 0 || j < 0 || i >= n || j >= n) {\\n return 0;\\n }\\n if (k == 0) {\\n return 1;\\n }\\n if (memo[k][i][j] > 0) { // 之前计算过\\n return memo[k][i][j];\\n }\\n double res = 0;\\n for (int[] d : DIRS) {\\n res += dfs(k - 1, i + d[0], j + d[1], n, memo);\\n }\\n return memo[k][i][j] = res / DIRS.length; // 记忆化\\n }\\n}\\n
\\nclass Solution {\\n static constexpr int DIRS[8][2] = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}};\\npublic:\\n double knightProbability(int n, int k, int row, int column) {\\n vector<vector<vector<double>>> memo(k + 1, vector<vector<double>>(n, vector<double>(n)));\\n auto dfs = [&](auto& dfs, int k, int i, int j) -> double {\\n if (i < 0 || i >= n || j < 0 || j >= n) {\\n return 0;\\n }\\n if (k == 0) {\\n return 1;\\n }\\n double& res = memo[k][i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n for (auto& [dx, dy] : DIRS) {\\n res += dfs(dfs, k - 1, i + dx, j + dy);\\n }\\n res /= 8;\\n return res;\\n };\\n return dfs(dfs, k, row, column);\\n }\\n};\\n
\\nvar dirs = []struct{ x, y int }{{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}}\\n\\nfunc knightProbability(n, k, row, column int) float64 {\\n memo := make([][][]float64, k+1)\\n for i := range memo {\\n memo[i] = make([][]float64, n)\\n for j := range memo[i] {\\n memo[i][j] = make([]float64, n)\\n }\\n }\\n var dfs func(int, int, int) float64\\n dfs = func(k, i, j int) float64 {\\n if i < 0 || j < 0 || i >= n || j >= n {\\n return 0\\n }\\n if k == 0 {\\n return 1\\n }\\n p := &memo[k][i][j]\\n if *p > 0 {\\n return *p\\n }\\n res := 0.0\\n for _, d := range dirs {\\n res += dfs(k-1, i+d.x, j+d.y)\\n }\\n res /= 8\\n *p = res\\n return res\\n }\\n return dfs(k, row, column)\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n为避免下标出现负数,把棋盘的边界从 $[0,n-1]$ 调整为 $[2,n+1]$。
\\n具体来说,$f[k][i][j]$ 的定义和 $\\\\textit{dfs}(k,i,j)$ 的定义是一样的,都表示马从 $(i,j)$ 出发,走 $k$ 步后仍然在棋盘上的概率。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[k][i][j] = \\\\dfrac{1}{8}\\\\sum_{(x,y)} f[k-1][x][y]
\\n$$
初始值:如果马出界,那么在棋盘上的概率为 $0$,即 $f[k][i][j]=0$。如果 $k=0$ 时马仍然在棋盘上,那么概率为 $1$,即 $f[0][i][j]=1$。
\\n答案为 $f[k][\\\\textit{row}+2][\\\\textit{column}+2]$,翻译自递归入口 $\\\\textit{dfs}(k,\\\\textit{row},\\\\textit{column})$。这里 $+2$ 是因为我们调整了棋盘的边界,所以起点也一同调整。
\\nDIRS = (2, 1), (1, 2), (-1, 2), (-2, 1), (-2, -1), (-1, -2), (1, -2), (2, -1)\\n\\nclass Solution:\\n def knightProbability(self, n: int, k: int, row: int, column: int) -> float:\\n f = [[[0] * (n + 4) for _ in range(n + 4)] for _ in range(k + 1)]\\n for i in range(2, n + 2):\\n f[0][i][2: n + 2] = [1] * n\\n for step in range(1, k + 1):\\n for i in range(2, n + 2):\\n for j in range(2, n + 2):\\n f[step][i][j] = sum(f[step - 1][i + dx][j + dy] for dx, dy in DIRS) / 8\\n return f[k][row + 2][column + 2]\\n
\\nclass Solution {\\n private static final int[][] DIRS = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n double[][][] f = new double[k + 1][n + 4][n + 4];\\n for (int i = 2; i < n + 2; i++) {\\n Arrays.fill(f[0][i], 2, n + 2, 1);\\n }\\n for (int step = 1; step <= k; step++) {\\n for (int i = 2; i < n + 2; i++) {\\n for (int j = 2; j < n + 2; j++) {\\n for (int[] d : DIRS) {\\n f[step][i][j] += f[step - 1][i + d[0]][j + d[1]];\\n }\\n f[step][i][j] /= DIRS.length;\\n }\\n }\\n }\\n return f[k][row + 2][column + 2];\\n }\\n}\\n
\\nclass Solution {\\n static constexpr int DIRS[8][2] = {{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}};\\npublic:\\n double knightProbability(int n, int k, int row, int column) {\\n vector<vector<vector<double>>> f(k + 1, vector<vector<double>>(n + 4, vector<double>(n + 4)));\\n for (int i = 2; i < n + 2; i++) {\\n fill(f[0][i].begin() + 2, f[0][i].begin() + n + 2, 1);\\n }\\n for (int step = 1; step <= k; step++) {\\n for (int i = 2; i < n + 2; i++) {\\n for (int j = 2; j < n + 2; j++) {\\n for (auto& [dx, dy] : DIRS) {\\n f[step][i][j] += f[step - 1][i + dx][j + dy];\\n }\\n f[step][i][j] /= 8;\\n }\\n }\\n }\\n return f[k][row + 2][column + 2];\\n }\\n};\\n
\\nvar dirs = []struct{ x, y int }{{2, 1}, {1, 2}, {-1, 2}, {-2, 1}, {-2, -1}, {-1, -2}, {1, -2}, {2, -1}}\\n\\nfunc knightProbability(n, k, row, column int) float64 {\\n f := make([][][]float64, k+1)\\n for i := range f {\\n f[i] = make([][]float64, n+4)\\n for j := range f[i] {\\n f[i][j] = make([]float64, n+4)\\n }\\n }\\n for i := 2; i < n+2; i++ {\\n for j := 2; j < n+2; j++ {\\n f[0][i][j] = 1\\n }\\n }\\n for steps := 1; steps <= k; steps++ {\\n for i := 2; i < n+2; i++ {\\n for j := 2; j < n+2; j++ {\\n for _, d := range dirs {\\n f[steps][i][j] += f[steps-1][i+d.x][j+d.y]\\n }\\n f[steps][i][j] /= 8\\n }\\n }\\n }\\n return f[k][row+2][column+2]\\n}\\n
\\n更多相似题目,见 动态规划题单 中的「§7.5 多维 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 在示例 1 中,我们要解决的问题(原问题)是:\\n\\n马从 $(0,0)$ 出发,走 $k=2$ 步后仍然在棋盘上的概率。\\n\\n枚举马走的八个方向,假设走到了 $(1,2)$,问题变成:\\n\\n马从 $(1,2)$ 出发,走 $k-1=1$ 步后仍然在棋盘上的概率。\\n\\n这是和原问题相似的、规模更小的子问题,可以用递归解决。\\n\\n二、状态定义与状态转移方程\\n\\n根据上面的讨论,我们需要在递归过程中跟踪以下信息:\\n\\n$k$:还剩下 $k$ 步要走。\\n$(i,j)$:马的位置。\\n\\n因此,定义状态为 $\\\\textit{dfs}(k,i,j)$,表示马从 $(i,j…","guid":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-dgt6","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-22T11:53:07.216Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图解】逆向思维(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/transform-to-chessboard//solution/tu-jie-ni-xiang-si-wei-pythonjavaccgojsr-mixb","content":"$\\\\textit{board}$ 要能变成棋盘,有哪些必要条件?
\\n根据上图,$\\\\textit{board}$ 要能变成棋盘,有两个必要条件:
\\n核心思路:统计 $A$ 行和 $B$ 行的个数。统计的过程中,如果发现有一行既不是 $A$ 行也不是 $B$ 行,直接返回 $-1$。统计结束后,如果发现 $A$ 行和 $B$ 行的个数相差超过 $1$,也返回 $-1$。
\\n可以用哈希表统计。但是,没有这个必要。
\\n以 $\\\\textit{board}$ 第一行(或者任意一行)为参照物。
\\n统计 $\\\\textit{board}$ 第一行中的 $0$ 和 $1$ 的个数,如果 $0$ 和 $1$ 的个数相差超过 $1$,说明这一行既不是 $A$ 行也不是 $B$ 行,无论如何交换,绝不可能得到棋盘,返回 $-1$。
\\n对于其余行 $\\\\textit{board}[i]$,首先比较 $\\\\textit{board}[i][0]$ 和 $\\\\textit{board}[0][0]$:
\\n如果没有返回 $-1$,那么说明只有 $A$ 行和 $B$ 行。我们也只需统计 $\\\\textit{board}[i][0]$(第一列)的 $0$ 和 $1$ 的个数,就知道有多少个 $A$ 行和 $B$ 行。如果 $0$ 和 $1$ 的个数相差超过 $1$,那么返回 $-1$。(代码实现时,关于第一列的判断可以放在前面)
\\n如果上述情况都没有返回 $-1$,那么 $\\\\textit{board}$ 一定可以变成棋盘,方法如下。
\\n比如把 $s=001110$ 通过交换元素,变成 $t=010101$。这其中 $s[i]\\\\ne t[i]$ 出现了 $4$ 次。我们需要让 $s$ 中的 $0$ 在偶数下标上,$1$ 在奇数下标上。那么把奇数上的 $0$ 和偶数上的 $1$ 交换,就可以满足要求,所以只需要 $\\\\dfrac{4}{2}=2$ 次交换。一般地,有如下定理。
\\n定理:如果有 $\\\\textit{diff}$ 个位置与目标值不同,那么只需交换 $\\\\dfrac{\\\\textit{diff}}{2}$ 次。
\\n证明:首先 $\\\\dfrac{\\\\textit{diff}}{2}$ 是交换次数的下界。下面证明可以只用 $\\\\dfrac{\\\\textit{diff}}{2}$ 次交换。
\\n不失一般性,假设要把 $s$ 通过交换,变成 $t=010101\\\\cdots$。注意这意味着当 $n$ 是奇数时,$0$ 的个数更多。
\\n根据该定理,得到如下计算方法:
\\n答案是第一行的最小交换次数,加上第一列的最小交换次数。
\\n交换完成后,就得到了棋盘。
\\n代码实现时,可以利用异或运算($\\\\oplus$),累加 $s[i]\\\\oplus t[i]$,就相当于统计 $s[i]\\\\ne t[i]$ 的个数。
\\n如果 $t=010101\\\\cdots$,那么 $t[i] = i\\\\bmod 2$。
\\n如果 $t=101010\\\\cdots$,那么 $t[i] = (i\\\\bmod 2)\\\\oplus 1$。
\\nclass Solution:\\n def movesToChessboard(self, board: List[List[int]]) -> int:\\n # 第一行,0 和 1 的个数之差不能超过 1\\n first_row = board[0]\\n row_cnt = Counter(first_row)\\n if abs(row_cnt[0] - row_cnt[1]) > 1:\\n return -1\\n\\n # 第一列,0 和 1 的个数之差不能超过 1\\n first_col = list(next(zip(*board)))\\n col_cnt = Counter(first_col)\\n if abs(col_cnt[0] - col_cnt[1]) > 1:\\n return -1\\n\\n # 每一行和第一行比较,要么完全相同,要么完全不同\\n for row in board:\\n same = row[0] == first_row[0]\\n for x, y in zip(row, first_row):\\n if (x == y) != same:\\n return -1\\n\\n # 计算最小交换次数\\n def min_swap(arr: List[int], cnt: Counter) -> int:\\n n = len(arr)\\n x0 = 1 if cnt[1] > cnt[0] else 0 # 如果 n 是偶数,x0 是 0\\n diff = sum(i % 2 ^ x ^ x0 for i, x in enumerate(arr))\\n return diff // 2 if n % 2 else min(diff, n - diff) // 2\\n\\n return min_swap(first_row, row_cnt) + min_swap(first_col, col_cnt)\\n
\\nclass Solution {\\n public int movesToChessboard(int[][] board) {\\n int n = board.length;\\n int[] firstRow = board[0];\\n int[] firstCol = new int[n];\\n int[] rowCnt = new int[2];\\n int[] colCnt = new int[2];\\n for (int i = 0; i < n; i++) {\\n rowCnt[firstRow[i]]++; // 统计 0 和 1 的个数\\n firstCol[i] = board[i][0];\\n colCnt[firstCol[i]]++;\\n }\\n\\n // 第一行,0 和 1 的个数之差不能超过 1\\n // 第一列,0 和 1 的个数之差不能超过 1\\n if (Math.abs(rowCnt[0] - rowCnt[1]) > 1 || Math.abs(colCnt[0] - colCnt[1]) > 1) {\\n return -1;\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for (int[] row : board) {\\n boolean same = row[0] == firstRow[0];\\n for (int i = 0; i < n; i++) {\\n if ((row[i] == firstRow[i]) != same) {\\n return -1;\\n }\\n }\\n }\\n\\n return minSwap(firstRow, rowCnt) + minSwap(firstCol, colCnt);\\n }\\n\\n // 计算最小交换次数\\n private int minSwap(int[] arr, int[] cnt) {\\n int n = arr.length;\\n int x0 = cnt[1] > cnt[0] ? 1 : 0; // 如果 n 是偶数,x0 是 0\\n int diff = 0;\\n for (int i = 0; i < n; i++) {\\n diff += i % 2 ^ arr[i] ^ x0;\\n }\\n return n % 2 > 0 ? diff / 2 : Math.min(diff, n - diff) / 2;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int movesToChessboard(vector<vector<int>>& board) {\\n int n = board.size();\\n auto& first_row = board[0];\\n vector<int> first_col(n);\\n int row_cnt[2]{}, col_cnt[2]{};\\n for (int i = 0; i < n; i++) {\\n row_cnt[first_row[i]]++; // 统计 0 和 1 的个数\\n first_col[i] = board[i][0];\\n col_cnt[first_col[i]]++;\\n }\\n\\n // 第一行,0 和 1 的个数之差不能超过 1\\n // 第一列,0 和 1 的个数之差不能超过 1\\n if (abs(row_cnt[0] - row_cnt[1]) > 1 || abs(col_cnt[0] - col_cnt[1]) > 1) {\\n return -1;\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for (auto& row : board) {\\n bool same = row[0] == first_row[0];\\n for (int i = 0; i < n; i++) {\\n if ((row[i] == first_row[i]) != same) {\\n return -1;\\n }\\n }\\n }\\n\\n // 计算最小交换次数\\n auto min_swap = [&](vector<int>& arr, int cnt[2]) {\\n int x0 = cnt[1] > cnt[0]; // 如果 n 是偶数,x0 是 0\\n int diff = 0;\\n for (int i = 0; i < n; i++) {\\n diff += i % 2 ^ arr[i] ^ x0;\\n }\\n return n % 2 ? diff / 2 : min(diff, n - diff) / 2;\\n };\\n\\n return min_swap(first_row, row_cnt) + min_swap(first_col, col_cnt);\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\n// 计算最小交换次数\\nint minSwap(int* arr, int n, int cnt[2]) {\\n int x0 = cnt[1] > cnt[0]; // 如果 n 是偶数,x0 是 0\\n int diff = 0;\\n for (int i = 0; i < n; i++) {\\n diff += i % 2 ^ arr[i] ^ x0;\\n }\\n return n % 2 ? diff / 2 : MIN(diff, n - diff) / 2;\\n}\\n\\nint movesToChessboard(int** board, int n, int* boardColSize) {\\n int* firstRow = board[0];\\n int rowCnt[2] = {}, colCnt[2] = {};\\n for (int i = 0; i < n; i++) {\\n rowCnt[firstRow[i]]++;\\n colCnt[board[i][0]]++;\\n }\\n // 第一行,0 和 1 的个数之差不能超过 1\\n // 第一列,0 和 1 的个数之差不能超过 1\\n if (abs(rowCnt[0] - rowCnt[1]) > 1 || abs(colCnt[0] - colCnt[1]) > 1) {\\n return -1;\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for (int i = 0; i < n; i++) {\\n bool same = board[i][0] == firstRow[0];\\n for (int j = 0; j < n; j++) {\\n if ((board[i][j] == firstRow[j]) != same) {\\n return -1;\\n }\\n }\\n }\\n\\n int* firstCol = malloc(n * sizeof(int));\\n for (int i = 0; i < n; i++) {\\n firstCol[i] = board[i][0];\\n }\\n int ans = minSwap(firstRow, n, rowCnt) + minSwap(firstCol, n, colCnt);\\n free(firstCol);\\n return ans;\\n}\\n
\\nfunc movesToChessboard(board [][]int) int {\\n n := len(board)\\n firstRow := board[0]\\n firstCol := make([]int, n)\\n var rowCnt, colCnt [2]int\\n for i, row := range board {\\n rowCnt[firstRow[i]]++ // 统计 0 和 1 的个数\\n firstCol[i] = row[0]\\n colCnt[firstCol[i]]++\\n }\\n\\n // 第一行,0 和 1 的个数之差不能超过 1\\n // 第一列,0 和 1 的个数之差不能超过 1\\n if abs(rowCnt[0]-rowCnt[1]) > 1 || abs(colCnt[0]-colCnt[1]) > 1 {\\n return -1\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for _, row := range board {\\n same := row[0] == firstRow[0]\\n for i, x := range row {\\n if (x == firstRow[i]) != same {\\n return -1\\n }\\n }\\n }\\n\\n return minSwap(firstRow, rowCnt) + minSwap(firstCol, colCnt)\\n}\\n\\n// 计算最小交换次数\\nfunc minSwap(arr []int, cnt [2]int) int {\\n x0 := 0 // 如果 n 是偶数,x0 是 0\\n if cnt[1] > cnt[0] {\\n x0 = 1\\n }\\n diff := 0\\n for i, x := range arr {\\n diff += i%2 ^ x ^ x0\\n }\\n n := len(arr)\\n if n%2 > 0 {\\n return diff / 2\\n }\\n return min(diff, n-diff) / 2\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\nvar movesToChessboard = function(board) {\\n // 第一行,0 和 1 的个数之差不能超过 1\\n const firstRow = board[0];\\n const rowCnt = [0, 0];\\n firstRow.forEach(x => rowCnt[x]++);\\n if (Math.abs(rowCnt[0] - rowCnt[1]) > 1) {\\n return -1;\\n }\\n\\n // 第一列,0 和 1 的个数之差不能超过 1\\n const firstCol = board.map(row => row[0]);\\n const colCnt = [0, 0];\\n firstCol.forEach(x => colCnt[x]++);\\n if (Math.abs(colCnt[0] - colCnt[1]) > 1) {\\n return -1;\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for (const row of board) {\\n const same = row[0] === firstRow[0];\\n for (let i = 0; i < row.length; i++) {\\n if ((row[i] === firstRow[i]) !== same) {\\n return -1;\\n }\\n }\\n }\\n\\n return minSwap(firstRow, rowCnt) + minSwap(firstCol, colCnt);\\n};\\n\\n// 计算最小交换次数\\nfunction minSwap(arr, cnt) {\\n const n = arr.length;\\n const x0 = cnt[1] > cnt[0] ? 1 : 0; // 如果 n 是偶数,x0 是 0\\n let diff = 0;\\n for (let i = 0; i < n; i++) {\\n diff += i % 2 ^ arr[i] ^ x0;\\n }\\n return n % 2 ? diff / 2 : Math.min(diff, n - diff) / 2;\\n}\\n
\\nimpl Solution {\\n pub fn moves_to_chessboard(board: Vec<Vec<i32>>) -> i32 {\\n // 第一行,0 和 1 的个数之差不能超过 1\\n let first_row = &board[0];\\n let mut row_cnt = [0i32; 2];\\n for &x in first_row {\\n row_cnt[x as usize] += 1;\\n }\\n if (row_cnt[0] - row_cnt[1]).abs() > 1 {\\n return -1;\\n }\\n\\n // 第一列,0 和 1 的个数之差不能超过 1\\n let first_col = board.iter().map(|row| row[0]).collect::<Vec<_>>();\\n let mut col_cnt = [0i32; 2];\\n for &x in &first_col {\\n col_cnt[x as usize] += 1;\\n }\\n if (col_cnt[0] - col_cnt[1]).abs() > 1 {\\n return -1;\\n }\\n\\n // 每一行和第一行比较,要么完全相同,要么完全不同\\n for row in &board {\\n let same = row[0] == first_row[0];\\n for (x, y) in row.iter().zip(first_row) {\\n if (x == y) != same {\\n return -1;\\n }\\n }\\n }\\n\\n Self::min_swap(first_row, row_cnt) + Self::min_swap(&first_col, col_cnt)\\n }\\n\\n // 计算最小交换次数\\n fn min_swap(arr: &[i32], cnt: [i32; 2]) -> i32 {\\n let x0 = if cnt[1] > cnt[0] { 1 } else { 0 }; // 如果 n 是偶数,x0 是 0\\n let diff = arr.iter()\\n .enumerate()\\n .map(|(i, &x)| (i % 2) as i32 ^ x ^ x0)\\n .sum::<i32>();\\n let n = arr.len() as i32;\\n if n % 2 > 0 { diff / 2 } else { diff.min(n - diff) / 2 }\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"$\\\\textit{board}$ 要能变成棋盘,有哪些必要条件? 分析性质\\n\\n根据上图,$\\\\textit{board}$ 要能变成棋盘,有两个必要条件:\\n\\n只有两种行:$A$ 行是从 $0101\\\\cdots$ 交换得到的,也就是说,$A$ 行中的 $0$ 的个数和 $1$ 的个数相差至多为 $1$;$B$ 行是从 $1010\\\\cdots$ 交换得到的,和 $A$ 行完全相反。\\n这两种行的个数相差至多为 $1$。\\n不能变成棋盘的情况\\n\\n核心思路:统计 $A$ 行和 $B$ 行的个数。统计的过程中,如果发现有一行既不是 $A$ 行也不是 $B$ 行,直接返回…","guid":"https://leetcode.cn/problems/transform-to-chessboard//solution/tu-jie-ni-xiang-si-wei-pythonjavaccgojsr-mixb","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-22T09:36:03.788Z","media":[{"url":"https://pic.leetcode.cn/1732265312-EypwGk-lc782-c.png","type":"photo","width":2305,"height":3392,"blurhash":"LCRC[5_2~q%3?bxuRioe4nWAMxt7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"离线+有序集合+双指针(Python/Java/C++/Go/Rust)","url":"https://leetcode.cn/problems/closest-room//solution/chi-xian-you-xu-ji-he-shuang-zhi-zhen-py-jch8","content":"把询问排序,通过改变回答询问的顺序,使问题更容易处理。
\\n比如有两个询问,其中 $\\\\textit{minSize}$ 分别为 $3$ 和 $6$。
\\n我们可以先回答 $\\\\textit{minSize}=6$ 的询问,再回答 $\\\\textit{minSize}=3$ 的询问。
\\n也就是先把面积 $\\\\ge 6$ 的房间号添加到一个有序集合中,回答 $\\\\textit{minSize}=6$ 的询问;然后把面积 $\\\\ge 3$ 的房间号添加到有序集合中,回答 $\\\\textit{minSize}=3$ 的询问。
\\n这里的关键是,由于面积 $\\\\ge 6$ 的房间编号已经添加到有序集合中了,所以后续只需把面积在 $[3,5]$ 中的房间号添加到有序集合中,不需要重复处理面积 $\\\\ge 6$ 的房间。
\\n直接对 $\\\\textit{queries}$ 排序是不行的,因为返回的答案必须按照询问的顺序。
\\n解决办法:设 $\\\\textit{q}$ 是 $\\\\textit{queries}$ 的长度,创建一个下标数组 $\\\\textit{queryIds}=[0,1,2,\\\\ldots,q-1]$,把下标根据 $\\\\textit{queries}$ 的 $\\\\textit{minSize}$ 从大到小排序,这样就避免直接对 $\\\\textit{queries}$ 排序了。
\\n把 $\\\\textit{rooms}$ 按照 $\\\\textit{size}$ 从小到大排序(也可以从大到小)。
\\n然后创建一个有序集合 $\\\\textit{roomIds}$。用双指针遍历 $\\\\textit{queryIds}$ 和 $\\\\textit{rooms}$,把房间面积 $\\\\ge \\\\textit{minSize}$ 的房间号添加到 $\\\\textit{roomIds}$ 中。然后在 $\\\\textit{roomIds}$ 中搜索离 $\\\\textit{preferred}$ 最近的左右两个房间号,其中离 $\\\\textit{preferred}$ 最近的房间号就是答案。
\\n###py
\\nfrom sortedcontainers import SortedList\\n\\nclass Solution:\\n def closestRoom(self, rooms: List[List[int]], queries: List[List[int]]) -> List[int]:\\n rooms.sort(key=lambda r: r[1]) # 按照 size 从小到大排序\\n q = len(queries)\\n ans = [-1] * q\\n room_ids = SortedList()\\n j = len(rooms) - 1\\n for i in sorted(range(q), key=lambda i: -queries[i][1]): # 按照 minSize 从大到小排序\\n preferred_id, min_size = queries[i]\\n while j >= 0 and rooms[j][1] >= min_size:\\n room_ids.add(rooms[j][0])\\n j -= 1\\n\\n diff = inf\\n k = room_ids.bisect_left(preferred_id)\\n if k:\\n diff = preferred_id - room_ids[k - 1] # 左边的差\\n ans[i] = room_ids[k - 1]\\n if k < len(room_ids) and room_ids[k] - preferred_id < diff: # 右边的差更小\\n ans[i] = room_ids[k]\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] closestRoom(int[][] rooms, int[][] queries) {\\n // 按照 size 从大到小排序\\n Arrays.sort(rooms, (a, b) -> (b[1] - a[1]));\\n\\n int q = queries.length;\\n Integer[] queryIds = new Integer[q];\\n Arrays.setAll(queryIds, i -> i);\\n // 按照 minSize 从大到小排序\\n Arrays.sort(queryIds, (i, j) -> queries[j][1] - queries[i][1]);\\n\\n int[] ans = new int[q];\\n Arrays.fill(ans, -1);\\n TreeSet<Integer> roomIds = new TreeSet<>();\\n int j = 0;\\n for (int i : queryIds) {\\n int preferredId = queries[i][0];\\n int minSize = queries[i][1];\\n while (j < rooms.length && rooms[j][1] >= minSize) {\\n roomIds.add(rooms[j][0]);\\n j++;\\n }\\n\\n int diff = Integer.MAX_VALUE;\\n Integer floor = roomIds.floor(preferredId);\\n if (floor != null) {\\n diff = preferredId - floor; // 左边的差\\n ans[i] = floor;\\n }\\n Integer ceiling = roomIds.ceiling(preferredId);\\n if (ceiling != null && ceiling - preferredId < diff) { // 右边的差更小\\n ans[i] = ceiling;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> closestRoom(vector<vector<int>>& rooms, vector<vector<int>>& queries) {\\n // 按照 size 从大到小排序\\n ranges::sort(rooms, {}, [](auto& a) { return -a[1]; });\\n\\n int q = queries.size();\\n vector<int> query_ids(q);\\n iota(query_ids.begin(), query_ids.end(), 0);\\n // 按照 minSize 从大到小排序\\n ranges::sort(query_ids, {}, [&](int i) { return -queries[i][1]; });\\n\\n vector<int> ans(q, -1);\\n set<int> room_ids;\\n int j = 0;\\n for (int i : query_ids) {\\n int preferred_id = queries[i][0], min_size = queries[i][1];\\n while (j < rooms.size() && rooms[j][1] >= min_size) {\\n room_ids.insert(rooms[j][0]);\\n j++;\\n }\\n\\n int diff = INT_MAX;\\n auto it = room_ids.lower_bound(preferred_id);\\n if (it != room_ids.begin()) {\\n auto p = prev(it);\\n diff = preferred_id - *p; // 左边的差\\n ans[i] = *p;\\n }\\n if (it != room_ids.end() && *it - preferred_id < diff) { // 右边的差更小\\n ans[i] = *it;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc closestRoom(rooms [][]int, queries [][]int) []int {\\n // 按照 size 从大到小排序\\n slices.SortFunc(rooms, func(a, b []int) int { return b[1] - a[1] })\\n\\n q := len(queries)\\n queryIds := make([]int, q)\\n for i := range queryIds {\\n queryIds[i] = i\\n }\\n // 按照 minSize 从大到小排序\\n slices.SortFunc(queryIds, func(i, j int) int { return queries[j][1] - queries[i][1] })\\n\\n ans := make([]int, q)\\n for i := range ans {\\n ans[i] = -1\\n }\\n roomIds := redblacktree.New[int, struct{}]() // import \\"github.com/emirpasic/gods/v2/trees/redblacktree\\"\\n j := 0\\n for _, i := range queryIds {\\n preferredId, minSize := queries[i][0], queries[i][1]\\n for j < len(rooms) && rooms[j][1] >= minSize {\\n roomIds.Put(rooms[j][0], struct{}{})\\n j++\\n }\\n\\n diff := math.MaxInt\\n // 左边的差\\n if node, ok := roomIds.Floor(preferredId); ok {\\n diff = preferredId - node.Key\\n ans[i] = node.Key\\n }\\n // 右边的差\\n if node, ok := roomIds.Ceiling(preferredId); ok && node.Key-preferredId < diff {\\n ans[i] = node.Key\\n }\\n }\\n return ans\\n}\\n
\\n###rust
\\nuse std::collections::BTreeSet;\\n\\nimpl Solution {\\n pub fn closest_room(mut rooms: Vec<Vec<i32>>, queries: Vec<Vec<i32>>) -> Vec<i32> {\\n rooms.sort_unstable_by_key(|r| -r[1]); // 按照 size 从大到小排序\\n\\n let q = queries.len();\\n let mut query_ids = (0..q).collect::<Vec<_>>();\\n query_ids.sort_unstable_by_key(|&i| -queries[i][1]); // 按照 minSize 从大到小排序\\n\\n let mut ans = vec![-1; q];\\n let mut room_ids = BTreeSet::new();\\n let mut j = 0;\\n for i in query_ids {\\n let preferred_id = queries[i][0];\\n let min_size = queries[i][1];\\n while j < rooms.len() && rooms[j][1] >= min_size {\\n room_ids.insert(rooms[j][0]);\\n j += 1;\\n }\\n\\n let mut diff = i32::MAX;\\n if let Some(&prev) = room_ids.range(..preferred_id).next_back() {\\n diff = preferred_id - prev; // 左边的差\\n ans[i] = prev;\\n }\\n if let Some(&next) = room_ids.range(preferred_id..).next() {\\n if next - preferred_id < diff { // 右边的差更小\\n ans[i] = next;\\n }\\n }\\n }\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单的「专题:离线算法」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路 把询问排序,通过改变回答询问的顺序,使问题更容易处理。\\n\\n比如有两个询问,其中 $\\\\textit{minSize}$ 分别为 $3$ 和 $6$。\\n\\n我们可以先回答 $\\\\textit{minSize}=6$ 的询问,再回答 $\\\\textit{minSize}=3$ 的询问。\\n\\n也就是先把面积 $\\\\ge 6$ 的房间号添加到一个有序集合中,回答 $\\\\textit{minSize}=6$ 的询问;然后把面积 $\\\\ge 3$ 的房间号添加到有序集合中,回答 $\\\\textit{minSize}=3$ 的询问。\\n\\n这里的关键是,由于面积 $\\\\ge 6…","guid":"https://leetcode.cn/problems/closest-room//solution/chi-xian-you-xu-ji-he-shuang-zhi-zhen-py-jch8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-22T03:38:00.430Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:数学(清晰题解)","url":"https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special//solution/python3javacgotypescript-yi-ti-yi-jie-sh-gc1k","content":"根据题目描述,我们可以发现,只有质数的平方才是特殊数字。因此,我们可以先预处理出小于等于 $\\\\sqrt{10^9}$ 的所有质数,然后遍历区间 $[\\\\lceil\\\\sqrt{l}\\\\rceil, \\\\lfloor\\\\sqrt{r}\\\\rfloor]$,统计出区间内的质数个数 $\\\\textit{cnt}$,最后返回 $r - l + 1 - \\\\textit{cnt}$ 即可。
\\n###python
\\nm = 31623\\nprimes = [True] * (m + 1)\\nprimes[0] = primes[1] = False\\nfor i in range(2, m + 1):\\n if primes[i]:\\n for j in range(i + i, m + 1, i):\\n primes[j] = False\\n\\n\\nclass Solution:\\n def nonSpecialCount(self, l: int, r: int) -> int:\\n lo = ceil(sqrt(l))\\n hi = floor(sqrt(r))\\n cnt = sum(primes[i] for i in range(lo, hi + 1))\\n return r - l + 1 - cnt\\n
\\n###java
\\nclass Solution {\\n static int m = 31623;\\n static boolean[] primes = new boolean[m + 1];\\n\\n static {\\n Arrays.fill(primes, true);\\n primes[0] = primes[1] = false;\\n for (int i = 2; i <= m; i++) {\\n if (primes[i]) {\\n for (int j = i + i; j <= m; j += i) {\\n primes[j] = false;\\n }\\n }\\n }\\n }\\n\\n public int nonSpecialCount(int l, int r) {\\n int lo = (int) Math.ceil(Math.sqrt(l));\\n int hi = (int) Math.floor(Math.sqrt(r));\\n int cnt = 0;\\n for (int i = lo; i <= hi; i++) {\\n if (primes[i]) {\\n cnt++;\\n }\\n }\\n return r - l + 1 - cnt;\\n }\\n}\\n
\\n###cpp
\\nconst int m = 31623;\\nbool primes[m + 1];\\n\\nauto init = [] {\\n memset(primes, true, sizeof(primes));\\n primes[0] = primes[1] = false;\\n for (int i = 2; i <= m; ++i) {\\n if (primes[i]) {\\n for (int j = i * 2; j <= m; j += i) {\\n primes[j] = false;\\n }\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int nonSpecialCount(int l, int r) {\\n int lo = ceil(sqrt(l));\\n int hi = floor(sqrt(r));\\n int cnt = 0;\\n for (int i = lo; i <= hi; ++i) {\\n if (primes[i]) {\\n ++cnt;\\n }\\n }\\n return r - l + 1 - cnt;\\n }\\n};\\n
\\n###go
\\nconst m = 31623\\n\\nvar primes [m + 1]bool\\n\\nfunc init() {\\nfor i := range primes {\\nprimes[i] = true\\n}\\nprimes[0] = false\\nprimes[1] = false\\nfor i := 2; i <= m; i++ {\\nif primes[i] {\\nfor j := i * 2; j <= m; j += i {\\nprimes[j] = false\\n}\\n}\\n}\\n}\\n\\nfunc nonSpecialCount(l int, r int) int {\\nlo := int(math.Ceil(math.Sqrt(float64(l))))\\nhi := int(math.Floor(math.Sqrt(float64(r))))\\ncnt := 0\\nfor i := lo; i <= hi; i++ {\\nif primes[i] {\\ncnt++\\n}\\n}\\nreturn r - l + 1 - cnt\\n}\\n
\\n###ts
\\nconst m = 31623;\\nconst primes: boolean[] = Array(m + 1).fill(true);\\n\\n(() => {\\n primes[0] = primes[1] = false;\\n for (let i = 2; i <= m; ++i) {\\n if (primes[i]) {\\n for (let j = i * 2; j <= m; j += i) {\\n primes[j] = false;\\n }\\n }\\n }\\n})();\\n\\nfunction nonSpecialCount(l: number, r: number): number {\\n const lo = Math.ceil(Math.sqrt(l));\\n const hi = Math.floor(Math.sqrt(r));\\n let cnt = 0;\\n for (let i = lo; i <= hi; ++i) {\\n if (primes[i]) {\\n ++cnt;\\n }\\n }\\n return r - l + 1 - cnt;\\n}\\n
\\n时间复杂度 $O(\\\\sqrt{m})$,空间复杂度 $O(\\\\sqrt{m})$。其中 $m = 10^9$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:数学 根据题目描述,我们可以发现,只有质数的平方才是特殊数字。因此,我们可以先预处理出小于等于 $\\\\sqrt{10^9}$ 的所有质数,然后遍历区间 $[\\\\lceil\\\\sqrt{l}\\\\rceil, \\\\lfloor\\\\sqrt{r}\\\\rfloor]$,统计出区间内的质数个数 $\\\\textit{cnt}$,最后返回 $r - l + 1 - \\\\textit{cnt}$ 即可。\\n\\n###python\\n\\nm = 31623\\nprimes = [True] * (m + 1)\\nprimes[0] = primes[1] = False\\nfor i in…","guid":"https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special//solution/python3javacgotypescript-yi-ti-yi-jie-sh-gc1k","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-22T01:34:17.539Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"检查棋盘方格颜色是否相同","url":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color//solution/jian-cha-qi-pan-fang-ge-yan-se-shi-fou-x-ypak","content":"思路与算法
\\n由于棋盘上的黑白格是间隔排列的,因此:
\\n因此,如果两个格子之间行数的差值,与列数的差值,二者的和为偶数,就说明它们的颜色相同,否则颜色不同。记两个格子分别是 $c_1$ 和 $c_2$,也就是:
\\n$$
\\n(c_1[0] - c_2[0]) + (c_1[1] - c_2[1])
\\n$$
为偶数即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n bool checkTwoChessboards(string coordinate1, string coordinate2) {\\n return (coordinate1[0] - coordinate2[0] + coordinate1[1] - coordinate2[1]) % 2 == 0;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean checkTwoChessboards(String coordinate1, String coordinate2) {\\n return (coordinate1.charAt(0) - coordinate2.charAt(0) + coordinate1.charAt(1) - coordinate2.charAt(1)) % 2 == 0;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool CheckTwoChessboards(string coordinate1, string coordinate2) {\\n return (coordinate1[0] - coordinate2[0] + coordinate1[1] - coordinate2[1]) % 2 == 0;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def checkTwoChessboards(self, coordinate1: str, coordinate2: str) -> bool:\\n return (ord(coordinate1[0]) - ord(coordinate2[0]) + ord(coordinate1[1]) - ord(coordinate2[1])) % 2 == 0\\n
\\n###Go
\\nfunc checkTwoChessboards(coordinate1 string, coordinate2 string) bool {\\n return (int(coordinate1[0]) - int(coordinate2[0]) + int(coordinate1[1]) - int(coordinate2[1])) % 2 == 0\\n}\\n
\\n###C
\\nbool checkTwoChessboards(char* coordinate1, char* coordinate2) {\\n return (coordinate1[0] - coordinate2[0] + coordinate1[1] - coordinate2[1]) % 2 == 0;\\n}\\n
\\n###JavaScript
\\nvar checkTwoChessboards = function(coordinate1, coordinate2) {\\n return (coordinate1.charCodeAt(0) - coordinate2.charCodeAt(0) + coordinate1.charCodeAt(1) - coordinate2.charCodeAt(1)) % 2 === 0;\\n};\\n
\\n###TypeScript
\\nfunction checkTwoChessboards(coordinate1: string, coordinate2: string): boolean {\\n return (coordinate1.charCodeAt(0) - coordinate2.charCodeAt(0) + coordinate1.charCodeAt(1) - coordinate2.charCodeAt(1)) % 2 === 0;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn check_two_chessboards(coordinate1: String, coordinate2: String) -> bool {\\n (coordinate1.chars().nth(0).unwrap() as i32 - coordinate2.chars().nth(0).unwrap() as i32 \\n + coordinate1.chars().nth(1).unwrap() as i32 - coordinate2.chars().nth(1).unwrap() as i32) % 2 == 0\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n参考现实中我们时常会使用 $Excel$ 表格来统计数据,此处由于球的颜色上限为 $11$ ,因此相似的,我们不妨可以使用一个行列为 $n\\\\times11$ 大小的二维数组来模拟这个表格。
\\n具体来说,我们创建一个大小为 $n\\\\times11$ 大小的二维数组 $count$ ,$count[i][j]$ 表示第 $i$ 位玩家所拥有的 $j$ 颜色球的数目。
\\n随后,我们遍历 $picks$ 数组。由于对于每一个 $pick$,有 $pick[0]$ 代表这是第几位玩家,$pick[1]$ 表示这名玩家拿了一个这种颜色的球,因此我们使用 $count[pick[0]][pick[1]]+=1$ 来更新我们的二维数组。
\\n最终我们遍历 $count$ 中的每一行,对于当前这一行 $row$ 代表的玩家,我们检查行中是否存在 $\\\\geq row$ 的元素,若是,我们将获胜的玩家加一,最后返回即可。
\\n###C#
\\npublic class Solution {\\n public int WinningPlayerCount(int n, int[][] picks) {\\n var count = Enumerable.Range(0, n).Select(_ => new int[11]).ToArray();\\n\\n foreach (var pick in picks) {\\n count[pick[0]][pick[1]]++;\\n }\\n\\n return Enumerable.Range(0, n)\\n .Count(i => Enumerable.Range(0, 11).Any(j => count[i][j] > i)\\n );\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static int winningPlayerCount(const int& n, std::vector<std::vector<int> > &picks) {\\n auto count = std::vector(n, std::vector(11, 0));\\n\\n for (const auto &pick: picks) {\\n count[pick[0]][pick[1]]++;\\n }\\n\\n auto result = 0;\\n for (auto i = 0; i < n; i++) {\\n result += std::ranges::any_of(count[i], [i](const int currColorNum) {\\n return currColorNum > i;\\n });\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int winningPlayerCount(int n, int[][] picks) {\\n var count = new int[n][11];\\n\\n for (var pick : picks) {\\n count[pick[0]][pick[1]]++;\\n }\\n\\n return (int) IntStream.range(0, n)\\n .filter(i -> IntStream.range(0, 11).anyMatch(j -> count[i][j] > i))\\n .count();\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def winningPlayerCount(self, n: int, picks: List[List[int]]) -> int:\\n count = [[0] * 11 for _ in range(n)]\\n\\n for pick in picks:\\n count[pick[0]][pick[1]] += 1\\n\\n return sum(1 for i in range(n) if any(count[i][j] > i for j in range(11)))\\n \\n
\\n###JavaScript
\\nvar winningPlayerCount = function (n, picks) {\\n let count = Array.from({ length: n }, () => Array(11).fill(0));\\n\\n picks.forEach(pick => {\\n count[pick[0]][pick[1]]++;\\n });\\n\\n return [...Array(n).keys()]\\n .filter(i => [...Array(11).keys()].some(j => count[i][j] > i))\\n .length;\\n};\\n
\\n给你两个 正整数 l
和 r
。对于任何数字 x
,x
的所有正因数(除了 x
本身)被称为 x
的 真因数。
如果一个数字恰好仅有两个 真因数,则称该数字为 特殊数字。例如:
\\n\\n返回区间 [l, r]
内 不是 特殊数字 的数字数量。
\\n\\n
示例 1:
\\n\\n输入: l = 5, r = 7
\\n\\n输出: 3
\\n\\n解释:
\\n\\n区间 [5, 7]
内不存在特殊数字。
示例 2:
\\n\\n输入: l = 4, r = 16
\\n\\n输出: 11
\\n\\n解释:
\\n\\n区间 [4, 16]
内的特殊数字为 4 和 9。
\\n\\n
提示:
\\n\\n1 <= l <= r <= 109
思路与算法
\\n根据棋盘中 车 的移动规则可以知道,对于棋盘中任意的位置 $(x, y)$,位置 $(i, j)$ 的 车 至多需要 $2$ 步即可移动到指定位置 $(x, y)$,移动方法如下:
\\n如果 白色象、黑皇后 处在同一条对角线或者 白色车、黑皇后 处于同一条线上,此时可能只需要 $1$ 次移动即可捕获 黑皇后,根据题意分析如下:
\\n如果 白色象 与 黑皇后 处在同一条对角线时,且此时该路径上无 白色车 阻挡时,此时只需移动 $1$ 次即可捕获 黑皇后;
\\n如果 白色车 与 黑皇后 处在同一行或者同一列时,且此时二者移动路线之间无 白色象 阻挡时,此时只需移动 $1$ 次即可捕获 黑皇后;
\\n其余情况下,白色车 最多需要 $2$ 次移动即可捕获 黑皇后;
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n // 车与皇后处在同一行,且中间没有象\\n if (a == e && (c != a || d <= min(b, f) || d >= max(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b == f && (d != b || c <= min(a, e) || c >= max(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (abs(c - e) == abs(d - f) && ((c - e) * (b - f) != (a - e) * (d - f) \\n || a < min(c, e) || a > max(c, e))) {\\n return 1;\\n }\\n return 2;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n // 车与皇后处在同一行,且中间没有象\\n if (a == e && (c != a || d <= Math.min(b, f) || d >= Math.max(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b == f && (d != b || c <= Math.min(a, e) || c >= Math.max(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (Math.abs(c - e) == Math.abs(d - f) && ((c - e) * (b - f) != (a - e) * (d - f) \\n || a < Math.min(c, e) || a > Math.max(c, e))) {\\n return 1;\\n }\\n return 2;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n // 车与皇后处在同一行,且中间没有象\\n if (a == e && (c != a || d <= Math.Min(b, f) || d >= Math.Max(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b == f && (d != b || c <= Math.Min(a, e) || c >= Math.Max(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (Math.Abs(c - e) == Math.Abs(d - f) && ((c - e) * (b - f) != (a - e) * (d - f) \\n || a < Math.Min(c, e) || a > Math.Max(c, e))) {\\n return 1;\\n }\\n return 2;\\n }\\n}\\n
\\n###Go
\\nfunc minMovesToCaptureTheQueen(a int, b int, c int, d int, e int, f int) int {\\n // 车与皇后处在同一行,且中间没有象\\n if a == e && (c != a || d <= min(b, f) || d >= max(b, f)) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if b == f && (d != b || c <= min(a, e) || c >= max(a, e)) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if abs(c - e) == abs(d - f) && ((c - e) * (b - f) != (a - e) * (d - f) || a < min(c, e) || a > max(c, e)) {\\n return 1;\\n }\\n return 2;\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
\\n###Python
\\nclass Solution:\\n def minMovesToCaptureTheQueen(self, a: int, b: int, c: int, d: int, e: int, f: int) -> int:\\n # 车与皇后处在同一行,且中间没有象\\n if a == e and (c != a or d <= min(b, f) or d >= max(b, f)):\\n return 1\\n # 车与皇后处在同一列,且中间没有象\\n if b == f and (d != b or c <= min(a, e) or c >= max(a, e)):\\n return 1\\n # 象、皇后处在同一条对角线,且中间没有车\\n if abs(c - e) == abs(d - f) and ((c - e) * (b - f) != (a - e) * (d - f) \\\\\\n or a < min(c, e) or a > max(c, e)):\\n return 1\\n return 2\\n
\\n###C
\\nint minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n // 车与皇后处在同一行,且中间没有象\\n if (a == e && (c != a || d <= fmin(b, f) || d >= fmax(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b == f && (d != b || c <= fmin(a, e) || c >= fmax(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (abs(c - e) == abs(d - f) && ((c - e) * (b - f) != (a - e) * (d - f) \\n || a < fmin(c, e) || a > fmax(c, e))) {\\n return 1;\\n }\\n return 2;\\n}\\n
\\n###JavaScript
\\nvar minMovesToCaptureTheQueen = function(a, b, c, d, e, f) {\\n // 车与皇后处在同一行,且中间没有象\\n if (a === e && (c !== a || d <= Math.min(b, f) || d >= Math.max(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b === f && (d !== b || c <= Math.min(a, e) || c >= Math.max(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (Math.abs(c - e) === Math.abs(d - f) && ((c - e) * (b - f) !== (a - e) * (d - f)\\n || a < Math.min(c, e) || a > Math.max(c, e))) {\\n return 1;\\n }\\n return 2;\\n};\\n
\\n###TypeScript
\\nfunction minMovesToCaptureTheQueen(a: number, b: number, c: number, d: number, e: number, f: number): number {\\n // 车与皇后处在同一行,且中间没有象\\n if (a === e && (c !== a || d <= Math.min(b, f) || d >= Math.max(b, f))) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if (b === f && (d !== b || c <= Math.min(a, e) || c >= Math.max(a, e))) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (Math.abs(c - e) === Math.abs(d - f) && ((c - e) * (b - f) !== (a - e) * (d - f)\\n || a < Math.min(c, e) || a > Math.max(c, e))) {\\n return 1;\\n }\\n return 2;\\n};\\n
\\n###Rust
\\nuse std::cmp::{min, max};\\n\\nimpl Solution {\\n pub fn min_moves_to_capture_the_queen(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {\\n // 车与皇后处在同一行,且中间没有象\\n if a == e && (c != a || d <= min(b, f) || d >= max(b, f)) {\\n return 1;\\n }\\n // 车与皇后处在同一列,且中间没有象\\n if b == f && (d != b || c <= min(a, e) || c >= max(a, e)) {\\n return 1;\\n }\\n // 象、皇后处在同一条对角线,且中间没有车\\n if (c - e).abs() == (d - f).abs() && ((c - e) * (b - f) != (a - e) * (d - f) \\n || a < min(c, e) || a > max(c, e)) {\\n return 1;\\n }\\n 2\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n我们可以用两个变量 $x$ 和 $y$ 来表示蛇的位置,初始时 $x = y = 0$,然后遍历 $\\\\textit{commands}$,根据当前的命令更新 $x$ 和 $y$ 的值,最后返回 $x \\\\times n + y$ 即可。
\\n###python
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n x = y = 0\\n for c in commands:\\n match c[0]:\\n case \\"U\\":\\n x -= 1\\n case \\"D\\":\\n x += 1\\n case \\"L\\":\\n y -= 1\\n case \\"R\\":\\n y += 1\\n return x * n + y\\n
\\n###java
\\nclass Solution {\\n public int finalPositionOfSnake(int n, List<String> commands) {\\n int x = 0, y = 0;\\n for (var c : commands) {\\n switch (c.charAt(0)) {\\n case \'U\' -> x--;\\n case \'D\' -> x++;\\n case \'L\' -> y--;\\n case \'R\' -> y++;\\n }\\n }\\n return x * n + y;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int finalPositionOfSnake(int n, vector<string>& commands) {\\n int x = 0, y = 0;\\n for (const auto& c : commands) {\\n switch (c[0]) {\\n case \'U\': x--; break;\\n case \'D\': x++; break;\\n case \'L\': y--; break;\\n case \'R\': y++; break;\\n }\\n }\\n return x * n + y;\\n }\\n};\\n
\\n###go
\\nfunc finalPositionOfSnake(n int, commands []string) int {\\nx, y := 0, 0\\nfor _, c := range commands {\\nswitch c[0] {\\ncase \'U\':\\nx--\\ncase \'D\':\\nx++\\ncase \'L\':\\ny--\\ncase \'R\':\\ny++\\n}\\n}\\nreturn x*n + y\\n}\\n
\\n###ts
\\nfunction finalPositionOfSnake(n: number, commands: string[]): number {\\n let [x, y] = [0, 0];\\n for (const c of commands) {\\n c[0] === \'U\' && x--;\\n c[0] === \'D\' && x++;\\n c[0] === \'L\' && y--;\\n c[0] === \'R\' && y++;\\n }\\n return x * n + y;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组 $\\\\textit{commands}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们可以用两个变量 $x$ 和 $y$ 来表示蛇的位置,初始时 $x = y = 0$,然后遍历 $\\\\textit{commands}$,根据当前的命令更新 $x$ 和 $y$ 的值,最后返回 $x \\\\times n + y$ 即可。\\n\\n###python\\n\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n x = y = 0\\n for c in commands…","guid":"https://leetcode.cn/problems/snake-in-matrix//solution/python3javacgotypescript-yi-ti-yi-jie-mo-620g","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-21T00:55:22.867Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-矩阵中的蛇🟢","url":"https://leetcode.cn/problems/snake-in-matrix/","content":"大小为 n x n
的矩阵 grid
中有一条蛇。蛇可以朝 四个可能的方向 移动。矩阵中的每个单元格都使用位置进行标识: grid[i][j] = (i * n) + j
。
蛇从单元格 0 开始,并遵循一系列命令移动。
\\n\\n给你一个整数 n
表示 grid
的大小,另给你一个字符串数组 commands
,其中包括 \\"UP\\"
、\\"RIGHT\\"
、\\"DOWN\\"
和 \\"LEFT\\"
。题目测评数据保证蛇在整个移动过程中将始终位于 grid
边界内。
返回执行 commands
后蛇所停留的最终单元格的位置。
\\n\\n
示例 1:
\\n\\n输入:n = 2, commands = [\\"RIGHT\\",\\"DOWN\\"]
\\n\\n输出:3
\\n\\n解释:
\\n\\n0 | \\n1 | \\n
2 | \\n3 | \\n
0 | \\n1 | \\n
2 | \\n3 | \\n
0 | \\n1 | \\n
2 | \\n3 | \\n
示例 2:
\\n\\n输入:n = 3, commands = [\\"DOWN\\",\\"RIGHT\\",\\"UP\\"]
\\n\\n输出:1
\\n\\n解释:
\\n\\n0 | \\n1 | \\n2 | \\n
3 | \\n4 | \\n5 | \\n
6 | \\n7 | \\n8 | \\n
0 | \\n1 | \\n2 | \\n
3 | \\n4 | \\n5 | \\n
6 | \\n7 | \\n8 | \\n
0 | \\n1 | \\n2 | \\n
3 | \\n4 | \\n5 | \\n
6 | \\n7 | \\n8 | \\n
0 | \\n1 | \\n2 | \\n
3 | \\n4 | \\n5 | \\n
6 | \\n7 | \\n8 | \\n
\\n\\n
提示:
\\n\\n2 <= n <= 10
1 <= commands.length <= 100
commands
仅由 \\"UP\\"
、\\"RIGHT\\"
、\\"DOWN\\"
和 \\"LEFT\\"
组成。给你一个整数 n
和一个二维整数数组 queries
。
有 n
个城市,编号从 0
到 n - 1
。初始时,每个城市 i
都有一条单向道路通往城市 i + 1
( 0 <= i < n - 1
)。
queries[i] = [ui, vi]
表示新建一条从城市 ui
到城市 vi
的单向道路。每次查询后,你需要找到从城市 0
到城市 n - 1
的最短路径的长度。
所有查询中不会存在两个查询都满足 queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1]
。
返回一个数组 answer
,对于范围 [0, queries.length - 1]
中的每个 i
,answer[i]
是处理完前 i + 1
个查询后,从城市 0
到城市 n - 1
的最短路径的长度。
\\n\\n
示例 1:
\\n\\n输入: n = 5, queries = [[2, 4], [0, 2], [0, 4]]
\\n\\n输出: [3, 2, 1]
\\n\\n解释:
\\n\\n新增一条从 2 到 4 的道路后,从 0 到 4 的最短路径长度为 3。
\\n\\n新增一条从 0 到 2 的道路后,从 0 到 4 的最短路径长度为 2。
\\n\\n新增一条从 0 到 4 的道路后,从 0 到 4 的最短路径长度为 1。
\\n示例 2:
\\n\\n输入: n = 4, queries = [[0, 3], [0, 2]]
\\n\\n输出: [1, 1]
\\n\\n解释:
\\n\\n新增一条从 0 到 3 的道路后,从 0 到 3 的最短路径长度为 1。
\\n\\n新增一条从 0 到 2 的道路后,从 0 到 3 的最短路径长度仍为 1。
\\n\\n\\n
提示:
\\n\\n3 <= n <= 105
1 <= queries.length <= 105
queries[i].length == 2
0 <= queries[i][0] < queries[i][1] < n
1 < queries[i][1] - queries[i][0]
i != j
且 queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1]
。由题意可知,$Alice$ 在一位数之和以及两位数之和中拥有选择较大者的权利,因此,只要一位数之和与两位数之和不相等,那么 $Alice$ 即可获胜。
\\n具体来说,我们可以选择遍历数组,当此时的数字为一位数,即 $num<10$ 时,我们将 $num$ 直接加入结果中;否则,我们将 $-num$ 加入结果中。最终我们返回结果是否为 $0$ 即可。
\\n###C#
\\npublic class Solution {\\n public bool CanAliceWin(int[] nums) {\\n return nums.Sum(i => i < 10 ? i : -i) != 0;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static bool canAliceWin(const std::vector<int>& nums) {\\n return std::accumulate(nums.begin(), nums.end(), 0, [](int last, int i) {\\n return last + (i < 10 ? i : -i);\\n }) != 0;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean canAliceWin(int[] nums) {\\n return Arrays.stream(nums).map(i -> i < 10 ? i : -i).sum() != 0;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def canAliceWin(self, nums: List[int]) -> bool:\\n return sum(i if i < 10 else -i for i in nums) != 0\\n \\n
\\n###Kotlin
\\nclass Solution {\\n fun canAliceWin(nums: IntArray): Boolean {\\n return nums.sumOf { if (it < 10) it else -it } != 0\\n }\\n}\\n
\\n根据题目的定义,我们可以得知,若一个数是特殊数字,则其有且仅有三个因数。而进一步的,我们可以注意到对于一个质数 $p$ 而言,其平方 $p^2$ 恰好有三个质数 ${1,p,p^2}$ 。
\\n因此,我们可以将原问题转换为计算在区间 $[left,right]$ 中有多少个数是某个质数的平方,等价于「计算在区间 $[2,\\\\sqrt{right}]$ 中有多少个质数 $p$ 满足 $p^2$ 位于 $[left,right]$ 区间内」,详细证明如下:
\\n考虑对于一般的正整数 $num$ ,不妨假设其有质因数分解结果:
\\n$$
\\nnum=p_1^{a_1}·p_2^{a_2}···p_k^{a_k}
\\n$$
其中 $p_1,p_2...p_n$ 为互不相同的质数,$a_1,a_2...a_n$ 为非负整数。根据如上公式,使用排列组合相应的我们可以求出其因数个数 $count$ 为:
\\n$$
\\ncount=(a_1+1)·(a_2+1)···(a_k+1)
\\n$$
现在由于 $num$ 是特殊数字,其因数个数 $count=3$,因此根据上述公式,即有
\\n$$
\\n(a_1+1)·(a_2+1)···(a_k+1)=3
\\n$$
同时,由于 $3$ 自身也是质数,而 $a_1,a_2...a_n$ 也皆为非负整数,因此仅可能存在一种情况,即
\\n$$
\\n(a_1+1)=3,即a_1=2\\\\(a_2+1)·(a_3+1)···(a_k+1)=1;等价于;a_2=a_3=...=a_n=0
\\n$$
因此,再回到 $num$ 的质因数分解结果,为
\\n$$
\\nnum=p_1^{2}
\\n$$
以上,我们证明了如果一个数 $num$ 有且仅有 $3$ 个因数,那么这个数一定是某个质数的平方,$num$ 的因数有 ${1,p,p^2}$。
\\n具体来说,由于我们希望计算在区间 $[2,\\\\sqrt{right}]$ 中有多少个质数 $p$ 满足 $p^2∈[left,right]$,因此我们可以选择使用埃氏筛法。
\\n我们可以首先定义一个数组 $isPrime$,其长度为 $\\\\sqrt{right}+1$,初始时我们令数组中的值全为 $true$。随后,我们遍历 $[2,\\\\sqrt{right}+1]$ 区间内的所有数 $i$。
\\n若 $i$ 为质数,即 $isPrime[i]==true$ ,则我们将 $i$ 在 $[2,\\\\sqrt{right}+1]$ 范围内的所有倍数标记为非质数。若对于 $i$ ,同时有 $i^2∈[left,right]$ ,则我们令计数 $count+=1$ 。
\\n最终我们返回 $[left,right]$ 区间内的所有数字,减去 $count$ 即为所求,也就是 $right-left+1-count$。
\\n###C#
\\npublic class Solution {\\n public int NonSpecialCount(int left, int right) {\\n var maxVal = (int)Math.Sqrt(right) + 1;\\n var result = 0;\\n var isPrime = Enumerable.Repeat(true, maxVal).ToArray();\\n\\n for (var i = 2; i < maxVal; i++) {\\n if (isPrime[i] is false) {\\n continue;\\n }\\n\\n if (i * i >= left && i * i <= right) {\\n result += 1;\\n }\\n\\n for (var j = i * 2; j < maxVal; j += i) {\\n isPrime[j] = false;\\n }\\n }\\n\\n return right - left + 1 - result;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static int nonSpecialCount(const int& left, const int& right) {\\n auto maxVal = int(std::sqrt(right)) + 1;\\n auto result = 0;\\n auto isPrime = std::vector<bool>(maxVal, true);\\n \\n for (auto i = 2; i < maxVal; i++) {\\n if (isPrime[i] == false) {\\n continue;\\n }\\n\\n if (i * i >= left && i * i <= right) {\\n result += 1;\\n }\\n\\n for (auto j = i * 2; j < maxVal; j += i) {\\n isPrime[j] = false;\\n }\\n }\\n \\n return right - left + 1 - result;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int nonSpecialCount(int left, int right) {\\n var result = 0;\\n var maxVal = (int)Math.sqrt(right) + 1;\\n\\n var isPrime = new boolean[maxVal];\\n Arrays.fill(isPrime, true);\\n\\n for (var i = 2; i < maxVal; i++) {\\n if (isPrime[i] == false) {\\n continue;\\n }\\n\\n if (isPrime[i] && i * i >= left && i * i <= right) {\\n result += 1;\\n }\\n\\n for (var j = i * 2; j < maxVal; j += i) {\\n isPrime[j] = false;\\n }\\n }\\n\\n return right - left + 1 - result;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def nonSpecialCount(self, left: int, right: int) -> int:\\n result = 0\\n maxVal = int(math.sqrt(right)) + 1\\n isPrime = [True] * maxVal\\n \\n for i in range(2, maxVal):\\n if not isPrime[i]:\\n continue\\n\\n if i * i >= left and i * i <= right:\\n result += 1\\n \\n for j in range(i * 2, maxVal, i):\\n isPrime[j] = False\\n \\n return right - left + 1 - result\\n \\n
\\n###Kotlin
\\nclass Solution {\\n fun nonSpecialCount(left: Int, right: Int): Int {\\n var result = 0\\n val maxVal = Math.sqrt(right.toDouble()).toInt()\\n val isPrime = BooleanArray(maxVal + 1) { true }\\n\\n for (i in 2..maxVal) {\\n if (isPrime[i] == false) {\\n continue\\n }\\n\\n if (i * i in left..right) {\\n result += 1\\n }\\n \\n for (j in i * 2..maxVal step i) {\\n isPrime[j] = false\\n }\\n }\\n\\n return right - left + 1 - result\\n }\\n}\\n
\\n###Go
\\nfunc nonSpecialCount(left int, right int) int {\\nresult := 0\\nmaxVal := int(math.Sqrt(float64(right))) + 1\\n \\nisPrime := make([]bool, maxVal)\\nfor i := 0; i < maxVal; i++ {\\nisPrime[i] = true\\n}\\n\\nfor i := 2; i < maxVal; i++ {\\n if isPrime[i] == false {\\n continue\\n }\\n\\nif isPrime[i] && i*i >= left && i*i <= right {\\nresult += 1\\n}\\n\\nfor j := i * 2; j < maxVal; j += i {\\nisPrime[j] = false\\n}\\n}\\n\\nreturn right - left + 1 - result\\n}\\n
\\n我们先建立一个有向图 $\\\\textit{g}$,其中 $\\\\textit{g}[i]$ 表示从城市 $i$ 出发可以到达的城市列表,初始时,每个城市 $i$ 都有一条单向道路通往城市 $i + 1$。
\\n然后,我们对每个查询 $[u, v]$,将 $u$ 添加到 $v$ 的出发城市列表中,然后使用 BFS 求出从城市 $0$ 到城市 $n - 1$ 的最短路径长度,将结果添加到答案数组中。
\\n最后返回答案数组即可。
\\n###python
\\nclass Solution:\\n def shortestDistanceAfterQueries(\\n self, n: int, queries: List[List[int]]\\n ) -> List[int]:\\n def bfs(i: int) -> int:\\n q = deque([i])\\n vis = [False] * n\\n vis[i] = True\\n d = 0\\n while 1:\\n for _ in range(len(q)):\\n u = q.popleft()\\n if u == n - 1:\\n return d\\n for v in g[u]:\\n if not vis[v]:\\n vis[v] = True\\n q.append(v)\\n d += 1\\n\\n g = [[i + 1] for i in range(n - 1)]\\n ans = []\\n for u, v in queries:\\n g[u].append(v)\\n ans.append(bfs(0))\\n return ans\\n
\\n###java
\\nclass Solution {\\n private List<Integer>[] g;\\n private int n;\\n\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n this.n = n;\\n g = new List[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int i = 0; i < n - 1; ++i) {\\n g[i].add(i + 1);\\n }\\n int m = queries.length;\\n int[] ans = new int[m];\\n for (int i = 0; i < m; ++i) {\\n int u = queries[i][0], v = queries[i][1];\\n g[u].add(v);\\n ans[i] = bfs(0);\\n }\\n return ans;\\n }\\n\\n private int bfs(int i) {\\n Deque<Integer> q = new ArrayDeque<>();\\n q.offer(i);\\n boolean[] vis = new boolean[n];\\n vis[i] = true;\\n for (int d = 0;; ++d) {\\n for (int k = q.size(); k > 0; --k) {\\n int u = q.poll();\\n if (u == n - 1) {\\n return d;\\n }\\n for (int v : g[u]) {\\n if (!vis[v]) {\\n vis[v] = true;\\n q.offer(v);\\n }\\n }\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> g[n];\\n for (int i = 0; i < n - 1; ++i) {\\n g[i].push_back(i + 1);\\n }\\n auto bfs = [&](int i) -> int {\\n queue<int> q{{i}};\\n vector<bool> vis(n);\\n vis[i] = true;\\n for (int d = 0;; ++d) {\\n for (int k = q.size(); k; --k) {\\n int u = q.front();\\n q.pop();\\n if (u == n - 1) {\\n return d;\\n }\\n for (int v : g[u]) {\\n if (!vis[v]) {\\n vis[v] = true;\\n q.push(v);\\n }\\n }\\n }\\n }\\n };\\n vector<int> ans;\\n for (const auto& q : queries) {\\n g[q[0]].push_back(q[1]);\\n ans.push_back(bfs(0));\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\ng := make([][]int, n)\\nfor i := range g {\\ng[i] = append(g[i], i+1)\\n}\\nbfs := func(i int) int {\\nq := []int{i}\\nvis := make([]bool, n)\\nvis[i] = true\\nfor d := 0; ; d++ {\\nfor k := len(q); k > 0; k-- {\\nu := q[0]\\nif u == n-1 {\\nreturn d\\n}\\nq = q[1:]\\nfor _, v := range g[u] {\\nif !vis[v] {\\nvis[v] = true\\nq = append(q, v)\\n}\\n}\\n}\\n}\\n}\\nans := make([]int, len(queries))\\nfor i, q := range queries {\\ng[q[0]] = append(g[q[0]], q[1])\\nans[i] = bfs(0)\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {\\n const g: number[][] = Array.from({ length: n }, () => []);\\n for (let i = 0; i < n - 1; ++i) {\\n g[i].push(i + 1);\\n }\\n const bfs = (i: number): number => {\\n const q: number[] = [i];\\n const vis: boolean[] = Array(n).fill(false);\\n vis[i] = true;\\n for (let d = 0; ; ++d) {\\n const nq: number[] = [];\\n for (const u of q) {\\n if (u === n - 1) {\\n return d;\\n }\\n for (const v of g[u]) {\\n if (!vis[v]) {\\n vis[v] = true;\\n nq.push(v);\\n }\\n }\\n }\\n q.splice(0, q.length, ...nq);\\n }\\n };\\n const ans: number[] = [];\\n for (const [u, v] of queries) {\\n g[u].push(v);\\n ans.push(bfs(0));\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(q \\\\times (n + q))$,空间复杂度 $O(n + q)$。其中 $n$ 和 $q$ 分别为城市数量和查询数量。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:BFS 我们先建立一个有向图 $\\\\textit{g}$,其中 $\\\\textit{g}[i]$ 表示从城市 $i$ 出发可以到达的城市列表,初始时,每个城市 $i$ 都有一条单向道路通往城市 $i + 1$。\\n\\n然后,我们对每个查询 $[u, v]$,将 $u$ 添加到 $v$ 的出发城市列表中,然后使用 BFS 求出从城市 $0$ 到城市 $n - 1$ 的最短路径长度,将结果添加到答案数组中。\\n\\n最后返回答案数组即可。\\n\\n###python\\n\\nclass Solution:\\n def shortestDistanceAfterQueries(…","guid":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-i//solution/python3javacgotypescript-yi-ti-yi-jie-bf-cxlq","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-19T00:14:41.040Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-新增道路查询后的最短距离 I🟡","url":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-i/","content":"给你一个整数 n
和一个二维整数数组 queries
。
有 n
个城市,编号从 0
到 n - 1
。初始时,每个城市 i
都有一条单向道路通往城市 i + 1
( 0 <= i < n - 1
)。
queries[i] = [ui, vi]
表示新建一条从城市 ui
到城市 vi
的单向道路。每次查询后,你需要找到从城市 0
到城市 n - 1
的最短路径的长度。
返回一个数组 answer
,对于范围 [0, queries.length - 1]
中的每个 i
,answer[i]
是处理完前 i + 1
个查询后,从城市 0
到城市 n - 1
的最短路径的长度。
\\n\\n
示例 1:
\\n\\n输入: n = 5, queries = [[2, 4], [0, 2], [0, 4]]
\\n\\n输出: [3, 2, 1]
\\n\\n解释:
\\n\\n新增一条从 2 到 4 的道路后,从 0 到 4 的最短路径长度为 3。
\\n\\n新增一条从 0 到 2 的道路后,从 0 到 4 的最短路径长度为 2。
\\n\\n新增一条从 0 到 4 的道路后,从 0 到 4 的最短路径长度为 1。
\\n示例 2:
\\n\\n输入: n = 4, queries = [[0, 3], [0, 2]]
\\n\\n输出: [1, 1]
\\n\\n解释:
\\n\\n新增一条从 0 到 3 的道路后,从 0 到 3 的最短路径长度为 1。
\\n\\n新增一条从 0 到 2 的道路后,从 0 到 3 的最短路径长度仍为 1。
\\n\\n\\n
提示:
\\n\\n3 <= n <= 500
1 <= queries.length <= 500
queries[i].length == 2
0 <= queries[i][0] < queries[i][1] < n
1 < queries[i][1] - queries[i][0]
思路与算法
\\n由于 $\\\\textit{nums}$ 中的数字都大于等于 $0$ 且小于等于 $99$,因此我们对 $\\\\textit{nums}$ 中的个位数和两位数分别进行求和,若两者的和不相等,则表明 $\\\\textit{Alice}$ 总能选出更大的那个获胜,否则 $\\\\textit{Alice}$ 不能获胜。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n bool canAliceWin(vector<int>& nums) {\\n int single_digit_sum = 0;\\n int double_digit_sum = 0;\\n for (auto num : nums) {\\n if (num < 10) {\\n single_digit_sum += num;\\n } else {\\n double_digit_sum += num;\\n }\\n }\\n return single_digit_sum != double_digit_sum;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public boolean canAliceWin(int[] nums) {\\n int singleDigitSum = 0;\\n int doubleDigitSum = 0;\\n for (int num : nums) {\\n if (num < 10) {\\n singleDigitSum += num;\\n } else {\\n doubleDigitSum += num;\\n }\\n }\\n return singleDigitSum != doubleDigitSum;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool CanAliceWin(int[] nums) {\\n int singleDigitSum = 0;\\n int doubleDigitSum = 0;\\n foreach (int num in nums) {\\n if (num < 10) {\\n singleDigitSum += num;\\n } else {\\n doubleDigitSum += num;\\n }\\n }\\n return singleDigitSum != doubleDigitSum;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def canAliceWin(self, nums: List[int]) -> bool:\\n single_digit_sum = 0\\n double_digit_sum = 0\\n for num in nums:\\n if num < 10:\\n single_digit_sum += num\\n else:\\n double_digit_sum += num\\n return single_digit_sum != double_digit_sum\\n
\\n###Go
\\nfunc canAliceWin(nums []int) bool {\\n singleDigitSum := 0\\n doubleDigitSum := 0\\n\\n for _, num := range nums {\\n if num < 10 {\\n singleDigitSum += num\\n } else {\\n doubleDigitSum += num\\n }\\n }\\n\\n return singleDigitSum != doubleDigitSum\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn can_alice_win(nums: Vec<i32>) -> bool {\\n let mut single_digit_sum : i32 = 0;\\n let mut double_digit_sum : i32 = 0;\\n for &num in &nums {\\n if num < 10 {\\n single_digit_sum += num;\\n } else {\\n double_digit_sum += num;\\n }\\n }\\n (single_digit_sum != double_digit_sum) as bool\\n }\\n}\\n
\\n###C
\\nbool canAliceWin(int* nums, int numsSize) {\\n int single_digit_sum = 0;\\n int double_digit_sum = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] < 10) {\\n single_digit_sum += nums[i];\\n } else {\\n double_digit_sum += nums[i];\\n }\\n }\\n return single_digit_sum != double_digit_sum;\\n}\\n
\\n###JavaScript
\\nvar canAliceWin = function(nums) {\\n let single_digit_sum = 0;\\n let double_digit_sum = 0;\\n for (const num of nums) {\\n if (num < 10) {\\n single_digit_sum += num;\\n } else {\\n double_digit_sum += num;\\n }\\n }\\n return single_digit_sum != double_digit_sum;\\n};\\n
\\n###TypeScript
\\nfunction canAliceWin(nums: number[]): boolean {\\n let single_digit_sum = 0;\\n let double_digit_sum = 0;\\n for (const num of nums) {\\n if (num < 10) {\\n single_digit_sum += num;\\n } else {\\n double_digit_sum += num;\\n }\\n }\\n return single_digit_sum != double_digit_sum;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n我们使用动态规划来解决本题目,定义 $\\\\textit{dp}[i][j]$ 表示当 $\\\\textit{arr}_1[i] = j$ 时,前 $i + 1$ 个元素组成的单调数组的数目。
\\n因为 $\\\\textit{arr}_1[0]$ 可以为 $0$ 到 $\\\\textit{nums}[0]$ 之间的任意数,初始化 $\\\\textit{dp}[0][j] = 1$,其中 $j$ 小于 $\\\\textit{nums}[0]$,其它初始化为零。
\\n我们遍历数据,并且枚举 $\\\\textit{arr}_1$ 中之前和现在的值,按照题目要求的检查单调性,可得到转移方程 $\\\\textit{dp}[i][v_2] = \\\\sum\\\\textit{dp}[i - 1][v_1]$。
\\n其中满足 $v_1 \\\\le v_2$ 和 $\\\\textit{nums}[i - 1] - v_1 \\\\ge \\\\textit{nums}[i] - v_2 \\\\ge 0$。
\\n最后,我们返回 $\\\\textit{dp}[n - i]$ 的和即为结果。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n int n = nums.size();\\n vector<vector<int>> dp(n, vector<int>(51, 0));\\n int mod = 1e9 + 7;\\n\\n for (int v = 0; v <= nums[0]; ++v) {\\n dp[0][v] = 1;\\n }\\n\\n for (int i = 1; i < n; ++i) {\\n for (int v2 = 0; v2 <= nums[i]; ++v2) {\\n for (int v1 = 0; v1 <= v2; ++v1) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n int res = 0;\\n for (int v : dp[n - 1]) {\\n res = (res + v) % mod;\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n int n = nums.length;\\n int[][] dp = new int[n][51];\\n int mod = 1000000007;\\n for (int v = 0; v <= nums[0]; v++) {\\n dp[0][v] = 1;\\n }\\n\\n for (int i = 1; i < n; i++) {\\n for (int v2 = 0; v2 <= nums[i]; v2++) {\\n for (int v1 = 0; v1 <= v2; v1++) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n int res = 0;\\n for (int v : dp[n - 1]) {\\n res = (res + v) % mod;\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n n = len(nums)\\n dp = [[0] * 51 for i in range(n)]\\n mod = 10 ** 9 + 7\\n for v in range(nums[0] + 1):\\n dp[0][v] = 1\\n for i in range(1, n):\\n for v2 in range(nums[i] + 1):\\n for v1 in range(v2 + 1):\\n if nums[i - 1] - v1 >= nums[i] - v2 >= 0:\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod\\n return sum(dp[n - 1]) % mod\\n
\\n###JavaScript
\\nvar countOfPairs = function(nums) {\\n const n = nums.length;\\n const dp = Array(n).fill(0).map(() => Array(51).fill(0));\\n const mod = 10 ** 9 + 7;\\n for (let v = 0; v <= nums[0]; v++) {\\n dp[0][v] = 1;\\n }\\n\\n for (let i = 1; i < n; i++) {\\n for (let v2 = 0; v2 <= nums[i]; v2++) {\\n for (let v1 = 0; v1 <= v2; v1++) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n return dp[n - 1].reduce((sum, v) => (sum + v) % mod, 0);\\n};\\n
\\n###TypeScript
\\nfunction countOfPairs(nums: number[]): number {\\n const n = nums.length;\\n const dp = Array(n).fill(0).map(() => Array(51).fill(0));\\n const mod = 10 ** 9 + 7;\\n for (let v = 0; v <= nums[0]; v++) {\\n dp[0][v] = 1;\\n }\\n\\n for (let i = 1; i < n; i++) {\\n for (let v2 = 0; v2 <= nums[i]; v2++) {\\n for (let v1 = 0; v1 <= v2; v1++) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n return dp[n - 1].reduce((sum, v) => (sum + v) % mod, 0);\\n};\\n
\\n###Go
\\nfunc countOfPairs(nums []int) int {\\n n := len(nums)\\n dp := make([][]int, n)\\n for i := range dp {\\n dp[i] = make([]int, 51)\\n }\\n mod := 1000000007\\n\\n for v := 0; v <= nums[0]; v++ {\\n dp[0][v] = 1\\n }\\n\\n for i := 1; i < n; i++ {\\n for v2 := 0; v2 <= nums[i]; v2++ {\\n for v1 := 0; v1 <= v2; v1++ {\\n if nums[i-1]-v1 >= nums[i]-v2 && nums[i]-v2 >= 0 {\\n dp[i][v2] = (dp[i][v2] + dp[i-1][v1]) % mod\\n }\\n }\\n }\\n }\\n\\n res := 0\\n for _, v := range dp[n-1] {\\n res = (res + v) % mod\\n }\\n return res\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountOfPairs(int[] nums) {\\n int n = nums.Length;\\n int[,] dp = new int[n, 51];\\n int mod = 1000000007;\\n for (int v = 0; v <= nums[0]; v++) {\\n dp[0, v] = 1;\\n }\\n\\n for (int i = 1; i < n; i++) {\\n for (int v2 = 0; v2 <= nums[i]; v2++) {\\n for (int v1 = 0; v1 <= v2; v1++) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i, v2] = (dp[i, v2] + dp[i - 1, v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n int res = 0;\\n for (int v = 0; v < 51; v++) {\\n res = (res + dp[n - 1, v]) % mod;\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nint countOfPairs(int* nums, int numsSize) {\\n int n = numsSize, mod = 1000000007;\\n int **dp = (int **)malloc(n * sizeof(int *));\\n for (int i = 0; i < n; i++) {\\n dp[i] = (int *)malloc(51 * sizeof(int));\\n for (int j = 0; j < 51; j++) {\\n dp[i][j] = 0;\\n }\\n }\\n\\n for (int v = 0; v <= nums[0]; v++) {\\n dp[0][v] = 1;\\n }\\n\\n for (int i = 1; i < n; i++) {\\n for (int v2 = 0; v2 <= nums[i]; v2++) {\\n for (int v1 = 0; v1 <= v2; v1++) {\\n if (nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0) {\\n dp[i][v2] = (dp[i][v2] + dp[i - 1][v1]) % mod;\\n }\\n }\\n }\\n }\\n\\n int res = 0;\\n for (int v = 0; v < 51; v++) {\\n res = (res + dp[n - 1][v]) % mod;\\n }\\n for (int i = 0; i < n; i++) {\\n free(dp[i]);\\n }\\n free(dp);\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_of_pairs(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let mut dp = vec![vec![0; 51]; n];\\n let modulo = 1000000007;\\n\\n for v in 0..=nums[0] {\\n dp[0][v as usize] = 1;\\n }\\n\\n for i in 1..n {\\n for v2 in 0..=nums[i] {\\n for v1 in 0..=v2 {\\n if nums[i - 1] - v1 >= nums[i] - v2 && nums[i] - v2 >= 0 {\\n dp[i][v2 as usize] = (dp[i][v2 as usize] + dp[i - 1][v1 as usize]) % modulo;\\n }\\n }\\n }\\n }\\n\\n dp[n - 1].iter().fold(0, |res, &v| (res + v) % modulo)\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(nm^2)$,其中 $n$ 是数组的长度,$m$ 是数组的最大值。
\\n空间复杂度:$O(nm)$,其中 $n$ 是数组的长度,$m$ 是数组的最大值,可以优化到一维空间 $O(m)$。
\\n思路与算法
\\n在动态规划的转移方程中,我们观察 $\\\\textit{dp}[i][j]$ 的公式,可以发现它是 $\\\\textit{dp}[i - 1]$ 的子数组的和。其中 $\\\\textit{dp}[i][j]$ 和 $\\\\textit{dp}[i][j - 1]$,类似于前缀和数组中相邻的两项,并且由 $\\\\textit{nums}[i - 1] - v_1 \\\\ge \\\\textit{nums}[i] - v_2 \\\\ge 0$ 的限制条件,我们可以推导出 $v_2 \\\\ge \\\\textit{nums}[i] - \\\\textit{nums}[i - 1] + v_1$。
\\n再结合 $v_2 \\\\ge v_1$,我们可以得到 $v_2 \\\\ge v_1 + d$,其中 $d = \\\\max(0, \\\\textit{nums}[i] - \\\\textit{nums}[i - 1])$。
\\n通过上面的观察和推导,我们可以得到 $\\\\textit{dp}[i][j]$ 和 $\\\\textit{dp}[i][j - 1]$ 的关系:$\\\\textit{dp}[i][j] = \\\\textit{dp}[i][j - 1] + \\\\textit{dp}[i - 1][j - d]$。
\\n由此我们得到新的动态转移方程,优化之前算法的复杂度。最后,我们返回 $\\\\textit{dp}[n - 1]$ 的和即为结果。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n int n = nums.size(), m = 0, mod = 1e9 + 7;\\n for (int num : nums) {\\n m = max(m, num);\\n }\\n vector<vector<int>> dp(n, vector<int>(m + 1, 0));\\n for (int a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (int i = 1; i < n; i++) {\\n int d = max(0, nums[i] - nums[i - 1]);\\n for (int j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n int res = 0;\\n for (int num : dp[n - 1]) {\\n res = (res + num) % mod;\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n int n = nums.length, m = 0, mod = 1000000007;\\n for (int num : nums) {\\n m = Math.max(m, num);\\n }\\n int[][] dp = new int[n][m + 1];\\n for (int a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (int i = 1; i < n; i++) {\\n int d = Math.max(0, nums[i] - nums[i - 1]);\\n for (int j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n int res = 0;\\n for (int num : dp[n - 1]) {\\n res = (res + num) % mod;\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n mod = 10 ** 9 + 7\\n n, m = len(nums), max(nums)\\n dp = [[0] * (m + 1) for _ in range(n)]\\n for a in range(nums[0] + 1):\\n dp[0][a] = 1\\n for i in range(1, n):\\n d = max(0, nums[i] - nums[i - 1])\\n for j in range(d, nums[i] + 1):\\n if j == 0:\\n dp[i][j] = dp[i - 1][j - d]\\n else:\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod\\n return sum(dp[n - 1]) % mod\\n
\\n###JavaScript
\\nvar countOfPairs = function(nums) {\\n const n = nums.length;\\n const m = Math.max(...nums);\\n const mod = 1e9 + 7;\\n const dp = Array(n).fill(0).map(() => Array(m + 1).fill(0));\\n for (let a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (let i = 1; i < n; i++) {\\n const d = Math.max(0, nums[i] - nums[i - 1]);\\n for (let j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n let res = 0;\\n for (let num of dp[n - 1]) {\\n res = (res + num) % mod;\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction countOfPairs(nums: number[]): number {\\n const n = nums.length;\\n const m = Math.max(...nums);\\n const mod = 1e9 + 7;\\n const dp = Array(n).fill(0).map(() => Array(m + 1).fill(0));\\n for (let a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (let i = 1; i < n; i++) {\\n const d = Math.max(0, nums[i] - nums[i - 1]);\\n for (let j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n let res = 0;\\n for (let num of dp[n - 1]) {\\n res = (res + num) % mod;\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc countOfPairs(nums []int) int {\\n n := len(nums)\\n m := 0\\n for _, num := range nums {\\n if num > m {\\n m = num\\n }\\n }\\n mod := int(1e9 + 7)\\n dp := make([][]int, n)\\n for i := range dp {\\n dp[i] = make([]int, m+1)\\n }\\n for a := 0; a <= nums[0]; a++ {\\n dp[0][a] = 1\\n }\\n for i := 1; i < n; i++ {\\n d := max(0, nums[i]-nums[i-1])\\n for j := d; j <= nums[i]; j++ {\\n if j == 0 {\\n dp[i][j] = dp[i-1][j-d]\\n } else {\\n dp[i][j] = (dp[i][j-1] + dp[i-1][j-d]) % mod\\n }\\n }\\n }\\n res := 0\\n for _, num := range dp[n-1] {\\n res = (res + num) % mod\\n }\\n return res\\n}\\n\\nfunc max(a, b int) int {\\n if a > b {\\n return a\\n }\\n return b\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountOfPairs(int[] nums) {\\n int n = nums.Length;\\n int m = nums.Max();\\n int mod = (int)(1e9 + 7);\\n int[][] dp = new int[n][];\\n for (int i = 0; i < n; i++) {\\n dp[i] = new int[m + 1];\\n }\\n for (int a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (int i = 1; i < n; i++) {\\n int d = Math.Max(0, nums[i] - nums[i - 1]);\\n for (int j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n int res = 0;\\n for (int num in dp[n - 1]) {\\n res = (res + num) % mod;\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nint countOfPairs(int* nums, int numsSize) {\\n int n = numsSize, m = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] > m) {\\n m = nums[i];\\n }\\n }\\n int mod = 1e9 + 7;\\n int **dp = (int **)malloc(n * sizeof(int *));\\n for (int i = 0; i < n; i++) {\\n dp[i] = (int *)malloc((m + 1) * sizeof(int));\\n for (int j = 0; j <= m; j++) {\\n dp[i][j] = 0;\\n }\\n }\\n for (int a = 0; a <= nums[0]; a++) {\\n dp[0][a] = 1;\\n }\\n for (int i = 1; i < n; i++) {\\n int d = (nums[i] - nums[i - 1]) > 0 ? (nums[i] - nums[i - 1]) : 0;\\n for (int j = d; j <= nums[i]; j++) {\\n if (j == 0) {\\n dp[i][j] = dp[i - 1][j - d];\\n } else {\\n dp[i][j] = (dp[i][j - 1] + dp[i - 1][j - d]) % mod;\\n }\\n }\\n }\\n int res = 0;\\n for (int j = 0; j <= m; j++) {\\n res = (res + dp[n - 1][j]) % mod;\\n }\\n for (int i = 0; i < n; i++) {\\n free(dp[i]);\\n }\\n free(dp);\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_of_pairs(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let m = *nums.iter().max().unwrap();\\n let mod_val = 1000000007;\\n let mut dp = vec![vec![0; (m + 1) as usize]; n];\\n for a in 0..=nums[0] {\\n dp[0][a as usize] = 1;\\n }\\n for i in 1..n {\\n let d = std::cmp::max(0, nums[i] - nums[i - 1]);\\n for j in d..=nums[i] {\\n if j == 0 {\\n dp[i][j as usize] = dp[i - 1][(j - d) as usize];\\n } else {\\n dp[i][j as usize] = (dp[i][(j - 1) as usize] + dp[i - 1][(j - d) as usize]) % mod_val;\\n }\\n }\\n }\\n dp[n - 1].iter().fold(0, |acc, &x| (acc + x) % mod_val)\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(nm)$,其中 $n$ 是数组的长度,$m$ 是数组的最大值。
\\n空间复杂度:$O(nm)$,其中 $n$ 是数组的长度,$m$ 是数组的最大值,可以优化到一维空间 $O(m)$。
\\n图像平滑器 是大小为 3 x 3
的过滤器,用于对图像的每个单元格平滑处理,平滑处理后单元格的值为该单元格的平均灰度。
每个单元格的 平均灰度 定义为:该单元格自身及其周围的 8 个单元格的平均值,结果需向下取整。(即,需要计算蓝色平滑器中 9 个单元格的平均值)。
\\n\\n如果一个单元格周围存在单元格缺失的情况,则计算平均灰度时不考虑缺失的单元格(即,需要计算红色平滑器中 4 个单元格的平均值)。
\\n\\n给你一个表示图像灰度的 m x n
整数矩阵 img
,返回对图像的每个单元格平滑处理后的图像 。
\\n\\n
示例 1:
\\n\\n输入:img = [[1,1,1],[1,0,1],[1,1,1]]\\n输出:[[0, 0, 0],[0, 0, 0], [0, 0, 0]]\\n解释:\\n对于点 (0,0), (0,2), (2,0), (2,2): 平均(3/4) = 平均(0.75) = 0\\n对于点 (0,1), (1,0), (1,2), (2,1): 平均(5/6) = 平均(0.83333333) = 0\\n对于点 (1,1): 平均(8/9) = 平均(0.88888889) = 0\\n\\n\\n
示例 2:
\\n输入: img = [[100,200,100],[200,50,200],[100,200,100]]\\n输出: [[137,141,137],[141,138,141],[137,141,137]]\\n解释:\\n对于点 (0,0), (0,2), (2,0), (2,2): floor((100+200+200+50)/4) = floor(137.5) = 137\\n对于点 (0,1), (1,0), (1,2), (2,1): floor((200+200+50+200+100+100)/6) = floor(141.666667) = 141\\n对于点 (1,1): floor((50+200+200+200+200+100+100+100+100)/9) = floor(138.888889) = 138\\n\\n\\n
\\n\\n
提示:
\\n\\nm == img.length
n == img[i].length
1 <= m, n <= 200
0 <= img[i][j] <= 255
具体来说,我们使用两个整数 $(row,col)$ 来模拟当前蛇的位置。随后,我们再根据 $commands$ 中的每一个指令使 $(row,col)$ 相应地变化,来模拟蛇的移动。
\\n当 $command$ 为 $UP$ 或 $DOWN$ 时,我们分别使 $row$ 相应地减少和增加 $1$;当 $command$ 为 $LEFT$ 或 $RIGHT$ 时,我们分别使 $col$ 相应地减少和增加 $1$。
\\n最后,由于矩阵大小为 $n×n$ ,因此返回 $row×n+col$ 即可。
\\n###C#
\\npublic class Solution {\\n public int FinalPositionOfSnake(int n, IList<string> commands) {\\n int row = 0, col = 0;\\n\\n foreach (var dir in commands) {\\n switch (dir[0]) {\\n case \'U\':\\n row -= 1;\\n break;\\n case \'D\':\\n row += 1;\\n break;\\n case \'L\':\\n col -= 1;\\n break;\\n default:\\n col += 1;\\n break;\\n }\\n }\\n\\n return row * n + col;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int finalPositionOfSnake(const int& n, const std::vector<string>& commands) {\\n auto row = 0, col = 0;\\n\\n for (const auto& dir : commands) {\\n switch (dir[0]) {\\n case \'U\':\\n row -= 1;\\n break;\\n case \'D\':\\n row += 1;\\n break;\\n case \'L\':\\n col -= 1;\\n break;\\n default:\\n col += 1;\\n break;\\n }\\n }\\n\\n return row * n + col;\\n }\\n};\\n
\\n###C
\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int row = 0, col = 0;\\n\\n for (int i = 0; i < commandsSize; i++) {\\n switch (commands[i][0]) {\\n case \'U\':\\n row -= 1;\\n break;\\n case \'D\':\\n row += 1;\\n break;\\n case \'L\':\\n col -= 1;\\n break;\\n default:\\n col += 1;\\n break;\\n }\\n }\\n\\n return row * n + col;\\n}\\n
\\n###Java
\\nclass Solution {\\n public int finalPositionOfSnake(int n, List<String> commands) {\\n int row = 0, col = 0;\\n\\n for (var dir : commands) {\\n switch (dir.charAt(0)) {\\n case \'U\' -> row -= 1;\\n case \'D\' -> row += 1;\\n case \'L\' -> col -= 1;\\n default -> col += 1;\\n }\\n }\\n\\n return row * n + col;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n row, col = 0, 0\\n\\n for dir in commands:\\n match dir[0]:\\n case \'U\': row -= 1\\n case \'D\': row += 1\\n case \'L\': col -= 1\\n case _: col += 1\\n\\n return row * n + col\\n \\n
\\n###Go
\\nfunc finalPositionOfSnake(n int, commands []string) int {\\nrow, col := 0, 0\\n\\n for _, dir := range commands {\\n switch dir[0] {\\n case \'U\':\\n row -= 1\\n case \'D\':\\n row += 1\\n case \'L\':\\n col -= 1\\n default:\\n col += 1\\n }\\n }\\n \\n return row * n + col\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun finalPositionOfSnake(n: Int, commands: List<String>): Int {\\n var row = 0\\n var col = 0\\n\\n for (dir in commands) {\\n when (dir[0]) {\\n \'U\' -> row -= 1\\n \'D\' -> row += 1\\n \'L\' -> col -= 1\\n else -> col += 1\\n }\\n }\\n \\n return row * n + col\\n }\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func finalPositionOfSnake(n: Int64, commands: ArrayList<String>): Int64 {\\n var row = 0;\\n var col = 0;\\n\\n for (dir in commands) {\\n match (dir[0]) {\\n case b\'U\' => row -= 1;\\n case b\'D\' => row += 1;\\n case b\'L\' => col -= 1;\\n case _ => col += 1;\\n }\\n }\\n\\n return row * n + col;\\n }\\n}\\n
\\n注意到对于 $n×n$ 的矩阵,对于其非边界格,其上下相邻格差值恰好为 $n$,因此我们可以选择使用 $cellVal$ 模拟当前蛇所处的单元格的数值。
\\n当 $command$ 为 $UP$ 或 $DOWN$ 时,我们分别使 $cellVal$ 相应地减少和增加 $n$;当 $command$ 为 $LEFT$ 或 $RIGHT$ 时,我们分别使 $cellVal$ 相应地减少和增加 $1$。
\\n最终我们返回 $cellVal$ 即为所求。
\\n###C#
\\npublic class Solution {\\n public int FinalPositionOfSnake(int n, IList<string> commands) {\\n return commands.Sum(c => c[0] switch {\\n \'U\' => -n,\\n \'D\' => n,\\n \'L\' => -1,\\n _ => 1\\n });\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int finalPositionOfSnake(const int& n, const std::vector<string>& commands) {\\n return std::accumulate(commands.begin(), commands.end(), 0, [n](int curr, const std::string& dir) {\\n switch (dir[0]) {\\n case \'U\': return curr - n;\\n case \'D\': return curr + n;\\n case \'L\': return curr - 1;\\n default: return curr + 1;\\n }\\n });\\n }\\n};\\n
\\n###C
\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int result = 0;\\n\\n for (int i = 0; i < commandsSize; i++) {\\n switch (commands[i][0]) {\\n case \'U\':\\n result -= n;\\n break;\\n case \'D\':\\n result += n;\\n break;\\n case \'L\':\\n result -= 1;\\n break;\\n default:\\n result += 1;\\n break;\\n }\\n }\\n\\n return result;\\n}\\n
\\n###Java
\\nclass Solution {\\n public int finalPositionOfSnake(int n, List<String> commands) {\\n return commands.stream().mapToInt(\\n command -> switch (command.charAt(0)) {\\n case \'U\' -> -n;\\n case \'D\' -> n;\\n case \'L\' -> -1;\\n default -> 1;\\n }\\n ).sum();\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n return sum(\\n -n if dir[0] == \'U\' else\\n n if dir[0] == \'D\' else\\n -1 if dir[0] == \'L\' else\\n 1\\n for dir in commands\\n )\\n\\n
\\n###Go
\\nfunc finalPositionOfSnake(n int, commands []string) int {\\nresult := 0\\n\\nfor _, dir := range commands {\\nswitch dir[0] {\\ncase \'U\':\\nresult -= n\\ncase \'D\':\\nresult += n\\ncase \'L\':\\nresult -= 1\\ndefault:\\nresult += 1\\n}\\n}\\n\\nreturn result\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun finalPositionOfSnake(n: Int, commands: List<String>): Int {\\n return commands.sumOf {\\n when (it[0]) {\\n \'U\' -> -n\\n \'D\' -> n\\n \'L\' -> -1\\n else -> 1\\n }\\n }\\n }\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func finalPositionOfSnake(n: Int64, commands: ArrayList<String>): Int64 {\\n var result = 0;\\n\\n for (dir in commands) {\\n match (dir[0]) {\\n case b\'U\' => result -= n;\\n case b\'D\' => result += n;\\n case b\'L\' => result -= 1;\\n case _ => result += 1;\\n }\\n }\\n\\n return result;\\n }\\n}\\n
\\n请先完成上一题 3355. 零数组变换 I。
\\n本题由于 $k$ 越大,越能满足要求;$k$ 越小,越无法满足要求。有单调性,可以二分答案求最小的 $k$。
\\n问题变成:
\\n用上一题的差分数组计算。
\\n下面代码采用开区间二分,这仅仅是二分的一种写法,使用闭区间或者半闭半开区间都是可以的,喜欢哪种写法就用哪种。
\\n对于开区间写法,简单来说 check(mid) == true
时更新的是谁,最后就返回谁。相比其他二分写法,开区间写法不需要思考加一减一等细节,更简单。推荐使用开区间写二分。
具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:\\n # 3355. 零数组变换 I\\n def check(k: int) -> bool:\\n diff = [0] * (len(nums) + 1)\\n for l, r, val in queries[:k]: # 前 k 个询问\\n diff[l] += val\\n diff[r + 1] -= val\\n\\n for x, sum_d in zip(nums, accumulate(diff)):\\n if x > sum_d:\\n return False\\n return True\\n\\n q = len(queries)\\n left, right = -1, q + 1\\n while left + 1 < right:\\n mid = (left + right) // 2\\n if check(mid):\\n right = mid\\n else:\\n left = mid\\n return right if right <= q else -1\\n
\\n###py
\\nclass Solution:\\n def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:\\n # 3355. 零数组变换 I\\n def check(k: int) -> bool:\\n diff = [0] * (len(nums) + 1)\\n for l, r, val in queries[:k]: # 前 k 个询问\\n diff[l] += val\\n diff[r + 1] -= val\\n\\n for x, sum_d in zip(nums, accumulate(diff)):\\n if x > sum_d:\\n return False\\n return True\\n\\n q = len(queries)\\n ans = bisect_left(range(q + 1), True, key=check)\\n return ans if ans <= q else -1\\n
\\n###java
\\nclass Solution {\\n public int minZeroArray(int[] nums, int[][] queries) {\\n int q = queries.length;\\n int left = -1, right = q + 1;\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n if (check(mid, nums, queries)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right <= q ? right : -1;\\n }\\n\\n // 3355. 零数组变换 I\\n private boolean check(int k, int[] nums, int[][] queries) {\\n int n = nums.length;\\n int[] diff = new int[n + 1];\\n for (int i = 0; i < k; i++) { // 前 k 个询问\\n int[] q = queries[i];\\n int l = q[0], r = q[1], val = q[2];\\n diff[l] += val;\\n diff[r + 1] -= val;\\n }\\n\\n int sumD = 0;\\n for (int i = 0; i < n; i++) {\\n sumD += diff[i];\\n if (nums[i] > sumD) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n // 3355. 零数组变换 I\\n int n = nums.size();\\n vector<int> diff(n + 1);\\n auto check = [&](int k) -> bool {\\n ranges::fill(diff, 0);\\n for (int i = 0; i < k; i++) { // 前 k 个询问\\n auto& q = queries[i];\\n int l = q[0], r = q[1], val = q[2];\\n diff[l] += val;\\n diff[r + 1] -= val;\\n }\\n\\n int sum_d = 0;\\n for (int i = 0; i < n; i++) {\\n sum_d += diff[i];\\n if (nums[i] > sum_d) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n int q = queries.size();\\n int left = -1, right = q + 1;\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n (check(mid) ? right : left) = mid;\\n }\\n return right <= q ? right : -1;\\n }\\n};\\n
\\n###go
\\nfunc minZeroArray(nums []int, queries [][]int) int {\\nq := len(queries)\\ndiff := make([]int, len(nums)+1)\\nans := sort.Search(q+1, func(k int) bool {\\n// 3355. 零数组变换 I\\nclear(diff)\\nfor _, q := range queries[:k] { // 前 k 个询问\\nl, r, val := q[0], q[1], q[2]\\ndiff[l] += val\\ndiff[r+1] -= val\\n}\\n\\nsumD := 0\\nfor i, x := range nums {\\nsumD += diff[i]\\nif x > sumD {\\nreturn false\\n}\\n}\\nreturn true\\n})\\nif ans > q {\\nreturn -1\\n}\\nreturn ans\\n}\\n
\\n用 Lazy 线段树模拟区间减法,同时维护区间最大值。
\\n处理完 $\\\\textit{queries}[i]$ 后,如果整个数组的最大值 $\\\\le 0$,返回 $i+1$。
\\n特判一开始数组全为 $0$ 的情况,返回 $0$。
\\n完整的 Lazy 线段树模板,见我的 数据结构题单。
\\n###py
\\nclass Solution:\\n def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:\\n n = len(nums)\\n m = 2 << n.bit_length()\\n mx = [0] * m\\n todo = [0] * m\\n\\n def do(o: int, v: int) -> None:\\n mx[o] -= v\\n todo[o] += v\\n\\n def spread(o: int) -> None:\\n if todo[o] != 0:\\n do(o * 2, todo[o])\\n do(o * 2 + 1, todo[o])\\n todo[o] = 0\\n\\n def maintain(o: int) -> None:\\n mx[o] = max(mx[o * 2], mx[o * 2 + 1])\\n\\n def build(o: int, l: int, r: int) -> None:\\n if l == r:\\n mx[o] = nums[l]\\n return\\n m = (l + r) // 2\\n build(o * 2, l, m)\\n build(o * 2 + 1, m + 1, r)\\n maintain(o)\\n\\n def update(o: int, l: int, r: int, ql: int, qr: int, v: int) -> None:\\n if ql <= l and r <= qr:\\n do(o, v)\\n return\\n spread(o)\\n m = (l + r) // 2\\n if ql <= m:\\n update(o * 2, l, m, ql, qr, v)\\n if m < qr:\\n update(o * 2 + 1, m + 1, r, ql, qr, v)\\n maintain(o)\\n\\n build(1, 0, n - 1)\\n if mx[1] <= 0:\\n return 0\\n\\n for i, (ql, qr, v) in enumerate(queries):\\n update(1, 0, n - 1, ql, qr, v)\\n if mx[1] <= 0:\\n return i + 1\\n return -1\\n
\\n###java
\\nclass SegmentTree {\\n private final int[] mx;\\n private final int[] todo;\\n\\n public SegmentTree(int[] nums) {\\n int n = nums.length;\\n int m = 2 << (32 - Integer.numberOfLeadingZeros(n));\\n mx = new int[m];\\n todo = new int[m];\\n build(1, 0, n - 1, nums);\\n }\\n\\n private void do_(int o, int v) {\\n mx[o] -= v;\\n todo[o] += v;\\n }\\n\\n private void spread(int o) {\\n if (todo[o] != 0) {\\n do_(o * 2, todo[o]);\\n do_(o * 2 + 1, todo[o]);\\n todo[o] = 0;\\n }\\n }\\n\\n private void maintain(int o) {\\n mx[o] = Math.max(mx[o * 2], mx[o * 2 + 1]);\\n }\\n\\n private void build(int o, int l, int r, int[] nums) {\\n if (l == r) {\\n mx[o] = nums[l];\\n return;\\n }\\n int m = (l + r) / 2;\\n build(o * 2, l, m, nums);\\n build(o * 2 + 1, m + 1, r, nums);\\n maintain(o);\\n }\\n\\n public void update(int o, int l, int r, int ql, int qr, int v) {\\n if (ql <= l && r <= qr) {\\n do_(o, v);\\n return;\\n }\\n spread(o);\\n int m = (l + r) / 2;\\n if (ql <= m) {\\n update(o * 2, l, m, ql, qr, v);\\n }\\n if (m < qr) {\\n update(o * 2 + 1, m + 1, r, ql, qr, v);\\n }\\n maintain(o);\\n }\\n\\n public int queryAll() {\\n return mx[1];\\n }\\n}\\n\\nclass Solution {\\n public int minZeroArray(int[] nums, int[][] queries) {\\n SegmentTree tree = new SegmentTree(nums);\\n if (tree.queryAll() <= 0) {\\n return 0;\\n }\\n for (int i = 0; i < queries.length; i++) {\\n int[] q = queries[i];\\n tree.update(1, 0, nums.length - 1, q[0], q[1], q[2]);\\n if (tree.queryAll() <= 0) {\\n return i + 1;\\n }\\n }\\n return -1;\\n }\\n}\\n
\\n###cpp
\\nclass SegmentTree {\\n int n;\\n vector<int> mx;\\n vector<int> todo;\\n\\n void do_(int o, int v) {\\n mx[o] -= v;\\n todo[o] += v;\\n }\\n\\n void spread(int o) {\\n if (todo[o]) {\\n do_(o * 2, todo[o]);\\n do_(o * 2 + 1, todo[o]);\\n todo[o] = 0;\\n }\\n }\\n\\n void maintain(int o) {\\n mx[o] = max(mx[o * 2], mx[o * 2 + 1]);\\n }\\n\\n void build(int o, int l, int r, vector<int>& nums) {\\n if (l == r) {\\n mx[o] = nums[l];\\n return;\\n }\\n int m = (l + r) / 2;\\n build(o * 2, l, m, nums);\\n build(o * 2 + 1, m + 1, r, nums);\\n maintain(o);\\n }\\n\\n void update(int o, int l, int r, int ql, int qr, int v) {\\n if (ql <= l && r <= qr) {\\n do_(o, v);\\n return;\\n }\\n spread(o);\\n int m = (l + r) / 2;\\n if (ql <= m) {\\n update(o * 2, l, m, ql, qr, v);\\n }\\n if (m < qr) {\\n update(o * 2 + 1, m + 1, r, ql, qr, v);\\n }\\n maintain(o);\\n }\\n\\npublic:\\n SegmentTree(vector<int>& nums) {\\n n = nums.size();\\n int m = 2 << (32 - __builtin_clz(n));\\n mx.resize(m);\\n todo.resize(m);\\n build(1, 0, n - 1, nums);\\n }\\n\\n void update(int ql, int qr, int v) {\\n update(1, 0, n - 1, ql, qr, v);\\n }\\n\\n int query_all() {\\n return mx[1];\\n }\\n};\\n\\nclass Solution {\\npublic:\\n int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n SegmentTree tree(nums);\\n if (tree.query_all() <= 0) {\\n return 0;\\n }\\n for (int i = 0; i < queries.size(); ++i) {\\n auto& q = queries[i];\\n tree.update(q[0], q[1], q[2]);\\n if (tree.query_all() <= 0) {\\n return i + 1;\\n }\\n }\\n return -1;\\n }\\n};\\n
\\n###go
\\ntype seg []struct {\\nl, r, mx, todo int\\n}\\n\\nfunc (t seg) do(o, v int) {\\nt[o].mx -= v\\nt[o].todo += v\\n}\\n\\nfunc (t seg) spread(o int) {\\nif v := t[o].todo; v != 0 {\\nt.do(o<<1, v)\\nt.do(o<<1|1, v)\\nt[o].todo = 0\\n}\\n}\\n\\nfunc (t seg) maintain(o int) {\\nt[o].mx = max(t[o<<1].mx, t[o<<1|1].mx)\\n}\\n\\nfunc (t seg) build(a []int, o, l, r int) {\\nt[o].l, t[o].r = l, r\\nif l == r {\\nt[o].mx = a[l]\\nreturn\\n}\\nm := (l + r) >> 1\\nt.build(a, o<<1, l, m)\\nt.build(a, o<<1|1, m+1, r)\\nt.maintain(o)\\n}\\n\\nfunc (t seg) update(o, l, r, v int) {\\nif l <= t[o].l && t[o].r <= r {\\nt.do(o, v)\\nreturn\\n}\\nt.spread(o)\\nm := (t[o].l + t[o].r) >> 1\\nif l <= m {\\nt.update(o<<1, l, r, v)\\n}\\nif m < r {\\nt.update(o<<1|1, l, r, v)\\n}\\nt.maintain(o)\\n}\\n\\nfunc minZeroArray(nums []int, queries [][]int) int {\\nn := len(nums)\\nt := make(seg, 2<<bits.Len(uint(n-1)))\\nt.build(nums, 1, 0, n-1)\\nif t[1].mx <= 0 {\\nreturn 0\\n}\\nfor i, q := range queries {\\nt.update(1, q[0], q[1], q[2])\\nif t[1].mx <= 0 {\\nreturn i + 1\\n}\\n}\\nreturn -1\\n}\\n
\\n和方法一一样,用一个差分数组处理询问。
\\n这次我们从左到右遍历 $x=\\\\textit{nums}[i]$,如果发现 $x>\\\\textit{sumD}$,那么就必须处理询问,直到 $x\\\\le \\\\textit{sumD}$ 为止。
\\n对于询问 $[l,r,\\\\textit{val}]$,如果发现 $l\\\\le i \\\\le r$,那么直接把 $\\\\textit{sumD}$ 增加 $\\\\textit{val}$。
\\n由于处理过的询问无需再处理,所以上述过程可以用双指针实现。
\\n###py
\\nclass Solution:\\n def minZeroArray(self, nums: List[int], queries: List[List[int]]) -> int:\\n diff = [0] * (len(nums) + 1)\\n sum_d = k = 0\\n for i, (x, d) in enumerate(zip(nums, diff)):\\n sum_d += d\\n while k < len(queries) and sum_d < x: # 需要添加询问,把 x 减小\\n l, r, val = queries[k]\\n diff[l] += val\\n diff[r + 1] -= val\\n if l <= i <= r: # x 在更新范围中\\n sum_d += val\\n k += 1\\n if sum_d < x: # 无法更新\\n return -1\\n return k\\n
\\n###java
\\nclass Solution {\\n public int minZeroArray(int[] nums, int[][] queries) {\\n int n = nums.length;\\n int[] diff = new int[n + 1];\\n int sumD = 0;\\n int k = 0;\\n for (int i = 0; i < n; i++) {\\n int x = nums[i];\\n sumD += diff[i];\\n while (k < queries.length && sumD < x) { // 需要添加询问,把 x 减小\\n int[] q = queries[k];\\n int l = q[0], r = q[1], val = q[2];\\n diff[l] += val;\\n diff[r + 1] -= val;\\n if (l <= i && i <= r) { // x 在更新范围中\\n sumD += val;\\n }\\n k++;\\n }\\n if (sumD < x) { // 无法更新\\n return -1;\\n }\\n }\\n return k;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n vector<int> diff(n + 1);\\n int sum_d = 0, k = 0;\\n for (int i = 0; i < n; i++) {\\n int x = nums[i];\\n sum_d += diff[i];\\n while (k < queries.size() && sum_d < x) { // 需要添加询问,把 x 减小\\n auto& q = queries[k];\\n int l = q[0], r = q[1], val = q[2];\\n diff[l] += val;\\n diff[r + 1] -= val;\\n if (l <= i && i <= r) { // x 在更新范围中\\n sum_d += val;\\n }\\n k++;\\n }\\n if (sum_d < x) { // 无法更新\\n return -1;\\n }\\n }\\n return k;\\n }\\n};\\n
\\n###go
\\nfunc minZeroArray(nums []int, queries [][]int) int {\\nn := len(nums)\\ndiff := make([]int, n+1)\\nsumD, k := 0, 0\\nfor i, x := range nums {\\nsumD += diff[i]\\nfor k < len(queries) && sumD < x { // 需要添加询问,把 x 减小\\nq := queries[k]\\nl, r, val := q[0], q[1], q[2]\\ndiff[l] += val\\ndiff[r+1] -= val\\nif l <= i && i <= r { // x 在更新范围中\\nsumD += val\\n}\\nk++\\n}\\nif sumD < x { // 无法更新\\nreturn -1\\n}\\n}\\nreturn k\\n}\\n
\\n如果询问可以按照任意顺序执行呢?这里限制 $\\\\textit{val}=1$。
\\n\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:二分答案+差分数组 请先完成上一题 3355. 零数组变换 I。\\n\\n本题由于 $k$ 越大,越能满足要求;$k$ 越小,越无法满足要求。有单调性,可以二分答案求最小的 $k$。\\n\\n问题变成:\\n\\n能否用前 $k$ 个询问(下标从 $0$ 到 $k-1$)把 $\\\\textit{nums}$ 的所有元素都变成 $\\\\le 0$?\\n\\n用上一题的差分数组计算。\\n\\n细节\\n\\n下面代码采用开区间二分,这仅仅是二分的一种写法,使用闭区间或者半闭半开区间都是可以的,喜欢哪种写法就用哪种。\\n\\n开区间左端点初始值:$-1$。一定无法满足要求。\\n开区间右端点初始值:$q+1$,其中…","guid":"https://leetcode.cn/problems/zero-array-transformation-ii//solution/liang-chong-fang-fa-er-fen-da-an-chai-fe-rcvg","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-17T08:56:20.603Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【模板】差分数组(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/zero-array-transformation-i//solution/mo-ban-chai-fen-shu-zu-pythonjavacgo-by-i4axs","content":"题意可以转换成:
\\n如果所有元素都 $\\\\le 0$,那么我们可以撤销一部分元素的减一,使其调整为 $0$,从而满足原始题意的要求。
\\n这可以用差分数组计算,原理讲解(推荐和【图解】从一维差分到二维差分 一起看)。
\\n本题视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def isZeroArray(self, nums: List[int], queries: List[List[int]]) -> bool:\\n diff = [0] * (len(nums) + 1)\\n for l, r in queries:\\n # 区间 [l,r] 中的数都加一\\n diff[l] += 1\\n diff[r + 1] -= 1\\n\\n for x, sum_d in zip(nums, accumulate(diff)):\\n # 此时 sum_d 表示 x=nums[i] 要减掉多少\\n if x > sum_d: # x 无法变成 0\\n return False\\n return True\\n
\\n###java
\\nclass Solution {\\n public boolean isZeroArray(int[] nums, int[][] queries) {\\n int n = nums.length;\\n int[] diff = new int[n + 1];\\n for (int[] q : queries) {\\n // 区间 [l,r] 中的数都加一\\n diff[q[0]]++;\\n diff[q[1] + 1]--;\\n }\\n\\n int sumD = 0;\\n for (int i = 0; i < n; i++) {\\n sumD += diff[i];\\n // 此时 sumD 表示 nums[i] 要减掉多少\\n if (nums[i] > sumD) { // nums[i] 无法变成 0\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n vector<int> diff(n + 1);\\n for (auto& q : queries) {\\n // 区间 [l,r] 中的数都加一\\n diff[q[0]]++;\\n diff[q[1] + 1]--;\\n }\\n\\n int sum_d = 0;\\n for (int i = 0; i < n; i++) {\\n sum_d += diff[i];\\n // 此时 sum_d 表示 nums[i] 要减掉多少\\n if (nums[i] > sum_d) { // nums[i] 无法变成 0\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n
\\n###go
\\nfunc isZeroArray(nums []int, queries [][]int) bool {\\ndiff := make([]int, len(nums)+1)\\nfor _, q := range queries {\\n// 区间 [l,r] 中的数都加一\\ndiff[q[0]]++\\ndiff[q[1]+1]--\\n}\\n\\nsumD := 0\\nfor i, x := range nums {\\nsumD += diff[i]\\n// 此时 sumD 表示 x=nums[i] 要减掉多少\\nif x > sumD { // x 无法变成 0\\nreturn false\\n}\\n}\\nreturn true\\n}\\n
\\n更多相似题目,见下面数据结构题单中的「§2.1 一维差分」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意可以转换成: 把 $[l_i,r_i]$ 中的元素都减一,最终数组中的所有元素是否都 $\\\\le 0$?\\n\\n如果所有元素都 $\\\\le 0$,那么我们可以撤销一部分元素的减一,使其调整为 $0$,从而满足原始题意的要求。\\n\\n这可以用差分数组计算,原理讲解(推荐和【图解】从一维差分到二维差分 一起看)。\\n\\n本题视频讲解,欢迎点赞关注~\\n\\n###py\\n\\nclass Solution:\\n def isZeroArray(self, nums: List[int], queries: List[List[int]]) -> bool:\\n diff…","guid":"https://leetcode.cn/problems/zero-array-transformation-i//solution/mo-ban-chai-fen-shu-zu-pythonjavacgo-by-i4axs","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-17T08:18:13.310Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"差分数组","url":"https://leetcode.cn/problems/zero-array-transformation-i//solution/chai-fen-shu-zu-by-huanmengxingshen-dtc9","content":"差分数组","description":"差分数组","guid":"https://leetcode.cn/problems/zero-array-transformation-i//solution/chai-fen-shu-zu-by-huanmengxingshen-dtc9","author":"HuanMengXingShen","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-17T06:18:15.932Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3356. 零数组变换 II","url":"https://leetcode.cn/problems/zero-array-transformation-ii//solution/3356-ling-shu-zu-bian-huan-ii-by-stormsu-cchg","content":"当数组 $\\\\textit{nums}$ 中的所有元素都等于 $0$ 时,不需要处理任何查询,数组 $\\\\textit{nums}$ 已经是零数组,因此返回 $0$。以下只考虑数组 $\\\\textit{nums}$ 中存在非零元素的情况,此时如果存在顺序处理的最小查询数目 $k$ 则一定有 $k \\\\ge 1$。
\\n如果至少顺序处理前 $k$ 个查询可以将 $\\\\textit{nums}$ 变成零数组,则当顺序处理的查询数目大于等于 $k$ 时一定可以将 $\\\\textit{nums}$ 变成零数组,当顺序处理的查询数目小于 $k$ 时一定不能将数组 $\\\\textit{nums}$ 变成零数组。因此,这道题是二分查找判定问题,需要找到顺序处理的最小查询数目。
\\n为了实现二分查找判定,对于顺序处理的特定查询数目 $k$,需要实现判断是否可以通过顺序处理前 $k$ 个查询将数组 $\\\\textit{nums}$ 转换为零数组。
\\n当顺序处理前 $k$ 个查询时,为了判断是否可以将数组 $\\\\textit{nums}$ 转换为零数组,应考虑按照最大减少量顺序处理前 $k$ 个查询之后是否可以将数组 $\\\\textit{nums}$ 中的所有元素都变成小于等于 $0$ 的元素。具体做法是,对于每个查询 $[\\\\textit{left}, \\\\textit{right}, \\\\textit{val}]$,将数组 $\\\\textit{nums}$ 的下标范围 $[\\\\textit{left}, \\\\textit{right}]$ 中的所有下标对应的元素值都减少 $\\\\textit{val}$。
\\n对于每个下标 $i$,用 $\\\\textit{numsMin}[i]$ 表示按照最大减少量顺序处理前 $k$ 个查询之后的数组 $\\\\textit{nums}$ 的下标 $i$ 的元素值。由于每次操作可以选择下标范围的一个下标子集,因此顺序处理前 $k$ 个查询之后,对于每个下标 $i$,其元素值可能是范围 $[\\\\textit{numsMin}[i], \\\\textit{nums}[i]]$ 中的任意整数。判断是否可以将数组 $\\\\textit{nums}$ 转换为零数组,等价于对于每个下标 $i$ 判断是否满足 $0 \\\\in [\\\\textit{numsMin}[i], \\\\textit{nums}[i]]$,由于 $\\\\textit{nums}[i] \\\\ge 0$,因此等价于是否满足判断 $\\\\textit{numsMin}[i] \\\\le 0$。
\\n用 $n$ 表示数组 $\\\\textit{nums}$ 的长度。考虑相邻两个下标的减少量之差。当查询更新的下标范围是 $[\\\\textit{left}, \\\\textit{right}, \\\\textit{val}]$ 时,下标 $\\\\textit{left}$ 与下标 $\\\\textit{left} - 1$ 的减少量之差增加 $\\\\textit{val}$(当 $\\\\textit{left} = 0$ 时不需要考虑下标 $\\\\textit{left} - 1$),下标 $\\\\textit{right} + 1$ 与下标 $\\\\textit{right}$ 的减少量之差减少 $\\\\textit{val}$(当 $\\\\textit{right} = n - 1$ 时不需要考虑下标 $\\\\textit{right} + 1$)。因此可以使用差分数组计算每个下标的减少量。创建长度为 $n$ 的数组 $\\\\textit{decrements}$,执行如下操作。
\\n遍历所有查询,对于每个查询 $[\\\\textit{left}, \\\\textit{right}, \\\\textit{val}]$,将 $\\\\textit{decrements}[\\\\textit{left}]$ 的值增加 $\\\\textit{val}$,当 $\\\\textit{right} < n - 1$ 时将 $\\\\textit{decrements}[\\\\textit{right} + 1]$ 的值减少 $\\\\textit{val}$。
\\n将数组 $\\\\textit{decrements}$ 更新为其前缀和数组。做法是从小到大遍历 $1 \\\\le i < n$,对于每个下标 $i$,将 $\\\\textit{decrements}[i]$ 的值增加 $\\\\textit{decrements}[i - 1]$。
\\n计算得到的数组 $\\\\textit{decrements}$ 即为每个下标的减少量。对于 $0 \\\\le i < n$ 的每个下标 $i$,有 $\\\\textit{numsMin}[i] = \\\\textit{nums}[i] - \\\\textit{decrements}[i]$。如果存在下标 $i$ 满足 $\\\\textit{nums}[i] > \\\\textit{decrements}[i]$,则 $\\\\textit{numsMin}[i] > 0$,因此不能将数组 $\\\\textit{nums}$ 转换为零数组;如果所有下标 $i$ 都满足 $\\\\textit{nums}[i] \\\\le \\\\textit{decrements}[i]$,则 $\\\\textit{numsMin}[i] \\\\le 0$,因此可以将数组 $\\\\textit{nums}$ 转换为零数组。
\\n根据上述实现,二分查找判定的做法如下。
\\n用 $q$ 表示数组 $\\\\textit{queries}$ 的长度。用 $\\\\textit{low}$ 和 $\\\\textit{high}$ 分别表示二分查找的下界和上界。由于当数组 $\\\\textit{nums}$ 中存在非零元素时至少需要处理 $1$ 个查询,因此 $\\\\textit{low}$ 的初始值等于 $1$;由于可能存在处理所有查询之后仍不能将数组 $\\\\textit{nums}$ 变成零数组的情况,因此用 $q + 1$ 表示不能将数组 $\\\\textit{nums}$ 变成零数组的顺序处理的查询数目,$\\\\textit{high}$ 的初始值等于 $q + 1$。
\\n每次查找时,取 $\\\\textit{mid}$ 为 $\\\\textit{low}$ 和 $\\\\textit{high}$ 的平均数向下取整,将 $\\\\textit{mid}$ 作为顺序处理的查询数目,判断是否可以将数组 $\\\\textit{nums}$ 转换为零数组,执行如下操作。
\\n如果顺序处理 $\\\\textit{mid}$ 个查询可以将数组 $\\\\textit{nums}$ 转换为零数组,则最小查询数目小于等于 $\\\\textit{mid}$,因此在 $[\\\\textit{low}, \\\\textit{mid}]$ 中继续查找。
\\n如果顺序处理 $\\\\textit{mid}$ 个查询不能将数组 $\\\\textit{nums}$ 转换为零数组,则最小查询数目大于 $\\\\textit{mid}$,因此在 $[\\\\textit{mid} + 1, \\\\textit{high}]$ 中继续查找。
\\n当 $\\\\textit{low} = \\\\textit{high}$ 时,查找结束。如果 $\\\\textit{low} \\\\le q$,则最小查询数目是 $\\\\textit{low}$,返回 $\\\\textit{low}$;如果 $\\\\textit{low} = q + 1$,则不能将数组 $\\\\textit{nums}$ 变成零数组,返回 $-1$。
\\n###Java
\\nclass Solution {\\n public int minZeroArray(int[] nums, int[][] queries) {\\n boolean allZero = true;\\n for (int num : nums) {\\n if (num > 0) {\\n allZero = false;\\n break;\\n }\\n }\\n if (allZero) {\\n return 0;\\n }\\n int q = queries.length;\\n int low = 1, high = q + 1;\\n while (low < high) {\\n int mid = low + (high - low) / 2;\\n if (isZeroArray(nums, queries, mid)) {\\n high = mid;\\n } else {\\n low = mid + 1;\\n }\\n }\\n return low <= q ? low : -1;\\n }\\n\\n public boolean isZeroArray(int[] nums, int[][] queries, int k) {\\n int n = nums.length;\\n int[] decrements = new int[n];\\n for (int i = 0; i < k; i++) {\\n int left = queries[i][0], right = queries[i][1], val = queries[i][2];\\n decrements[left] += val;\\n if (right < n - 1) {\\n decrements[right + 1] -= val;\\n }\\n }\\n for (int i = 1; i < n; i++) {\\n decrements[i] += decrements[i - 1];\\n }\\n for (int i = 0; i < n; i++) {\\n if (nums[i] > decrements[i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinZeroArray(int[] nums, int[][] queries) {\\n bool allZero = true;\\n foreach (int num in nums) {\\n if (num > 0) {\\n allZero = false;\\n break;\\n }\\n }\\n if (allZero) {\\n return 0;\\n }\\n int q = queries.Length;\\n int low = 1, high = q + 1;\\n while (low < high) {\\n int mid = low + (high - low) / 2;\\n if (IsZeroArray(nums, queries, mid)) {\\n high = mid;\\n } else {\\n low = mid + 1;\\n }\\n }\\n return low <= q ? low : -1;\\n }\\n\\n public bool IsZeroArray(int[] nums, int[][] queries, int k) {\\n int n = nums.Length;\\n int[] decrements = new int[n];\\n for (int i = 0; i < k; i++) {\\n int left = queries[i][0], right = queries[i][1], val = queries[i][2];\\n decrements[left] += val;\\n if (right < n - 1) {\\n decrements[right + 1] -= val;\\n }\\n }\\n for (int i = 1; i < n; i++) {\\n decrements[i] += decrements[i - 1];\\n }\\n for (int i = 0; i < n; i++) {\\n if (nums[i] > decrements[i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n时间复杂度:$O((n + q) \\\\log q)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$q$ 是数组 $\\\\textit{queries}$ 的长度。需要执行 $O(\\\\log q)$ 轮二分查找,每轮二分查找遍历数组 $\\\\textit{nums}$ 和 $\\\\textit{queries}$ 计算数组 $\\\\textit{decrements}$ 并判断是否可以将 $\\\\textit{nums}$ 变成零数组的时间是 $O(n + q)$,因此时间复杂度是 $O((n + q) \\\\log q)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要创建长度为 $n$ 的数组 $\\\\textit{decrements}$。
\\n我们首先把题目的包装拆开。如果有 $x$ 个区间覆盖了某个元素,则那个元素最多可以被减去 $x$ 次。因此题目等价于:问每个元素 nums[i]
是否被至少 nums[i]
个询问区间覆盖。
这就是非常经典的差分问题。用差分维护每个元素被几个区间覆盖即可。复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n // 差分维护每个元素被几个区间覆盖\\n vector<int> d(n + 1);\\n for (auto &qry : queries) {\\n d[qry[0]]++;\\n d[qry[1] + 1]--;\\n }\\n // 枚举每个元素,求区间覆盖数\\n for (int i = 0, now = 0; i < n; i++) {\\n now += d[i];\\n if (now < nums[i]) return false;\\n }\\n return true;\\n }\\n};\\n
\\n","description":"解法:差分 我们首先把题目的包装拆开。如果有 $x$ 个区间覆盖了某个元素,则那个元素最多可以被减去 $x$ 次。因此题目等价于:问每个元素 nums[i] 是否被至少 nums[i] 个询问区间覆盖。\\n\\n这就是非常经典的差分问题。用差分维护每个元素被几个区间覆盖即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n bool isZeroArray(vector如果只问所有操作结束后是否能得到零数组,思路和 零数组变换 I 非常相似,只是把区间覆盖数改成覆盖区间的权值之和。
\\n最早第几次操作后可以得到零数组怎么求呢?这种问题一般都是二分操作数 $k$,再用上述方法检验,只用前 $k$ 个操作能否得到零数组。复杂度 $\\\\mathcal{O}(n\\\\log n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int minZeroArray(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size(), q = queries.size();\\n\\n // 二分检查只用前 k 个操作能否得到零数组\\n auto check = [&](int k) {\\n // 差分维护每个元素被覆盖的权值之和\\n vector<long long> d(n + 1);\\n for (int i = 0; i < k; i++) {\\n auto &qry = queries[i];\\n d[qry[0]] += qry[2];\\n d[qry[1] + 1] -= qry[2];\\n }\\n // 枚举每个元素,求覆盖的权值之和\\n long long now = 0;\\n for (int i = 0; i < n; i++) {\\n now += d[i];\\n if (now < nums[i]) return false;\\n }\\n return true;\\n };\\n\\n if (!check(q)) return -1;\\n // 二分答案\\n int head = 0, tail = q;\\n while (head < tail) {\\n int mid = (head + tail) >> 1;\\n if (check(mid)) tail = mid;\\n else head = mid + 1;\\n }\\n return head;\\n }\\n};\\n\\n
\\n","description":"解法:二分 & 差分 如果只问所有操作结束后是否能得到零数组,思路和 零数组变换 I 非常相似,只是把区间覆盖数改成覆盖区间的权值之和。\\n\\n最早第几次操作后可以得到零数组怎么求呢?这种问题一般都是二分操作数 $k$,再用上述方法检验,只用前 $k$ 个操作能否得到零数组。复杂度 $\\\\mathcal{O}(n\\\\log n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int minZeroArray(vector根据题意,$x$ 向 $y$ 发送好友请求,只需满足 $x\\\\ne y$ 且
\\n$$
\\n\\\\dfrac{1}{2}\\\\cdot \\\\textit{ages}[x] + 7 < \\\\textit{ages}[y] \\\\le \\\\textit{ages}[x]
\\n$$
注意,只要满足了 $\\\\textit{ages}[y] \\\\le \\\\textit{ages}[x]$,题目的第三个条件一定为假。
\\n由于 $n$ 很大而 $\\\\textit{ages}[i]\\\\le 120$,我们可以用一个长为 $121$ 的 $\\\\textit{cnt}$ 数组统计每个年龄的人数。
\\n枚举年龄 $\\\\textit{ageX}$,我们需要知道:
\\n由于 $\\\\textit{ageX}$ 越大,$\\\\textit{ageY}$ 也越大,可以用滑动窗口解决。如果你不了解滑动窗口,可以看视频【基础算法精讲 03】。
\\n窗口内维护年龄在区间 $[\\\\textit{ageY},\\\\textit{ageX}]$ 中的人数 $\\\\textit{cntWindow}$。
\\n如果发现 $\\\\textit{cntWindow} > 0$,说明存在可以发送好友请求的用户:
\\n所以把
\\n$$
\\n\\\\textit{cnt}[\\\\textit{ageX}]\\\\cdot \\\\textit{cntWindow} - \\\\textit{cnt}[\\\\textit{ageX}]
\\n$$
加入答案。
\\n$\\\\textit{ageY} \\\\le \\\\dfrac{1}{2}\\\\cdot \\\\textit{ageX} + 7$ 等价于 $\\\\textit{ageY}\\\\cdot 2 \\\\le \\\\textit{ageX} + 14$。
\\n由于当 $\\\\textit{ageX}$ 增加 $1$ 时,$\\\\textit{ageY}$ 至多增加 $1$,所以滑动窗口的内层 $\\\\texttt{while}$ 循环至多循环一次,可以改成 $\\\\texttt{if}$ 语句。
\\n注:年龄可以从 $15$ 开始枚举,但考虑到如果题目条件改了,就不适用了,所以简单起见,从 $0$ 开始枚举。
\\nclass Solution:\\n def numFriendRequests(self, ages: List[int]) -> int:\\n cnt = [0] * 121\\n for age in ages:\\n cnt[age] += 1\\n\\n ans = cnt_window = age_y = 0\\n for age_x, c in enumerate(cnt):\\n cnt_window += c\\n if age_y * 2 <= age_x + 14: # 不能发送好友请求\\n cnt_window -= cnt[age_y]\\n age_y += 1\\n if cnt_window: # 存在可以发送好友请求的用户\\n ans += c * cnt_window - c\\n return ans\\n
\\nclass Solution {\\n public int numFriendRequests(int[] ages) {\\n int[] cnt = new int[121];\\n for (int age : ages) {\\n cnt[age]++;\\n }\\n\\n int ans = 0;\\n int ageY = 0;\\n int cntWindow = 0;\\n for (int ageX = 0; ageX < cnt.length; ageX++) {\\n cntWindow += cnt[ageX];\\n if (ageY * 2 <= ageX + 14) { // 不能发送好友请求\\n cntWindow -= cnt[ageY];\\n ageY++;\\n }\\n if (cntWindow > 0) { // 存在可以发送好友请求的用户\\n ans += cnt[ageX] * cntWindow - cnt[ageX];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int numFriendRequests(vector<int>& ages) {\\n int cnt[121]{};\\n for (int age : ages) {\\n cnt[age]++;\\n }\\n\\n int ans = 0, cnt_window = 0, age_y = 0;\\n for (int age_x = 0; age_x < 121; age_x++) {\\n cnt_window += cnt[age_x];\\n if (age_y * 2 <= age_x + 14) { // 不能发送好友请求\\n cnt_window -= cnt[age_y];\\n age_y++;\\n }\\n if (cnt_window > 0) { // 存在可以发送好友请求的用户\\n ans += cnt[age_x] * cnt_window - cnt[age_x];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint numFriendRequests(int* ages, int agesSize) {\\n int cnt[121] = {};\\n for (int i = 0; i < agesSize; i++) {\\n cnt[ages[i]]++;\\n }\\n\\n int ans = 0, cnt_window = 0, age_y = 0;\\n for (int age_x = 0; age_x < 121; age_x++) {\\n cnt_window += cnt[age_x];\\n if (age_y * 2 <= age_x + 14) { // 不能发送好友请求\\n cnt_window -= cnt[age_y];\\n age_y++;\\n }\\n if (cnt_window > 0) { // 存在可以发送好友请求的用户\\n ans += cnt[age_x] * cnt_window - cnt[age_x];\\n }\\n }\\n return ans;\\n}\\n
\\nfunc numFriendRequests(ages []int) (ans int) {\\n cnt := [121]int{}\\n for _, age := range ages {\\n cnt[age]++\\n }\\n\\n cntWindow, ageY := 0, 0\\n for ageX, c := range cnt[:] {\\n cntWindow += c\\n if ageY*2 <= ageX+14 { // 不能发送好友请求\\n cntWindow -= cnt[ageY]\\n ageY++\\n }\\n if cntWindow > 0 { // 存在可以发送好友请求的用户\\n ans += c*cntWindow - c\\n }\\n }\\n return\\n}\\n
\\nvar numFriendRequests = function(ages) {\\n const cnt = Array(121).fill(0);\\n for (const age of ages) {\\n cnt[age]++;\\n }\\n\\n let ans = 0, cntWindow = 0, ageY = 0;\\n for (let ageX = 0; ageX < cnt.length; ageX++) {\\n cntWindow += cnt[ageX];\\n if (ageY * 2 <= ageX + 14) { // 不能发送好友请求\\n cntWindow -= cnt[ageY];\\n ageY++;\\n }\\n if (cntWindow > 0) { // 存在可以发送好友请求的用户\\n ans += cnt[ageX] * cntWindow - cnt[ageX];\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn num_friend_requests(ages: Vec<i32>) -> i32 {\\n let mut cnt = vec![0; 121];\\n for age in ages {\\n cnt[age as usize] += 1;\\n }\\n\\n let mut ans = 0;\\n let mut age_y = 0;\\n let mut cnt_window = 0;\\n for age_x in 0..cnt.len() {\\n cnt_window += cnt[age_x];\\n if age_y * 2 <= age_x + 14 { // 不能发送好友请求\\n cnt_window -= cnt[age_y];\\n age_y += 1;\\n }\\n if cnt_window > 0 { // 存在可以发送好友请求的用户\\n ans += cnt[age_x] * cnt_window - cnt[age_x];\\n }\\n }\\n ans\\n }\\n}\\n
\\n改成 $\\\\dfrac{1}{2}\\\\cdot \\\\textit{ages}[x] + 7 < \\\\textit{ages}[y] \\\\le 2\\\\cdot \\\\textit{ages}[x]$,要怎么做?
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据题意,$x$ 向 $y$ 发送好友请求,只需满足 $x\\\\ne y$ 且 $$\\n \\\\dfrac{1}{2}\\\\cdot \\\\textit{ages}[x] + 7 < \\\\textit{ages}[y] \\\\le \\\\textit{ages}[x]\\n $$\\n\\n注意,只要满足了 $\\\\textit{ages}[y] \\\\le \\\\textit{ages}[x]$,题目的第三个条件一定为假。\\n\\n由于 $n$ 很大而 $\\\\textit{ages}[i]\\\\le 120$,我们可以用一个长为 $121$ 的 $\\\\textit{cnt}$ 数组统计每个年龄的人数。\\n\\n枚举年龄…","guid":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/ji-shu-hua-dong-chuang-kou-pythonjavaccg-jfya","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-16T23:09:05.755Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-适龄的朋友🟡","url":"https://leetcode.cn/problems/friends-of-appropriate-ages/","content":"在社交媒体网站上有 n
个用户。给你一个整数数组 ages
,其中 ages[i]
是第 i
个用户的年龄。
如果下述任意一个条件为真,那么用户 x
将不会向用户 y
(x != y
)发送好友请求:
ages[y] <= 0.5 * ages[x] + 7
ages[y] > ages[x]
ages[y] > 100 && ages[x] < 100
否则,x
将会向 y
发送一条好友请求。
注意,如果 x
向 y
发送一条好友请求,y
不必也向 x
发送一条好友请求。另外,用户不会向自己发送好友请求。
返回在该社交媒体网站上产生的好友请求总数。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:ages = [16,16]\\n输出:2\\n解释:2 人互发好友请求。\\n\\n\\n
示例 2:
\\n\\n输入:ages = [16,17,18]\\n输出:2\\n解释:产生的好友请求为 17 -> 16 ,18 -> 17 。\\n\\n\\n
示例 3:
\\n\\n输入:ages = [20,30,100,110,120]\\n输出:3\\n解释:产生的好友请求为 110 -> 100 ,120 -> 110 ,120 -> 100 。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == ages.length
1 <= n <= 2 * 104
1 <= ages[i] <= 120
给你一个 m x n
的二进制矩阵 grid
。
如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。
\\n\\n你可以将 grid
中任意格子的值 翻转 ,也就是将格子里的值从 0
变成 1
,或者从 1
变成 0
。
请你返回 最少 翻转次数,使得矩阵中 所有 行和列都是 回文的 ,且矩阵中 1
的数目可以被 4
整除 。
\\n\\n
示例 1:
\\n\\n输入:grid = [[1,0,0],[0,1,0],[0,0,1]]
\\n\\n输出:3
\\n\\n解释:
\\n\\n示例 2:
\\n\\n输入:grid = [[0,1],[0,1],[0,0]]
\\n\\n输出:2
\\n\\n解释:
\\n\\n示例 3:
\\n\\n输入:grid = [[1],[1]]
\\n\\n输出:2
\\n\\n解释:
\\n\\n\\n\\n
提示:
\\n\\nm == grid.length
n == grid[i].length
1 <= m * n <= 2 * 105
0 <= grid[i][j] <= 1
我们分别计算行和列的翻转次数,记为 $\\\\textit{cnt1}$ 和 $\\\\textit{cnt2}$,最后取二者的最小值即可。
\\n###python
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n m, n = len(grid), len(grid[0])\\n cnt1 = cnt2 = 0\\n for row in grid:\\n for j in range(n // 2):\\n if row[j] != row[n - j - 1]:\\n cnt1 += 1\\n for j in range(n):\\n for i in range(m // 2):\\n if grid[i][j] != grid[m - i - 1][j]:\\n cnt2 += 1\\n return min(cnt1, cnt2)\\n
\\n###java
\\nclass Solution {\\n public int minFlips(int[][] grid) {\\n int m = grid.length, n = grid[0].length;\\n int cnt1 = 0, cnt2 = 0;\\n for (var row : grid) {\\n for (int j = 0; j < n / 2; ++j) {\\n if (row[j] != row[n - j - 1]) {\\n ++cnt1;\\n }\\n }\\n }\\n for (int j = 0; j < n; ++j) {\\n for (int i = 0; i < m / 2; ++i) {\\n if (grid[i][j] != grid[m - i - 1][j]) {\\n ++cnt2;\\n }\\n }\\n }\\n return Math.min(cnt1, cnt2);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n int cnt1 = 0, cnt2 = 0;\\n for (const auto& row : grid) {\\n for (int j = 0; j < n / 2; ++j) {\\n if (row[j] != row[n - j - 1]) {\\n ++cnt1;\\n }\\n }\\n }\\n for (int j = 0; j < n; ++j) {\\n for (int i = 0; i < m / 2; ++i) {\\n if (grid[i][j] != grid[m - i - 1][j]) {\\n ++cnt2;\\n }\\n }\\n }\\n return min(cnt1, cnt2);\\n }\\n};\\n
\\n###go
\\nfunc minFlips(grid [][]int) int {\\nm, n := len(grid), len(grid[0])\\ncnt1, cnt2 := 0, 0\\nfor _, row := range grid {\\nfor j := 0; j < n/2; j++ {\\nif row[j] != row[n-j-1] {\\ncnt1++\\n}\\n}\\n}\\nfor j := 0; j < n; j++ {\\nfor i := 0; i < m/2; i++ {\\nif grid[i][j] != grid[m-i-1][j] {\\ncnt2++\\n}\\n}\\n}\\nreturn min(cnt1, cnt2)\\n}\\n
\\n###ts
\\nfunction minFlips(grid: number[][]): number {\\n const [m, n] = [grid.length, grid[0].length];\\n let [cnt1, cnt2] = [0, 0];\\n for (const row of grid) {\\n for (let j = 0; j < n / 2; ++j) {\\n if (row[j] !== row[n - 1 - j]) {\\n ++cnt1;\\n }\\n }\\n }\\n for (let j = 0; j < n; ++j) {\\n for (let i = 0; i < m / 2; ++i) {\\n if (grid[i][j] !== grid[m - 1 - i][j]) {\\n ++cnt2;\\n }\\n }\\n }\\n return Math.min(cnt1, cnt2);\\n}\\n
\\n时间复杂度 $O(m \\\\times n)$,其中 $m$ 和 $n$ 分别是矩阵 $\\\\textit{grid}$ 的行数和列数。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 我们分别计算行和列的翻转次数,记为 $\\\\textit{cnt1}$ 和 $\\\\textit{cnt2}$,最后取二者的最小值即可。\\n\\n###python\\n\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n m, n = len(grid), len(grid[0])\\n cnt1 = cnt2 = 0\\n for row in grid:\\n for j in range(n // 2):…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i//solution/python3javacgotypescript-yi-ti-yi-jie-ji-sa6k","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-15T00:08:24.722Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最少翻转次数使二进制矩阵回文 I🟡","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i/","content":"给你一个 m x n
的二进制矩阵 grid
。
如果矩阵中一行或者一列从前往后与从后往前读是一样的,那么我们称这一行或者这一列是 回文 的。
\\n\\n你可以将 grid
中任意格子的值 翻转 ,也就是将格子里的值从 0
变成 1
,或者从 1
变成 0
。
请你返回 最少 翻转次数,使得矩阵 要么 所有行是 回文的 ,要么所有列是 回文的 。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:grid = [[1,0,0],[0,0,0],[0,0,1]]
\\n\\n输出:2
\\n\\n解释:
\\n\\n将高亮的格子翻转,得到所有行都是回文的。
\\n示例 2:
\\n\\n输入:grid = [[0,1],[0,1],[0,0]]
\\n\\n输出:1
\\n\\n解释:
\\n\\n将高亮的格子翻转,得到所有列都是回文的。
\\n示例 3:
\\n\\n输入:grid = [[1],[0]]
\\n\\n输出:0
\\n\\n解释:
\\n\\n所有行已经是回文的。
\\n\\n\\n
提示:
\\n\\nm == grid.length
n == grid[i].length
1 <= m * n <= 2 * 105
0 <= grid[i][j] <= 1
我们先根据题目给定的边 $\\\\textit{edges}$ 构建出树的邻接表 $\\\\textit{g}$,其中 $\\\\textit{g}[a]$ 表示节点 $a$ 的所有邻居节点。
\\n然后,我们设计一个函数 $\\\\textit{dfs}(a, \\\\textit{fa})$,表示计算以节点 $a$ 为根的子树中的节点数,并累计好节点的数量。其中 $\\\\textit{fa}$ 表示节点 $a$ 的父节点。
\\n函数 $\\\\textit{dfs}(a, \\\\textit{fa})$ 的执行过程如下:
\\n在主函数中,我们调用 $\\\\textit{dfs}(0, -1)$,最后返回答案。
\\n###python
\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n def dfs(a: int, fa: int) -> int:\\n pre = -1\\n cnt = ok = 1\\n for b in g[a]:\\n if b != fa:\\n cur = dfs(b, a)\\n cnt += cur\\n if pre < 0:\\n pre = cur\\n elif pre != cur:\\n ok = 0\\n nonlocal ans\\n ans += ok\\n return cnt\\n\\n g = defaultdict(list)\\n for a, b in edges:\\n g[a].append(b)\\n g[b].append(a)\\n ans = 0\\n dfs(0, -1)\\n return ans\\n
\\n###java
\\nclass Solution {\\n private int ans;\\n private List<Integer>[] g;\\n\\n public int countGoodNodes(int[][] edges) {\\n int n = edges.length + 1;\\n g = new List[n];\\n Arrays.setAll(g, k -> new ArrayList<>());\\n for (var e : edges) {\\n int a = e[0], b = e[1];\\n g[a].add(b);\\n g[b].add(a);\\n }\\n dfs(0, -1);\\n return ans;\\n }\\n\\n private int dfs(int a, int fa) {\\n int pre = -1, cnt = 1, ok = 1;\\n for (int b : g[a]) {\\n if (b != fa) {\\n int cur = dfs(b, a);\\n cnt += cur;\\n if (pre < 0) {\\n pre = cur;\\n } else if (pre != cur) {\\n ok = 0;\\n }\\n }\\n }\\n ans += ok;\\n return cnt;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countGoodNodes(vector<vector<int>>& edges) {\\n int n = edges.size() + 1;\\n vector<int> g[n];\\n for (const auto& e : edges) {\\n int a = e[0], b = e[1];\\n g[a].push_back(b);\\n g[b].push_back(a);\\n }\\n int ans = 0;\\n auto dfs = [&](auto&& dfs, int a, int fa) -> int {\\n int pre = -1, cnt = 1, ok = 1;\\n for (int b : g[a]) {\\n if (b != fa) {\\n int cur = dfs(dfs, b, a);\\n cnt += cur;\\n if (pre < 0) {\\n pre = cur;\\n } else if (pre != cur) {\\n ok = 0;\\n }\\n }\\n }\\n ans += ok;\\n return cnt;\\n };\\n dfs(dfs, 0, -1);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countGoodNodes(edges [][]int) (ans int) {\\nn := len(edges) + 1\\ng := make([][]int, n)\\nfor _, e := range edges {\\na, b := e[0], e[1]\\ng[a] = append(g[a], b)\\ng[b] = append(g[b], a)\\n}\\nvar dfs func(int, int) int\\ndfs = func(a, fa int) int {\\npre, cnt, ok := -1, 1, 1\\nfor _, b := range g[a] {\\nif b != fa {\\ncur := dfs(b, a)\\ncnt += cur\\nif pre < 0 {\\npre = cur\\n} else if pre != cur {\\nok = 0\\n}\\n}\\n}\\nans += ok\\nreturn cnt\\n}\\ndfs(0, -1)\\nreturn\\n}\\n
\\n###ts
\\nfunction countGoodNodes(edges: number[][]): number {\\n const n = edges.length + 1;\\n const g: number[][] = Array.from({ length: n }, () => []);\\n for (const [a, b] of edges) {\\n g[a].push(b);\\n g[b].push(a);\\n }\\n let ans = 0;\\n const dfs = (a: number, fa: number): number => {\\n let [pre, cnt, ok] = [-1, 1, 1];\\n for (const b of g[a]) {\\n if (b !== fa) {\\n const cur = dfs(b, a);\\n cnt += cur;\\n if (pre < 0) {\\n pre = cur;\\n } else if (pre !== cur) {\\n ok = 0;\\n }\\n }\\n }\\n ans += ok;\\n return cnt;\\n };\\n dfs(0, -1);\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 表示节点的数量。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:DFS 我们先根据题目给定的边 $\\\\textit{edges}$ 构建出树的邻接表 $\\\\textit{g}$,其中 $\\\\textit{g}[a]$ 表示节点 $a$ 的所有邻居节点。\\n\\n然后,我们设计一个函数 $\\\\textit{dfs}(a, \\\\textit{fa})$,表示计算以节点 $a$ 为根的子树中的节点数,并累计好节点的数量。其中 $\\\\textit{fa}$ 表示节点 $a$ 的父节点。\\n\\n函数 $\\\\textit{dfs}(a, \\\\textit{fa})$ 的执行过程如下:\\n\\n初始化变量 $\\\\textit{pre} = -1…","guid":"https://leetcode.cn/problems/count-the-number-of-good-nodes//solution/python3javacgotypescript-yi-ti-yi-jie-df-vfxm","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-14T00:27:16.972Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计好节点的数目🟡","url":"https://leetcode.cn/problems/count-the-number-of-good-nodes/","content":"现有一棵 无向 树,树中包含 n
个节点,按从 0
到 n - 1
标记。树的根节点是节点 0
。给你一个长度为 n - 1
的二维整数数组 edges
,其中 edges[i] = [ai, bi]
表示树中节点 ai
与节点 bi
之间存在一条边。
如果一个节点的所有子节点为根的 子树 包含的节点数相同,则认为该节点是一个 好节点。
\\n\\n返回给定树中 好节点 的数量。
\\n\\n子树 指的是一个节点以及它所有后代节点构成的一棵树。
\\n\\n\\n\\n
\\n\\n
示例 1:
\\n\\n输入:edges = [[0,1],[0,2],[1,3],[1,4],[2,5],[2,6]]
\\n\\n输出:7
\\n\\n说明:
\\n树的所有节点都是好节点。
\\n示例 2:
\\n\\n输入:edges = [[0,1],[1,2],[2,3],[3,4],[0,5],[1,6],[2,7],[3,8]]
\\n\\n输出:6
\\n\\n说明:
\\n树中有 6 个好节点。上图中已将这些节点着色。
\\n示例 3:
\\n\\n输入:edges = [[0,1],[1,2],[1,3],[1,4],[0,5],[5,6],[6,7],[7,8],[0,9],[9,10],[9,12],[10,11]]
\\n\\n输出:12
\\n\\n解释:
\\n除了节点 9 以外其他所有节点都是好节点。
\\n\\n\\n
提示:
\\n\\n2 <= n <= 105
edges.length == n - 1
edges[i].length == 2
0 <= ai, bi < n
edges
总表示一棵有效的树。思路
\\n遍历数组 $\\\\textit{colors}$,用一个整数 $\\\\textit{cnt}$ 代表遍历到当前元素时,已经有的连续交替瓷砖的数量。如果当前元素与前一个元素不同,则将 $\\\\textit{cnt}$ 加 $1$,否则将其置为 $1$。 如果当前 $\\\\textit{cnt}$ 大于等于 $k$,则将结果加 $1$。注意到瓷砖是环形的,因此,在遍历到第一个数时,我们就需要知道当前的 $\\\\textit{cnt}$。为了得到初始的 $\\\\textit{cnt}$ 值,我们需要将遍历的起点往前推 $k-2$ 步,这样在遍历到数组的第一个元素时,我们就可以知道当前是否有 $k$ 块连续的交替瓷砖。最后返回结果。
\\n代码
\\n###Python
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int], k: int) -> int:\\n n = len(colors)\\n res, cnt = 0, 1\\n for i in range(-k + 2, n, 1):\\n if colors[i] != colors[i - 1]:\\n cnt += 1\\n else:\\n cnt = 1\\n if cnt >= k:\\n res += 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int numberOfAlternatingGroups(int[] colors, int k) {\\n int n = colors.length;\\n int res = 0, cnt = 1;\\n for (int i = -k + 2; i < n; i++) {\\n if (colors[(i + n) % n] != colors[(i - 1 + n) % n]) {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfAlternatingGroups(int[] colors, int k) {\\n int n = colors.Length;\\n int res = 0, cnt = 1;\\n for (int i = -k + 2; i < n; i++) {\\n if (colors[(i + n) % n] != colors[(i - 1 + n) % n]) {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors, int k) {\\n int n = colors.size();\\n int res = 0, cnt = 1;\\n for (int i = -k + 2; i < n; i++) {\\n if (colors[(i + n) % n] != colors[(i - 1 + n) % n]) {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc numberOfAlternatingGroups(colors []int, k int) int {\\n n := len(colors)\\n res, cnt := 0, 1\\n for i := -k + 2; i < n; i++ {\\n if colors[(i + n) % n] != colors[(i - 1 + n) % n] {\\n cnt++\\n } else {\\n cnt = 1\\n }\\n if cnt >= k {\\n res++\\n }\\n }\\n return res\\n}\\n
\\n###C
\\nint numberOfAlternatingGroups(int* colors, int colorsSize, int k) {\\n int res = 0, cnt = 1;\\n for (int i = -k + 2; i < colorsSize; i++) {\\n if (colors[(i + colorsSize) % colorsSize] != colors[(i - 1 + colorsSize) % colorsSize]) {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res += 1;\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar numberOfAlternatingGroups = function(colors, k) {\\n const n = colors.length;\\n let res = 0, cnt = 1;\\n for (let i = -k + 2; i < n; i++) {\\n if (colors[(i + n) % n] !== colors[(i - 1 + n) % n]) {\\n cnt++;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction numberOfAlternatingGroups(colors: number[], k: number): number {\\n const n = colors.length;\\n let res = 0, cnt = 1;\\n for (let i = -k + 2; i < n; i++) {\\n if (colors[(i + n) % n] !== colors[(i - 1 + n) % n]) {\\n cnt++;\\n } else {\\n cnt = 1;\\n }\\n if (cnt >= k) {\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn number_of_alternating_groups(colors: Vec<i32>, k: i32) -> i32 {\\n let n = colors.len() as i32;\\n let mut res = 0;\\n let mut cnt = 1;\\n for i in (-k + 2)..n {\\n if colors[((i + n) % n) as usize] != colors[((i - 1 + n) % n) as usize] {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if cnt >= k {\\n res += 1;\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$。
\\n空间复杂度:$O(1)$。
\\n思路
\\n按照题意遍历数组 $\\\\textit{colors}$ 的每个元素,判断其前一个元素和后一个元素是否都与当前元素不同,如果满足,则将结果加 $1$。注意瓷砖是环形的,则数组的首尾元素是相邻的。最后返回结果。
\\n代码
\\n###Python
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int]) -> int:\\n n = len(colors)\\n res = 0\\n for i in range(n):\\n if colors[i] != colors[i - 1] and colors[i] != colors[(i + 1) % n]:\\n res += 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int numberOfAlternatingGroups(int[] colors) {\\n int n = colors.length;\\n int res = 0;\\n for (int i = 0; i < n; i++) {\\n if (colors[i] != colors[(i - 1 + n) % n] && colors[i] != colors[(i + 1) % n]) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfAlternatingGroups(int[] colors) {\\n int n = colors.Length;\\n int res = 0;\\n for (int i = 0; i < n; i++) {\\n if (colors[i] != colors[(i - 1 + n) % n] && colors[i] != colors[(i + 1) % n]) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors) {\\n int n = colors.size();\\n int res = 0;\\n for (int i = 0; i < n; i++) {\\n if (colors[i] != colors[(i - 1 + n) % n] && colors[i] != colors[(i + 1) % n]) {\\n res += 1;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc numberOfAlternatingGroups(colors []int) int {\\n n := len(colors)\\n res := 0\\n for i := 0; i < n; i++ {\\n if colors[i] != colors[(i-1+n)%n] && colors[i] != colors[(i+1)%n] {\\n res++\\n }\\n }\\n return res\\n}\\n
\\n###C
\\nint numberOfAlternatingGroups(int* colors, int colorsSize) {\\n int res = 0;\\n for (size_t i = 0; i < colorsSize; i++) {\\n if (colors[i] != colors[(i - 1 + colorsSize) % colorsSize] && colors[i] != colors[(i + 1) % colorsSize]) {\\n res += 1;\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar numberOfAlternatingGroups = function(colors) {\\n const n = colors.length;\\n let res = 0;\\n for (let i = 0; i < n; i++) {\\n if (colors[i] !== colors[(i - 1 + n) % n] && colors[i] !== colors[(i + 1) % n]) {\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction numberOfAlternatingGroups(colors: number[]): number {\\n const n = colors.length;\\n let res = 0;\\n for (let i = 0; i < n; i++) {\\n if (colors[i] !== colors[(i - 1 + n) % n] && colors[i] !== colors[(i + 1) % n]) {\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn number_of_alternating_groups(colors: Vec<i32>) -> i32 {\\n let n = colors.len();\\n let mut res = 0;\\n for i in 0..n {\\n if colors[i] != colors[(i + n - 1) % n] && colors[i] != colors[(i + 1) % n] {\\n res += 1;\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$。
\\n空间复杂度:$O(1)$。
\\n我们用两个变量 $\\\\textit{cnt0}$ 和 $\\\\textit{cnt1}$ 分别记录当前窗口内的 $0$ 和 $1$ 的个数,指针 $i$ 和 $j$ 分别标识窗口的左右边界。用一个数组 $d$ 记录每个位置 $i$ 右边第一个不满足 $k$ 约束的位置,初始时 $d[i] = n$。另外,用一个长度为 $n + 1$ 的前缀和数组 $\\\\textit{pre}[i]$ 记录以前 $i$ 个位置作为右边界的满足 $k$ 约束的子字符串的个数。
\\n当我们右移窗口时,如果窗口内的 $0$ 和 $1$ 的个数都大于 $k$,我们将 $d[i]$ 更新为 $j$,表示位置 $i$ 右边第一个不满足 $k$ 约束的位置。然后我们将 $i$ 右移一位,直到窗口内的 $0$ 和 $1$ 的个数都不大于 $k$。此时,我们可以计算出以 $j$ 为右边界的满足 $k$ 约束的子字符串的个数,即 $j - i + 1$,我们更新到前缀和数组中。
\\n最后,对于每个查询 $[l, r]$,我们首先找出 $l$ 右边第一个不满足 $k$ 约束的位置 $p$,那么 $p = \\\\min(r + 1, d[l])$,那么 $[l, p - 1]$ 的所有子字符串都满足 $k$ 约束,个数为 $(1 + p - l) \\\\times (p - l) / 2$,然后,我们计算以 $[p, r]$ 为右边界的满足 $k$ 约束的子字符串的个数,即 $\\\\textit{pre}[r + 1] - \\\\textit{pre}[p]$,最后将两者相加即可。
\\n###python
\\nclass Solution:\\n def countKConstraintSubstrings(\\n self, s: str, k: int, queries: List[List[int]]\\n ) -> List[int]:\\n cnt = [0, 0]\\n i, n = 0, len(s)\\n d = [n] * n\\n pre = [0] * (n + 1)\\n for j, x in enumerate(map(int, s)):\\n cnt[x] += 1\\n while cnt[0] > k and cnt[1] > k:\\n d[i] = j\\n cnt[int(s[i])] -= 1\\n i += 1\\n pre[j + 1] = pre[j] + j - i + 1\\n ans = []\\n for l, r in queries:\\n p = min(r + 1, d[l])\\n a = (1 + p - l) * (p - l) // 2\\n b = pre[r + 1] - pre[p]\\n ans.append(a + b)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String s, int k, int[][] queries) {\\n int[] cnt = new int[2];\\n int n = s.length();\\n int[] d = new int[n];\\n Arrays.fill(d, n);\\n long[] pre = new long[n + 1];\\n for (int i = 0, j = 0; j < n; ++j) {\\n cnt[s.charAt(j) - \'0\']++;\\n while (cnt[0] > k && cnt[1] > k) {\\n d[i] = j;\\n cnt[s.charAt(i++) - \'0\']--;\\n }\\n pre[j + 1] = pre[j] + j - i + 1;\\n }\\n int m = queries.length;\\n long[] ans = new long[m];\\n for (int i = 0; i < m; ++i) {\\n int l = queries[i][0], r = queries[i][1];\\n int p = Math.min(r + 1, d[l]);\\n long a = (1L + p - l) * (p - l) / 2;\\n long b = pre[r + 1] - pre[p];\\n ans[i] = a + b;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {\\n int cnt[2]{};\\n int n = s.size();\\n vector<int> d(n, n);\\n long long pre[n + 1];\\n pre[0] = 0;\\n for (int i = 0, j = 0; j < n; ++j) {\\n cnt[s[j] - \'0\']++;\\n while (cnt[0] > k && cnt[1] > k) {\\n d[i] = j;\\n cnt[s[i++] - \'0\']--;\\n }\\n pre[j + 1] = pre[j] + j - i + 1;\\n }\\n vector<long long> ans;\\n for (const auto& q : queries) {\\n int l = q[0], r = q[1];\\n int p = min(r + 1, d[l]);\\n long long a = (1LL + p - l) * (p - l) / 2;\\n long long b = pre[r + 1] - pre[p];\\n ans.push_back(a + b);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int, queries [][]int) (ans []int64) {\\ncnt := [2]int{}\\nn := len(s)\\nd := make([]int, n)\\nfor i := range d {\\nd[i] = n\\n}\\npre := make([]int, n+1)\\nfor i, j := 0, 0; j < n; j++ {\\ncnt[s[j]-\'0\']++\\nfor cnt[0] > k && cnt[1] > k {\\nd[i] = j\\ncnt[s[i]-\'0\']--\\ni++\\n}\\npre[j+1] = pre[j] + j - i + 1\\n}\\nfor _, q := range queries {\\nl, r := q[0], q[1]\\np := min(r+1, d[l])\\na := (1 + p - l) * (p - l) / 2\\nb := pre[r+1] - pre[p]\\nans = append(ans, int64(a+b))\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countKConstraintSubstrings(s: string, k: number, queries: number[][]): number[] {\\n const cnt: [number, number] = [0, 0];\\n const n = s.length;\\n const d: number[] = Array(n).fill(n);\\n const pre: number[] = Array(n + 1).fill(0);\\n for (let i = 0, j = 0; j < n; ++j) {\\n cnt[+s[j]]++;\\n while (Math.min(cnt[0], cnt[1]) > k) {\\n d[i] = j;\\n cnt[+s[i++]]--;\\n }\\n pre[j + 1] = pre[j] + j - i + 1;\\n }\\n const ans: number[] = [];\\n for (const [l, r] of queries) {\\n const p = Math.min(r + 1, d[l]);\\n const a = ((1 + p - l) * (p - l)) / 2;\\n const b = pre[r + 1] - pre[p];\\n ans.push(a + b);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n + m)$,空间复杂度 $O(n)$。其中 $n$ 和 $m$ 分别为字符串 $s$ 的长度和查询数组 $\\\\textit{queries}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:滑动窗口 + 前缀和 我们用两个变量 $\\\\textit{cnt0}$ 和 $\\\\textit{cnt1}$ 分别记录当前窗口内的 $0$ 和 $1$ 的个数,指针 $i$ 和 $j$ 分别标识窗口的左右边界。用一个数组 $d$ 记录每个位置 $i$ 右边第一个不满足 $k$ 约束的位置,初始时 $d[i] = n$。另外,用一个长度为 $n + 1$ 的前缀和数组 $\\\\textit{pre}[i]$ 记录以前 $i$ 个位置作为右边界的满足 $k$ 约束的子字符串的个数。\\n\\n当我们右移窗口时,如果窗口内的 $0$ 和 $1$ 的个数都大于 $k…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/python3javacgotypescript-yi-ti-yi-jie-hu-zfa8","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-13T00:35:28.299Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计满足 K 约束的子字符串数量 II🔴","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii/","content":"给你一个 二进制 字符串 s
和一个整数 k
。
另给你一个二维整数数组 queries
,其中 queries[i] = [li, ri]
。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
\\n\\n0
的数量最多为 k
。1
的数量最多为 k
。返回一个整数数组 answer
,其中 answer[i]
表示 s[li..ri]
中满足 k 约束 的 子字符串 的数量。
\\n\\n
示例 1:
\\n\\n输入:s = \\"0001111\\", k = 2, queries = [[0,6]]
\\n\\n输出:[26]
\\n\\n解释:
\\n\\n对于查询 [0, 6]
, s[0..6] = \\"0001111\\"
的所有子字符串中,除 s[0..5] = \\"000111\\"
和 s[0..6] = \\"0001111\\"
外,其余子字符串都满足 k 约束。
示例 2:
\\n\\n输入:s = \\"010101\\", k = 1, queries = [[0,5],[1,4],[2,3]]
\\n\\n输出:[15,9,3]
\\n\\n解释:
\\n\\ns
的所有子字符串中,长度大于 3 的子字符串都不满足 k 约束。
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s[i]
是 \'0\'
或 \'1\'
1 <= k <= s.length
1 <= queries.length <= 105
queries[i] == [li, ri]
0 <= li <= ri < s.length
我们用两个变量 $\\\\textit{cnt0}$ 和 $\\\\textit{cnt1}$ 分别记录当前窗口内的 $0$ 和 $1$ 的个数,用 $\\\\textit{ans}$ 记录满足 $k$ 约束的子字符串的个数,用 $l$ 记录窗口的左边界。
\\n当我们右移窗口时,如果窗口内的 $0$ 和 $1$ 的个数都大于 $k$,我们就需要左移窗口,直到窗口内的 $0$ 和 $1$ 的个数都不大于 $k$。此时,窗口内所有以 $r$ 作为右端点的子字符串都满足 $k$ 约束,个数为 $r - l + 1$,其中 $r$ 是窗口的右边界。我们将这个个数累加到 $\\\\textit{ans}$ 中。
\\n最后,我们返回 $\\\\textit{ans}$ 即可。
\\n###python
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int) -> int:\\n cnt = [0, 0]\\n ans = l = 0\\n for r, x in enumerate(map(int, s)):\\n cnt[x] += 1\\n while cnt[0] > k and cnt[1] > k:\\n cnt[int(s[l])] -= 1\\n l += 1\\n ans += r - l + 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int countKConstraintSubstrings(String s, int k) {\\n int[] cnt = new int[2];\\n int ans = 0, l = 0;\\n for (int r = 0; r < s.length(); ++r) {\\n ++cnt[s.charAt(r) - \'0\'];\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s.charAt(l++) - \'0\']--;\\n }\\n ans += r - l + 1;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countKConstraintSubstrings(string s, int k) {\\n int cnt[2]{};\\n int ans = 0, l = 0;\\n for (int r = 0; r < s.length(); ++r) {\\n cnt[s[r] - \'0\']++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l++] - \'0\']--;\\n }\\n ans += r - l + 1;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int) (ans int) {\\ncnt := [2]int{}\\nl := 0\\nfor r, c := range s {\\ncnt[c-\'0\']++\\nfor ; cnt[0] > k && cnt[1] > k; l++ {\\ncnt[s[l]-\'0\']--\\n}\\nans += r - l + 1\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countKConstraintSubstrings(s: string, k: number): number {\\n const cnt: [number, number] = [0, 0];\\n let [ans, l] = [0, 0];\\n for (let r = 0; r < s.length; ++r) {\\n cnt[+s[r]]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[+s[l++]]--;\\n }\\n ans += r - l + 1;\\n }\\n return ans;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn count_k_constraint_substrings(s: String, k: i32) -> i32 {\\n let mut cnt = [0; 2];\\n let mut l = 0;\\n let mut ans = 0;\\n let s = s.as_bytes();\\n\\n for (r, &c) in s.iter().enumerate() {\\n cnt[(c - b\'0\') as usize] += 1;\\n while cnt[0] > k && cnt[1] > k {\\n cnt[(s[l] - b\'0\') as usize] -= 1;\\n l += 1;\\n }\\n ans += r - l + 1;\\n }\\n\\n ans as i32\\n }\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是字符串 $s$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:滑动窗口 我们用两个变量 $\\\\textit{cnt0}$ 和 $\\\\textit{cnt1}$ 分别记录当前窗口内的 $0$ 和 $1$ 的个数,用 $\\\\textit{ans}$ 记录满足 $k$ 约束的子字符串的个数,用 $l$ 记录窗口的左边界。\\n\\n当我们右移窗口时,如果窗口内的 $0$ 和 $1$ 的个数都大于 $k$,我们就需要左移窗口,直到窗口内的 $0$ 和 $1$ 的个数都不大于 $k$。此时,窗口内所有以 $r$ 作为右端点的子字符串都满足 $k$ 约束,个数为 $r - l + 1$,其中 $r$ 是窗口的右边界…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/python3javacgotypescript-yi-ti-yi-jie-hu-tkmr","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-12T00:15:25.522Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计满足 K 约束的子字符串数量 I🟢","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i/","content":"给你一个 二进制 字符串 s
和一个整数 k
。
如果一个 二进制字符串 满足以下任一条件,则认为该字符串满足 k 约束:
\\n\\n0
的数量最多为 k
。1
的数量最多为 k
。返回一个整数,表示 s
的所有满足 k 约束 的子字符串的数量。
\\n\\n
示例 1:
\\n\\n输入:s = \\"10101\\", k = 1
\\n\\n输出:12
\\n\\n解释:
\\n\\ns
的所有子字符串中,除了 \\"1010\\"
、\\"10101\\"
和 \\"0101\\"
外,其余子字符串都满足 k 约束。
示例 2:
\\n\\n输入:s = \\"1010101\\", k = 2
\\n\\n输出:25
\\n\\n解释:
\\n\\ns
的所有子字符串中,除了长度大于 5 的子字符串外,其余子字符串都满足 k 约束。
示例 3:
\\n\\n输入:s = \\"11111\\", k = 1
\\n\\n输出:15
\\n\\n解释:
\\n\\ns
的所有子字符串都满足 k 约束。
\\n\\n
提示:
\\n\\n1 <= s.length <= 50
1 <= k <= s.length
s[i]
是 \'0\'
或 \'1\'
。由题意可知:
\\n对于任一从城市 $u$ 到城市 $v$ 的单向道路,都有 $u \\\\lt v$。
\\n不会存在两条单向道路 $[u_0, v_0]$ 和 $[u_1, v_1]$,满足 $u_0 \\\\lt u_1 \\\\lt v_0 \\\\lt v_1$。将单向道路看成区间,那么任意两条单向道路要么是不相交的关系,要么是包含的关系。
\\n基于以上两点,我们可以贪心地选择最短路径经过的单向道路。具体地,初始时所有单向道路都是互不包含的关系,那么选择所有单向道路是最优的,最短路径的长度 $\\\\textit{dist} = n - 1$ 为所有单向道路的数目。当我们新增一条单向道路时:
\\n如果它已经被任一现有的单向道路所包含,那么选择它不会使路径更短,直接忽略它。
\\n否则,我们去掉所有被新增单向道路所包含的现有单向道路,记数目为 $m$,然后将该新增单向道路加入最短路径中,此时最短路径的长度更新为 $\\\\textit{dist} - m + 1$。
\\n具体实现上,我们可以使用 $\\\\textit{roads}$ 表示最短路径经过的所有单向道路。$\\\\textit{roads}[u] = v$ 表示从城市 $u$ 到城市 $v$ 的一条单向道路,而 $\\\\textit{roads}[u] = -1$ 时,表示不经过城市 $u$(也表示以 $u$ 起始的所有单向道路已经有对应的范围更大的单向道路)。记新增的道路为 $\\\\textit{query} = [u, v]$:
\\n如果 $\\\\textit{roads}[u] = -1$,那么说明选择 $\\\\textit{query}$ 不会使路径更短,忽略它。
\\n否则我们不断地删除 $[u, v]$ 之间的所有单向道路,然后将 $[u, v]$ 加入最短路径中。
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> roads(n);\\n iota(roads.begin(), roads.end(), 1);\\n vector<int> res;\\n int dist = n - 1;\\n for (auto &query : queries) {\\n int k = roads[query[0]];\\n roads[query[0]] = query[1];\\n while (k != -1 && k < query[1]) {\\n int t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res.push_back(dist); \\n }\\n return res;\\n }\\n};\\n
\\n###C
\\nint *shortestDistanceAfterQueries(int n, int **queries, int queriesSize, int *queriesColSize, int *returnSize) {\\n int *roads = (int *)malloc(n * sizeof(int));\\n for (int i = 0; i < n; i++) {\\n roads[i] = i + 1;\\n }\\n int *res = (int *)malloc(queriesSize * sizeof(int));\\n *returnSize = queriesSize;\\n int dist = n - 1;\\n for (int i = 0; i < queriesSize; i++) {\\n int k = roads[queries[i][0]];\\n roads[queries[i][0]] = queries[i][1];\\n while (k != -1 && k < queries[i][1]) {\\n int t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res[i] = dist;\\n }\\n free(roads);\\n return res;\\n}\\n
\\n###Go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\n roads := make([]int, n)\\n for i := 0; i < n; i++ {\\n roads[i] = i + 1;\\n }\\n var res []int\\n dist := n - 1\\n for _, query := range queries {\\n k := roads[query[0]]\\n roads[query[0]] = query[1]\\n for k != -1 && k < query[1] {\\n k, roads[k] = roads[k], -1\\n dist--\\n }\\n res = append(res, dist)\\n }\\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n roads = [i + 1 for i in range(n)]\\n res = []\\n dist = n - 1\\n for query in queries:\\n k = roads[query[0]]\\n roads[query[0]] = query[1]\\n while k != -1 and k < query[1]:\\n roads[k], k = -1, roads[k]\\n dist -= 1\\n res.append(dist)\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n int[] roads = new int[n];\\n for (int i = 0; i < n; i++) {\\n roads[i] = i + 1;\\n }\\n int[] res = new int[queries.length];\\n int dist = n - 1;\\n for (int i = 0; i < queries.length; i++) {\\n int k = roads[queries[i][0]];\\n roads[queries[i][0]] = queries[i][1];\\n while (k != -1 && k < queries[i][1]) {\\n int t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res[i] = dist;\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ShortestDistanceAfterQueries(int n, int[][] queries) {\\n int[] roads = new int[n];\\n for (int i = 0; i < n; i++) {\\n roads[i] = i + 1;\\n }\\n int[] res = new int[queries.Length];\\n int dist = n - 1;\\n for (int i = 0; i < queries.Length; i++) {\\n int k = roads[queries[i][0]];\\n roads[queries[i][0]] = queries[i][1];\\n while (k != -1 && k < queries[i][1]) {\\n int t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res[i] = dist;\\n }\\n return res;\\n }\\n}\\n
\\n###JavaScript
\\nvar shortestDistanceAfterQueries = function(n, queries) {\\n let roads = new Array(n).fill(0).map((_, i) => i + 1);\\n let res = [];\\n let dist = n - 1;\\n for (let i = 0; i < queries.length; i++) {\\n let k = roads[queries[i][0]];\\n roads[queries[i][0]] = queries[i][1];\\n while (k !== -1 && k < queries[i][1]) {\\n let t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res.push(dist);\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {\\n let roads = new Array(n).fill(0).map((_, i) => i + 1);\\n let res = [];\\n let dist = n - 1;\\n for (let i = 0; i < queries.length; i++) {\\n let k = roads[queries[i][0]];\\n roads[queries[i][0]] = queries[i][1];\\n while (k !== -1 && k < queries[i][1]) {\\n let t = roads[k];\\n roads[k] = -1;\\n k = t;\\n dist--;\\n }\\n res.push(dist);\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn shortest_distance_after_queries(n: i32, queries: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut roads: Vec<i32> = (1..=n).collect();\\n let mut res: Vec<i32> = Vec::new();\\n let mut dist = n - 1;\\n for query in &queries {\\n let mut k = roads[query[0] as usize];\\n roads[query[0] as usize] = query[1];\\n while k != -1 && k < query[1] {\\n let t = roads[k as usize];\\n roads[k as usize] = -1;\\n k = t;\\n dist -= 1;\\n }\\n res.push(dist);\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + q)$,其中 $n$ 是城市的数目,$q$ 为查询数目。第二个循环的总迭代次数最多为初始的单向道路数目 $n$ 加查询新增的单向道路数目 $q$。
\\n空间复杂度:$O(n)$。
\\n因为城市之间的道路的长度都是 $1$,即边权相同,所以我们可以使用广度优先搜索算法来获取两个城市之间的最短路径。
\\n具体地,我们首先构造一个图 $\\\\textit{neighbors}$,$\\\\textit{neighbors}[i]$ 表示城市 $i$ 可以通往的其他城市集合。那么初始时,对于 $0 \\\\le i \\\\lt n - 1$,有 $\\\\textit{neighbors}[i] = { i + 1}$,表示从城市 $i$ 有一条单向道路通往城市 $i + 1$。然后我们遍历查询数组 $\\\\textit{queries}$,令当前遍历的查询值为 $[u, v]$,我们将 $v$ 加入到 $\\\\textit{neighbors}[u]$ 中,然后对新的图 $\\\\textit{neighbors}$ 执行广度优先搜索算法,获取城市 $0$ 到城市 $n - 1$ 的最短路径长度。遍历结束后,返回最终结果。
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>> &queries) {\\n vector<vector<int>> neighbors(n);\\n for (int i = 0; i < n - 1; i++) {\\n neighbors[i].push_back(i + 1);\\n }\\n vector<int> res;\\n for (auto &query : queries) {\\n neighbors[query[0]].push_back(query[1]);\\n res.push_back(bfs(n, neighbors));\\n }\\n return res;\\n }\\n\\n int bfs(int n, const vector<vector<int>> &neighbors) {\\n vector<int> dist(n, -1);\\n queue<int> q;\\n q.push(0);\\n dist[0] = 0;\\n while (!q.empty()) {\\n int x = q.front();\\n q.pop();\\n for (int y : neighbors[x]) {\\n if (dist[y] >= 0) {\\n continue;\\n }\\n q.push(y);\\n dist[y] = dist[x] + 1;\\n }\\n }\\n return dist[n - 1];\\n }\\n};\\n
\\n###C
\\ntypedef struct Node {\\n int val;\\n struct Node *next;\\n} Node;\\n\\ntypedef struct List {\\n Node *node;\\n} List;\\n\\nvoid push(List *list, int val) {\\n Node *node = (Node *)malloc(sizeof(Node));\\n node->next = list->node;\\n node->val = val;\\n list->node = node;\\n}\\n\\nvoid freeList(List *list) {\\n for (Node *node = list->node; node != NULL;) {\\n Node *tmp = node;\\n node = node->next;\\n free(tmp);\\n }\\n}\\n\\nint bfs(int n, List *neighbors) {\\n int *dist = (int *)malloc(n * sizeof(int));\\n memset(dist, 0xff, n * sizeof(int));\\n dist[0] = 0;\\n int *q = (int *)malloc(n * sizeof(int));\\n int front = 0, back = 0;\\n q[back++] = 0;\\n while (front < back) {\\n int x = q[front++];\\n for (Node *node = neighbors[x].node; node != NULL; node = node->next) {\\n if (dist[node->val] >= 0) {\\n continue;\\n }\\n q[back++] = node->val;\\n dist[node->val] = dist[x] + 1;\\n }\\n }\\n int ret = dist[n - 1];\\n free(dist);\\n free(q);\\n return ret;\\n}\\n\\nint *shortestDistanceAfterQueries(int n, int **queries, int queriesSize, int *queriesColSize, int *returnSize) {\\n List *neighbors = (List *)malloc(n * sizeof(List));\\n memset(neighbors, 0, n * sizeof(List));\\n for (int i = 0; i < n - 1; i++) {\\n push(&neighbors[i], i + 1);\\n }\\n int* res = (int*)malloc(queriesSize * sizeof(int));\\n *returnSize = queriesSize;\\n for (int i = 0; i < queriesSize; i++) {\\n push(&neighbors[queries[i][0]], queries[i][1]);\\n res[i] = bfs(n, neighbors);\\n }\\n for (int i = 0; i < n; i++) {\\n freeList(&neighbors[i]);\\n }\\n free(neighbors);\\n return res;\\n}\\n
\\n###Go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\n neighbors := make([][]int, n)\\n for i := 0; i < n - 1; i++ {\\n neighbors[i] = append(neighbors[i], i + 1)\\n }\\n var res []int\\n for _, query := range queries {\\n neighbors[query[0]] = append(neighbors[query[0]], query[1])\\n res = append(res, bfs(n, neighbors))\\n }\\n return res\\n}\\n\\nfunc bfs(n int, neighbors [][]int) int {\\n dist := make([]int, n)\\n for i := 1; i < n; i++ {\\n dist[i] = -1\\n }\\n q := []int{0}\\n for len(q) > 0 {\\n x := q[0]\\n q = q[1:]\\n for _, y := range neighbors[x] {\\n if dist[y] >= 0 {\\n continue\\n }\\n q = append(q, y)\\n dist[y] = dist[x] + 1\\n }\\n }\\n return dist[n - 1]\\n}\\n
\\n###Python
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n neighbors = [[i + 1] for i in range(n)]\\n neighbors[-1] = []\\n res = []\\n for (u, v) in queries:\\n neighbors[u].append(v)\\n res.append(self.bfs(n, neighbors))\\n return res\\n\\n def bfs(self, n: int, neighbors: List[List[int]]) -> int:\\n dist = [-1 for _ in range(n)]\\n dist[0] = 0\\n q = deque([0])\\n while len(q) > 0:\\n x = q.popleft()\\n for y in neighbors[x]:\\n if dist[y] >= 0:\\n continue\\n q.append(y)\\n dist[y] = dist[x] + 1\\n return dist[n - 1]\\n
\\n###Java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n List<List<Integer>> neighbors = new ArrayList<>();\\n for (int i = 0; i < n; i++) {\\n neighbors.add(new ArrayList<>());\\n }\\n for (int i = 0; i < n - 1; i++) {\\n neighbors.get(i).add(i + 1);\\n }\\n int[] res = new int[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n neighbors.get(queries[i][0]).add(queries[i][1]);\\n res[i] = bfs(n, neighbors);\\n }\\n return res;\\n }\\n private int bfs(int n, List<List<Integer>> neighbors) {\\n int[] dist = new int[n];\\n for (int i = 1; i < n; i++) {\\n dist[i] = -1;\\n }\\n Queue<Integer> q = new LinkedList<>();\\n q.add(0);\\n while (!q.isEmpty()) {\\n int x = q.poll();\\n for (int y : neighbors.get(x)) {\\n if (dist[y] >= 0) {\\n continue;\\n }\\n q.add(y);\\n dist[y] = dist[x] + 1;\\n }\\n }\\n return dist[n - 1];\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ShortestDistanceAfterQueries(int n, int[][] queries) {\\n List<List<int>> neighbors = new List<List<int>>();\\n for (int i = 0; i < n; i++) {\\n neighbors.Add(new List<int>());\\n }\\n for (int i = 0; i < n - 1; i++) {\\n neighbors[i].Add(i + 1);\\n }\\n int[] res = new int[queries.Length];\\n for (int i = 0; i < queries.Length; i++) {\\n neighbors[queries[i][0]].Add(queries[i][1]);\\n res[i] = Bfs(n, neighbors);\\n }\\n return res;\\n }\\n\\n private int Bfs(int n, List<List<int>> neighbors) {\\n int[] dist = new int[n];\\n for (int i = 1; i < n; i++) {\\n dist[i] = -1;\\n }\\n Queue<int> q = new Queue<int>();\\n q.Enqueue(0);\\n dist[0] = 0;\\n while (q.Count > 0) {\\n int x = q.Dequeue();\\n foreach (int y in neighbors[x]) {\\n if (dist[y] >= 0) {\\n continue;\\n }\\n q.Enqueue(y);\\n dist[y] = dist[x] + 1;\\n }\\n }\\n return dist[n - 1];\\n }\\n}\\n
\\n###JavaScript
\\nvar shortestDistanceAfterQueries = function(n, queries) {\\n let neighbors = new Array(n).fill().map(() => []);\\n for (let i = 0; i < n - 1; i++) {\\n neighbors[i].push(i + 1);\\n }\\n let res = [];\\n for (let i = 0; i < queries.length; i++) {\\n neighbors[queries[i][0]].push(queries[i][1]);\\n res.push(bfs(n, neighbors));\\n }\\n return res;\\n};\\n\\nvar bfs = function(n, neighbors) {\\n let dist = new Array(n).fill(-1);\\n dist[0] = 0;\\n let q = [0];\\n while (q.length > 0) {\\n let x = q.shift();\\n for (let y of neighbors[x]) {\\n if (dist[y] >= 0) {\\n continue;\\n }\\n q.push(y);\\n dist[y] = dist[x] + 1;\\n }\\n }\\n return dist[n - 1];\\n};\\n
\\n###TypeScript
\\nfunction shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {\\n let neighbors: number[][] = new Array(n).fill([]).map(() => []);\\n for (let i = 0; i < n - 1; i++) {\\n neighbors[i].push(i + 1);\\n }\\n let res = [];\\n for (let i = 0; i < queries.length; i++) {\\n neighbors[queries[i][0]].push(queries[i][1]);\\n res.push(bfs(n, neighbors));\\n }\\n return res;\\n};\\n\\nfunction bfs(n: number, neighbors: number[][]): number {\\n let dist = new Array(n).fill(-1);\\n dist[0] = 0;\\n let q = [0];\\n while (q.length > 0) {\\n let x = q.shift();\\n for (let y of neighbors[x]) {\\n if (dist[y] >= 0) {\\n continue;\\n }\\n q.push(y);\\n dist[y] = dist[x] + 1;\\n }\\n }\\n return dist[n - 1];\\n};\\n
\\n###Rust
\\nuse std::collections::VecDeque;\\n\\nimpl Solution {\\n pub fn shortest_distance_after_queries(n: i32, queries: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut neighbors: Vec<Vec<i32>> = vec![Vec::new(); n as usize];\\n for i in 0..n-1 {\\n neighbors[i as usize].push(i + 1);\\n }\\n let mut res: Vec<i32> = Vec::new();\\n for query in queries {\\n neighbors[query[0] as usize].push(query[1]);\\n res.push(Self::bfs(n as usize, &neighbors));\\n }\\n res\\n }\\n\\n fn bfs(n: usize, neighbors: &Vec<Vec<i32>>) -> i32 {\\n let mut dist = vec![-1; n];\\n let mut q = VecDeque::new();\\n q.push_back(0);\\n dist[0] = 0;\\n while let Some(x) = q.pop_front() {\\n for &y in &neighbors[x] {\\n if dist[y as usize] >= 0 {\\n continue;\\n }\\n q.push_back(y as usize);\\n dist[y as usize] = dist[x] + 1;\\n }\\n }\\n dist[n - 1]\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(q * (n + q))$,其中 $n$ 是城市数目,$q$ 是查询次数。每次广度优先搜索需要 $O(n + q)$,总共有 $q$ 次。
\\n空间复杂度:$O(n + q)$。
\\n根据题意,对于任一单向道路的起始点 $u$,终止点 $v$,都有 $u \\\\lt v$,那么从城市 $0$ 到任一城市的路径上,所经过的城市编号是单调递增的。令 $\\\\textit{dp}[i]$ 表示城市 $0$ 到城市 $i$ 的最短路径,同时使用 $\\\\textit{prev}[i]$ 记录通往城市 $i$ 的所有单向道路的起始城市集合,那么对于 $i \\\\gt 0$,有 $\\\\textit{dp}[i] = \\\\min_{j \\\\in \\\\textit{prev}[i]} \\\\textit{dp}[j] + 1$。
\\n根据以上推论,我们可以遍历 $\\\\textit{queries}$,在每次查询时,更新 $\\\\textit{prev}$ 数组,然后更新 $\\\\textit{dp}$ 数组。注意到,每次新建一条从城市 $u$ 到城市 $v$ 的单向道路时,只有 $i \\\\ge v$ 的 $\\\\textit{dp}[i]$ 会发生变化,因此更新 $\\\\textit{dp}$ 可以从 $v$ 开始更新。
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>> &queries) {\\n vector<vector<int>> prev(n);\\n vector<int> dp(n);\\n for (int i = 1; i < n; i++) {\\n prev[i].push_back(i - 1);\\n dp[i] = i;\\n }\\n vector<int> res;\\n for (auto &query : queries) {\\n prev[query[1]].push_back(query[0]);\\n for (int v = query[1]; v < n; v++) {\\n for (int u : prev[v]) {\\n dp[v] = min(dp[v], dp[u] + 1);\\n }\\n }\\n res.push_back(dp[n - 1]);\\n }\\n return res;\\n }\\n};\\n
\\n###C
\\ntypedef struct Node {\\n int val;\\n struct Node *next;\\n} Node;\\n\\ntypedef struct List {\\n Node *node;\\n} List;\\n\\nvoid push(List *list, int val) {\\n Node *node = (Node *)malloc(sizeof(Node));\\n node->next = list->node;\\n node->val = val;\\n list->node = node;\\n}\\n\\nvoid freeList(List *list) {\\n for (Node *node = list->node; node != NULL;) {\\n Node *tmp = node;\\n node = node->next;\\n free(tmp);\\n }\\n}\\n\\nint *shortestDistanceAfterQueries(int n, int **queries, int queriesSize, int *queriesColSize, int *returnSize) {\\n List *prev = (List *)malloc(n * sizeof(List));\\n memset(prev, 0, n * sizeof(List));\\n int *dp = (int *)malloc(n * sizeof(int));\\n memset(dp, 0, n * sizeof(int));\\n for (int i = 1; i < n; i++) {\\n push(&prev[i], i - 1);\\n dp[i] = i;\\n }\\n int *res = (int *)malloc(queriesSize * sizeof(int));\\n for (int i = 0; i < queriesSize; i++) {\\n push(&prev[queries[i][1]], queries[i][0]);\\n for (int v = queries[i][1]; v < n; v++) {\\n for (Node *node = prev[v].node; node != NULL; node = node->next) {\\n dp[v] = fmin(dp[v], dp[node->val] + 1);\\n }\\n }\\n res[i] = dp[n - 1];\\n }\\n for (int i = 0; i < n; i++) {\\n freeList(&prev[i]);\\n }\\n free(prev);\\n free(dp);\\n *returnSize = queriesSize;\\n return res;\\n}\\n
\\n###Go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\n prev := make([][]int, n)\\n dp := make([]int, n)\\n for i := 1; i < n; i++ {\\n prev[i] = append(prev[i], i - 1)\\n dp[i] = i\\n }\\n var res []int\\n for _, query := range queries {\\n prev[query[1]] = append(prev[query[1]], query[0])\\n for v := query[1]; v < n; v++ {\\n for _, u := range prev[v] {\\n dp[v] = min(dp[v], dp[u] + 1)\\n }\\n }\\n res = append(res, dp[n - 1])\\n }\\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n prev = [[i - 1] for i in range(n)]\\n prev[0] = []\\n dp = [i for i in range(n)]\\n res = []\\n for (x, y) in queries:\\n prev[y].append(x)\\n for v in range(y, n):\\n for u in prev[v]:\\n dp[v] = min(dp[v], dp[u] + 1)\\n res.append(dp[-1])\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n List<List<Integer>> prev = new ArrayList<>();\\n for (int i = 0; i < n; i++) {\\n prev.add(new ArrayList<>());\\n }\\n int[] dp = new int[n];\\n for (int i = 1; i < n; i++) {\\n prev.get(i).add(i - 1);\\n dp[i] = i;\\n }\\n int [] res = new int[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n prev.get(queries[i][1]).add(queries[i][0]);\\n for (int v = queries[i][1]; v < n; v++) {\\n for (int u : prev.get(v)) {\\n dp[v] = Math.min(dp[v], dp[u] + 1);\\n }\\n }\\n res[i] = dp[n - 1];\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ShortestDistanceAfterQueries(int n, int[][] queries) {\\n List<List<int>> prev = new List<List<int>>(n);\\n for (int i = 0; i < n; i++) {\\n prev.Add(new List<int>());\\n }\\n int[] dp = new int[n];\\n for (int i = 1; i < n; i++) {\\n prev[i].Add(i - 1);\\n dp[i] = i;\\n }\\n int[] res = new int[queries.Length];\\n for (int i = 0; i < queries.Length; i++) {\\n prev[queries[i][1]].Add(queries[i][0]);\\n for (int v = queries[i][1]; v < n; v++) {\\n foreach (int u in prev[v]) {\\n dp[v] = Math.Min(dp[v], dp[u] + 1);\\n }\\n }\\n res[i] = dp[n - 1];\\n }\\n return res;\\n }\\n}\\n
\\n###JavaScript
\\nvar shortestDistanceAfterQueries = function(n, queries) {\\n let prev = new Array(n).fill().map(() => []);\\n for (let i = 1; i < n; i++) {\\n prev[i].push(i - 1);\\n }\\n let dp = new Array(n).fill(0).map((_, i) => i);\\n let res = new Array(queries.length);\\n for (let i = 0; i < queries.length; i++) {\\n prev[queries[i][1]].push(queries[i][0]);\\n for (let v = queries[i][1]; v < n; v++) {\\n for (let u of prev[v]) {\\n dp[v] = Math.min(dp[v], dp[u] + 1);\\n }\\n }\\n res[i] = dp[n - 1];\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction shortestDistanceAfterQueries(n: number, queries: number[][]): number[] {\\n let prev: number[][] = new Array(n).fill([]).map(() => []);\\n for (let i = 1; i < n; i++) {\\n prev[i].push(i - 1);\\n }\\n let dp = new Array(n).fill(0).map((_, i) => i);\\n let res = new Array(queries.length);\\n for (let i = 0; i < queries.length; i++) {\\n prev[queries[i][1]].push(queries[i][0]);\\n for (let v = queries[i][1]; v < n; v++) {\\n for (let u of prev[v]) {\\n dp[v] = Math.min(dp[v], dp[u] + 1);\\n }\\n }\\n res[i] = dp[n - 1];\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn shortest_distance_after_queries(n: i32, queries: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut prev: Vec<Vec<i32>> = vec![Vec::new(); n as usize];\\n let mut dp: Vec<i32> = vec![0; n as usize];\\n for i in 1..n {\\n prev[i as usize].push(i - 1);\\n dp[i as usize] = i;\\n }\\n let mut res: Vec<i32> = Vec::new();\\n for query in queries {\\n prev[query[1] as usize].push(query[0]);\\n for v in query[1] as usize..n as usize {\\n for &u in &prev[v] {\\n dp[v] = dp[v].min(dp[u as usize] + 1);\\n }\\n }\\n res.push(dp[(n - 1) as usize]);\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(q * (n + q))$,其中 $n$ 是城市数目,$q$ 是查询次数。
\\n空间复杂度:$O(n + q)$。
\\n思路与算法
\\n首先我们需要统计每个玩家得到的每种颜色的球的数目,此时需要遍历 数组 $\\\\textit{pick}$。由于颜色 $y$ 的取值范围为 $[0,10]$,此时我们用一个 $n \\\\times 11$ 的二维数组统计每个玩家得到的每种颜色的球的数目。然后我们从 $0$ 到 $n-1$ 依次遍历每个玩家,如果d当前第 $i$ 个玩家至少有一种颜色的球大于玩家编号 $i$,则胜利玩家数目加 $1$,返回总的胜利玩家数目即可。
\\n代码
\\n###C++
\\nint winningPlayerCount(int n, vector<vector<int>>& pick) {\\n vector<vector<int>> cnt(n, vector<int>(11));\\n for (auto &p : pick) {\\n cnt[p[0]][p[1]]++;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 10; j++) {\\n if (cnt[i][j] > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n}\\n
\\n###Java
\\nclass Solution {\\n public int winningPlayerCount(int n, int[][] pick) {\\n int[][] cnt = new int[n][11];\\n for (int[] p : pick) {\\n cnt[p[0]][p[1]]++;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 10; j++) {\\n if (cnt[i][j] > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int WinningPlayerCount(int n, int[][] pick) {\\n int[,] cnt = new int[n, 11];\\n foreach (var p in pick) {\\n cnt[p[0], p[1]]++;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 10; j++) {\\n if (cnt[i, j] > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc winningPlayerCount(n int, pick [][]int) int {\\n cnt := make([][]int, n)\\nfor i := range cnt {\\ncnt[i] = make([]int, 11)\\n}\\nfor _, p := range pick {\\ncnt[p[0]][p[1]]++\\n}\\n\\nans := 0\\nfor i := 0; i < n; i++ {\\nfor j := 0; j <= 10; j++ {\\nif cnt[i][j] > i {\\nans++\\nbreak\\n}\\n}\\n}\\nreturn ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def winningPlayerCount(self, n: int, pick: List[List[int]]) -> int:\\n cnt = [[0] * 11 for _ in range(n)]\\n for p in pick:\\n cnt[p[0]][p[1]] += 1\\n ans = 0\\n for i, arr in enumerate(cnt):\\n if any(x > i for x in arr):\\n ans += 1\\n return ans\\n
\\n###C
\\nint winningPlayerCount(int n, int** pick, int pickSize, int* pickColSize) {\\n int cnt[n][11];\\n memset(cnt, 0, sizeof(cnt));\\n for (int i = 0; i < pickSize; i++) {\\n cnt[pick[i][0]][pick[i][1]]++;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 10; j++) {\\n if (cnt[i][j] > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar winningPlayerCount = function(n, pick) {\\n let cnt = Array.from({ length: n }, () => Array(11).fill(0)); \\n pick.forEach(p => {\\n cnt[p[0]][p[1]]++;\\n });\\n return cnt.filter((player, i) => \\n player.some((count, j) => count > i)\\n ).length;\\n};\\n
\\n###TypeScript
\\nfunction winningPlayerCount(n: number, pick: number[][]): number {\\n const cnt: number[][] = Array.from({length: n}, () => Array(11).fill(0));\\n pick.forEach(p => {\\n cnt[p[0]][p[1]]++;\\n });\\n return cnt.filter((player, i) =>\\n player.some((count, j) => count > i)\\n ).length;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn winning_player_count(n: i32, pick: Vec<Vec<i32>>) -> i32 {\\n let mut cnt = vec![vec![0; 11]; n as usize];\\n for p in pick {\\n cnt[p[0] as usize][p[1] as usize] += 1;\\n }\\n (0..n as usize).filter(|&i|{(0..=10).any(|j| cnt[i][j] > i)}).count() as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m + n \\\\times U)$,$m$ 表示给定数组 $\\\\textit{pick}$ 的长度,$n$ 表示给定的数字,$U$ 表示球的颜色数目的上限。统计每个玩家得到的每种颜色的球的数目需要遍历数组 $\\\\textit{pick}$,需要的时间为 $O(m)$,然后依次遍历每个玩家,并检测该玩家拥有的每种颜色球的数目,需要的时间为 $O(n \\\\times U)$。
\\n空间复杂度:$O(n \\\\times U)$,$n$ 表示给定的数字,$U$ 表示球的颜色数值的上限。需要保存每个玩家每种颜色球的统计数目,需要的空间为 $O(n \\\\times U)$。
\\n思路与算法
\\n特殊数字首先是一个平方数,并且除去自身和 $1$ 之后的另一个因子一定是一个质数。这是因为:
\\n因此,我们可以在 $[1, \\\\sqrt r]$ 的范围内遍历所有质数(使用质数筛,具体方法可以参考题解 204. 计数质数),然后将它们的平方从 $[l, r]$ 的范围中去除即可。
\\n由于 $r$ 的范围不超过 $10^9$,因此质数的遍历范围不超过 $31622$,而使用很简单的埃氏筛(复杂度为 $O(n\\\\log n\\\\log n)$,其中 $n$ 为质数遍历范围)就可以轻松通过本题。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int nonSpecialCount(int l, int r) {\\n int n = sqrt(r);\\n vector<int> v(n + 1);\\n int res = r - l + 1;\\n for (int i = 2; i <= n; i++) {\\n if (v[i] == 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (int j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int nonSpecialCount(int l, int r) {\\n int n = (int) Math.sqrt(r);\\n int[] v = new int[n + 1];\\n int res = r - l + 1;\\n for (int i = 2; i <= n; i++) {\\n if (v[i] == 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (int j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NonSpecialCount(int l, int r) {\\n int n = (int) Math.Sqrt(r);\\n int[] v = new int[n + 1];\\n int res = r - l + 1;\\n for (int i = 2; i <= n; i++) {\\n if (v[i] == 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (int j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def nonSpecialCount(self, l: int, r: int) -> int:\\n n = int(math.sqrt(r))\\n v = [0] * (n + 1)\\n res = r - l + 1\\n for i in range(2, n + 1):\\n if v[i] == 0:\\n if l <= i * i <= r:\\n res -= 1\\n for j in range(i * 2, n + 1, i):\\n v[j] = 1\\n return res\\n
\\n###Rust
\\nimpl Solution {\\n pub fn non_special_count(l: i32, r: i32) -> i32 {\\n let n = (r as f64).sqrt() as usize;\\n let mut v = vec![0; n + 1];\\n let mut res = r - l + 1;\\n\\n for i in 2..=n {\\n if v[i] == 0 {\\n let square = (i * i) as i32;\\n if square >= l && square <= r {\\n res -= 1;\\n }\\n for j in (i * 2..=n).step_by(i) {\\n v[j] = 1;\\n }\\n }\\n }\\n res\\n }\\n}\\n
\\n###Go
\\nfunc nonSpecialCount(l int, r int) int {\\n n := int(math.Sqrt(float64(r)))\\nv := make([]int, n + 1)\\nres := r - l + 1\\nfor i := 2; i <= n; i++ {\\nif v[i] == 0 {\\nif i * i >= l && i * i <= r {\\nres--\\n}\\nfor j := i * 2; j <= n; j += i {\\nv[j] = 1\\n}\\n}\\n}\\nreturn res\\n}\\n
\\n###C
\\nint nonSpecialCount(int l, int r) {\\n int n = (int)sqrt(r);\\n int v[n + 1];\\n int res = r - l + 1;\\n for (int i = 0; i <= n; i++) {\\n v[i] = 0;\\n }\\n for (int i = 2; i <= n; i++) {\\n if (v[i] == 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (int j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar nonSpecialCount = function(l, r) {\\n const n = Math.floor(Math.sqrt(r));\\n const v = new Array(n + 1).fill(0);\\n let res = r - l + 1;\\n for (let i = 2; i <= n; i++) {\\n if (v[i] === 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (let j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction nonSpecialCount(l: number, r: number): number {\\n const n = Math.floor(Math.sqrt(r));\\n const v = new Array(n + 1).fill(0);\\n let res = r - l + 1;\\n for (let i = 2; i <= n; i++) {\\n if (v[i] === 0) {\\n if (i * i >= l && i * i <= r) {\\n res--;\\n }\\n for (let j = i * 2; j <= n; j += i) {\\n v[j] = 1;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n\\\\log\\\\log n)$,其中 $n$ 为 $\\\\sqrt r$。
\\n空间复杂度:$O(n)$。
\\n有一根长度为 n
个单位的木棍,棍上从 0
到 n
标记了若干位置。例如,长度为 6 的棍子可以标记如下:
给你一个整数数组 cuts
,其中 cuts[i]
表示你需要将棍子切开的位置。
你可以按顺序完成切割,也可以根据需要更改切割的顺序。
\\n\\n每次切割的成本都是当前要切割的棍子的长度,切棍子的总成本是历次切割成本的总和。对棍子进行切割将会把一根木棍分成两根较小的木棍(这两根木棍的长度和就是切割前木棍的长度)。请参阅第一个示例以获得更直观的解释。
\\n\\n返回切棍子的 最小总成本 。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:n = 7, cuts = [1,3,4,5]\\n输出:16\\n解释:按 [1, 3, 4, 5] 的顺序切割的情况如下所示:\\n\\n\\n\\n第一次切割长度为 7 的棍子,成本为 7 。第二次切割长度为 6 的棍子(即第一次切割得到的第二根棍子),第三次切割为长度 4 的棍子,最后切割长度为 3 的棍子。总成本为 7 + 6 + 4 + 3 = 20 。\\n而将切割顺序重新排列为 [3, 5, 1, 4] 后,总成本 = 16(如示例图中 7 + 4 + 3 + 2 = 16)。\\n
示例 2:
\\n\\n输入:n = 9, cuts = [5,6,1,4,2]\\n输出:22\\n解释:如果按给定的顺序切割,则总成本为 25 。总成本 <= 25 的切割顺序很多,例如,[4, 6, 5, 2, 1] 的总成本 = 22,是所有可能方案中成本最小的。\\n\\n
\\n\\n
提示:
\\n\\n2 <= n <= 10^6
1 <= cuts.length <= min(n - 1, 100)
1 <= cuts[i] <= n - 1
cuts
数组中的所有整数都 互不相同题目有两个已知条件:
\\n第二个条件意味着,数组的长度一定是奇数。
\\n第一个条件意味着,出现两次的数,必然相邻,不可能出现 $1,2,1$ 这样的顺序。
\\n这也意味着,只出现一次的那个数,一定位于偶数下标上。
\\n这启发我们去检查偶数下标 $2k$。
\\n示例 1 的 $\\\\textit{nums} = [1,1,2,3,3,4,4,8,8]$:
\\n也就是说,随着 $k$ 的变大,不等式 $\\\\textit{nums}[2k] \\\\ne \\\\textit{nums}[2k+1]$ 越可能满足,有单调性,可以二分。
\\n关于二分的原理,请看视频【基础算法精讲 04】。
\\n讨论二分的上下界。本文用开区间二分,其他二分写法也是可以的。
\\n二分 $k$,其中 $k$ 的范围是 $0\\\\le k \\\\le \\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor - 1$。
\\n⚠注意:$k=\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$,也就是 $2k=n-1$ 的位置是不需要检查的,如果我们在 $<n-1$ 的位置都没有找到只出现一次的数,那么只出现一次的数必然位于 $n-1$。
\\n所以开区间二分的左右边界分别为 $-1$ 和 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$。
\\n如果你要用闭区间二分,左右边界分别为 $0$ 和 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor - 1$。
\\nclass Solution:\\n def singleNonDuplicate(self, nums: List[int]) -> int:\\n left, right = -1, len(nums) // 2\\n while left + 1 < right:\\n mid = (left + right) // 2\\n if nums[mid * 2] != nums[mid * 2 + 1]:\\n right = mid\\n else:\\n left = mid\\n return nums[right * 2]\\n
\\nclass Solution:\\n def singleNonDuplicate(self, nums: List[int]) -> int:\\n check = lambda k: nums[k * 2] != nums[k * 2 + 1]\\n k = bisect_left(range(len(nums) // 2), True, key=check)\\n return nums[k * 2]\\n
\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int left = -1;\\n int right = nums.length / 2;\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n if (nums[mid * 2] != nums[mid * 2 + 1]) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right * 2];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int singleNonDuplicate(vector<int>& nums) {\\n int left = -1, right = nums.size() / 2;\\n while (left + 1 < right) {\\n int mid = left + (right - left) / 2;\\n (nums[mid * 2] != nums[mid * 2 + 1] ? right : left) = mid;\\n }\\n return nums[right * 2];\\n }\\n};\\n
\\nint singleNonDuplicate(int* nums, int numsSize) {\\n int left = -1, right = numsSize / 2;\\n while (left + 1 < right) {\\n int mid = left + (right - left) / 2;\\n if (nums[mid * 2] != nums[mid * 2 + 1]) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right * 2];\\n}\\n
\\nfunc singleNonDuplicate(nums []int) int {\\n left, right := -1, len(nums)/2\\n for left+1 < right {\\n mid := left + (right-left)/2\\n if nums[mid*2] != nums[mid*2+1] {\\n right = mid\\n } else {\\n left = mid\\n }\\n }\\n return nums[right*2]\\n}\\n
\\nfunc singleNonDuplicate(nums []int) int {\\n k := sort.Search(len(nums)/2, func(k int) bool {\\n return nums[k*2] != nums[k*2+1]\\n })\\n return nums[k*2]\\n}\\n
\\nvar singleNonDuplicate = function(nums) {\\n let left = -1, right = Math.floor(nums.length / 2);\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (nums[mid * 2] !== nums[mid * 2 + 1]) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return nums[right * 2];\\n};\\n
\\nimpl Solution {\\n pub fn single_non_duplicate(nums: Vec<i32>) -> i32 {\\n // 左闭右开区间\\n let mut left = 0;\\n let mut right = nums.len() / 2;\\n while left < right {\\n let mid = (left + right) / 2;\\n if nums[mid * 2] != nums[mid * 2 + 1] {\\n right = mid;\\n } else {\\n left = mid + 1;\\n }\\n }\\n nums[right * 2]\\n }\\n}\\n
\\n改成除了一个数出现一次外,其余每个数都出现 $3$ 次呢?$m$ 次呢?
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 题目有两个已知条件:\\n\\n数组是有序的。\\n除了一个数出现一次外,其余每个数都出现两次。\\n\\n第二个条件意味着,数组的长度一定是奇数。\\n\\n第一个条件意味着,出现两次的数,必然相邻,不可能出现 $1,2,1$ 这样的顺序。\\n\\n这也意味着,只出现一次的那个数,一定位于偶数下标上。\\n\\n这启发我们去检查偶数下标 $2k$。\\n\\n示例 1 的 $\\\\textit{nums} = [1,1,2,3,3,4,4,8,8]$:\\n\\n如果 $\\\\textit{nums}[2k] = \\\\textit{nums}[2k+1]$,说明只出现一次的数的下标 $>2k$。\\n如果 $\\\\textit…","guid":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/er-fen-xing-zhi-fen-xi-jian-ji-xie-fa-py-0rng","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-10T00:05:49.500Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-有序数组中的单一元素🟡","url":"https://leetcode.cn/problems/single-element-in-a-sorted-array/","content":"给你一个仅由整数组成的有序数组,其中每个元素都会出现两次,唯有一个数只会出现一次。
\\n\\n请你找出并返回只出现一次的那个数。
\\n\\n你设计的解决方案必须满足 O(log n)
时间复杂度和 O(1)
空间复杂度。
\\n\\n
示例 1:
\\n\\n输入: nums = [1,1,2,3,3,4,4,8,8]\\n输出: 2\\n\\n\\n
示例 2:
\\n\\n输入: nums = [3,3,7,7,10,11,11]\\n输出: 10\\n\\n\\n
\\n\\n\\n\\n
提示:
\\n\\n1 <= nums.length <= 105
0 <= nums[i] <= 105
示例 1 的 $\\\\textit{cuts}=[1,3,4,5]$,为方便描述,把 $0$ 和 $n=7$ 也视作切开的位置(木棍端点),得到 $\\\\textit{cuts}=[0,1,3,4,5,7]$。
\\n我们要解决的问题(原问题)是:
\\n第一刀切在哪?枚举:
\\n接下来,继续计算这两段木棍各自的最小切割成本。同样地,枚举切割的位置。依此类推。
\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n\\n\\n注:动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。本题用到的是「枚举选哪个」。
\\n
根据上面的讨论,我们需要在递归过程中,知道当前切的这根棍子,左端点在哪,右端点在哪。
\\n因此,定义状态为 $\\\\textit{dfs}(i,j)$,表示切割一根左端点为 $\\\\textit{cuts}[i]$,右端点为 $\\\\textit{cuts}[j]$ 的棍子的最小成本。
\\n枚举在 $\\\\textit{cuts}[k]$ 处切一刀,其中 $k=i+1,i+2,\\\\ldots,j-1$,木棍变成两段:
\\n$$
\\n\\\\textit{dfs}(i,k) + \\\\textit{dfs}(k,j) + \\\\textit{cuts}[j] - \\\\textit{cuts}[i]
\\n$$
枚举 $k=i+1,i+2,\\\\ldots,j-1$,所有成本取最小值,得
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\min\\\\limits_{k=i+1}^{j-1} \\\\textit{dfs}(i,k) + \\\\textit{dfs}(k,j) + \\\\textit{cuts}[j] - \\\\textit{cuts}[i]
\\n$$
其中 $\\\\textit{cuts}[j] - \\\\textit{cuts}[i]$ 与 $k$ 无关,可以提到循环外面。
\\n递归边界:$\\\\textit{dfs}(i,i+1)=0$。此时木棍中没有要切割的位置,所以切割成本为 $0$。
\\n递归入口:$\\\\textit{dfs}(0,m-1)$,也就是答案。其中 $m$ 是添加了 $0$ 和 $n$ 之后的 $\\\\textit{cuts}$ 数组的长度。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。本题由于 $\\\\textit{cuts}[j] - \\\\textit{cuts}[i] > 0$,所以除了递归边界以外,$\\\\textit{dfs}$ 的返回值均为正数,所以也可以把初始值设置为 $0$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】 以及 区间 DP【基础算法精讲 22】。
\\n###py
\\nclass Solution:\\n def minCost(self, n: int, cuts: List[int]) -> int:\\n cuts.sort()\\n cuts = [0] + cuts + [n]\\n\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i + 1 == j: # 无需切割\\n return 0\\n # 枚举切割位置 cuts[k]\\n return min(dfs(i, k) + dfs(k, j) for k in range(i + 1, j)) + cuts[j] - cuts[i]\\n\\n return dfs(0, len(cuts) - 1)\\n
\\n###java
\\nclass Solution {\\n public int minCost(int n, int[] cuts) {\\n Arrays.sort(cuts);\\n int m = cuts.length + 2;\\n int[] newCuts = new int[m];\\n System.arraycopy(cuts, 0, newCuts, 1, m - 2);\\n newCuts[m - 1] = n;\\n\\n int[][] memo = new int[m][m];\\n return dfs(0, m - 1, newCuts, memo);\\n }\\n\\n private int dfs(int i, int j, int[] cuts, int[][] memo) {\\n if (i + 1 == j) { // 无需切割\\n return 0;\\n }\\n if (memo[i][j] > 0) { // 之前计算过\\n return memo[i][j];\\n }\\n int res = Integer.MAX_VALUE;\\n for (int k = i + 1; k < j; k++) {\\n res = Math.min(res, dfs(i, k, cuts, memo) + dfs(k, j, cuts, memo));\\n }\\n return memo[i][j] = res + cuts[j] - cuts[i];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minCost(int n, vector<int>& cuts) {\\n cuts.push_back(0);\\n cuts.push_back(n);\\n ranges::sort(cuts);\\n\\n int m = cuts.size();\\n vector<vector<int>> memo(m, vector<int>(m));\\n auto dfs = [&](auto& dfs, int i, int j) -> int {\\n if (i + 1 == j) { // 无需切割\\n return 0;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n res = INT_MAX;\\n for (int k = i + 1; k < j; k++) {\\n res = min(res, dfs(dfs, i, k) + dfs(dfs, k, j));\\n }\\n res += cuts[j] - cuts[i];\\n return res;\\n };\\n return dfs(dfs, 0, m - 1);\\n }\\n};\\n
\\n###go
\\nfunc minCost(n int, cuts []int) int {\\n cuts = append(cuts, 0, n)\\n slices.Sort(cuts)\\n\\n m := len(cuts)\\n memo := make([][]int, m)\\n for i := range memo {\\n memo[i] = make([]int, m)\\n }\\n var dfs func(int, int) int\\n dfs = func(i, j int) int {\\n if i+1 == j { // 无需切割\\n return 0\\n }\\n p := &memo[i][j]\\n if *p > 0 { // 之前计算过\\n return *p\\n }\\n res := math.MaxInt\\n for k := i + 1; k < j; k++ {\\n res = min(res, dfs(i, k)+dfs(k, j))\\n }\\n *p = res + cuts[j] - cuts[i] // 记忆化\\n return *p\\n }\\n return dfs(0, m-1)\\n}\\n
\\n###js
\\nvar minCost = function(n, cuts) {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort((a, b) => a - b);\\n\\n const m = cuts.length;\\n const memo = Array.from({ length: m }, () => Array(m));\\n function dfs(i, j) {\\n if (i + 1 === j) { // 无需切割\\n return 0;\\n }\\n if (memo[i][j]) { // 之前计算过\\n return memo[i][j];\\n }\\n let res = Infinity;\\n for (let k = i + 1; k < j; k++) {\\n res = Math.min(res, dfs(i, k) + dfs(k, j));\\n }\\n return memo[i][j] = res + cuts[j] - cuts[i];\\n }\\n return dfs(0, m - 1);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_cost(n: i32, mut cuts: Vec<i32>) -> i32 {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort_unstable();\\n\\n fn dfs(i: usize, j: usize, cuts: &Vec<i32>, memo: &mut Vec<Vec<i32>>) -> i32 {\\n if i + 1 == j { // 无需切割\\n return 0;\\n }\\n if memo[i][j] > 0 { // 之前计算过\\n return memo[i][j];\\n }\\n let mut res = i32::MAX;\\n for k in i + 1..j {\\n res = res.min(dfs(i, k, cuts, memo) + dfs(k, j, cuts, memo));\\n }\\n memo[i][j] = res + cuts[j] - cuts[i];\\n memo[i][j]\\n }\\n let m = cuts.len();\\n let mut memo = vec![vec![0; m]; m];\\n dfs(0, m - 1, &cuts, &mut memo)\\n }\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示切割一根左端点为 $\\\\textit{cuts}[i]$,右端点为 $\\\\textit{cuts}[j]$ 的棍子的最小成本。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i][j] = \\\\min\\\\limits_{k=i+1}^{j-1} f[i][k] + f[k][j] + \\\\textit{cuts}[j] - \\\\textit{cuts}[i]
\\n$$
初始值 $f[i][i+1]=0$,翻译自递归边界 $\\\\textit{dfs}(i,i+1)=0$。
\\n答案为 $f[0][m-1]$,翻译自递归入口 $\\\\textit{dfs}(0,m-1)$。
\\n问:如何思考循环顺序?什么时候要正序枚举,什么时候要倒序枚举?
\\n答:这里有一个通用的做法:盯着状态转移方程,想一想,要计算 $f[i][j]$,必须先把 $f[k][j]$ 算出来,由于 $i<k$,那么只有 $i$ 从大到小枚举才能做到。同理,必须先把同一行的 $f[i][k]$ 算出来,由于 $j>k$,那么只有 $j$ 从小到大枚举才能做到。
\\n###py
\\nclass Solution:\\n def minCost(self, n: int, cuts: List[int]) -> int:\\n cuts.sort()\\n cuts = [0] + cuts + [n]\\n\\n m = len(cuts)\\n f = [[0] * m for _ in range(m)]\\n for i in range(m - 3, -1, -1):\\n for j in range(i + 2, m):\\n f[i][j] = min(f[i][k] + f[k][j] for k in range(i + 1, j)) + cuts[j] - cuts[i]\\n return f[0][-1]\\n
\\n###java
\\nclass Solution {\\n public int minCost(int n, int[] cuts) {\\n Arrays.sort(cuts);\\n int m = cuts.length + 2;\\n int[] newCuts = new int[m];\\n System.arraycopy(cuts, 0, newCuts, 1, m - 2);\\n newCuts[m - 1] = n;\\n\\n int[][] f = new int[m][m];\\n for (int i = m - 3; i >= 0; i--) {\\n for (int j = i + 2; j < m; j++) {\\n int res = Integer.MAX_VALUE;\\n for (int k = i + 1; k < j; k++) {\\n res = Math.min(res, f[i][k] + f[k][j]);\\n }\\n f[i][j] = res + newCuts[j] - newCuts[i];\\n }\\n }\\n return f[0][m - 1];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minCost(int n, vector<int>& cuts) {\\n cuts.push_back(0);\\n cuts.push_back(n);\\n ranges::sort(cuts);\\n\\n int m = cuts.size();\\n vector<vector<int>> f(m, vector<int>(m));\\n for (int i = m - 3; i >= 0; i--) {\\n for (int j = i + 2; j < m; j++) {\\n int res = INT_MAX;\\n for (int k = i + 1; k < j; k++) {\\n res = min(res, f[i][k] + f[k][j]);\\n }\\n f[i][j] = res + cuts[j] - cuts[i];\\n }\\n }\\n return f[0][m - 1];\\n }\\n};\\n
\\n###go
\\nfunc minCost(n int, cuts []int) int {\\n cuts = append(cuts, 0, n)\\n slices.Sort(cuts)\\n\\n m := len(cuts)\\n f := make([][]int, m)\\n for i := range f {\\n f[i] = make([]int, m)\\n }\\n for i := m - 3; i >= 0; i-- {\\n for j := i + 2; j < m; j++ {\\n res := math.MaxInt\\n for k := i + 1; k < j; k++ {\\n res = min(res, f[i][k]+f[k][j])\\n }\\n f[i][j] = res + cuts[j] - cuts[i]\\n }\\n }\\n return f[0][m-1]\\n}\\n
\\n###js
\\nvar minCost = function(n, cuts) {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort((a, b) => a - b);\\n\\n const m = cuts.length;\\n const f = Array.from({ length: m }, () => Array(m).fill(0));\\n for (let i = m - 3; i >= 0; i--) {\\n for (let j = i + 2; j < m; j++) {\\n let res = Infinity;\\n for (let k = i + 1; k < j; k++) {\\n res = Math.min(res, f[i][k] + f[k][j]);\\n }\\n f[i][j] = res + cuts[j] - cuts[i];\\n }\\n }\\n return f[0][m - 1];\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_cost(n: i32, mut cuts: Vec<i32>) -> i32 {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort_unstable();\\n\\n let m = cuts.len();\\n let mut f = vec![vec![0; m]; m];\\n for i in (0..m - 2).rev() {\\n for j in i + 2..m {\\n let mut res = i32::MAX;\\n for k in i + 1..j {\\n res = res.min(f[i][k] + f[k][j]);\\n }\\n f[i][j] = res + cuts[j] - cuts[i];\\n }\\n }\\n f[0][m - 1]\\n }\\n}\\n
\\n更多相似题目,见 动态规划题单 中的「八、区间 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 示例 1 的 $\\\\textit{cuts}=[1,3,4,5]$,为方便描述,把 $0$ 和 $n=7$ 也视作切开的位置(木棍端点),得到 $\\\\textit{cuts}=[0,1,3,4,5,7]$。\\n\\n我们要解决的问题(原问题)是:\\n\\n切割一根左端点为 $\\\\textit{cuts}[0]=0$,右端点为 $\\\\textit{cuts}[5]=7$ 的棍子的最小成本。\\n\\n第一刀切在哪?枚举:\\n\\n在 $\\\\textit{cuts}[1]=1$ 切一刀,木棍分成两段。第一段左端点为 $\\\\textit{cuts}[0]=0$,右端点为…","guid":"https://leetcode.cn/problems/minimum-cost-to-cut-a-stick//solution/jiao-ni-yi-bu-bu-si-kao-qu-jian-dpcong-j-f8px","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-09T05:40:49.434Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:堆/排序+滑动窗口(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/liang-chong-fang-fa-dui-pai-xu-hua-dong-luih5","content":"把「每个列表至少有一个数包含在其中」的区间叫做合法区间。
\\n先求出最左边的合法区间,然后求出第二个合法区间,第三个合法区间,依此类推。
\\n比如示例 1,最左边的合法区间是 $[0,5]$。
\\n枚举所有合法区间的左端点,或者枚举所有合法区间的右端点。其中第一个最短的合法区间就是答案。
\\n在示例 1 中,有三个列表:
\\n我们来计算最左边的合法区间,第二个合法区间,第三个合法区间,……
\\n也就是左端点为 $0$ 的合法区间,左端点为 $4$ 的合法区间,左端点为 $5$ 的合法区间。
\\n求出左端点对应的右端点,就知道了区间的长度,其中第一个最短的区间就是答案。
\\n左端点为 $0$ 的合法区间,右端点是这三个列表的第一个元素的最大值,即 $5$。
\\n接下来,去掉 $0$,列表 $[0,9,12,20]$ 变成 $[9,12,20]$,问题变成如下三个列表:
\\n这三个列表的最左边的合法区间是什么?
\\n左端点是这三个列表的第一个元素的最小值 $4$,右端点是这三个列表的第一个元素的最大值 $9$,所以合法区间为 $[4,9]$。
\\n接下来,去掉 $4$,列表 $[4,10,15,24,26]$ 变成 $[10,15,24,26]$,重复上述过程。
\\n在上述过程中,需要快速地求出合法区间的左端点和右端点:
\\n\\n\\n注:实际没有去掉元素,而是用下标表示元素在列表中的位置。
\\n
###py
\\nclass Solution:\\n def smallestRange(self, nums: List[List[int]]) -> List[int]:\\n # 把每个列表的第一个元素入堆\\n h = [(arr[0], i, 0) for i, arr in enumerate(nums)]\\n heapify(h)\\n\\n ans_l = h[0][0] # 第一个合法区间的左端点\\n ans_r = r = max(arr[0] for arr in nums) # 第一个合法区间的右端点\\n while h[0][2] + 1 < len(nums[h[0][1]]): # 堆顶列表有下一个元素\\n _, i, j = h[0]\\n x = nums[i][j + 1] # 堆顶列表的下一个元素\\n heapreplace(h, (x, i, j + 1)) # 替换堆顶\\n r = max(r, x) # 更新合法区间的右端点\\n l = h[0][0] # 当前合法区间的左端点\\n if r - l < ans_r - ans_l:\\n ans_l, ans_r = l, r\\n return [ans_l, ans_r]\\n
\\n###java
\\nclass Solution {\\n public int[] smallestRange(List<List<Integer>> nums) {\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n int r = Integer.MIN_VALUE;\\n for (int i = 0; i < nums.size(); i++) {\\n // 把每个列表的第一个元素入堆\\n int x = nums.get(i).get(0);\\n pq.offer(new int[]{x, i, 0});\\n r = Math.max(r, x);\\n }\\n\\n int ansL = pq.peek()[0]; // 第一个合法区间的左端点\\n int ansR = r; // 第一个合法区间的右端点\\n while (pq.peek()[2] + 1 < nums.get(pq.peek()[1]).size()) { // 堆顶列表有下一个元素\\n int[] top = pq.poll();\\n top[0] = nums.get(top[1]).get(++top[2]); // 堆顶列表的下一个元素\\n r = Math.max(r, top[0]); // 更新合法区间的右端点\\n pq.offer(top); // 入堆(复用 int[],提高效率)\\n int l = pq.peek()[0]; // 当前合法区间的左端点\\n if (r - l < ansR - ansL) {\\n ansL = l;\\n ansR = r;\\n }\\n }\\n return new int[]{ansL, ansR};\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> smallestRange(vector<vector<int>>& nums) {\\n priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> pq;\\n int r = INT_MIN;\\n for (int i = 0; i < nums.size(); i++) {\\n pq.emplace(nums[i][0], i, 0); // 把每个列表的第一个元素入堆\\n r = max(r, nums[i][0]);\\n }\\n\\n int ans_l = get<0>(pq.top()); // 第一个合法区间的左端点\\n int ans_r = r; // 第一个合法区间的右端点\\n while (true) {\\n auto [_, i, j] = pq.top();\\n if (j + 1 == nums[i].size()) { // 堆顶列表没有下一个元素\\n break;\\n }\\n pq.pop();\\n int x = nums[i][j + 1]; // 堆顶列表的下一个元素\\n pq.emplace(x, i, j + 1); // 入堆\\n r = max(r, x); // 更新合法区间的右端点\\n int l = get<0>(pq.top()); // 当前合法区间的左端点\\n if (r - l < ans_r - ans_l) {\\n ans_l = l;\\n ans_r = r;\\n }\\n }\\n return {ans_l, ans_r};\\n }\\n};\\n
\\n###go
\\nfunc smallestRange(nums [][]int) []int {\\n h := make(hp, len(nums))\\n r := math.MinInt\\n for i, arr := range nums {\\n h[i] = tuple{arr[0], i, 0} // 把每个列表的第一个元素入堆\\n r = max(r, arr[0])\\n }\\n heap.Init(&h)\\n\\n ansL, ansR := h[0].x, r // 第一个合法区间的左右端点\\n for h[0].j+1 < len(nums[h[0].i]) { // 堆顶列表有下一个元素\\n x := nums[h[0].i][h[0].j+1] // 堆顶列表的下一个元素\\n r = max(r, x) // 更新合法区间的右端点\\n h[0].x = x // 替换堆顶\\n h[0].j++\\n heap.Fix(&h, 0)\\n l := h[0].x // 当前合法区间的左端点\\n if r-l < ansR-ansL {\\n ansL, ansR = l, r\\n }\\n }\\n return []int{ansL, ansR}\\n}\\n\\ntype tuple struct{ x, i, j int }\\ntype hp []tuple\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].x < h[j].x }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (hp) Push(any) {} // 没用到,可以不写\\nfunc (hp) Pop() (_ any) { return }\\n
\\n###js
\\nvar smallestRange = function(nums) {\\n const pq = new MinPriorityQueue({priority: a => a[0]});\\n let r = -Infinity;\\n for (let i = 0; i < nums.length; i++) {\\n pq.enqueue([nums[i][0], i, 0]); // 每个列表的第一个元素入堆\\n r = Math.max(r, nums[i][0]);\\n }\\n\\n let ansL = pq.front().element[0]; // 第一个合法区间的左端点\\n let ansR = r; // 第一个合法区间的右端点\\n while (true) {\\n const [_, i, j] = pq.dequeue().element;\\n if (j + 1 === nums[i].length) { // 堆顶列表没有下一个元素\\n break;\\n }\\n const x = nums[i][j + 1]; // 堆顶列表的下一个元素\\n pq.enqueue([x, i, j + 1]); // 入堆\\n r = Math.max(r, x); // 更新合法区间的右端点\\n const l = pq.front().element[0]; // 当前合法区间的左端点\\n if (r - l < ansR - ansL) {\\n ansL = l;\\n ansR = r;\\n }\\n }\\n return [ansL, ansR];\\n};\\n
\\n###rust
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn smallest_range(nums: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut h = BinaryHeap::with_capacity(nums.len()); // 预分配空间\\n let mut r = i32::MIN;\\n for (i, arr) in nums.iter().enumerate() {\\n // 把每个列表的第一个元素入堆\\n h.push((-arr[0], i, 0)); // 取反变成最小堆\\n r = r.max(arr[0]);\\n }\\n\\n let mut ans_l = -h.peek().unwrap().0; // 第一个合法区间的左端点\\n let mut ans_r = r; // 第一个合法区间的右端点\\n while h.peek().unwrap().2 + 1 < nums[h.peek().unwrap().1].len() { // 堆顶列表有下一个元素\\n let (_, i, j) = h.pop().unwrap();\\n let x = nums[i][j + 1]; // 堆顶列表的下一个元素\\n h.push((-x, i, j + 1)); // 入堆\\n r = r.max(x); // 更新合法区间的右端点\\n let l = -h.peek().unwrap().0; // 当前合法区间的左端点\\n if r - l < ans_r - ans_l {\\n ans_l = l;\\n ans_r = r;\\n }\\n }\\n vec![ans_l, ans_r]\\n }\\n}\\n
\\n对于示例 1 的这三个列表:
\\n把所有元素都合在一起排序,可以得到如下结果:
\\n$$
\\n\\\\begin{array}{r|}
\\n元素值 & 0 & 4 & 5 & 9 & 10 & 12 & 15 & 18 & 20 & 22 & 24 & 26 & 30 \\\\
\\n所属列表编号 & 1 & 0 & 2 & 1 & 0 & 1 & 0 & 2 & 1 & 2 & 0 & 0 & 2 \\\\
\\n\\\\end{array}
\\n$$
把上表视作一个由(元素值,所属列表编号)组成的数组,即
\\n$$
\\n\\\\textit{pairs} = [(0, 1), (4, 0), (5, 2), \\\\ldots, (24, 0), (26, 0), (30, 2)]
\\n$$
合法区间等价于 $\\\\textit{pairs}$ 的一个连续子数组,满足列表编号 $0,1,2,\\\\ldots,k-1$ 都在这个子数组中。
\\n由于子数组越长,越能包含 $0,1,2,\\\\ldots,k-1$ 所有编号,有单调性,可以用滑动窗口解决。如果你不了解滑动窗口,可以看视频【基础算法精讲 03】。
\\n\\n\\n注:方法一相当于枚举合法区间的左端点,而方法二相当于枚举合法区间的右端点。
\\n
###py
\\nclass Solution:\\n def smallestRange(self, nums: List[List[int]]) -> List[int]:\\n pairs = sorted((x, i) for (i, arr) in enumerate(nums) for x in arr)\\n ans_l, ans_r = -inf, inf\\n empty = len(nums)\\n cnt = [0] * empty\\n left = 0\\n for r, i in pairs:\\n if cnt[i] == 0: # 包含 nums[i] 的数字\\n empty -= 1\\n cnt[i] += 1\\n while empty == 0: # 每个列表都至少包含一个数\\n l, i = pairs[left]\\n if r - l < ans_r - ans_l:\\n ans_l, ans_r = l, r\\n cnt[i] -= 1\\n if cnt[i] == 0: # 不包含 nums[i] 的数字\\n empty += 1\\n left += 1\\n return [ans_l, ans_r]\\n
\\n###java
\\nclass Solution {\\n public int[] smallestRange(List<List<Integer>> nums) {\\n int sumLen = 0;\\n for (List<Integer> list : nums) {\\n sumLen += list.size();\\n }\\n\\n int[][] pairs = new int[sumLen][2];\\n int pi = 0;\\n for (int i = 0; i < nums.size(); i++) {\\n for (int x : nums.get(i)) {\\n pairs[pi][0] = x;\\n pairs[pi++][1] = i;\\n }\\n }\\n Arrays.sort(pairs, (a, b) -> a[0] - b[0]);\\n\\n int ansL = pairs[0][0];\\n int ansR = pairs[sumLen - 1][0];\\n int empty = nums.size();\\n int[] cnt = new int[empty];\\n int left = 0;\\n for (int[] p : pairs) {\\n int r = p[0];\\n int i = p[1];\\n if (cnt[i] == 0) { // 包含 nums[i] 的数字\\n empty--;\\n }\\n cnt[i]++;\\n while (empty == 0) { // 每个列表都至少包含一个数\\n int l = pairs[left][0];\\n if (r - l < ansR - ansL) {\\n ansL = l;\\n ansR = r;\\n }\\n i = pairs[left][1];\\n cnt[i]--;\\n if (cnt[i] == 0) { // 不包含 nums[i] 的数字\\n empty++;\\n }\\n left++;\\n }\\n }\\n return new int[]{ansL, ansR};\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> smallestRange(vector<vector<int>>& nums) {\\n vector<pair<int, int>> pairs;\\n for (int i = 0; i < nums.size(); i++) {\\n for (int x : nums[i]) {\\n pairs.emplace_back(x, i);\\n }\\n }\\n // 看上去 std::sort 比 ranges::sort 更快\\n sort(pairs.begin(), pairs.end());\\n\\n int ans_l = pairs[0].first;\\n int ans_r = pairs.back().first;\\n int empty = nums.size();\\n vector<int> cnt(empty);\\n int left = 0;\\n for (auto [r, i] : pairs) {\\n if (cnt[i] == 0) { // 包含 nums[i] 的数字\\n empty--;\\n }\\n cnt[i]++;\\n while (empty == 0) { // 每个列表都至少包含一个数\\n auto [l, i] = pairs[left];\\n if (r - l < ans_r - ans_l) {\\n ans_l = l;\\n ans_r = r;\\n }\\n cnt[i]--;\\n if (cnt[i] == 0) { // 不包含 nums[i] 的数字\\n empty++;\\n }\\n left++;\\n }\\n }\\n return {ans_l, ans_r};\\n }\\n};\\n
\\n###go
\\nfunc smallestRange(nums [][]int) []int {\\n type pair struct{ x, i int }\\n pairs := []pair{}\\n for i, arr := range nums {\\n for _, x := range arr {\\n pairs = append(pairs, pair{x, i})\\n }\\n }\\n slices.SortFunc(pairs, func(a, b pair) int { return a.x - b.x })\\n\\n ansL, ansR := pairs[0].x, pairs[len(pairs)-1].x\\n empty := len(nums)\\n cnt := make([]int, empty)\\n left := 0\\n for _, p := range pairs {\\n r, i := p.x, p.i\\n if cnt[i] == 0 { // 包含 nums[i] 的数字\\n empty--\\n }\\n cnt[i]++\\n for empty == 0 { // 每个列表都至少包含一个数\\n l, i := pairs[left].x, pairs[left].i\\n if r-l < ansR-ansL {\\n ansL, ansR = l, r\\n }\\n cnt[i]--\\n if cnt[i] == 0 {\\n // 不包含 nums[i] 的数字\\n empty++\\n }\\n left++\\n }\\n }\\n return []int{ansL, ansR}\\n}\\n
\\n###js
\\nvar smallestRange = function(nums) {\\n const pairs = [];\\n for (let i = 0; i < nums.length; i++) {\\n for (const x of nums[i]) {\\n pairs.push([x, i]);\\n }\\n }\\n pairs.sort((a, b) => a[0] - b[0]);\\n\\n let ansL = -Infinity, ansR = Infinity;\\n let empty = nums.length;\\n const cnt = Array(empty).fill(0);\\n let left = 0;\\n for (const [r, i] of pairs) {\\n if (cnt[i] === 0) { // 包含 nums[i] 的数字\\n empty--;\\n }\\n cnt[i]++;\\n while (empty === 0) { // 每个列表都至少包含一个数\\n const [l, i] = pairs[left];\\n if (r - l < ansR - ansL) {\\n ansL = l;\\n ansR = r;\\n }\\n cnt[i]--;\\n if (cnt[i] === 0) { // 不包含 nums[i] 的数字\\n empty++;\\n }\\n left++;\\n }\\n }\\n return [ansL, ansR];\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn smallest_range(nums: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut pairs = vec![];\\n for (i, arr) in nums.iter().enumerate() {\\n for &x in arr {\\n pairs.push((x, i));\\n }\\n }\\n pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0));\\n\\n let mut ans_l = pairs[0].0;\\n let mut ans_r = pairs[pairs.len() - 1].0;\\n let mut empty = nums.len();\\n let mut cnt = vec![0; empty];\\n let mut left = 0;\\n for &(r, i) in &pairs {\\n if cnt[i] == 0 { // 包含 nums[i] 的数字\\n empty -= 1;\\n }\\n cnt[i] += 1;\\n while empty == 0 { // 每个列表都至少包含一个数\\n let (l, i) = pairs[left];\\n if r - l < ans_r - ans_l {\\n ans_l = l;\\n ans_r = r;\\n }\\n cnt[i] -= 1;\\n if cnt[i] == 0 { // 不包含 nums[i] 的数字\\n empty += 1;\\n }\\n left += 1;\\n }\\n }\\n vec![ans_l, ans_r]\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单中的「五、堆(优先队列)」,以及滑动窗口题单中的「§2.2 求最短/最小」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路 把「每个列表至少有一个数包含在其中」的区间叫做合法区间。\\n\\n先求出最左边的合法区间,然后求出第二个合法区间,第三个合法区间,依此类推。\\n\\n比如示例 1,最左边的合法区间是 $[0,5]$。\\n\\n枚举所有合法区间的左端点,或者枚举所有合法区间的右端点。其中第一个最短的合法区间就是答案。\\n\\n方法一:堆\\n\\n在示例 1 中,有三个列表:\\n\\n$[4,10,15,24,26]$。\\n$[0,9,12,20]$。\\n$[5,18,22,30]$。\\n\\n我们来计算最左边的合法区间,第二个合法区间,第三个合法区间,……\\n\\n也就是左端点为 $0$ 的合法区间,左端点为 $4…","guid":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/liang-chong-fang-fa-dui-pai-xu-hua-dong-luih5","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-09T04:08:56.411Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:哈希表(清晰题解)","url":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/python3javacgotypescript-yi-ti-yi-jie-ha-rbet","content":"我们可以用一个哈希表 $\\\\textit{d}$ 来存储每个元素的坐标,然后根据题意,分别计算相邻元素和对角线相邻元素的和。
\\n###python
\\nclass NeighborSum:\\n\\n def __init__(self, grid: List[List[int]]):\\n self.grid = grid\\n self.d = {}\\n self.dirs = ((-1, 0, 1, 0, -1), (-1, 1, 1, -1, -1))\\n for i, row in enumerate(grid):\\n for j, x in enumerate(row):\\n self.d[x] = (i, j)\\n\\n def adjacentSum(self, value: int) -> int:\\n return self.cal(value, 0)\\n\\n def cal(self, value: int, k: int):\\n i, j = self.d[value]\\n s = 0\\n for a, b in pairwise(self.dirs[k]):\\n x, y = i + a, j + b\\n if 0 <= x < len(self.grid) and 0 <= y < len(self.grid[0]):\\n s += self.grid[x][y]\\n return s\\n\\n def diagonalSum(self, value: int) -> int:\\n return self.cal(value, 1)\\n\\n\\n# Your NeighborSum object will be instantiated and called as such:\\n# obj = NeighborSum(grid)\\n# param_1 = obj.adjacentSum(value)\\n# param_2 = obj.diagonalSum(value)\\n
\\n###java
\\nclass NeighborSum {\\n private int[][] grid;\\n private final Map<Integer, int[]> d = new HashMap<>();\\n private final int[][] dirs = {{-1, 0, 1, 0, -1}, {-1, 1, 1, -1, -1}};\\n\\n public NeighborSum(int[][] grid) {\\n this.grid = grid;\\n int m = grid.length, n = grid[0].length;\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n d.put(grid[i][j], new int[] {i, j});\\n }\\n }\\n }\\n\\n public int adjacentSum(int value) {\\n return cal(value, 0);\\n }\\n\\n public int diagonalSum(int value) {\\n return cal(value, 1);\\n }\\n\\n private int cal(int value, int k) {\\n int[] p = d.get(value);\\n int s = 0;\\n for (int q = 0; q < 4; ++q) {\\n int x = p[0] + dirs[k][q], y = p[1] + dirs[k][q + 1];\\n if (x >= 0 && x < grid.length && y >= 0 && y < grid[0].length) {\\n s += grid[x][y];\\n }\\n }\\n return s;\\n }\\n}\\n\\n/**\\n * Your NeighborSum object will be instantiated and called as such:\\n * NeighborSum obj = new NeighborSum(grid);\\n * int param_1 = obj.adjacentSum(value);\\n * int param_2 = obj.diagonalSum(value);\\n */\\n
\\n###cpp
\\nclass NeighborSum {\\npublic:\\n NeighborSum(vector<vector<int>>& grid) {\\n this->grid = grid;\\n int m = grid.size(), n = grid[0].size();\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n d[grid[i][j]] = {i, j};\\n }\\n }\\n }\\n\\n int adjacentSum(int value) {\\n return cal(value, 0);\\n }\\n\\n int diagonalSum(int value) {\\n return cal(value, 1);\\n }\\n\\nprivate:\\n vector<vector<int>> grid;\\n unordered_map<int, pair<int, int>> d;\\n int dirs[2][5] = {{-1, 0, 1, 0, -1}, {-1, 1, 1, -1, -1}};\\n\\n int cal(int value, int k) {\\n auto [i, j] = d[value];\\n int s = 0;\\n for (int q = 0; q < 4; ++q) {\\n int x = i + dirs[k][q], y = j + dirs[k][q + 1];\\n if (x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size()) {\\n s += grid[x][y];\\n }\\n }\\n return s;\\n }\\n};\\n\\n/**\\n * Your NeighborSum object will be instantiated and called as such:\\n * NeighborSum* obj = new NeighborSum(grid);\\n * int param_1 = obj->adjacentSum(value);\\n * int param_2 = obj->diagonalSum(value);\\n */\\n
\\n###go
\\ntype NeighborSum struct {\\ngrid [][]int\\nd map[int][2]int\\ndirs [2][5]int\\n}\\n\\nfunc Constructor(grid [][]int) NeighborSum {\\nd := map[int][2]int{}\\nfor i, row := range grid {\\nfor j, x := range row {\\nd[x] = [2]int{i, j}\\n}\\n}\\ndirs := [2][5]int{{-1, 0, 1, 0, -1}, {-1, 1, 1, -1, -1}}\\nreturn NeighborSum{grid, d, dirs}\\n}\\n\\nfunc (this *NeighborSum) AdjacentSum(value int) int {\\nreturn this.cal(value, 0)\\n}\\n\\nfunc (this *NeighborSum) DiagonalSum(value int) int {\\nreturn this.cal(value, 1)\\n}\\n\\nfunc (this *NeighborSum) cal(value, k int) int {\\np := this.d[value]\\ns := 0\\nfor q := 0; q < 4; q++ {\\nx, y := p[0]+this.dirs[k][q], p[1]+this.dirs[k][q+1]\\nif x >= 0 && x < len(this.grid) && y >= 0 && y < len(this.grid[0]) {\\ns += this.grid[x][y]\\n}\\n}\\nreturn s\\n}\\n\\n/**\\n * Your NeighborSum object will be instantiated and called as such:\\n * obj := Constructor(grid);\\n * param_1 := obj.AdjacentSum(value);\\n * param_2 := obj.DiagonalSum(value);\\n */\\n
\\n###ts
\\nclass NeighborSum {\\n private grid: number[][];\\n private d: Map<number, [number, number]> = new Map();\\n private dirs: number[][] = [\\n [-1, 0, 1, 0, -1],\\n [-1, 1, 1, -1, -1],\\n ];\\n constructor(grid: number[][]) {\\n for (let i = 0; i < grid.length; ++i) {\\n for (let j = 0; j < grid[0].length; ++j) {\\n this.d.set(grid[i][j], [i, j]);\\n }\\n }\\n this.grid = grid;\\n }\\n\\n adjacentSum(value: number): number {\\n return this.cal(value, 0);\\n }\\n\\n diagonalSum(value: number): number {\\n return this.cal(value, 1);\\n }\\n\\n cal(value: number, k: number): number {\\n const [i, j] = this.d.get(value)!;\\n let s = 0;\\n for (let q = 0; q < 4; ++q) {\\n const [x, y] = [i + this.dirs[k][q], j + this.dirs[k][q + 1]];\\n if (x >= 0 && x < this.grid.length && y >= 0 && y < this.grid[0].length) {\\n s += this.grid[x][y];\\n }\\n }\\n return s;\\n }\\n}\\n\\n/**\\n * Your NeighborSum object will be instantiated and called as such:\\n * var obj = new NeighborSum(grid)\\n * var param_1 = obj.adjacentSum(value)\\n * var param_2 = obj.diagonalSum(value)\\n */\\n
\\n时间复杂度方面,初始化哈希表的时间复杂度为 $O(m \\\\times n)$,计算相邻元素和对角线相邻元素的和的时间复杂度为 $O(1)$。空间复杂度为 $O(m \\\\times n)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:哈希表 我们可以用一个哈希表 $\\\\textit{d}$ 来存储每个元素的坐标,然后根据题意,分别计算相邻元素和对角线相邻元素的和。\\n\\n###python\\n\\nclass NeighborSum:\\n\\n def __init__(self, grid: List[List[int]]):\\n self.grid = grid\\n self.d = {}\\n self.dirs = ((-1, 0, 1, 0, -1), (-1, 1, 1, -1, -1))\\n for i, row in…","guid":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/python3javacgotypescript-yi-ti-yi-jie-ha-rbet","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-09T00:04:06.716Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-设计相邻元素求和服务🟢","url":"https://leetcode.cn/problems/design-neighbor-sum-service/","content":"给你一个 n x n
的二维数组 grid
,它包含范围 [0, n2 - 1]
内的不重复元素。
实现 neighborSum
类:
neighborSum(int [][]grid)
初始化对象。int adjacentSum(int value)
返回在 grid
中与 value
相邻的元素之和,相邻指的是与 value
在上、左、右或下的元素。int diagonalSum(int value)
返回在 grid
中与 value
对角线相邻的元素之和,对角线相邻指的是与 value
在左上、右上、左下或右下的元素。\\n\\n
示例 1:
\\n\\n输入:
\\n\\n[\\"neighborSum\\", \\"adjacentSum\\", \\"adjacentSum\\", \\"diagonalSum\\", \\"diagonalSum\\"]
\\n\\n[[[[0, 1, 2], [3, 4, 5], [6, 7, 8]]], [1], [4], [4], [8]]
\\n\\n输出: [null, 6, 16, 16, 4]
\\n\\n解释:
\\n\\n示例 2:
\\n\\n输入:
\\n\\n[\\"neighborSum\\", \\"adjacentSum\\", \\"diagonalSum\\"]
\\n\\n[[[[1, 2, 0, 3], [4, 7, 15, 6], [8, 9, 10, 11], [12, 13, 14, 5]]], [15], [9]]
\\n\\n输出: [null, 23, 45]
\\n\\n解释:
\\n\\n\\n\\n
提示:
\\n\\n3 <= n == grid.length == grid[0].length <= 10
0 <= grid[i][j] <= n2 - 1
grid[i][j]
值均不重复。adjacentSum
和 diagonalSum
中的 value
均在范围 [0, n2 - 1]
内。adjacentSum
和 diagonalSum
总共 2 * n2
次。思路
\\n题目要求所有行列都必须是回文的,即满足
\\n$$
\\n\\\\textit{grid}[i][j]=\\\\textit{grid}[i][n-1-j]=\\\\textit{grid}[m-1-i][j]=\\\\textit{grid}[m-1-i][n-1-j]
\\n$$
其中,$i$ 和 $j$ 满足 $0\\\\leq i \\\\leq \\\\Big\\\\lfloor \\\\dfrac{m}{2} \\\\Big\\\\rfloor$,$0 \\\\leq j \\\\leq \\\\Big\\\\lfloor \\\\dfrac{n}{2} \\\\Big\\\\rfloor$。
\\n将这四个数都变为 $0$ 需要的次数记作 $\\\\textit{cnt}$ 次,那么将它们都变为 $1$ 则需要 $4-\\\\textit{cnt}$ 次。将这四个数变为相同所需要的次数就是 $\\\\min(\\\\textit{cnt},4-\\\\textit{cnt})$ 次。当 $m$,$n$ 都为偶数时,答案就是所有将四个数变为相同数字所需次数之和。
\\n接下来讨论 $m$ 或 $n$ 为奇数时的情况。当 $m$ 是奇数,矩阵正中间会多出一行;当 $n$ 为奇数,矩阵正中间会多出一列。
\\n当 $m$ 和 $n$ 都为奇数时,由于矩阵中 $1$ 的数目可以被 $4$ 整除,所以正中间的元素必须是 $0$。
\\n当只有行数 $n$ 为奇数时,需要满足对称性 $\\\\textit{grid}[i][j] = \\\\textit{grid}[i][m-1-j]$。除为了满足对称性所需要的操作次数外,我们可能还需要额外的操作来使该行中 $1$ 的个数为 $4$ 的整数倍。
\\n将对称位置相同的 $1$ 的个数记为 $\\\\textit{cnt}_1$,对称位置的数不同的数对个数记作 $\\\\textit{diff}$。
\\n对于列数 $m$ 为奇数时的讨论结果类似。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size(), ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += min(cnt1, 4 - cnt1);\\n }\\n }\\n\\n int diff = 0, cnt1 = 0;\\n if (m & 1) {\\n for (int j = 0; j < n / 2; j++) {\\n if (grid[m / 2][j] ^ grid[m / 2][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += grid[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n & 1) {\\n for (int i = 0; i < m / 2; i++) {\\n if (grid[i][n / 2] ^ grid[m - 1 - i][n / 2]) {\\n diff++;\\n } else {\\n cnt1 += grid[i][n / 2] * 2;\\n }\\n }\\n }\\n if (m & 1 && n & 1) {\\n ans += grid[m / 2][n / 2];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minFlips(int[][] grid) {\\n int m = grid.length, n = grid[0].length, ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += Math.min(cnt1, 4 - cnt1);\\n }\\n }\\n int diff = 0, cnt1 = 0;\\n if (m % 2 == 1) {\\n for (int j = 0; j < n / 2; j++) {\\n if ((grid[m / 2][j] ^ grid[m / 2][n - 1 - j]) != 0) {\\n diff++;\\n } else {\\n cnt1 += grid[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2 == 1) {\\n for (int i = 0; i < m / 2; i++) {\\n if ((grid[i][n / 2] ^ grid[m - 1 - i][n / 2]) != 0) {\\n diff++;\\n } else {\\n cnt1 += grid[i][n / 2] * 2;\\n }\\n }\\n }\\n if (m % 2 == 1 && n % 2 == 1) {\\n ans += grid[m / 2][n / 2];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans; \\n }\\n}\\n\\n
\\n###C#
\\npublic class Solution {\\n public int MinFlips(int[][] grid) {\\n int m = grid.Length, n = grid[0].Length, ans = 0;\\n int cnt1 = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n cnt1 = grid[i][j] + grid[i][n - 1 - j] + \\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += Math.Min(cnt1, 4 - cnt1);\\n }\\n }\\n\\n int diff = 0;\\n cnt1 = 0;\\n if (m % 2 == 1) {\\n for (int j = 0; j < n / 2; j++) {\\n if ((grid[m / 2][j] ^ grid[m / 2][n - 1 - j]) != 0) {\\n diff++;\\n } else {\\n cnt1 += grid[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2 == 1) {\\n for (int i = 0; i < m / 2; i++) {\\n if ((grid[i][n / 2] ^ grid[m - 1 - i][n / 2]) != 0) {\\n diff++;\\n } else {\\n cnt1 += grid[i][n / 2] * 2;\\n }\\n }\\n }\\n if (m % 2 == 1 && n % 2 == 1) {\\n ans += grid[m / 2][n / 2];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n m, n, ans = len(grid), len(grid[0]), 0\\n for i in range(m // 2):\\n for j in range(n // 2):\\n cnt1 = grid[i][j] + grid[i][n - 1 - j] + \\\\\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j]\\n ans += min(cnt1, 4 - cnt1)\\n\\n diff, cnt1 = 0, 0\\n if m % 2 == 1:\\n for j in range(n // 2):\\n if grid[m // 2][j] ^ grid[m // 2][n - 1 - j]:\\n diff += 1\\n else:\\n cnt1 += grid[m // 2][j] * 2\\n if n % 2 == 1:\\n for i in range(m // 2):\\n if grid[i][n // 2] ^ grid[m - 1 - i][n // 2]:\\n diff += 1\\n else:\\n cnt1 += grid[i][n // 2] * 2\\n if m % 2 == 1 and n % 2 == 1:\\n ans += grid[m // 2][n // 2]\\n if diff > 0:\\n ans += diff\\n else:\\n ans += cnt1 % 4\\n return ans\\n
\\n###Go
\\nfunc minFlips(grid [][]int) int {\\n m, n, ans := len(grid), len(grid[0]), 0\\n for i := 0; i < m / 2; i++ {\\n for j := 0; j < n / 2; j++ {\\n cnt1 := grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j]\\n ans += min(cnt1, 4 - cnt1)\\n }\\n }\\n\\n diff, cnt1 := 0, 0\\n if m % 2 == 1 {\\n for j := 0; j < n / 2; j++ {\\n if grid[m / 2][j] ^ grid[m / 2][n - 1 - j] != 0 {\\n diff++\\n } else {\\n cnt1 += grid[m / 2][j] * 2\\n }\\n }\\n }\\n if n % 2 == 1 {\\n for i := 0; i < m / 2; i++ {\\n if grid[i][n / 2] ^ grid[m - 1 - i][n / 2] != 0 {\\n diff++\\n } else {\\n cnt1 += grid[i][n / 2] * 2\\n }\\n }\\n }\\n if m % 2 == 1 && n % 2 == 1 {\\n ans += grid[m / 2][n / 2]\\n }\\n if diff > 0 {\\n ans += diff\\n } else {\\n ans += cnt1 % 4\\n }\\n return ans\\n}\\n
\\n###C
\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize;\\n int n = gridColSize[0];\\n int ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += fmin(cnt1, 4 - cnt1);\\n }\\n }\\n\\n int diff = 0, cnt1 = 0;\\n if (m % 2 == 1) {\\n for (int j = 0; j < n / 2; j++) {\\n if (grid[m / 2][j] ^ grid[m / 2][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += grid[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2 == 1) {\\n for (int i = 0; i < m / 2; i++) {\\n if (grid[i][n / 2] ^ grid[m - 1 - i][n / 2]) {\\n diff++;\\n } else {\\n cnt1 += grid[i][n / 2] * 2;\\n }\\n }\\n }\\n if (m % 2 == 1 && n % 2 == 1) {\\n ans += grid[m / 2][n / 2];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar minFlips = function(grid) {\\n const m = grid.length, n = grid[0].length;\\n let ans = 0;\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n const cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += Math.min(cnt1, 4 - cnt1);\\n }\\n }\\n let diff = 0, cnt1 = 0;\\n if (m % 2 === 1) {\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n if (grid[Math.floor(m / 2)][j] ^ grid[Math.floor(m / 2)][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += grid[Math.floor(m / 2)][j] * 2;\\n }\\n }\\n }\\n if (n % 2 === 1) {\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n if (grid[i][Math.floor(n / 2)] ^ grid[m - 1 - i][Math.floor(n / 2)]) {\\n diff++;\\n } else {\\n cnt1 += grid[i][Math.floor(n / 2)] * 2;\\n }\\n }\\n }\\n if (m % 2 === 1 && n % 2 === 1) {\\n ans += grid[Math.floor(m / 2)][Math.floor(n / 2)];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction minFlips(grid: number[][]): number {\\n const m = grid.length, n = grid[0].length;\\n let ans = 0;\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n const cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += Math.min(cnt1, 4 - cnt1);\\n }\\n }\\n let diff = 0, cnt1 = 0;\\n if (m % 2 === 1) {\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n if (grid[Math.floor(m / 2)][j] ^ grid[Math.floor(m / 2)][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += grid[Math.floor(m / 2)][j] * 2;\\n }\\n }\\n }\\n if (n % 2 === 1) {\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n if (grid[i][Math.floor(n / 2)] ^ grid[m - 1 - i][Math.floor(n / 2)]) {\\n diff++;\\n } else {\\n cnt1 += grid[i][Math.floor(n / 2)] * 2;\\n }\\n }\\n }\\n if (m % 2 === 1 && n % 2 === 1) {\\n ans += grid[Math.floor(m / 2)][Math.floor(n / 2)];\\n }\\n if (diff > 0) {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_flips(grid: Vec<Vec<i32>>) -> i32 {\\n let m = grid.len();\\n let n = grid[0].len();\\n let mut ans = 0;\\n for i in 0..m / 2 {\\n for j in 0..n / 2 {\\n let cnt1 = grid[i][j] + grid[i][n - 1 - j] +\\n grid[m - 1 - i][j] + grid[m - 1 - i][n - 1 - j];\\n ans += cnt1.min(4 - cnt1);\\n }\\n }\\n let mut diff = 0;\\n let mut cnt1 = 0;\\n if m % 2 == 1 {\\n for j in 0..n / 2 {\\n if grid[m / 2][j] ^ grid[m / 2][n - 1 - j] != 0 {\\n diff += 1;\\n } else {\\n cnt1 += grid[m / 2][j] * 2;\\n }\\n }\\n }\\n if n % 2 == 1 {\\n for i in 0..m / 2 {\\n if grid[i][n / 2] ^ grid[m - 1 - i][n / 2] != 0 {\\n diff += 1;\\n } else {\\n cnt1 += grid[i][n / 2] * 2;\\n }\\n }\\n }\\n if m % 2 == 1 && n % 2 == 1 {\\n ans += grid[m / 2][n / 2];\\n }\\n if diff > 0 {\\n ans += diff;\\n } else {\\n ans += cnt1 % 4;\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n思路
\\n将矩阵中对称的元素分组,每一组都必须相等。(当行数或列数为奇数时,不一定每一组都有 $4$ 个元素)要么都为 $1$,要么都为 $0$。要使整个矩阵的 $1$ 的数量模 $4$ 等于 $0$,就是让每一组的 $1$ 的个数的总和等于 $4$ 的倍数。
\\n定义 $f[i][j]$ 表示在前 $i$ 组中,$1$ 的数量模 $4$ 的余数为 $j$ 时的最小操作数。那么对于第 $i + 1$ 组:
\\n设总分组的数量为 $\\\\textit{group}$,$f[\\\\textit{group}][0]$ 即所求答案。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n vector<int> f(4, INT_MAX / 2);\\n f[0] = 0;\\n for (int i = 0; i < (m + 1) / 2; i++) {\\n for (int j = 0; j < (n + 1) / 2; j++) {\\n int ones = grid[i][j], cnt = 1;\\n if (j != n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i != m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i != m - 1 - i && j != n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 将这一组全部变成 1 的代价\\n int cnt1 = cnt - ones;\\n // 将这一组全部变成 0 的代价\\n int cnt0 = ones;\\n vector<int> tmp(4);\\n for (int k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (int k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = min(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n swap(f, tmp);\\n }\\n }\\n return f[0];\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minFlips(int[][] grid) {\\n int m = grid.length, n = grid[0].length;\\n int[] f = new int[4];\\n Arrays.fill(f, Integer.MAX_VALUE / 2); \\n f[0] = 0;\\n\\n for (int i = 0; i < (m + 1) / 2; i++) {\\n for (int j = 0; j < (n + 1) / 2; j++) {\\n int ones = grid[i][j], cnt = 1;\\n if (j != n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i != m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i != m - 1 - i && j != n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 计算将这一组全部变为 1 的代价\\n int cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n int cnt0 = ones;\\n int[] tmp = new int[4];\\n for (int k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (int k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = Math.min(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n f = tmp;\\n }\\n }\\n return f[0];\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinFlips(int[][] grid) {\\n int m = grid.Length, n = grid[0].Length;\\n int[] f = new int[4];\\n Array.Fill(f, int.MaxValue / 2);\\n f[0] = 0;\\n for (int i = 0; i < (m + 1) / 2; i++) {\\n for (int j = 0; j < (n + 1) / 2; j++) {\\n int ones = grid[i][j], cnt = 1;\\n if (j != n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i != m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i != m - 1 - i && j != n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 计算将这一组全部变为 1 的代价\\n int cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n int cnt0 = ones;\\n int[] tmp = new int[4];\\n for (int k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (int k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = Math.Min(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n f = tmp;\\n }\\n }\\n return f[0];\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n m, n = len(grid), len(grid[0])\\n f = [float(\'inf\')] * 4\\n f[0] = 0\\n for i in range((m + 1) // 2):\\n for j in range((n + 1) // 2):\\n ones = grid[i][j]\\n cnt = 1\\n if j != n - 1 - j:\\n ones += grid[i][n - 1 - j]\\n cnt += 1\\n if i != m - 1 - i:\\n ones += grid[m - 1 - i][j]\\n cnt += 1\\n if i != m - 1 - i and j != n - 1 - j:\\n ones += grid[m - 1 - i][n - 1 - j]\\n cnt += 1\\n # 计算将这一组全部变为 1 的代价\\n cnt1 = cnt - ones\\n # 计算将这一组全部变为 0 的代价\\n cnt0 = ones\\n tmp = [0] * 4\\n for k in range(4):\\n tmp[k] = f[k] + cnt0\\n for k in range(4):\\n tmp[(k + cnt) % 4] = min(tmp[(k + cnt) % 4], f[k] + cnt1)\\n f = tmp\\n return f[0]\\n
\\n###Go
\\nfunc minFlips(grid [][]int) int {\\n m, n := len(grid), len(grid[0])\\n f := make([]int, 4)\\n for i := range f {\\n f[i] = math.MaxInt32 / 2\\n }\\n f[0] = 0\\n for i := 0; i < (m + 1) / 2; i++ {\\n for j := 0; j < (n + 1) / 2; j++ {\\n ones := grid[i][j]\\n cnt := 1\\n if j != n - 1 - j {\\n ones += grid[i][n - 1 - j]\\n cnt++\\n }\\n if i != m - 1 - i {\\n ones += grid[m - 1 - i][j]\\n cnt++\\n }\\n if i != m - 1 - i && j != n - 1 - j {\\n ones += grid[m - 1 - i][n - 1 - j]\\n cnt++\\n }\\n // 计算将这一组全部变为 1 的代价\\n cnt1 := cnt - ones\\n // 计算将这一组全部变为 0 的代价\\n cnt0 := ones\\n tmp := make([]int, 4)\\n for k := 0; k < 4; k++ {\\n tmp[k] = f[k] + cnt0\\n }\\n for k := 0; k < 4; k++ {\\n tmp[(k + cnt) % 4] = min(tmp[(k + cnt) % 4], f[k] + cnt1)\\n }\\n f = tmp\\n }\\n }\\n return f[0]\\n}\\n
\\n###C
\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize, n = gridColSize[0];\\n int f[4];\\n for (int i = 0; i < 4; i++) {\\n f[i] = INT_MAX / 2;\\n }\\n f[0] = 0;\\n for (int i = 0; i < (m + 1) / 2; i++) {\\n for (int j = 0; j < (n + 1) / 2; j++) {\\n int ones = grid[i][j], cnt = 1;\\n if (j != n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i != m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i != m - 1 - i && j != n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 计算将这一组全部变为 1 的代价\\n int cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n int cnt0 = ones;\\n int tmp[4];\\n for (int k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (int k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = fmin(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n for (int k = 0; k < 4; k++) {\\n f[k] = tmp[k]; \\n }\\n }\\n }\\n return f[0]; \\n}\\n
\\n###JavaScript
\\nvar minFlips = function(grid) {\\n const m = grid.length, n = grid[0].length;\\n let f = new Array(4).fill(Infinity);\\n f[0] = 0;\\n for (let i = 0; i < Math.floor((n + 1) / 2); i++) {\\n for (let j = 0; j < Math.floor((m + 1) / 2); j++) {\\n let ones = grid[i][j];\\n let cnt = 1;\\n if (j !== n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i !== m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i !== m - 1 - i && j !== n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 计算将这一组全部变为 1 的代价\\n const cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n const cnt0 = ones;\\n let tmp = new Array(4).fill(0);\\n for (let k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (let k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = Math.min(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n f = tmp;\\n }\\n }\\n return f[0];\\n};\\n
\\n###TypeScript
\\nfunction minFlips(grid: number[][]): number {\\n const m = grid.length, n = grid[0].length;\\n let f: number[] = new Array(4).fill(Infinity);\\n f[0] = 0;\\n for (let i = 0; i < Math.floor((m + 1) / 2); i++) {\\n for (let j = 0; j < Math.floor((n + 1) / 2); j++) {\\n let ones = grid[i][j];\\n let cnt = 1;\\n if (j !== n - 1 - j) {\\n ones += grid[i][n - 1 - j];\\n cnt++;\\n }\\n if (i !== m - 1 - i) {\\n ones += grid[m - 1 - i][j];\\n cnt++;\\n }\\n if (i !== m - 1 - i && j !== n - 1 - j) {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt++;\\n }\\n // 计算将这一组全部变为 1 的代价\\n const cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n const cnt0 = ones;\\n let tmp: number[] = new Array(4).fill(0);\\n for (let k = 0; k < 4; k++) {\\n tmp[k] = f[k] + cnt0;\\n }\\n for (let k = 0; k < 4; k++) {\\n tmp[(k + cnt) % 4] = Math.min(tmp[(k + cnt) % 4], f[k] + cnt1);\\n }\\n f = tmp;\\n }\\n }\\n return f[0];\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_flips(grid: Vec<Vec<i32>>) -> i32 {\\n let m = grid.len();\\n let n = grid[0].len();\\n let mut f = vec![i32::MAX / 2; 4];\\n f[0] = 0;\\n for i in 0..(m + 1) / 2 {\\n for j in 0..(n + 1) / 2 {\\n let mut ones = grid[i][j];\\n let mut cnt = 1;\\n if j != n - 1 - j {\\n ones += grid[i][n - 1 - j];\\n cnt += 1;\\n }\\n if i != m - 1 - i {\\n ones += grid[m - 1 - i][j];\\n cnt += 1;\\n }\\n if i != m - 1 - i && j != n - 1 - j {\\n ones += grid[m - 1 - i][n - 1 - j];\\n cnt += 1;\\n }\\n // 计算将这一组全部变为 1 的代价\\n let cnt1 = cnt - ones;\\n // 计算将这一组全部变为 0 的代价\\n let cnt0 = ones;\\n let mut tmp = vec![0; 4];\\n for k in 0..4 {\\n tmp[k] = f[k] + cnt0;\\n }\\n for k in 0..4 {\\n tmp[(k + cnt) as usize % 4] = tmp[(k + cnt) as usize % 4].min(f[k as usize] + cnt1);\\n }\\n f = tmp;\\n }\\n }\\n f[0]\\n }\\n}\\n
\\n复杂度分析
\\n我们可以分别考虑将所有行变为回文所需要的翻转次数 $\\\\textit{rowCnt}$ 或将所有列变为回文所需要的翻转次数 $textit{colCnt}$,那么所需要的最少反转次数就是 $\\\\min(\\\\textit{rowCnt}, \\\\textit{colCnt})$。
\\n可以使用双指针同时从一行或一列的开头和结尾开始枚举,如果两个指针指向的矩阵元素不同,那么所需要的翻转次数就增加 $1$。使用双指针遍历矩阵的每一行和每一列,就能够得到 $\\\\textit{rowCnt}$ 和 $\\\\textit{colCnt}$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int rowCnt = 0, colCnt = 0;\\n int m = grid.size(), n = grid[0].size();\\n for (int i = 0; i < m; i++) {\\n for (int j1 = 0, j2 = n - 1; j1 < j2; j1++, j2--) {\\n if (grid[i][j1] ^ grid[i][j2]) {\\n rowCnt++;\\n }\\n }\\n }\\n for (int j = 0; j < n; j++) {\\n for (int i1 = 0, i2 = m - 1; i1 < i2; i1++, i2--) {\\n if (grid[i1][j] ^ grid[i2][j]) {\\n colCnt++;\\n }\\n }\\n }\\n return min(colCnt, rowCnt);\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minFlips(int[][] grid) {\\n int rowCnt = 0, colCnt = 0;\\n int m = grid.length, n = grid[0].length;\\n for (int i = 0; i < m; i++) {\\n for (int j1 = 0, j2 = n - 1; j1 < j2; j1++, j2--) {\\n if ((grid[i][j1] ^ grid[i][j2]) != 0) {\\n rowCnt++;\\n }\\n }\\n }\\n for (int j = 0; j < n; j++) {\\n for (int i1 = 0, i2 = m - 1; i1 < i2; i1++, i2--) {\\n if ((grid[i1][j] ^ grid[i2][j]) != 0) {\\n colCnt++;\\n }\\n }\\n }\\n return Math.min(colCnt, rowCnt);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinFlips(int[][] grid) {\\n int rowCnt = 0, colCnt = 0;\\n int m = grid.Length, n = grid[0].Length;\\n for (int i = 0; i < m; i++) {\\n for (int j1 = 0, j2 = n - 1; j1 < j2; j1++, j2--) {\\n if ((grid[i][j1] ^ grid[i][j2]) != 0) {\\n rowCnt++;\\n }\\n }\\n }\\n for (int j = 0; j < n; j++) {\\n for (int i1 = 0, i2 = m - 1; i1 < i2; i1++, i2--) {\\n if ((grid[i1][j] ^ grid[i2][j]) != 0) {\\n colCnt++;\\n }\\n }\\n }\\n return Math.Min(colCnt, rowCnt);\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n row_cnt, col_cnt = 0, 0\\n m, n = len(grid), len(grid[0])\\n for i in range(m):\\n for j1 in range(n // 2):\\n j2 = n - 1 - j1\\n if grid[i][j1] != grid[i][j2]:\\n row_cnt += 1\\n for j in range(n):\\n for i1 in range(m // 2):\\n i2 = m - 1 - i1\\n if grid[i1][j] != grid[i2][j]:\\n col_cnt += 1\\n return min(col_cnt, row_cnt)\\n \\n
\\n###Go
\\nfunc minFlips(grid [][]int) int {\\n rowCnt, colCnt := 0, 0\\n m, n := len(grid), len(grid[0])\\n for i := 0; i < m; i++ {\\n for j1 := 0; j1 < n / 2; j1++ {\\n j2 := n - 1 - j1\\n if grid[i][j1] != grid[i][j2] {\\n rowCnt++\\n }\\n }\\n }\\n for j := 0; j < n; j++ {\\n for i1 := 0; i1 < m / 2; i1++ {\\n i2 := m - 1 - i1\\n if grid[i1][j] != grid[i2][j] {\\n colCnt++\\n }\\n }\\n }\\n return min(colCnt, rowCnt)\\n}\\n
\\n###C
\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize, n = gridColSize[0];\\n int rowCnt = 0, colCnt = 0;\\n for (int i = 0; i < m; i++) {\\n for (int j1 = 0; j1 < n / 2; j1++) {\\n int j2 = n - 1 - j1;\\n if (grid[i][j1] != grid[i][j2]) {\\n rowCnt++;\\n }\\n }\\n }\\n for (int j = 0; j < n; j++) {\\n for (int i1 = 0; i1 < m / 2; i1++) {\\n int i2 = m - 1 - i1;\\n if (grid[i1][j] != grid[i2][j]) {\\n colCnt++;\\n }\\n }\\n }\\n return fmin(colCnt, rowCnt);\\n}\\n
\\n###JavaScript
\\nvar minFlips = function(grid) {\\n let rowCnt = 0;\\n let colCnt = 0;\\n const m = grid.length;\\n const n = grid[0].length;\\n for (let i = 0; i < m; i++) {\\n for (let j1 = 0; j1 < Math.floor(n / 2); j1++) {\\n const j2 = n - 1 - j1;\\n if (grid[i][j1] !== grid[i][j2]) {\\n rowCnt++;\\n }\\n }\\n }\\n for (let j = 0; j < n; j++) {\\n for (let i1 = 0; i1 < Math.floor(m / 2); i1++) {\\n const i2 = m - 1 - i1;\\n if (grid[i1][j] !== grid[i2][j]) {\\n colCnt++;\\n }\\n }\\n }\\n return Math.min(colCnt, rowCnt);\\n};\\n
\\n###TypeScript
\\nfunction minFlips(grid: number[][]): number {\\n let rowCnt = 0;\\n let colCnt = 0;\\n const m = grid.length;\\n const n = grid[0].length;\\n for (let i = 0; i < m; i++) {\\n for (let j1 = 0; j1 < Math.floor(n / 2); j1++) {\\n const j2 = n - 1 - j1;\\n if (grid[i][j1] !== grid[i][j2]) {\\n rowCnt++;\\n }\\n }\\n }\\n for (let j = 0; j < n; j++) {\\n for (let i1 = 0; i1 < Math.floor(m / 2); i1++) {\\n const i2 = m - 1 - i1;\\n if (grid[i1][j] !== grid[i2][j]) {\\n colCnt++;\\n }\\n }\\n }\\n return Math.min(colCnt, rowCnt);\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_flips(grid: Vec<Vec<i32>>) -> i32 {\\n let mut row_cnt = 0;\\n let mut col_cnt = 0;\\n let m = grid.len();\\n let n = grid[0].len();\\n for i in 0..m {\\n for j1 in 0..(n / 2) {\\n let j2 = n - 1 - j1;\\n if grid[i][j1] != grid[i][j2] {\\n row_cnt += 1;\\n }\\n }\\n }\\n for j in 0..n {\\n for i1 in 0..(m / 2) {\\n let i2 = m - 1 - i1;\\n if grid[i1][j] != grid[i2][j] {\\n col_cnt += 1;\\n }\\n }\\n }\\n col_cnt.min(row_cnt)\\n }\\n}\\n
\\n复杂度分析
\\n给你两个正整数 xCorner
和 yCorner
和一个二维整数数组 circles
,其中 circles[i] = [xi, yi, ri]
表示一个圆心在 (xi, yi)
半径为 ri
的圆。
坐标平面内有一个左下角在原点,右上角在 (xCorner, yCorner)
的矩形。你需要判断是否存在一条从左下角到右上角的路径满足:路径 完全 在矩形内部,不会 触碰或者经过 任何 圆的内部和边界,同时 只 在起点和终点接触到矩形。
如果存在这样的路径,请你返回 true
,否则返回 false
。
\\n\\n
示例 1:
\\n\\n输入:X = 3, Y = 4, circles = [[2,1,1]]
\\n\\n输出:true
\\n\\n解释:
\\n\\n黑色曲线表示一条从 (0, 0)
到 (3, 4)
的路径。
示例 2:
\\n\\n输入:X = 3, Y = 3, circles = [[1,1,2]]
\\n\\n输出:false
\\n\\n解释:
\\n\\n不存在从 (0, 0)
到 (3, 3)
的路径。
示例 3:
\\n\\n输入:X = 3, Y = 3, circles = [[2,1,1],[1,2,1]]
\\n\\n输出:false
\\n\\n解释:
\\n\\n不存在从 (0, 0)
到 (3, 3)
的路径。
示例 4:
\\n\\n输入:X = 4, Y = 4, circles = [[5,5,1]]
\\n\\n输出:true
\\n\\n解释:
\\n\\n\\n\\n
提示:
\\n\\n3 <= xCorner, yCorner <= 109
1 <= circles.length <= 1000
circles[i].length == 3
1 <= xi, yi, ri <= 109
我们定义一个数组 $f$,其中 $f[i]$ 表示以第 $i$ 个元素结尾的连续上升子序列的长度。初始时 $f[i] = 1$。
\\n接下来,我们遍历数组 $\\\\textit{nums}$,计算数组 $f$ 的值。如果 $nums[i] = nums[i - 1] + 1$,则 $f[i] = f[i - 1] + 1$;否则 $f[i] = 1$。
\\n然后,我们在 $[k - 1, n)$ 的范围内遍历数组 $f$,如果 $f[i] \\\\ge k$,那么答案数组添加 $\\\\textit{nums}$,否则添加 $-1$。
\\n遍历结束后,返回答案数组。
\\n###python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n f = [1] * n\\n for i in range(1, n):\\n if nums[i] == nums[i - 1] + 1:\\n f[i] = f[i - 1] + 1\\n return [nums[i] if f[i] >= k else -1 for i in range(k - 1, n)]\\n
\\n###java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] f = new int[n];\\n Arrays.fill(f, 1);\\n for (int i = 1; i < n; ++i) {\\n if (nums[i] == nums[i - 1] + 1) {\\n f[i] = f[i - 1] + 1;\\n }\\n }\\n int[] ans = new int[n - k + 1];\\n for (int i = k - 1; i < n; ++i) {\\n ans[i - k + 1] = f[i] >= k ? nums[i] : -1;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n int f[n];\\n f[0] = 1;\\n for (int i = 1; i < n; ++i) {\\n f[i] = nums[i] == nums[i - 1] + 1 ? f[i - 1] + 1 : 1;\\n }\\n vector<int> ans;\\n for (int i = k - 1; i < n; ++i) {\\n ans.push_back(f[i] >= k ? nums[i] : -1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc resultsArray(nums []int, k int) (ans []int) {\\nn := len(nums)\\nf := make([]int, n)\\nf[0] = 1\\nfor i := 1; i < n; i++ {\\nif nums[i] == nums[i-1]+1 {\\nf[i] = f[i-1] + 1\\n} else {\\nf[i] = 1\\n}\\n}\\nfor i := k - 1; i < n; i++ {\\nif f[i] >= k {\\nans = append(ans, nums[i])\\n} else {\\nans = append(ans, -1)\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n const n = nums.length;\\n const f: number[] = Array(n).fill(1);\\n for (let i = 1; i < n; ++i) {\\n if (nums[i] === nums[i - 1] + 1) {\\n f[i] = f[i - 1] + 1;\\n }\\n }\\n const ans: number[] = [];\\n for (let i = k - 1; i < n; ++i) {\\n ans.push(f[i] >= k ? nums[i] : -1);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 表示数组 $\\\\textit{nums}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:递推 我们定义一个数组 $f$,其中 $f[i]$ 表示以第 $i$ 个元素结尾的连续上升子序列的长度。初始时 $f[i] = 1$。\\n\\n接下来,我们遍历数组 $\\\\textit{nums}$,计算数组 $f$ 的值。如果 $nums[i] = nums[i - 1] + 1$,则 $f[i] = f[i - 1] + 1$;否则 $f[i] = 1$。\\n\\n然后,我们在 $[k - 1, n)$ 的范围内遍历数组 $f$,如果 $f[i] \\\\ge k$,那么答案数组添加 $\\\\textit{nums}$,否则添加 $-1$。\\n\\n遍历结束后,返回答案数组。…","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii//solution/python3javacgotypescript-yi-ti-yi-jie-di-v8rz","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-07T00:19:12.178Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-长度为 K 的子数组的能量值 II🟡","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii/","content":"给你一个长度为 n
的整数数组 nums
和一个正整数 k
。
一个数组的 能量值 定义为:
\\n\\n你需要求出 nums
中所有长度为 k
的 子数组 的能量值。
请你返回一个长度为 n - k + 1
的整数数组 results
,其中 results[i]
是子数组 nums[i..(i + k - 1)]
的能量值。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3,4,3,2,5], k = 3
\\n\\n输出:[3,4,-1,-1,-1]
\\n\\n解释:
\\n\\nnums
中总共有 5 个长度为 3 的子数组:
[1, 2, 3]
中最大元素为 3 。[2, 3, 4]
中最大元素为 4 。[3, 4, 3]
中元素 不是 连续的。[4, 3, 2]
中元素 不是 上升的。[3, 2, 5]
中元素 不是 连续的。示例 2:
\\n\\n输入:nums = [2,2,2,2,2], k = 4
\\n\\n输出:[-1,-1]
\\n示例 3:
\\n\\n输入:nums = [3,2,3,2,3,2], k = 2
\\n\\n输出:[-1,3,-1,3,-1]
\\n\\n\\n
提示:
\\n\\n1 <= n == nums.length <= 105
1 <= nums[i] <= 106
1 <= k <= n
我们定义一个数组 $f$,其中 $f[i]$ 表示以第 $i$ 个元素结尾的连续上升子序列的长度。初始时 $f[i] = 1$。
\\n接下来,我们遍历数组 $\\\\textit{nums}$,计算数组 $f$ 的值。如果 $nums[i] = nums[i - 1] + 1$,则 $f[i] = f[i - 1] + 1$;否则 $f[i] = 1$。
\\n然后,我们在 $[k - 1, n)$ 的范围内遍历数组 $f$,如果 $f[i] \\\\ge k$,那么答案数组添加 $\\\\textit{nums}$,否则添加 $-1$。
\\n遍历结束后,返回答案数组。
\\n###python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n f = [1] * n\\n for i in range(1, n):\\n if nums[i] == nums[i - 1] + 1:\\n f[i] = f[i - 1] + 1\\n return [nums[i] if f[i] >= k else -1 for i in range(k - 1, n)]\\n
\\n###java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] f = new int[n];\\n Arrays.fill(f, 1);\\n for (int i = 1; i < n; ++i) {\\n if (nums[i] == nums[i - 1] + 1) {\\n f[i] = f[i - 1] + 1;\\n }\\n }\\n int[] ans = new int[n - k + 1];\\n for (int i = k - 1; i < n; ++i) {\\n ans[i - k + 1] = f[i] >= k ? nums[i] : -1;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n int f[n];\\n f[0] = 1;\\n for (int i = 1; i < n; ++i) {\\n f[i] = nums[i] == nums[i - 1] + 1 ? f[i - 1] + 1 : 1;\\n }\\n vector<int> ans;\\n for (int i = k - 1; i < n; ++i) {\\n ans.push_back(f[i] >= k ? nums[i] : -1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc resultsArray(nums []int, k int) (ans []int) {\\nn := len(nums)\\nf := make([]int, n)\\nf[0] = 1\\nfor i := 1; i < n; i++ {\\nif nums[i] == nums[i-1]+1 {\\nf[i] = f[i-1] + 1\\n} else {\\nf[i] = 1\\n}\\n}\\nfor i := k - 1; i < n; i++ {\\nif f[i] >= k {\\nans = append(ans, nums[i])\\n} else {\\nans = append(ans, -1)\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n const n = nums.length;\\n const f: number[] = Array(n).fill(1);\\n for (let i = 1; i < n; ++i) {\\n if (nums[i] === nums[i - 1] + 1) {\\n f[i] = f[i - 1] + 1;\\n }\\n }\\n const ans: number[] = [];\\n for (let i = k - 1; i < n; ++i) {\\n ans.push(f[i] >= k ? nums[i] : -1);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 表示数组 $\\\\textit{nums}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:递推 我们定义一个数组 $f$,其中 $f[i]$ 表示以第 $i$ 个元素结尾的连续上升子序列的长度。初始时 $f[i] = 1$。\\n\\n接下来,我们遍历数组 $\\\\textit{nums}$,计算数组 $f$ 的值。如果 $nums[i] = nums[i - 1] + 1$,则 $f[i] = f[i - 1] + 1$;否则 $f[i] = 1$。\\n\\n然后,我们在 $[k - 1, n)$ 的范围内遍历数组 $f$,如果 $f[i] \\\\ge k$,那么答案数组添加 $\\\\textit{nums}$,否则添加 $-1$。\\n\\n遍历结束后,返回答案数组。…","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/python3javacgotypescript-yi-ti-yi-jie-di-bfe5","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-06T00:06:32.515Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-长度为 K 的子数组的能量值 I🟡","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i/","content":"给你一个长度为 n
的整数数组 nums
和一个正整数 k
。
一个数组的 能量值 定义为:
\\n\\n你需要求出 nums
中所有长度为 k
的 子数组 的能量值。
请你返回一个长度为 n - k + 1
的整数数组 results
,其中 results[i]
是子数组 nums[i..(i + k - 1)]
的能量值。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,3,4,3,2,5], k = 3
\\n\\n输出:[3,4,-1,-1,-1]
\\n\\n解释:
\\n\\nnums
中总共有 5 个长度为 3 的子数组:
[1, 2, 3]
中最大元素为 3 。[2, 3, 4]
中最大元素为 4 。[3, 4, 3]
中元素 不是 连续的。[4, 3, 2]
中元素 不是 上升的。[3, 2, 5]
中元素 不是 连续的。示例 2:
\\n\\n输入:nums = [2,2,2,2,2], k = 4
\\n\\n输出:[-1,-1]
\\n示例 3:
\\n\\n输入:nums = [3,2,3,2,3,2], k = 2
\\n\\n输出:[-1,3,-1,3,-1]
\\n\\n\\n
提示:
\\n\\n1 <= n == nums.length <= 500
1 <= nums[i] <= 105
1 <= k <= n
思路
\\n首先根据边数组 $\\\\textit{edges}$ 构建邻接表 $g$。在树中,边的数量为节点数量减 $1$。因此,$n$ 为 $\\\\textit{edges}$ 的长度加 $1$。再构造深度优先搜索,输入为当前遍历的节点 $\\\\textit{node}$ 和其父节点 $\\\\textit{parent}$,返回值为以 $\\\\textit{node}$ 为根节点的树的节点数量。需要递归调用 $\\\\textit{node}$ 的所有子节点。因为 $g$ 中存的是邻接关系,所以要跳过节点 $\\\\textit{parent}$。在计算节点数量和的同时,需要判断 $\\\\textit{node}$ 的所有子节点是否拥有相同的节点数。如果是的话,将结果加 $1$。最后调用 $\\\\textit{dfs}(0, -1)$ 并返回结果。
\\n代码
\\n###Python
\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n n = len(edges) + 1\\n g = [[] for _ in range(n)]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n self.res = 0\\n\\n def dfs(node: int, parent: int) -> int:\\n valid = True\\n treeSize = 0\\n subTreeSize = 0\\n for child in g[node]:\\n if child != parent:\\n size = dfs(child, node)\\n if subTreeSize == 0:\\n subTreeSize = size\\n elif size != subTreeSize:\\n valid = False\\n treeSize += size\\n if valid:\\n self.res += 1\\n return treeSize + 1\\n\\n dfs(0, -1)\\n return self.res\\n
\\n###Java
\\nclass Solution {\\n int res = 0;\\n List<Integer>[] g;\\n\\n public int countGoodNodes(int[][] edges) {\\n int n = edges.length + 1;\\n g = new List[n];\\n for (int i = 0; i < n; i++) {\\n g[i] = new ArrayList<Integer>();\\n }\\n for (int[] edge : edges) {\\n g[edge[0]].add(edge[1]);\\n g[edge[1]].add(edge[0]);\\n }\\n dfs(0, -1);\\n return res;\\n }\\n\\n public int dfs(int node, int parent) {\\n boolean valid = true;\\n int treeSize = 0;\\n int subTreeSize = 0;\\n for (int child : g[node]) {\\n if (child != parent) {\\n int size = dfs(child, node);\\n if (subTreeSize == 0) {\\n subTreeSize = size;\\n } else if (size != subTreeSize) {\\n valid = false;\\n }\\n treeSize += size;\\n }\\n }\\n if (valid) {\\n res++;\\n }\\n return treeSize + 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n int res = 0;\\n IList<int>[] g;\\n\\n public int CountGoodNodes(int[][] edges) {\\n int n = edges.Length + 1;\\n g = new IList<int>[n];\\n for (int i = 0; i < n; i++) {\\n g[i] = new List<int>();\\n }\\n foreach (int[] edge in edges) {\\n g[edge[0]].Add(edge[1]);\\n g[edge[1]].Add(edge[0]);\\n }\\n DFS(0, -1);\\n return res;\\n }\\n\\n public int DFS(int node, int parent) {\\n bool valid = true;\\n int treeSize = 0;\\n int subTreeSize = 0;\\n foreach (int child in g[node]) {\\n if (child != parent) {\\n int size = DFS(child, node);\\n if (subTreeSize == 0) {\\n subTreeSize = size;\\n } else if (size != subTreeSize) {\\n valid = false;\\n }\\n treeSize += size;\\n }\\n }\\n if (valid) {\\n res++;\\n }\\n return treeSize + 1;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int countGoodNodes(vector<vector<int>>& edges) {\\n int n = edges.size() + 1;\\n int res = 0;\\n vector<vector<int>> g(n);\\n for (const auto& edge : edges) {\\n g[edge[0]].push_back(edge[1]);\\n g[edge[1]].push_back(edge[0]);\\n }\\n\\n function<int(int, int)> dfs = [&](int node, int parent) -> int {\\n bool valid = true;\\n int treeSize = 0;\\n int subTreeSize = 0;\\n\\n for (int child : g[node]) {\\n if (child != parent) {\\n int size = dfs(child, node);\\n if (subTreeSize == 0) {\\n subTreeSize = size;\\n } else if (size != subTreeSize) {\\n valid = false;\\n }\\n treeSize += size;\\n }\\n }\\n if (valid) {\\n res++;\\n }\\n return treeSize + 1;\\n };\\n\\n dfs(0, -1);\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc countGoodNodes(edges [][]int) int {\\n n := len(edges) + 1\\ng := make([][]int, n)\\nfor _, edge := range edges {\\nx, y := edge[0], edge[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\nres := 0\\nvar dfs func(node, parent int) int\\ndfs = func(node, parent int) int {\\nvalid := true\\ntreeSize := 0\\nsubTreeSize := 0\\n\\nfor _, child := range g[node] {\\nif child != parent {\\nsize := dfs(child, node)\\nif subTreeSize == 0 {\\nsubTreeSize = size\\n} else if size != subTreeSize {\\nvalid = false\\n}\\ntreeSize += size\\n}\\n}\\nif valid {\\nres++\\n}\\nreturn treeSize + 1\\n}\\n\\ndfs(0, -1)\\nreturn res\\n}\\n
\\n###C
\\ntypedef struct {\\n int *data;\\n int size;\\n int capacity;\\n} Vector;\\n\\nVector* createVector(int capacity) {\\n Vector* arr = (Vector*)malloc(sizeof(Vector));\\n arr->data = (int*)malloc(capacity * sizeof(int));\\n arr->size = 0;\\n arr->capacity = capacity;\\n return arr;\\n}\\n\\nvoid append(Vector* arr, int value) {\\n if (arr->size == arr->capacity) {\\n arr->capacity *= 2;\\n arr->data = (int*)realloc(arr->data, arr->capacity * sizeof(int));\\n }\\n arr->data[arr->size++] = value;\\n}\\n\\nvoid freeVector(Vector* arr) {\\n free(arr->data);\\n free(arr);\\n}\\n\\nint dfs(int node, int parent, int *res, int *counts, Vector** g) {\\n int valid = 1;\\n int treeSize = 0;\\n int subTreeSize = 0;\\n for (int i = 0; i < counts[node]; i++) {\\n int child = g[node]->data[i];\\n if (child != parent) {\\n int size = dfs(child, node, res, counts, g);\\n if (subTreeSize == 0) {\\n subTreeSize = size;\\n } else if (size != subTreeSize) {\\n valid = 0;\\n }\\n treeSize += size;\\n }\\n }\\n\\n if (valid) {\\n (*res)++;\\n }\\n return treeSize + 1;\\n}\\n\\nint countGoodNodes(int** edges, int edgesSize, int* edgesColSize) {\\n int n = edgesSize + 1;\\n int *counts = (int *)calloc(n, sizeof(int));\\n int res = 0;\\n Vector* g[n];\\n for (int i = 0; i < n; i++) {\\n g[i] = createVector(2);\\n }\\n for (int i = 0; i < edgesSize; i++) {\\n int x = edges[i][0];\\n int y = edges[i][1];\\n counts[x]++;\\n counts[y]++;\\n append(g[x], y);\\n append(g[y], x);\\n }\\n\\n dfs(0, -1, &res, counts, g);\\n for (int i = 0; i < n; i++) {\\n freeVector(g[i]);\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar countGoodNodes = function(edges) {\\n const n = edges.length + 1;\\n const g = Array.from({ length: n }, () => []);\\n for (const [x, y] of edges) {\\n g[x].push(y);\\n g[y].push(x);\\n }\\n let res = 0;\\n const dfs = (node, parent) => {\\n let valid = true;\\n let treeSize = 0;\\n let subTreeSize = 0;\\n for (const child of g[node]) {\\n if (child !== parent) {\\n const size = dfs(child, node);\\n if (subTreeSize === 0) {\\n subTreeSize = size;\\n } else if (size !== subTreeSize) {\\n valid = false;\\n }\\n treeSize += size;\\n }\\n }\\n if (valid) {\\n res++;\\n }\\n return treeSize + 1;\\n };\\n\\n dfs(0, -1);\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction countGoodNodes(edges: number[][]): number {\\n const n = edges.length + 1;\\n const g: number[][] = Array.from({ length: n }, () => []);\\n for (const [x, y] of edges) {\\n g[x].push(y);\\n g[y].push(x);\\n }\\n let res = 0;\\n const dfs = (node: number, parent: number): number => {\\n let valid = true;\\n let treeSize = 0;\\n let subTreeSize = 0;\\n\\n for (const child of g[node]) {\\n if (child !== parent) {\\n const size = dfs(child, node);\\n if (subTreeSize === 0) {\\n subTreeSize = size;\\n } else if (size !== subTreeSize) {\\n valid = false;\\n }\\n treeSize += size;\\n }\\n }\\n if (valid) {\\n res++;\\n }\\n return treeSize + 1;\\n };\\n dfs(0, -1);\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_good_nodes(edges: Vec<Vec<i32>>) -> i32 {\\n let n = edges.len() + 1;\\n let mut g = vec![vec![]; n];\\n let mut res = 0;\\n for edge in edges {\\n let (x, y) = (edge[0] as usize, edge[1] as usize);\\n g[x].push(y);\\n g[y].push(x);\\n }\\n\\n fn dfs(node: usize, parent: isize, g: &Vec<Vec<usize>>, res: &mut i32) -> usize {\\n let mut valid = true;\\n let mut tree_size = 0;\\n let mut sub_tree_size = 0;\\n\\n for &child in &g[node] {\\n if child != parent as usize {\\n let size = dfs(child, node as isize, g, res);\\n if sub_tree_size == 0 {\\n sub_tree_size = size;\\n } else if size != sub_tree_size {\\n valid = false;\\n }\\n tree_size += size;\\n }\\n }\\n if valid {\\n *res += 1;\\n }\\n tree_size + 1\\n }\\n\\n dfs(0, -1, &g, &mut res);\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$。
\\n空间复杂度:$O(n)$。
\\n思路与算法
\\n由于询问时给定的是元素而不是元素在二维数组中的位置,因此在初始化时,我们可以使用一个哈希表 $\\\\textit{pos}$ 存储每一个元素所在的位置:$\\\\textit{pos}$ 中的每个键表示一个元素,对应的值是一个二元组,表示其在二维数组中的位置。
\\n同时,在初始化时,我们存储给定的二维数组 $\\\\textit{grid}$ 的一份拷贝。这样一来,在查询操作 $\\\\textit{adjacentSum}(\\\\textit{value})$ 和 $\\\\textit{diagonalSum}(\\\\textit{value})$ 中,我们首先通过 $\\\\textit{pos}$ 获取 $\\\\textit{value}$ 的位置,随后根据查询的类型,返回四个相邻元素的和即可。
\\n细节
\\n为了防止重复编写代码,可以使用一个辅助函数 $\\\\textit{getSum}(\\\\textit{value}, \\\\textit{idx})$,其中 $\\\\textit{idx} = 0$ 表示相邻,$\\\\textit{idx} = 1$ 表示对角线相邻。
\\n代码
\\n###C++
\\nclass NeighborSum {\\npublic:\\n NeighborSum(vector<vector<int>>& grid) {\\n for (int i = 0; i < grid.size(); ++i) {\\n for (int j = 0; j < grid[0].size(); ++j) {\\n pos[grid[i][j]] = {i, j};\\n }\\n }\\n this->grid = move(grid);\\n }\\n \\n int adjacentSum(int value) {\\n return getSum(value, 0);\\n }\\n \\n int diagonalSum(int value) {\\n return getSum(value, 1);\\n }\\n\\n int getSum(int value, int idx) {\\n auto [x, y] = pos[value];\\n int ans = 0;\\n for (int d = 0; d < 4; ++d) {\\n int nx = x + dirs[idx][d][0];\\n int ny = y + dirs[idx][d][1];\\n if (0 <= nx && nx < grid.size() && 0 <= ny && ny < grid[0].size()) {\\n ans += grid[nx][ny];\\n }\\n }\\n return ans;\\n }\\n\\nprivate:\\n vector<vector<int>> grid;\\n unordered_map<int, pair<int, int>> pos;\\n static constexpr int dirs[2][4][2] = {\\n {{-1, 0}, {1, 0}, {0, -1}, {0, 1}},\\n {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}\\n };\\n};\\n
\\n###Python
\\nclass NeighborSum:\\n dirs = [\\n [(-1, 0), (1, 0), (0, -1), (0, 1)],\\n [(-1, -1), (-1, 1), (1, -1), (1, 1)],\\n ]\\n\\n def __init__(self, grid: List[List[int]]):\\n self.pos = dict()\\n for i in range(len(grid)):\\n for j in range(len(grid[0])):\\n self.pos[grid[i][j]] = (i, j)\\n self.grid = grid\\n\\n def adjacentSum(self, value: int) -> int:\\n return self.getSum(value, 0)\\n\\n def diagonalSum(self, value: int) -> int:\\n return self.getSum(value, 1)\\n \\n def getSum(self, value: int, idx: int) -> int:\\n x, y = self.pos[value]\\n ans = 0\\n for (dx, dy) in NeighborSum.dirs[idx]:\\n nx, ny = x + dx, y + dy\\n if 0 <= nx < len(self.grid) and 0 <= ny < len(self.grid[0]):\\n ans += self.grid[nx][ny]\\n return ans\\n
\\n###Java
\\nclass NeighborSum {\\n private int[][] grid;\\n private Map<Integer, int[]> pos;\\n private final int[][][] dirs = {\\n {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}, \\n {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}\\n };\\n\\n public NeighborSum(int[][] grid) {\\n this.grid = grid;\\n this.pos = new HashMap<>();\\n for (int i = 0; i < grid.length; i++) {\\n for (int j = 0; j < grid[0].length; j++) {\\n pos.put(grid[i][j], new int[]{i, j});\\n }\\n }\\n }\\n \\n public int adjacentSum(int value) {\\n return getSum(value, 0);\\n }\\n \\n public int diagonalSum(int value) {\\n return getSum(value, 1);\\n }\\n\\n public int getSum(int value, int idx) {\\n int[] p = pos.get(value);\\n int x = p[0], y = p[1];\\n int sum = 0;\\n for (int[] dir : dirs[idx]) {\\n int nx = x + dir[0];\\n int ny = y + dir[1];\\n if (nx >= 0 && nx < grid.length && ny >= 0 && ny < grid[0].length) {\\n sum += grid[nx][ny];\\n }\\n }\\n return sum;\\n }\\n}\\n
\\n###C#
\\npublic class NeighborSum {\\n private readonly int[][] grid;\\n private readonly Dictionary<int, (int, int)> pos;\\n private static int[,,] dirs = new int[2, 4, 2] {\\n { {-1, 0}, {1, 0}, {0, -1}, {0, 1} }, \\n { {-1, -1}, {-1, 1}, {1, -1}, {1, 1} } \\n };\\n\\n public NeighborSum(int[][] grid) {\\n this.grid = grid;\\n this.pos = new Dictionary<int, (int, int)>();\\n for (int i = 0; i < grid.Length; ++i) {\\n for (int j = 0; j < grid[0].Length; ++j) {\\n pos[grid[i][j]] = (i, j);\\n }\\n }\\n }\\n \\n public int AdjacentSum(int value) {\\n return GetSum(value, 0);\\n }\\n \\n public int DiagonalSum(int value) {\\n return GetSum(value, 1);\\n }\\n\\n private int GetSum(int value, int idx) {\\n if (!pos.TryGetValue(value, out var position)) {\\n return 0;\\n }\\n int x = position.Item1;\\n int y = position.Item2;\\n int sum = 0;\\n for (int d = 0; d < 4; ++d) {\\n int nx = x + dirs[idx, d, 0];\\n int ny = y + dirs[idx, d, 1];\\n if (nx >= 0 && nx < grid.Length && ny >= 0 && ny < grid[0].Length) {\\n sum += grid[nx][ny];\\n }\\n }\\n return sum;\\n }\\n}\\n
\\n###Go
\\ntype NeighborSum struct {\\n grid [][]int\\n pos map[int][2]int\\n}\\n\\nvar dirs = [2][4][2]int{\\n {{-1, 0}, {1, 0}, {0, -1}, {0, 1}}, \\n {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}},\\n}\\n\\nfunc Constructor(grid [][]int) NeighborSum {\\n this := NeighborSum {\\n grid: grid,\\n pos: make(map[int][2]int),\\n }\\n for i := range grid {\\n for j := range grid[0] {\\n this.pos[grid[i][j]] = [2]int{i, j}\\n }\\n }\\n\\n return this\\n}\\n\\nfunc (this *NeighborSum) AdjacentSum(value int) int {\\n return this.getSum(value, 0)\\n}\\n\\nfunc (this *NeighborSum) DiagonalSum(value int) int {\\n return this.getSum(value, 1)\\n}\\n\\nfunc (this *NeighborSum) getSum(value, idx int) int {\\n pos := this.pos[value]\\n x, y := pos[0], pos[1]\\n sum := 0\\n for _, dir := range dirs[idx] {\\n nx, ny := x + dir[0], y + dir[1]\\n if nx >= 0 && nx < len(this.grid) && ny >= 0 && ny < len(this.grid[0]) {\\n sum += this.grid[nx][ny]\\n }\\n }\\n return sum\\n}\\n
\\n###C
\\nint dirs[2][4][2] = {\\n {{-1, 0}, {1, 0}, {0, -1}, {0, 1}},\\n {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}}\\n};\\n\\ntypedef struct {\\n int **grid;\\n int rows;\\n int cols;\\n int **positions; \\n} NeighborSum;\\n\\nNeighborSum* neighborSumCreate(int** grid, int gridSize, int* gridColSize) {\\n NeighborSum* obj = (NeighborSum*)malloc(sizeof(NeighborSum));\\n obj->grid = grid;\\n obj->rows = gridSize;\\n obj->cols = gridColSize[0];\\n obj->positions = (int**)malloc(obj->rows * obj->cols * sizeof(int*));\\n for (int i = 0; i < obj->rows; i++) {\\n for (int j = 0; j < obj->cols; j++) {\\n obj->positions[grid[i][j]] = (int *)malloc(sizeof(int) * 2);\\n obj->positions[grid[i][j]][0] = i;\\n obj->positions[grid[i][j]][1] = j;\\n }\\n }\\n return obj;\\n}\\n\\nint NeighborSumGetSum(NeighborSum* obj, int value, int idx) {\\n int *p = obj->positions[value];\\n int x = p[0], y = p[1];\\n int sum = 0;\\n for (int i = 0; i < 4; i++) {\\n int nx = x + dirs[idx][i][0];\\n int ny = y + dirs[idx][i][1];\\n if (nx >= 0 && nx < obj->rows && ny >= 0 && ny < obj->cols) {\\n sum += obj->grid[nx][ny];\\n }\\n }\\n return sum;\\n}\\n\\nint neighborSumAdjacentSum(NeighborSum* obj, int value) {\\n return NeighborSumGetSum(obj, value, 0);\\n}\\n\\nint neighborSumDiagonalSum(NeighborSum* obj, int value) {\\n return NeighborSumGetSum(obj, value, 1);\\n}\\n\\nvoid neighborSumFree(NeighborSum* obj) {\\n for (int i = 0; i < obj->rows * obj->cols; i++) {\\n free(obj->positions[i]);\\n }\\n free(obj->positions);\\n free(obj);\\n}\\n
\\n###JavaScript
\\nvar NeighborSum = function(grid) {\\n this.grid = grid;\\n this.pos = {};\\n for (let i = 0; i < grid.length; i++) {\\n for (let j = 0; j < grid[0].length; j++) {\\n this.pos[grid[i][j]] = [i, j];\\n }\\n }\\n};\\n\\nconst dirs = [\\n [[-1, 0], [1, 0], [0, -1], [0, 1]], \\n [[-1, -1], [-1, 1], [1, -1], [1, 1]]\\n];\\n\\nNeighborSum.prototype.adjacentSum = function(value) {\\n return this.getSum(value, 0);\\n};\\n\\nNeighborSum.prototype.diagonalSum = function(value) {\\n return this.getSum(value, 1);\\n};\\n\\nNeighborSum.prototype.getSum = function(value, idx) {\\n const [x, y] = this.pos[value];\\n let sum = 0;\\n for (const [dx, dy] of dirs[idx]) {\\n const nx = x + dx;\\n const ny = y + dy;\\n if (nx >= 0 && nx < this.grid.length && ny >= 0 && ny < this.grid[0].length) {\\n sum += this.grid[nx][ny];\\n }\\n }\\n return sum;\\n}\\n
\\n###TypeScript
\\nconst dirs = [\\n [[-1, 0], [1, 0], [0, -1], [0, 1]], \\n [[-1, -1], [-1, 1], [1, -1], [1, 1]]\\n];\\n\\nclass NeighborSum {\\n private grid: number[][];\\n private pos: { [key: number]: [number, number] };\\n private dirs: [number, number][][];\\n constructor(grid: number[][]) {\\n this.grid = grid;\\n this.pos = {};\\n for (let i = 0; i < grid.length; i++) {\\n for (let j = 0; j < grid[0].length; j++) {\\n this.pos[grid[i][j]] = [i, j];\\n }\\n }\\n }\\n\\n adjacentSum(value: number): number {\\n return this.getSum(value, 0);\\n }\\n\\n diagonalSum(value: number): number {\\n return this.getSum(value, 1);\\n }\\n\\n getSum(value: number, idx: number): number {\\n const [x, y] = this.pos[value];\\n let sum = 0;\\n for (const [dx, dy] of dirs[idx]) {\\n const nx = x + dx;\\n const ny = y + dy;\\n if (nx >= 0 && nx < this.grid.length && ny >= 0 && ny < this.grid[0].length) {\\n sum += this.grid[nx][ny];\\n }\\n }\\n return sum;\\n }\\n}\\n
\\n###Rust
\\nuse std::collections::HashMap;\\n\\nconst dirs: [ &[(i32, i32)]; 2] = [\\n &[(-1, 0), (1, 0), (0, -1), (0, 1)], \\n &[(-1, -1), (-1, 1), (1, -1), (1, 1)],\\n];\\n\\nstruct NeighborSum {\\n grid: Vec<Vec<i32>>,\\n pos: HashMap<i32, (usize, usize)>,\\n}\\n\\nimpl NeighborSum {\\n\\n fn new(grid: Vec<Vec<i32>>) -> Self {\\n let mut pos = HashMap::new();\\n for (i, row) in grid.iter().enumerate() {\\n for (j, &val) in row.iter().enumerate() {\\n pos.insert(val, (i, j));\\n }\\n }\\n NeighborSum { grid, pos }\\n }\\n \\n fn adjacent_sum(&self, value: i32) -> i32 {\\n self.get_sum(value, 0)\\n }\\n \\n fn diagonal_sum(&self, value: i32) -> i32 {\\n self.get_sum(value, 1)\\n }\\n\\n fn get_sum(&self, value: i32, idx: usize) -> i32 {\\n if let Some(&(x, y)) = self.pos.get(&value) {\\n let mut sum = 0;\\n for &(dx, dy) in dirs[idx] {\\n let nx = x as i32 + dx;\\n let ny = y as i32 + dy;\\n if nx >= 0 && (nx as usize) < self.grid.len() && ny >= 0 && (ny as usize) < self.grid[0].len() {\\n sum += self.grid[nx as usize][ny as usize];\\n }\\n }\\n sum\\n } else {\\n 0\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:初始化需要的时间为 $O(n^2)$,每一次查询操作需要的时间为 $O(1)$。
\\n空间复杂度:整个类需要的空间为 $O(n^2)$,其中包含 $O(n^2)$ 的哈希表空间或 $O(n^2) / O(1)$ 的二维数组 $\\\\textit{grid}$ 的副本的空间。每一次查询操作需要的空间为 $O(1)$。
\\n由于每一轮的操作,会消耗 $2$ 枚价值为 $75$ 的硬币和 $8$ 枚价值为 $10$ 的硬币,因此,我们可以计算得到操作的轮数 $k = \\\\min(x / 2, y / 8)$,然后更新 $x$ 和 $y$ 的值,此时 $x$ 和 $y$ 就是经过 $k$ 轮操作后剩余的硬币数目。
\\n如果 $x > 0$ 且 $y \\\\geq 4$,那么 Alice 还可以继续操作,此时 Bob 就输了,返回 \\"Alice\\";否则,返回 \\"Bob\\"。
\\n###python
\\nclass Solution:\\n def losingPlayer(self, x: int, y: int) -> str:\\n k = min(x // 2, y // 8)\\n x -= k * 2\\n y -= k * 8\\n return \\"Alice\\" if x and y >= 4 else \\"Bob\\"\\n
\\n###java
\\nclass Solution {\\n public String losingPlayer(int x, int y) {\\n int k = Math.min(x / 2, y / 8);\\n x -= k * 2;\\n y -= k * 8;\\n return x > 0 && y >= 4 ? \\"Alice\\" : \\"Bob\\";\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string losingPlayer(int x, int y) {\\n int k = min(x / 2, y / 8);\\n x -= k * 2;\\n y -= k * 8;\\n return x && y >= 4 ? \\"Alice\\" : \\"Bob\\";\\n }\\n};\\n
\\n###go
\\nfunc losingPlayer(x int, y int) string {\\nk := min(x/2, y/8)\\nx -= 2 * k\\ny -= 8 * k\\nif x > 0 && y >= 4 {\\nreturn \\"Alice\\"\\n}\\nreturn \\"Bob\\"\\n}\\n
\\n###ts
\\nfunction losingPlayer(x: number, y: number): string {\\n const k = Math.min((x / 2) | 0, (y / 8) | 0);\\n x -= k * 2;\\n y -= k * 8;\\n return x && y >= 4 ? \'Alice\' : \'Bob\';\\n}\\n
\\n时间复杂度 $O(1)$,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:数学 由于每一轮的操作,会消耗 $2$ 枚价值为 $75$ 的硬币和 $8$ 枚价值为 $10$ 的硬币,因此,我们可以计算得到操作的轮数 $k = \\\\min(x / 2, y / 8)$,然后更新 $x$ 和 $y$ 的值,此时 $x$ 和 $y$ 就是经过 $k$ 轮操作后剩余的硬币数目。\\n\\n如果 $x > 0$ 且 $y \\\\geq 4$,那么 Alice 还可以继续操作,此时 Bob 就输了,返回 \\"Alice\\";否则,返回 \\"Bob\\"。\\n\\n###python\\n\\nclass Solution:\\n def losingPlayer(self,…","guid":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/python3javacgotypescript-yi-ti-yi-jie-sh-napa","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-05T00:04:37.048Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-求出硬币游戏的赢家🟢","url":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game/","content":"给你两个 正 整数 x
和 y
,分别表示价值为 75 和 10 的硬币的数目。
Alice 和 Bob 正在玩一个游戏。每一轮中,Alice 先进行操作,Bob 后操作。每次操作中,玩家需要拿出价值 总和 为 115 的硬币。如果一名玩家无法执行此操作,那么这名玩家 输掉 游戏。
\\n\\n两名玩家都采取 最优 策略,请你返回游戏的赢家。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:x = 2, y = 7
\\n\\n输出:\\"Alice\\"
\\n\\n解释:
\\n\\n游戏一次操作后结束:
\\n\\n示例 2:
\\n\\n输入:x = 4, y = 11
\\n\\n输出:\\"Bob\\"
\\n\\n解释:
\\n\\n游戏 2 次操作后结束:
\\n\\n\\n\\n
提示:
\\n\\n1 <= x, y <= 100
思路与算法
\\n枚举所有子字符串的开始位置,从开始位置向右侧遍历,并且统计字符串内 $0$ 和 $1$ 的个数。
\\n如果符合 $k$ 约束,就更新结果,如果不符合则跳出遍历,从下一个字符重新开始遍历。
\\n最后可以遍历到所有符合条件的子字符串,返回它们的数量作为结果。
代码
\\n###C++
\\nclass Solution {\\npublic:\\n int countKConstraintSubstrings(string s, int k) {\\n int n = s.size(), res = 0;\\n for (int i = 0; i < n; ++i) {\\n int count[2] = {0};\\n for (int j = i; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int countKConstraintSubstrings(String s, int k) {\\n int n = s.length(), res = 0;\\n for (int i = 0; i < n; ++i) {\\n int[] count = new int[2];\\n for (int j = i; j < n; ++j) {\\n count[s.charAt(j) - \'0\']++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int) -> int:\\n n = len(s)\\n res = 0\\n for i in range(n):\\n count = [0, 0]\\n for j in range(i, n):\\n count[int(s[j])] += 1\\n if count[0] > k and count[1] > k:\\n break\\n res += 1\\n return res\\n
\\n###JavaScript
\\nvar countKConstraintSubstrings = function(s, k) {\\n const n = s.length;\\n let res = 0;\\n for (let i = 0; i < n; ++i) {\\n const count = [0, 0];\\n for (let j = i; j < n; ++j) {\\n count[parseInt(s[j], 10)]++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction countKConstraintSubstrings(s: string, k: number): number {\\n const n = s.length;\\n let res = 0;\\n for (let i = 0; i < n; ++i) {\\n const count = [0, 0];\\n for (let j = i; j < n; ++j) {\\n count[parseInt(s[j], 10)]++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc countKConstraintSubstrings(s string, k int) int {\\n n := len(s)\\n res := 0\\n for i := 0; i < n; i++ {\\n count := [2]int{}\\n for j := i; j < n; j++ {\\n count[int(s[j]-\'0\')]++\\n if count[0] > k && count[1] > k {\\n break\\n }\\n res++\\n }\\n }\\n return res\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountKConstraintSubstrings(string s, int k) {\\n int n = s.Length, res = 0;\\n for (int i = 0; i < n; ++i) {\\n int[] count = new int[2];\\n for (int j = i; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nint countKConstraintSubstrings(char* s, int k) {\\n int n = strlen(s), res = 0;\\n for (int i = 0; i < n; ++i) {\\n int count[2] = {0};\\n for (int j = i; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n if (count[0] > k && count[1] > k) {\\n break;\\n }\\n res++;\\n }\\n }\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_k_constraint_substrings(s: String, k: i32) -> i32 {\\n let n = s.len();\\n let s: Vec<u8> = s.bytes().collect();\\n let mut res = 0;\\n for i in 0..n {\\n let mut count = [0, 0];\\n for j in i..n {\\n count[s[j] as usize - b\'0\' as usize] += 1;\\n if count[0] > k && count[1] > k {\\n break;\\n }\\n res += 1;\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times n)$,其中 $n$ 是数组的长度。
\\n空间复杂度:$O(1)$,其中 $n$ 是数组的长度。
\\n思路与算法
\\n我们使用「滑动窗口」的技巧。
\\n首先,我们枚举每一个子字符串结束位置 $j$,更新子字符串 $0$ 和 $1$ 的数量统计。如果当前子字符串不再满足 $k$ 约束,则对于当前左端点 $i$,字符串的右端点在 $j$ 开始不符合 $k$ 约束,我们记录 $\\\\textit{right}[i] = j$。然后我们向右移动左端点,重复这个过程直到子字符串满足 $k$ 约束。
此时子字符串长度为 $j - i + 1$,以位置 $j$ 结束的子字符串,小于等于这个长度都满足 $k$ 约束,我们更新前缀数组,用于计算滑动窗口找到的子字符串数量的前缀和。
\\n对于每一个查询 $[l, r]$, 我们分为两部分:
\\n两部分相加即为查询结果,最后返回所有查询的结果。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {\\n int n = s.size();\\n vector<int> count(2, 0);\\n vector<int> right(n, n);\\n vector<long long> prefix(n + 1, 0);\\n int i = 0;\\n for (int j = 0; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n while (count[0] > k && count[1] > k) {\\n count[s[i] - \'0\']--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n vector<long long> res;\\n for (auto& query : queries) {\\n int l = query[0], r = query[1];\\n int i = min(right[l], r + 1);\\n long long part1 = 1LL * (i - l + 1) * (i - l) / 2;\\n long long part2 = prefix[r + 1] - prefix[i];\\n res.push_back(part1 + part2);\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String s, int k, int[][] queries) {\\n int n = s.length();\\n int[] count = new int[2];\\n int[] right = new int[n];\\n Arrays.fill(right, n);\\n long[] prefix = new long[n + 1];\\n for (int i = 0, j = 0; j < n; ++j) {\\n count[s.charAt(j) - \'0\']++;\\n while (count[0] > k && count[1] > k) {\\n count[s.charAt(i) - \'0\']--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n long[] res = new long[queries.length];\\n for (int q = 0; q < queries.length; q++) {\\n int l = queries[q][0], r = queries[q][1];\\n int i = Math.min(right[l], r + 1);\\n long part1 = (long) (i - l + 1) * (i - l) / 2;\\n long part2 = prefix[r + 1] - prefix[i];\\n res[q] = part1 + part2;\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int, queries: List[List[int]]) -> List[int]:\\n n = len(s)\\n count = [0, 0]\\n prefix = [0] * (n + 1)\\n right = [n] * n\\n i = 0\\n for j in range(n):\\n count[int(s[j])] += 1\\n while count[0] > k and count[1] > k:\\n count[int(s[i])] -= 1\\n right[i] = j\\n i += 1\\n prefix[j + 1] = prefix[j] + j - i + 1\\n\\n res = []\\n for l, r in queries:\\n i = min(right[l], r + 1)\\n part1 = (i - l + 1) * (i - l) // 2\\n part2 = prefix[r + 1] - prefix[i]\\n res.append(part1 + part2)\\n return res\\n
\\n###JavaScript
\\nvar countKConstraintSubstrings = function(s, k, queries) {\\n const n = s.length;\\n const count = [0, 0];\\n const right = Array(n).fill(n);\\n const prefix = Array(n + 1).fill(0);\\n let i = 0;\\n for (let j = 0; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n while (count[0] > k && count[1] > k) {\\n count[s[i] - \'0\']--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n const res = [];\\n for (const query of queries) {\\n const l = query[0], r = query[1];\\n const i = Math.min(right[l], r + 1);\\n const part1 = Math.floor((i - l + 1) * (i - l) / 2);\\n const part2 = prefix[r + 1] - prefix[i];\\n res.push(part1 + part2);\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction countKConstraintSubstrings(s: string, k: number, queries: number[][]): number[] {\\n const n = s.length;\\n const count = [0, 0];\\n const right = Array(n).fill(n);\\n const prefix = Array(n + 1).fill(0);\\n let i = 0;\\n for (let j = 0; j < n; ++j) {\\n count[parseInt(s[j], 10)]++;\\n while (count[0] > k && count[1] > k) {\\n count[parseInt(s[i], 10)]--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n const res = [];\\n for (const query of queries) {\\n const l = query[0], r = query[1];\\n const i = Math.min(right[l], r + 1);\\n const part1 = Math.floor((i - l + 1) * (i - l) / 2);\\n const part2 = prefix[r + 1] - prefix[i];\\n res.push(part1 + part2);\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc countKConstraintSubstrings(s string, k int, queries [][]int) []int64 {\\n n := len(s)\\n count := [2]int{}\\n right := make([]int, n)\\n for i := range right {\\n right[i] = n\\n }\\n prefix := make([]int64, n+1)\\n i := 0\\n for j := 0; j < n; j++ {\\n count[int(s[j]-\'0\')]++\\n for count[0] > k && count[1] > k {\\n count[int(s[i]-\'0\')]--\\n right[i] = j\\n i++\\n }\\n prefix[j+1] = prefix[j] + int64(j-i+1)\\n }\\n\\n res := make([]int64, 0, len(queries))\\n for _, query := range queries {\\n l, r := query[0], query[1]\\n i := min(right[l], r+1)\\n part1 := int64(i-l+1) * int64(i-l) / 2\\n part2 := prefix[r+1] - prefix[i]\\n res = append(res, part1+part2)\\n }\\n return res\\n}\\n\\nfunc min(a, b int) int {\\n if a < b {\\n return a\\n }\\n return b\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long[] CountKConstraintSubstrings(string s, int k, int[][] queries) {\\n int n = s.Length;\\n int[] count = new int[2];\\n int[] right = new int[n];\\n Array.Fill(right, n);\\n long[] prefix = new long[n + 1];\\n for (int i = 0, j = 0; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n while (count[0] > k && count[1] > k) {\\n count[s[i] - \'0\']--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n long[] res = new long[queries.Length];\\n for (int q = 0; q < queries.Length; q++) {\\n int l = queries[q][0], r = queries[q][1];\\n int i = Math.Min(right[l], r + 1);\\n long part1 = (long)(i - l + 1) * (i - l) / 2;\\n long part2 = prefix[r + 1] - prefix[i];\\n res[q] = part1 + part2;\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nlong long* countKConstraintSubstrings(char* s, int k, int** queries, int queriesSize, int* queriesColSize, int* returnSize) {\\n int n = strlen(s);\\n int count[2] = {0};\\n int *right = (int*)malloc(sizeof(int) * n);\\n for (int i = 0; i < n; i++) {\\n right[i] = n;\\n }\\n long long *prefix = (long long*)malloc(sizeof(long long) * (n + 1));\\n memset(prefix, 0, sizeof(long long) * (n + 1));\\n int i = 0;\\n for (int j = 0; j < n; ++j) {\\n count[s[j] - \'0\']++;\\n while (count[0] > k && count[1] > k) {\\n count[s[i] - \'0\']--;\\n right[i] = j;\\n i++;\\n }\\n prefix[j + 1] = prefix[j] + j - i + 1;\\n }\\n\\n *returnSize = queriesSize;\\n long long *res = (long long*)malloc(sizeof(long long) * queriesSize);\\n for (int q = 0; q < queriesSize; q++) {\\n int l = queries[q][0], r = queries[q][1];\\n int i = (right[l] < r + 1) ? right[l] : r + 1;\\n long long part1 = (long long)(i - l + 1) * (i - l) / 2;\\n long long part2 = prefix[r + 1] - prefix[i];\\n res[q] = part1 + part2;\\n }\\n free(right);\\n free(prefix);\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_k_constraint_substrings(s: String, k: i32, queries: Vec<Vec<i32>>) -> Vec<i64> {\\n let n = s.len();\\n let s: Vec<u8> = s.bytes().collect(); // Convert to bytes for faster access\\n let mut count = [0, 0];\\n let mut right: Vec<usize> = vec![n; n];\\n let mut prefix: Vec<i64> = vec![0; n + 1];\\n let mut i = 0;\\n\\n for j in 0..n {\\n count[s[j] as usize - b\'0\' as usize] += 1; // Direct byte access\\n while count[0] > k && count[1] > k {\\n count[s[i] as usize - b\'0\' as usize] -= 1;\\n right[i] = j;\\n i += 1;\\n }\\n prefix[j + 1] = prefix[j] + (j - i + 1) as i64;\\n }\\n\\n let mut res: Vec<i64> = Vec::with_capacity(queries.len()); // Pre-allocate for efficiency\\n for query in queries {\\n let l = query[0] as usize;\\n let r = query[1] as usize;\\n let i = std::cmp::min(right[l], r + 1);\\n let part1 = (i - l + 1) * (i - l) / 2;\\n let part2 = prefix[r + 1] - prefix[i];\\n res.push(part1 as i64 + part2);\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + q)$,其中 $n$ 是数组的长度,$q$ 是查询的数量。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组的长度,不包含返回结果。
\\n给定一个非负整数 c
,你要判断是否存在两个整数 a
和 b
,使得 a2 + b2 = c
。
\\n\\n
示例 1:
\\n\\n输入:c = 5\\n输出:true\\n解释:1 * 1 + 2 * 2 = 5\\n\\n\\n
示例 2:
\\n\\n输入:c = 3\\n输出:false\\n\\n\\n
\\n\\n
提示:
\\n\\n0 <= c <= 231 - 1
地窖的大小是 $n \\\\times m$,需要计算从左上角房间 $(0, 0)$ 到达右下角房间 $(n - 1, m - 1)$ 的最少时间,并遵循如下规则。
\\n每个房间都有最早开始进入时间,对于 $0 \\\\le i < n$ 和 $0 \\\\le j < m$,进入房间 $(i, j)$ 的时间不得早于 $\\\\textit{moveTime}[i][j]$。
\\n每次移动到相邻房间的时间是 $1$ 秒。
\\n计算最少时间可以使用最短路算法实现。由于每次移动的时间的秒数都是正整数,因此可以使用 Dijkstra 算法实现。由于地窖中的相邻房间对的数量是 $O(nm)$,因此地窖可以看成边稀疏图,适合使用 Dijkstra 的基于最小堆的实现。
\\n初始时位于房间 $(0, 0)$ 且总时间是 $0$ 秒。如果一次移动时位于房间 $(\\\\textit{row}, \\\\textit{col})$ 且总时间是 $\\\\textit{time}$ 秒,则从房间 $(\\\\textit{row}, \\\\textit{col})$ 移动到相邻房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 时,到达房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的总时间计算如下。
\\n从房间 $(\\\\textit{row}, \\\\textit{col})$ 进入房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的时间不早于 $\\\\textit{time}$,且房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的最早开始进入时间是 $\\\\textit{moveTime}[\\\\textit{newRow}][\\\\textit{newCol}]$,因此从房间 $(\\\\textit{row}, \\\\textit{col})$ 进入房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的最早开始时间是 $\\\\max(\\\\textit{time}, \\\\textit{moveTime}[\\\\textit{newRow}][\\\\textit{newCol}])$。
\\n移动到房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的时间是 $1$ 秒,因此从房间 $(\\\\textit{row}, \\\\textit{col})$ 移动到相邻房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 时,到达房间 $(\\\\textit{newRow}, \\\\textit{newCol})$ 的总时间是 $\\\\max(\\\\textit{time}, \\\\textit{moveTime}[\\\\textit{newRow}][\\\\textit{newCol}]) + 1$ 秒。
\\n当计算得到房间 $(n - 1, m - 1)$ 的最少到达时间之后,即可得到答案。
\\n###Java
\\nclass Solution {\\n static int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n\\n public int minTimeToReach(int[][] moveTime) {\\n int n = moveTime.length, m = moveTime[0].length;\\n int[][] times = new int[n][m];\\n for (int i = 0; i < n; i++) {\\n Arrays.fill(times[i], Integer.MAX_VALUE);\\n }\\n times[0][0] = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[2] - b[2]);\\n pq.offer(new int[]{0, 0, 0});\\n while (!pq.isEmpty() && times[n - 1][m - 1] == Integer.MAX_VALUE) {\\n int[] cell = pq.poll();\\n int row = cell[0], col = cell[1], time = cell[2];\\n if (times[row][col] < time) {\\n continue;\\n }\\n for (int[] dir : dirs) {\\n int newRow = row + dir[0], newCol = col + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < m) {\\n int newTime = Math.max(time, moveTime[newRow][newCol]) + 1;\\n if (times[newRow][newCol] > newTime) {\\n times[newRow][newCol] = newTime;\\n pq.offer(new int[]{newRow, newCol, newTime});\\n }\\n }\\n }\\n }\\n return times[n - 1][m - 1];\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n static int[][] dirs = {new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1}};\\n\\n public int MinTimeToReach(int[][] moveTime) {\\n int n = moveTime.Length, m = moveTime[0].Length;\\n int[][] times = new int[n][];\\n for (int i = 0; i < n; i++) {\\n times[i] = new int[m];\\n Array.Fill(times[i], int.MaxValue);\\n }\\n times[0][0] = 0;\\n PriorityQueue<int[], int> pq = new PriorityQueue<int[], int>();\\n pq.Enqueue(new int[]{0, 0, 0}, 0);\\n while (pq.Count > 0 && times[n - 1][m - 1] == int.MaxValue) {\\n int[] cell = pq.Dequeue();\\n int row = cell[0], col = cell[1], time = cell[2];\\n if (times[row][col] < time) {\\n continue;\\n }\\n foreach (int[] dir in dirs) {\\n int newRow = row + dir[0], newCol = col + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < m) {\\n int newTime = Math.Max(time, moveTime[newRow][newCol]) + 1;\\n if (times[newRow][newCol] > newTime) {\\n times[newRow][newCol] = newTime;\\n pq.Enqueue(new int[]{newRow, newCol, newTime}, newTime);\\n }\\n }\\n }\\n }\\n return times[n - 1][m - 1];\\n }\\n}\\n
\\n时间复杂度:$O(nm \\\\log (nm))$,其中 $n$ 和 $m$ 分别是地窖 $\\\\textit{moveTime}$ 的行数和列数。Dijkstra 算法的基于最小堆的实现的时间复杂度是 $O(nm \\\\log (nm))$。
\\n空间复杂度:$O(nm)$,其中 $n$ 和 $m$ 分别是地窖 $\\\\textit{moveTime}$ 的行数和列数。Dijkstra 算法的空间复杂度是 $O(nm)$。
\\n遍历字符串 $\\\\textit{num}$,分别计算偶数下标处的数字之和与奇数下标处的数字之和,判断是否相等,即可判断字符串 $\\\\textit{num}$ 是否为平衡字符串。
\\n###Java
\\nclass Solution {\\n public boolean isBalanced(String num) {\\n int evenSum = 0, oddSum = 0;\\n int length = num.length();\\n for (int i = 0; i < length; i++) {\\n int digit = num.charAt(i) - \'0\';\\n if (i % 2 == 0) {\\n evenSum += digit;\\n } else {\\n oddSum += digit;\\n }\\n }\\n return evenSum == oddSum;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool IsBalanced(string num) {\\n int evenSum = 0, oddSum = 0;\\n int length = num.Length;\\n for (int i = 0; i < length; i++) {\\n int digit = num[i] - \'0\';\\n if (i % 2 == 0) {\\n evenSum += digit;\\n } else {\\n oddSum += digit;\\n }\\n }\\n return evenSum == oddSum;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $\\\\textit{num}$ 的长度。需要遍历字符串一次。
\\n空间复杂度:$O(1)$。
\\n\\n\\nProblem: 3341. 到达最后一个房间的最少时间 I
\\n
[TOC]
\\n典型的bfs求最短路径,那么怎么做呢?
\\n###C++
\\nconst int INF=0x3f3f3f3f;\\nconst int N=55;\\nconst int M=4;\\nclass Solution {\\n #define x first\\n #define y second\\n using pii=pair<int,int> ;\\n int n,m; //分别为行和列\\n const int dx[M]={0,1,0,-1};\\n const int dy[M]={1,0,-1,0};\\n int res=INF;\\n int st[N][N];\\npublic:\\n int minTimeToReach(vector<vector<int>>& moveTime) {\\n memset(st,0x3f,sizeof(st));\\n n=moveTime.size(),m=moveTime[0].size();\\n queue<pii> q;\\n q.push({0,0}); //第一个表示映射关系,使用i*m+j来进行映射\\n st[0][0]=0;\\n while(q.size()){\\n auto t=q.front();\\n int a=t.x/m;\\n int b=t.x%m;\\n q.pop();\\n if(a==n-1&&b==m-1){\\n res=min(res,t.y);\\n }\\n for(int i=0;i<M;i++){\\n int x=dx[i]+a;\\n int y=dy[i]+b;\\n if(x<0||x>=n||y<0||y>=m)\\n continue;\\n if(moveTime[x][y]>=st[a][b]){\\n if(st[x][y]<=moveTime[x][y]+1)\\n continue;\\n st[x][y]=moveTime[x][y]+1;\\n q.push({x*m+y,st[x][y] });\\n }\\n else {\\n if(st[x][y]<=st[a][b]+1)\\n continue;\\n st[x][y]=st[a][b]+1;\\n q.push({x*m+y,st[x][y]});\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
\\n","description":"Problem: 3341. 到达最后一个房间的最少时间 I [TOC]\\n\\n典型的bfs求最短路径,那么怎么做呢?\\n\\n设(x,y)为一个二维位置,集合(i,j)为所有满足(x,y)位置的相邻结点\\n举个例子(1,1)的相邻块可以是(i,j)={ (0,1),(1,0),(1,2),(2,1) }\\n我们想一下,我们的目的是求出每个到达(i,j)这个位置的最小值,我们可以不断将符合是(x,y)的所有相邻块(i,j)进行与(x,y)的比较,然后放入到对应队列中\\n但是此时有个问题,没有标记状态,将会重复次数过多,而导致超时…","guid":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-i//solution/jing-dian-bfs-by-huan-xin-27-hwgc","author":"huan-xin-27","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T05:49:36.792Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"检查平衡字符串","url":"https://leetcode.cn/problems/check-balanced-string//solution/jian-cha-ping-heng-zi-fu-chuan-by-ran-fk-5kjs","content":"检查平衡字符串","description":"检查平衡字符串","guid":"https://leetcode.cn/problems/check-balanced-string//solution/jian-cha-ping-heng-zi-fu-chuan-by-ran-fk-5kjs","author":"ran-fk0","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T05:48:52.222Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历求和(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/check-balanced-string//solution/mo-ni-pythonjavacgo-by-endlesscheng-021m","content":"$奇数之和 = 偶数之和$,等价于 $奇数之和 - 偶数之和 = 0$。把偶数下标对应的数字取相反数,这样只需一个变量 $s$ 记录 $奇数之和 - 偶数之和$。
\\n初始化 $s=0$,遍历字符串,奇数下标数字加到 $s$ 中,偶数下标数字的相反数加到 $s$ 中。
\\n如果最终 $s=0$,说明奇数下标数字之和等于偶数下标数字之和,返回 $\\\\texttt{true}$,否则返回 $\\\\texttt{false}$。
\\n###py
\\nclass Solution:\\n def isBalanced(self, num: str) -> bool:\\n s = 0\\n for i, c in enumerate(map(int, num)):\\n s += c if i % 2 else -c\\n return s == 0\\n
\\n###py
\\nclass Solution:\\n def isBalanced(self, num: str) -> bool:\\n a = list(map(int, num))\\n return sum(a[::2]) == sum(a[1::2])\\n
\\n###java
\\nclass Solution {\\n boolean isBalanced(String num) {\\n int s = 0;\\n char[] digits = num.toCharArray();\\n for (int i = 0; i < digits.length; i++) {\\n int c = digits[i] - \'0\';\\n s += i % 2 > 0 ? c : -c;\\n }\\n return s == 0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isBalanced(string num) {\\n int s = 0;\\n for (int i = 0; i < num.length(); i++) {\\n int c = num[i] - \'0\';\\n s += i % 2 ? c : -c;\\n }\\n return s == 0;\\n }\\n};\\n
\\n###c
\\nbool isBalanced(const char* num) {\\n int s = 0;\\n for (int i = 0; num[i]; i++) {\\n int c = num[i] - \'0\';\\n s += i % 2 ? c : -c;\\n }\\n return s == 0;\\n}\\n
\\n###go
\\nfunc isBalanced(num string) bool {\\n s := 0\\n for i, b := range num {\\n c := int(b - \'0\')\\n if i%2 > 0 {\\n s += c\\n } else {\\n s -= c\\n }\\n }\\n return s == 0\\n}\\n
\\n###go
\\nfunc isBalanced(num string) bool {\\n s := 0\\n for i, b := range num {\\n s += (i%2*2 - 1) * int(b-\'0\')\\n }\\n return s == 0\\n}\\n
\\n###js
\\nvar isBalanced = function(num) {\\n let s = 0;\\n for (let i = 0; i < num.length; i++) {\\n const d = Number(num[i]);\\n s += i % 2 ? d : -d;\\n }\\n return s === 0;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn is_balanced(num: String) -> bool {\\n let mut s = 0;\\n for (i, c) in num.bytes().enumerate() {\\n let c = (c - b\'0\') as i32;\\n if i % 2 > 0 {\\n s += c;\\n } else {\\n s -= c;\\n }\\n }\\n s == 0\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"$奇数之和 = 偶数之和$,等价于 $奇数之和 - 偶数之和 = 0$。把偶数下标对应的数字取相反数,这样只需一个变量 $s$ 记录 $奇数之和 - 偶数之和$。 初始化 $s=0$,遍历字符串,奇数下标数字加到 $s$ 中,偶数下标数字的相反数加到 $s$ 中。\\n\\n如果最终 $s=0$,说明奇数下标数字之和等于偶数下标数字之和,返回 $\\\\texttt{true}$,否则返回 $\\\\texttt{false}$。\\n\\n###py\\n\\nclass Solution:\\n def isBalanced(self, num: str) -> bool:…","guid":"https://leetcode.cn/problems/check-balanced-string//solution/mo-ni-pythonjavacgo-by-endlesscheng-021m","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T05:30:18.045Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Dijkstra 最短路(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-i//solution/dijkstra-zui-duan-lu-pythonjavacgo-by-en-7nj3","content":"本题思路与 3342. 到达最后一个房间的最少时间 II 一样,把 题解 中的 $\\\\textit{time}$ 改成 $1$。
\\n","description":"本题思路与 3342. 到达最后一个房间的最少时间 II 一样,把 题解 中的 $\\\\textit{time}$ 改成 $1$。","guid":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-i//solution/dijkstra-zui-duan-lu-pythonjavacgo-by-en-7nj3","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T05:23:14.601Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Dijkstra 最短路(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-ii//solution/dijkstra-zui-duan-lu-pythonjavacgo-by-en-alms","content":"题目要计算从左上角到右下角的最短路,这可以用 Dijkstra 算法解决。Dijkstra 算法介绍
\\n设从起点走到 $(i,j)$ 的最短路为 $\\\\textit{dis}[i][j]$。
\\n那么从 $(i,j)$ 走到相邻格子 $(x,y)$,到达 $(x,y)$ 的时间为
\\n$$
\\n\\\\max(\\\\textit{dis}[i][j], \\\\textit{moveTime}[x][y]) + \\\\textit{time}
\\n$$
上一题 3341. 到达最后一个房间的最少时间 I,$\\\\textit{time}$ 恒为 $1$。
\\n本题由于每走一步 $\\\\textit{time}$ 都会在 $1,2$ 之间变化,类似国际象棋棋盘,$(i+j)$ 的奇偶性就决定了 $\\\\textit{time}$,即
\\n$$
\\n\\\\textit{time} = (i+j)\\\\bmod 2 + 1
\\n$$
由于一定可以从起点走到终点,我们可以在循环中判断,只要出堆的点是终点,就立刻返回 $\\\\textit{dis}[n-1][m-1]$。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n问:为什么代码要判断 d > dis[i][j]
?可以不写 continue
吗?
答:对于同一个点 $(i,j)$,例如先入堆一个比较大的 $\\\\textit{dis}[i][j]=10$,后面又把 $\\\\textit{dis}[i][j]$ 更新成 $5$。之后这个 $5$ 会先出堆,然后再把 $10$ 出堆。$10$ 出堆时候是没有必要去更新周围邻居的最短路的,因为 $5$ 出堆之后,就已经把邻居的最短路更新过了,用 $10$ 是无法把邻居的最短路变得更短的,所以直接 continue
。本题由于只有 $4$ 个邻居,写不写其实无所谓。但如果是一般图,不写这个复杂度就不对了,可能会超时。
###py
\\nclass Solution:\\n def minTimeToReach(self, moveTime: List[List[int]]) -> int:\\n n, m = len(moveTime), len(moveTime[0])\\n dis = [[inf] * m for _ in range(n)]\\n dis[0][0] = 0\\n h = [(0, 0, 0)]\\n while True:\\n d, i, j = heappop(h)\\n if i == n - 1 and j == m - 1:\\n return d\\n if d > dis[i][j]:\\n continue\\n time = (i + j) % 2 + 1\\n for x, y in (i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1): # 枚举周围四个格子\\n if 0 <= x < n and 0 <= y < m:\\n new_dis = max(d, moveTime[x][y]) + time\\n if new_dis < dis[x][y]:\\n dis[x][y] = new_dis\\n heappush(h, (new_dis, x, y))\\n
\\n###java
\\nclass Solution {\\n private final static int[][] DIRS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n\\n public int minTimeToReach(int[][] moveTime) {\\n int n = moveTime.length;\\n int m = moveTime[0].length;\\n\\n int[][] dis = new int[n][m];\\n for (int[] row : dis) {\\n Arrays.fill(row, Integer.MAX_VALUE);\\n }\\n dis[0][0] = 0;\\n\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);\\n pq.add(new int[]{0, 0, 0});\\n\\n while (true) {\\n int[] p = pq.poll();\\n int d = p[0], i = p[1], j = p[2];\\n if (i == n - 1 && j == m - 1) {\\n return d;\\n }\\n if (d > dis[i][j]) {\\n continue;\\n }\\n int time = (i + j) % 2 + 1;\\n for (int[] q : DIRS) {\\n int x = i + q[0], y = j + q[1];\\n if (0 <= x && x < n && 0 <= y && y < m) {\\n int newDis = Math.max(d, moveTime[x][y]) + time;\\n if (newDis < dis[x][y]) {\\n dis[x][y] = newDis;\\n pq.add(new int[]{newDis, x, y});\\n }\\n }\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\npublic:\\n int minTimeToReach(vector<vector<int>>& moveTime) {\\n int n = moveTime.size(), m = moveTime[0].size();\\n vector<vector<int>> dis(n, vector<int>(m, INT_MAX));\\n dis[0][0] = 0;\\n priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<>> pq;\\n pq.emplace(0, 0, 0);\\n while (true) {\\n auto [d, i, j] = pq.top();\\n pq.pop();\\n if (i == n - 1 && j == m - 1) {\\n return d;\\n }\\n if (d > dis[i][j]) {\\n continue;\\n }\\n int time = (i + j) % 2 + 1;\\n for (auto& q : dirs) {\\n int x = i + q[0], y = j + q[1];\\n if (0 <= x && x < n && 0 <= y && y < m) {\\n int new_dis = max(d, moveTime[x][y]) + time;\\n if (new_dis < dis[x][y]) {\\n dis[x][y] = new_dis;\\n pq.emplace(new_dis, x, y);\\n }\\n }\\n }\\n }\\n }\\n};\\n
\\n###go
\\nvar dirs = []struct{ x, y int }{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}\\n\\nfunc minTimeToReach(moveTime [][]int) (ans int) {\\nn, m := len(moveTime), len(moveTime[0])\\ndis := make([][]int, n)\\nfor i := range dis {\\ndis[i] = make([]int, m)\\nfor j := range dis[i] {\\ndis[i][j] = math.MaxInt\\n}\\n}\\ndis[0][0] = 0\\n\\nh := hp{{}}\\nfor {\\ntop := heap.Pop(&h).(tuple)\\ni, j := top.x, top.y\\nif i == n-1 && j == m-1 {\\nreturn top.dis\\n}\\nif top.dis > dis[i][j] {\\ncontinue\\n}\\ntime := (i+j)%2 + 1\\nfor _, d := range dirs {\\nx, y := i+d.x, j+d.y\\nif 0 <= x && x < n && 0 <= y && y < m {\\nnewD := max(top.dis, moveTime[x][y]) + time\\nif newD < dis[x][y] {\\ndis[x][y] = newD\\nheap.Push(&h, tuple{newD, x, y})\\n}\\n}\\n}\\n}\\n}\\n\\ntype tuple struct{ dis, x, y int }\\ntype hp []tuple\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].dis < h[j].dis }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(tuple)) }\\nfunc (h *hp) Pop() (v any) { a := *h; *h, v = a[:len(a)-1], a[len(a)-1]; return }\\n
\\n更多相似题目,见下面网格图题单中的「BFS」以及图论题单中的「单源最短路:Dijkstra」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题目要计算从左上角到右下角的最短路,这可以用 Dijkstra 算法解决。Dijkstra 算法介绍 设从起点走到 $(i,j)$ 的最短路为 $\\\\textit{dis}[i][j]$。\\n\\n那么从 $(i,j)$ 走到相邻格子 $(x,y)$,到达 $(x,y)$ 的时间为\\n\\n$$\\n \\\\max(\\\\textit{dis}[i][j], \\\\textit{moveTime}[x][y]) + \\\\textit{time}\\n $$\\n\\n上一题 3341. 到达最后一个房间的最少时间 I,$\\\\textit{time}$ 恒为 $1$。\\n\\n本题由于每走一步 $\\\\textit…","guid":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-ii//solution/dijkstra-zui-duan-lu-pythonjavacgo-by-en-alms","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T05:20:18.713Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"记忆化搜索+组合计数","url":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/ji-yi-hua-sou-suo-zu-he-ji-shu-by-ddddok-iy4k","content":"记忆化搜索+组合计数","description":"记忆化搜索+组合计数","guid":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/ji-yi-hua-sou-suo-zu-he-ji-shu-by-ddddok-iy4k","author":"ddddoki","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T04:37:54.029Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"多重集排列数 + 计数 DP(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/duo-zhong-ji-pai-lie-shu-ji-shu-dppython-42ky","content":"设 $\\\\textit{num}$ 中的数字之和为 $\\\\textit{total}$。
\\n如果 $\\\\textit{total}$ 是奇数,那么无法把 $\\\\textit{num}$ 分成两个和相等的集合,返回 $0$。
\\n否则,可以把 $\\\\textit{num}$ 分成两个多重集,大小分别为 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$ 和 $\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil$($n$ 是 $\\\\textit{num}$ 的长度),每个多重集的元素和均为 $\\\\dfrac{\\\\textit{total}}{2}$。
\\n例如多重集 ${1,1,2,2,2}$,这 $5$ 个数有 $5!$ 个排列,其中 $2$ 个 $1$ 的排列是重复的,要除以 $2!$;$3$ 个 $2$ 的排列是重复的,要除以 $3!$。所以这个多重集的排列数为 $\\\\dfrac{5!}{2!3!}$。
\\n设 $\\\\textit{num}$ 中数字 $i$ 的出现次数为 $\\\\textit{cnt}[i]$。设有 $k_i$ 个数字 $i$ 分给第一个多重集,那么剩余的 $\\\\textit{cnt}[i] - k_i$ 个数字 $i$ 分给第二个多重集。
\\n第一个多重集的大小为 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$,排列数为
\\n$$
\\n\\\\dfrac{\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor!}{\\\\prod\\\\limits_{i=0}^{i=9}k_i!}
\\n$$
第二个多重集的大小为 $\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil$,排列数为
\\n$$
\\n\\\\dfrac{\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil!}{\\\\prod\\\\limits_{i=0}^{i=9}(\\\\textit{cnt}[i]-k_i)!}
\\n$$
二者相乘,总的排列数为
\\n$$
\\n\\\\dfrac{\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor!\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil!}{\\\\prod\\\\limits_{i=0}^{i=9}k_i!(\\\\textit{cnt}[i]-k_i)!}
\\n$$
枚举 $k_i$,把分子提出来,答案为
\\n$$
\\n\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor!\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil! \\\\sum_{\\\\substack{k_0+\\\\cdots+k_9=\\\\lfloor\\\\frac{n}{2}\\\\rfloor \\\\ 0k_0 + \\\\cdots+9k_9=\\\\frac{\\\\textit{total}}{2} }} \\\\dfrac{1}{\\\\prod\\\\limits_{i=0}^{i=9}k_i!(\\\\textit{cnt}[i]-k_i)!}
\\n$$
下面计算
\\n$$
\\nf_9(k_0,k_1,\\\\ldots,k_9) = \\\\dfrac{1}{\\\\prod\\\\limits_{i=0}^{i=9}k_i!(\\\\textit{cnt}[i]-k_i)!}
\\n$$
如果只枚举 $k_9$ 的话,有
\\n$$
\\n\\\\sum_{k_9=0}^{\\\\textit{cnt}[9]} f_9(k_0,k_1,\\\\ldots,k_9) = \\\\sum_{k_9=0}^{\\\\textit{cnt}[9]} f_8(k_0,k_1,\\\\ldots,k_8)\\\\cdot \\\\dfrac{1}{k_9!(\\\\textit{cnt}[9]-k_9)!}
\\n$$
其中 $f_8(k_0,k_1,\\\\ldots,k_8) = \\\\dfrac{1}{\\\\prod\\\\limits_{i=0}^{i=8}k_i!(\\\\textit{cnt}[i]-k_i)!}$,这又可以通过枚举 $k_8$ 计算,转换成计算 $f_7(k_0,k_1,\\\\ldots,k_7)$ 的子问题。
\\n对于每个 $i=0,1,2,\\\\ldots,9$,我们需要枚举分配多少个数字 $i$ 给第一个多重集。此外有如下约束:
\\n为此,我们需要在记忆化搜索/递推的过程中,维护如下变量:
\\n所以,定义 $\\\\textit{dfs}(i,\\\\textit{left}_1,\\\\textit{leftS})$ 表示在剩余要分配的数字是 $[0,i]$,第一个多重集还剩下 $\\\\textit{left}_1$ 个数字需要分配,第一个多重集还剩下 $\\\\textit{leftS}$ 的元素和需要分配的情况下,下式的结果:
\\n$$
\\n\\\\sum_{k_i=0}^{\\\\textit{cnt}[i]} f_i(k_0,k_1,\\\\ldots,k_i)
\\n$$
枚举数字 $i$ 分出 $k$ 个数给第一个多重集,要解决的问题变为:
\\n即 $\\\\textit{dfs}(i-1,\\\\textit{left}_1 - k, \\\\textit{leftS} - k\\\\cdot i)$。
\\n累加得
\\n$$
\\n\\\\textit{dfs}(i,\\\\textit{left}1,\\\\textit{leftS}) = \\\\sum{k=0}^{\\\\textit{cnt}[i]} \\\\textit{dfs}(i-1,\\\\textit{left}_1 - k, \\\\textit{leftS} - k\\\\cdot i)\\\\cdot \\\\dfrac{1}{k!(\\\\textit{cnt}[i]-k)!}
\\n$$
由于 $\\\\textit{left}_1+\\\\textit{left}2 = \\\\displaystyle\\\\sum\\\\limits{j=0}^{i} \\\\textit{cnt}[j]$ 恒成立,所以第二个多重集的大小 $\\\\textit{left}_2$ 可以省略。
\\n注意枚举 $k$ 的时候,还要满足 $k\\\\le \\\\textit{left}_1$ 且 $\\\\textit{cnt}[i]-k \\\\le \\\\textit{left}_2$,所以 $k$ 的实际范围为
\\n$$
\\n[\\\\max(\\\\textit{cnt}[i]-\\\\textit{left}_2,0), \\\\min(\\\\textit{cnt}[i], \\\\textit{left}_1)]
\\n$$
递归边界:$\\\\textit{dfs}(-1,0,0)=1$,其余 $\\\\textit{dfs}(-1,0,\\\\textit{leftS})=0$。
\\n递归入口:$\\\\textit{dfs}(9,n_1,\\\\textit{total}/2)$,其中 $n_1= \\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$。
\\n最终答案为
\\n$$
\\nn_1!\\\\times (n-n_1)!\\\\times \\\\textit{dfs}(9,n_1,\\\\textit{total}/2)
\\n$$
代码实现时,可以预处理阶乘及其逆元,原理请看 模运算的世界:当加减乘除遇上取模。
\\n###py
\\nMOD = 1_000_000_007\\nMX = 41\\n\\nfac = [0] * MX # f[i] = i!\\nfac[0] = 1\\nfor i in range(1, MX):\\n fac[i] = fac[i - 1] * i % MOD\\n\\ninv_f = [0] * MX # inv_f[i] = i!^-1\\ninv_f[-1] = pow(fac[-1], -1, MOD)\\nfor i in range(MX - 1, 0, -1):\\n inv_f[i - 1] = inv_f[i] * i % MOD\\n\\nclass Solution:\\n def countBalancedPermutations(self, num: str) -> int:\\n cnt = [0] * 10\\n total = 0\\n for c in map(int, num):\\n cnt[c] += 1\\n total += c\\n\\n if total % 2:\\n return 0\\n\\n pre = list(accumulate(cnt))\\n\\n @cache\\n def dfs(i: int, left1: int, left_s: int) -> int:\\n if i < 0:\\n return 1 if left_s == 0 else 0\\n res = 0\\n c = cnt[i]\\n left2 = pre[i] - left1\\n for k in range(max(c - left2, 0), min(c, left1) + 1):\\n if k * i > left_s:\\n break\\n r = dfs(i - 1, left1 - k, left_s - k * i)\\n res += r * inv_f[k] * inv_f[c - k]\\n return res % MOD\\n\\n n = len(num)\\n n1 = n // 2\\n return fac[n1] * fac[n - n1] * dfs(9, n1, total // 2) % MOD\\n
\\n###py
\\nMOD = 1_000_000_007\\nMX = 41\\n\\nfac = [0] * MX # f[i] = i!\\nfac[0] = 1\\nfor i in range(1, MX):\\n fac[i] = fac[i - 1] * i % MOD\\n\\ninv_f = [0] * MX # inv_f[i] = i!^-1\\ninv_f[-1] = pow(fac[-1], -1, MOD)\\nfor i in range(MX - 1, 0, -1):\\n inv_f[i - 1] = inv_f[i] * i % MOD\\n\\nclass Solution:\\n def countBalancedPermutations(self, num: str) -> int:\\n cnt = [0] * 10\\n total = 0\\n for c in map(int, num):\\n cnt[c] += 1\\n total += c\\n\\n if total % 2:\\n return 0\\n\\n pre = list(accumulate(cnt))\\n\\n @cache\\n def dfs(i: int, left1: int, left_s: int) -> int:\\n if i == 0:\\n return inv_f[left1] * inv_f[cnt[0] - left1] % MOD if left_s == 0 and left1 <= cnt[0] else 0\\n res = 0\\n c = cnt[i]\\n left2 = pre[i] - left1\\n for k in range(max(c - left2, 0), min(c, left1, left_s // i) + 1):\\n r = dfs(i - 1, left1 - k, left_s - k * i)\\n res += r * inv_f[k] * inv_f[c - k]\\n return res % MOD\\n\\n n = len(num)\\n n1 = n // 2\\n return fac[n1] * fac[n - n1] * dfs(9, n1, total // 2) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MX = 41;\\n\\n private static final long[] F = new long[MX]; // f[i] = i!\\n private static final long[] INV_F = new long[MX]; // inv_f[i] = i!^-1\\n\\n static {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i > 0; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n }\\n\\n public int countBalancedPermutations(String num) {\\n int[] cnt = new int[10];\\n int total = 0;\\n for (char c : num.toCharArray()) {\\n cnt[c - \'0\']++;\\n total += c - \'0\';\\n }\\n\\n if (total % 2 > 0) {\\n return 0;\\n }\\n\\n for (int i = 1; i < 10; i++) {\\n cnt[i] += cnt[i - 1];\\n }\\n\\n int n = num.length();\\n int n1 = n / 2;\\n int[][][] memo = new int[10][n1 + 1][total / 2 + 1];\\n for (int[][] mat : memo) {\\n for (int[] row : mat) {\\n Arrays.fill(row, -1);\\n }\\n }\\n return (int) (F[n1] * F[n - n1] % MOD * dfs(9, n1, total / 2, cnt, memo) % MOD);\\n }\\n\\n private int dfs(int i, int left1, int leftS, int[] cnt, int[][][] memo) {\\n if (i < 0) {\\n return leftS == 0 ? 1 : 0;\\n }\\n if (memo[i][left1][leftS] != -1) {\\n return memo[i][left1][leftS];\\n }\\n long res = 0;\\n int c = cnt[i] - (i > 0 ? cnt[i - 1] : 0);\\n int left2 = cnt[i] - left1;\\n for (int k = Math.max(c - left2, 0); k <= Math.min(c, left1) && k * i <= leftS; k++) {\\n long r = dfs(i - 1, left1 - k, leftS - k * i, cnt, memo);\\n res = (res + r * INV_F[k] % MOD * INV_F[c - k]) % MOD;\\n }\\n return memo[i][left1][leftS] = (int) res;\\n }\\n\\n private static long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 41;\\n\\nlong long F[MX]; // F[i] = i!\\nlong long INV_F[MX]; // INV_F[i] = i!^-1\\n\\nlong long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n}\\n\\nauto init = [] {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int countBalancedPermutations(string num) {\\n int cnt[10];\\n int total = 0;\\n for (char c : num) {\\n cnt[c - \'0\']++;\\n total += c - \'0\';\\n }\\n\\n if (total % 2) {\\n return 0;\\n }\\n\\n // 原地求前缀和\\n partial_sum(cnt, cnt + 10, cnt);\\n\\n int n = num.size(), n1 = n / 2;\\n vector<vector<vector<int>>> memo(10, vector(n1 + 1, vector<int>(total / 2 + 1, -1))); // -1 表示没有计算过\\n auto dfs = [&](auto& dfs, int i, int left1, int left_s) -> int {\\n if (i < 0) {\\n return left_s == 0;\\n }\\n int& res = memo[i][left1][left_s]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n res = 0;\\n int c = cnt[i] - (i ? cnt[i - 1] : 0);\\n int left2 = cnt[i] - left1;\\n for (int k = max(c - left2, 0); k <= min(c, left1) && k * i <= left_s; k++) {\\n int r = dfs(dfs, i - 1, left1 - k, left_s - k * i);\\n res = (res + r * INV_F[k] % MOD * INV_F[c - k]) % MOD;\\n }\\n return res;\\n };\\n return F[n1] * F[n - n1] % MOD * dfs(dfs, 9, n1, total / 2) % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\nconst mx = 40\\n\\nvar fac, invF [mx + 1]int\\n\\nfunc init() {\\nfac[0] = 1\\nfor i := 1; i <= mx; i++ {\\nfac[i] = fac[i-1] * i % mod\\n}\\ninvF[mx] = pow(fac[mx], mod-2)\\nfor i := mx; i > 0; i-- {\\ninvF[i-1] = invF[i] * i % mod\\n}\\n}\\n\\nfunc countBalancedPermutations(num string) int {\\ncnt := [10]int{}\\ntotal := 0\\nfor _, c := range num {\\ncnt[c-\'0\']++\\ntotal += int(c - \'0\')\\n}\\n\\nif total%2 > 0 {\\nreturn 0\\n}\\n\\nfor i := 1; i < 10; i++ {\\ncnt[i] += cnt[i-1]\\n}\\n\\nn := len(num)\\nn1 := n / 2\\nmemo := [10][][]int{}\\nfor i := range memo {\\nmemo[i] = make([][]int, n1+1)\\nfor j := range memo[i] {\\nmemo[i][j] = make([]int, total/2+1)\\nfor k := range memo[i][j] {\\nmemo[i][j][k] = -1 // -1 表示没有计算过\\n}\\n}\\n}\\nvar dfs func(int, int, int) int\\ndfs = func(i, left1, leftS int) int {\\nif i < 0 {\\nif leftS > 0 {\\nreturn 0\\n}\\nreturn 1\\n}\\np := &memo[i][left1][leftS]\\nif *p != -1 { // 之前计算过\\nreturn *p\\n}\\nres := 0\\nc := cnt[i]\\nif i > 0 {\\nc -= cnt[i-1]\\n}\\nleft2 := cnt[i] - left1\\nfor k := max(c-left2, 0); k <= min(c, left1) && k*i <= leftS; k++ {\\nr := dfs(i-1, left1-k, leftS-k*i)\\nres = (res + r*invF[k]%mod*invF[c-k]) % mod\\n}\\n*p = res // 记忆化\\nreturn res\\n}\\nreturn fac[n1] * fac[n-n1] % mod * dfs(9, n1, total/2) % mod\\n}\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n同上,定义 $f[i+1][\\\\textit{left}_1][\\\\textit{leftS}]$ 表示在剩余要分配的数字是 $[0,i]$,第一个多重集还剩下 $\\\\textit{left}_1$ 个数字需要分配,第一个多重集还剩下 $\\\\textit{leftS}$ 的元素和需要分配的情况下,下式的结果:
\\n$$
\\n\\\\sum_{k_i=0}^{\\\\textit{cnt}[i]} f_i(k_0,k_1,\\\\ldots,k_i)
\\n$$
递推式
\\n$$
\\nf[i+1][\\\\textit{left}1][\\\\textit{leftS}] = \\\\sum{k=0}^{\\\\textit{cnt}[i]} f[i][\\\\textit{left}_1 - k][\\\\textit{leftS} - k\\\\cdot i]\\\\cdot \\\\dfrac{1}{k!(\\\\textit{cnt}[i]-k)!}
\\n$$
初始值:$f[0][0][0] = 1$,其余 $f[0][0][\\\\textit{leftS}]=0$。
\\n最终答案为
\\n$$
\\nn_1!\\\\times (n-n_1)!\\\\times f[10][n_1][\\\\textit{total}/2]
\\n$$
代码实现时,类似 0-1 背包,去掉第一个维度,倒序循环 $\\\\textit{left}_1$ 和 $\\\\textit{leftS}$。
\\n⚠注意:递推会计算一些无效状态,不一定比记忆化搜索快。
\\n###py
\\nMOD = 1_000_000_007\\nMX = 41\\n\\nfac = [0] * MX # f[i] = i!\\nfac[0] = 1\\nfor i in range(1, MX):\\n fac[i] = fac[i - 1] * i % MOD\\n\\ninv_f = [0] * MX # inv_f[i] = i!^-1\\ninv_f[-1] = pow(fac[-1], -1, MOD)\\nfor i in range(MX - 1, 0, -1):\\n inv_f[i - 1] = inv_f[i] * i % MOD\\n\\nclass Solution:\\n def countBalancedPermutations(self, num: str) -> int:\\n cnt = [0] * 10\\n total = 0\\n for c in map(int, num):\\n cnt[c] += 1\\n total += c\\n\\n if total % 2:\\n return 0\\n\\n n = len(num)\\n n1 = n // 2\\n f = [[0] * (total // 2 + 1) for _ in range(n1 + 1)]\\n f[0][0] = 1\\n sc = s = 0\\n for i, c in enumerate(cnt):\\n sc += c\\n s += c * i\\n # 保证 left2 <= n-n1,即 left1 >= sc-(n-n1)\\n for left1 in range(min(sc, n1), max(sc - (n - n1) - 1, -1), -1):\\n left2 = sc - left1\\n # 保证分给第二个集合的元素和 <= total/2,即 left_s >= s-total/2\\n for left_s in range(min(s, total // 2), max(s - total // 2 - 1, -1), -1):\\n res = 0\\n for k in range(max(c - left2, 0), min(c, left1) + 1):\\n if k * i > left_s:\\n break\\n res += f[left1 - k][left_s - k * i] * inv_f[k] * inv_f[c - k]\\n f[left1][left_s] = res % MOD\\n return fac[n1] * fac[n - n1] * f[n1][total // 2] % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MX = 41;\\n\\n private static final long[] F = new long[MX]; // f[i] = i!\\n private static final long[] INV_F = new long[MX]; // inv_f[i] = i!^-1\\n\\n static {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i > 0; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n }\\n\\n public int countBalancedPermutations(String num) {\\n int[] cnt = new int[10];\\n int total = 0;\\n for (char c : num.toCharArray()) {\\n cnt[c - \'0\']++;\\n total += c - \'0\';\\n }\\n\\n if (total % 2 > 0) {\\n return 0;\\n }\\n\\n int n = num.length();\\n int n1 = n / 2;\\n int[][] f = new int[n1 + 1][total / 2 + 1];\\n f[0][0] = 1;\\n int sc = 0;\\n int s = 0;\\n for (int i = 0; i < 10; i++) {\\n int c = cnt[i];\\n sc += c;\\n s += c * i;\\n // 保证 left2 <= n-n1,即 left1 >= sc-(n-n1)\\n for (int left1 = Math.min(sc, n1); left1 >= Math.max(sc - (n - n1), 0); left1--) {\\n int left2 = sc - left1;\\n // 保证分给第二个集合的元素和 <= total/2,即 leftS >= s-total/2\\n for (int leftS = Math.min(s, total / 2); leftS >= Math.max(s - total / 2, 0); leftS--) {\\n long res = 0;\\n for (int k = Math.max(c - left2, 0); k <= Math.min(c, left1) && k * i <= leftS; k++) {\\n res = (res + f[left1 - k][leftS - k * i] * INV_F[k] % MOD * INV_F[c - k]) % MOD;\\n }\\n f[left1][leftS] = (int) res;\\n }\\n }\\n }\\n return (int) (F[n1] * F[n - n1] % MOD * f[n1][total / 2] % MOD);\\n }\\n\\n private static long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 41;\\n\\nlong long F[MX]; // F[i] = i!\\nlong long INV_F[MX]; // INV_F[i] = i!^-1\\n\\nlong long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n}\\n\\nauto init = [] {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int countBalancedPermutations(string num) {\\n int cnt[10];\\n int total = 0;\\n for (char c : num) {\\n cnt[c - \'0\']++;\\n total += c - \'0\';\\n }\\n\\n if (total % 2) {\\n return 0;\\n }\\n\\n int n = num.size();\\n int n1 = n / 2;\\n vector<vector<int>> f(n1 + 1, vector<int>(total / 2 + 1));\\n f[0][0] = 1;\\n int sc = 0, s = 0;\\n for (int i = 0; i < 10; i++) {\\n int c = cnt[i];\\n sc += c;\\n s += c * i;\\n // 保证 left2 <= n-n1,即 left1 >= sc-(n-n1)\\n for (int left1 = min(sc, n1); left1 >= max(sc - (n - n1), 0); left1--) {\\n int left2 = sc - left1;\\n // 保证分给第二个集合的元素和 <= total/2,即 leftS >= s-total/2\\n for (int left_s = min(s, total / 2); left_s >= max(s - total / 2, 0); left_s--) {\\n int res = 0;\\n for (int k = max(c - left2, 0); k <= min(c, left1) && k * i <= left_s; k++) {\\n res = (res + f[left1 - k][left_s - k * i] * INV_F[k] % MOD * INV_F[c - k]) % MOD;\\n }\\n f[left1][left_s] = res;\\n }\\n }\\n }\\n return F[n1] * F[n - n1] % MOD * f[n1][total / 2] % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\nconst mx = 40\\n\\nvar fac, invF [mx + 1]int\\n\\nfunc init() {\\nfac[0] = 1\\nfor i := 1; i <= mx; i++ {\\nfac[i] = fac[i-1] * i % mod\\n}\\ninvF[mx] = pow(fac[mx], mod-2)\\nfor i := mx; i > 0; i-- {\\ninvF[i-1] = invF[i] * i % mod\\n}\\n}\\n\\nfunc countBalancedPermutations(num string) int {\\ncnt := [10]int{}\\ntotal := 0\\nfor _, c := range num {\\ncnt[c-\'0\']++\\ntotal += int(c - \'0\')\\n}\\n\\nif total%2 > 0 {\\nreturn 0\\n}\\n\\nn := len(num)\\nn1 := n / 2\\nf := make([][]int, n1+1)\\nfor i := range f {\\nf[i] = make([]int, total/2+1)\\n}\\nf[0][0] = 1\\nsc := 0\\ns := 0\\nfor i, c := range cnt {\\nsc += c\\ns += c * i\\n// 保证 left2 <= n-n1,即 left1 >= sc-(n-n1)\\nfor left1 := min(sc, n1); left1 >= max(sc-(n-n1), 0); left1-- {\\nleft2 := sc - left1\\n// 保证分给第二个集合的元素和 <= total/2,即 leftS >= s-total/2\\nfor leftS := min(s, total/2); leftS >= max(s-total/2, 0); leftS-- {\\nres := 0\\nfor k := max(c-left2, 0); k <= min(c, left1) && k*i <= leftS; k++ {\\nres = (res + f[left1-k][leftS-k*i]*invF[k]%mod*invF[c-k]) % mod\\n}\\nf[left1][leftS] = res\\n}\\n}\\n}\\nreturn fac[n1] * fac[n-n1] % mod * f[n1][total/2] % mod\\n}\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n更多相似题目,见下面动态规划题单的「§7.6 多维 DP」和数学题单的「§2.2 组合计数」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"公式推导 设 $\\\\textit{num}$ 中的数字之和为 $\\\\textit{total}$。\\n\\n如果 $\\\\textit{total}$ 是奇数,那么无法把 $\\\\textit{num}$ 分成两个和相等的集合,返回 $0$。\\n\\n否则,可以把 $\\\\textit{num}$ 分成两个多重集,大小分别为 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$ 和 $\\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil$($n$ 是 $\\\\textit{num}$ 的长度),每个多重集的元素和均为 $\\\\dfrac…","guid":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/duo-zhong-ji-pai-lie-shu-ji-shu-dppython-42ky","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T04:30:58.599Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简简单单最短路","url":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-ii//solution/jian-jian-dan-dan-zui-duan-lu-by-mipha-2-zank","content":"简简单单最短路","description":"简简单单最短路","guid":"https://leetcode.cn/problems/find-minimum-time-to-reach-last-room-ii//solution/jian-jian-dan-dan-zui-duan-lu-by-mipha-2-zank","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T04:13:51.976Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"计数 + dp + 组合数学 + 逆元","url":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/ji-shu-dp-zu-he-shu-xue-ni-yuan-by-mipha-pgzp","content":"计数 + dp + 组合数学 + 逆元","description":"计数 + dp + 组合数学 + 逆元","guid":"https://leetcode.cn/problems/count-number-of-balanced-permutations//solution/ji-shu-dp-zu-he-shu-xue-ni-yuan-by-mipha-pgzp","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-03T04:05:53.075Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-大礼包🟡","url":"https://leetcode.cn/problems/shopping-offers/","content":"在 LeetCode 商店中, 有 n
件在售的物品。每件物品都有对应的价格。然而,也有一些大礼包,每个大礼包以优惠的价格捆绑销售一组物品。
给你一个整数数组 price
表示物品价格,其中 price[i]
是第 i
件物品的价格。另有一个整数数组 needs
表示购物清单,其中 needs[i]
是需要购买第 i
件物品的数量。
还有一个数组 special
表示大礼包,special[i]
的长度为 n + 1
,其中 special[i][j]
表示第 i
个大礼包中内含第 j
件物品的数量,且 special[i][n]
(也就是数组中的最后一个整数)为第 i
个大礼包的价格。
返回 确切 满足购物清单所需花费的最低价格,你可以充分利用大礼包的优惠活动。你不能购买超出购物清单指定数量的物品,即使那样会降低整体价格。任意大礼包可无限次购买。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:price = [2,5], special = [[3,0,5],[1,2,10]], needs = [3,2]\\n输出:14\\n解释:有 A 和 B 两种物品,价格分别为 ¥2 和 ¥5 。 \\n大礼包 1 ,你可以以 ¥5 的价格购买 3A 和 0B 。 \\n大礼包 2 ,你可以以 ¥10 的价格购买 1A 和 2B 。 \\n需要购买 3 个 A 和 2 个 B , 所以付 ¥10 购买 1A 和 2B(大礼包 2),以及 ¥4 购买 2A 。\\n\\n
示例 2:
\\n\\n输入:price = [2,3,4], special = [[1,1,0,4],[2,2,1,9]], needs = [1,2,1]\\n输出:11\\n解释:A ,B ,C 的价格分别为 ¥2 ,¥3 ,¥4 。\\n可以用 ¥4 购买 1A 和 1B ,也可以用 ¥9 购买 2A ,2B 和 1C 。 \\n需要买 1A ,2B 和 1C ,所以付 ¥4 买 1A 和 1B(大礼包 1),以及 ¥3 购买 1B , ¥4 购买 1C 。 \\n不可以购买超出待购清单的物品,尽管购买大礼包 2 更加便宜。\\n\\n
\\n\\n
提示:
\\n\\nn == price.length == needs.length
1 <= n <= 6
0 <= price[i], needs[i] <= 10
1 <= special.length <= 100
special[i].length == n + 1
0 <= special[i][j] <= 50
0 <= j <= n - 1
至少有一个 special[i][j]
非零。如果 $n$ 和 $k$ 的按位与结果不等于 $k$,说明 $k$ 存在某一位为 $1$,而 $n$ 对应的位为 $0$,此时无法通过改变 $n$ 的某一位使得 $n$ 等于 $k$,返回 $-1$;否则,我们统计 $n \\\\oplus k$ 的二进制表示中 $1$ 的个数即可。
\\n###python
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return -1 if n & k != k else (n ^ k).bit_count()\\n
\\n###java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (n & k) != k ? -1 : Integer.bitCount(n ^ k);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n return (n & k) != k ? -1 : __builtin_popcount(n ^ k);\\n }\\n};\\n
\\n###go
\\nfunc minChanges(n int, k int) int {\\nif n&k != k {\\nreturn -1\\n}\\nreturn bits.OnesCount(uint(n ^ k))\\n}\\n
\\n###ts
\\nfunction minChanges(n: number, k: number): number {\\n return (n & k) !== k ? -1 : bitCount(n ^ k);\\n}\\n\\nfunction bitCount(i: number): number {\\n i = i - ((i >>> 1) & 0x55555555);\\n i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);\\n i = (i + (i >>> 4)) & 0x0f0f0f0f;\\n i = i + (i >>> 8);\\n i = i + (i >>> 16);\\n return i & 0x3f;\\n}\\n
\\n时间复杂度 $O(\\\\log n)$,空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:位运算 如果 $n$ 和 $k$ 的按位与结果不等于 $k$,说明 $k$ 存在某一位为 $1$,而 $n$ 对应的位为 $0$,此时无法通过改变 $n$ 的某一位使得 $n$ 等于 $k$,返回 $-1$;否则,我们统计 $n \\\\oplus k$ 的二进制表示中 $1$ 的个数即可。\\n\\n###python\\n\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return -1 if n & k != k else (n ^ k).bit_count()…","guid":"https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal//solution/python3javacgotypescript-yi-ti-yi-jie-we-aagp","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-02T00:19:55.382Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-使两个整数相等的位更改次数🟢","url":"https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal/","content":"给你两个正整数 n
和 k
。
你可以选择 n
的 二进制表示 中任意一个值为 1 的位,并将其改为 0。
返回使得 n
等于 k
所需要的更改次数。如果无法实现,返回 -1。
\\n\\n
示例 1:
\\n\\n输入: n = 13, k = 4
\\n\\n输出: 2
\\n\\n解释:
\\n最初,n
和 k
的二进制表示分别为 n = (1101)2
和 k = (0100)2
,
我们可以改变 n
的第一位和第四位。结果整数为 n = (0100)2 = k
。
示例 2:
\\n\\n输入: n = 21, k = 21
\\n\\n输出: 0
\\n\\n解释:
\\nn
和 k
已经相等,因此不需要更改。
示例 3:
\\n\\n输入: n = 14, k = 13
\\n\\n输出: -1
\\n\\n解释:
\\n无法使 n
等于 k
。
\\n\\n
提示:
\\n\\n1 <= n, k <= 106
枚举 $a=0,1,2,\\\\cdots$,把等式变成
\\n$$
\\nb^2 = c - a^2
\\n$$
即
\\n$$
\\nb = \\\\sqrt{c - a^2}
\\n$$
如果 $b$ 是整数,返回 $\\\\texttt{true}$。
\\n或者,设 $b\' = \\\\lfloor\\\\sqrt{c - a^2}\\\\rfloor$,如果 $a^2+b\'^2=c$ 成立,返回 $\\\\texttt{true}$。
\\n$a$ 最大枚举到哪?
\\n如果枚举到 $2a^2 > c$ 时,仍然没有找到符合等式的 $a$ 和 $b$,则停止枚举,返回 $\\\\texttt{false}$。
\\n为什么?此时 $a^2 > c-a^2=b^2$,假如继续枚举能找到符合等式的 $a$ 和 $b$,比如 $a=5,\\\\ b=3$,那么之前在枚举到 $a=3$ 时,也能发现 $a=3,\\\\ b=5$ 符合等式,矛盾。所以当枚举到 $2a^2 > c$ 时,后面不可能找到符合等式的 $a$ 和 $b$。
\\n对于 C++ 等部分语言,如果直接计算 $2a^2$,可能会发生溢出。可以把循环条件 $2a^2 \\\\le c$ 改成等价的 $a^2\\\\le \\\\left\\\\lfloor\\\\dfrac{c}{2}\\\\right\\\\rfloor$,从而避免溢出。
\\nclass Solution:\\n def judgeSquareSum(self, c: int) -> bool:\\n a = 0\\n while a * a * 2 <= c:\\n b = isqrt(c - a * a)\\n if a * a + b * b == c:\\n return True\\n a += 1\\n return False\\n
\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n for (int a = 0; a * a <= c / 2; a++) {\\n int b = (int) Math.sqrt(c - a * a);\\n if (a * a + b * b == c) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool judgeSquareSum(int c) {\\n for (int a = 0; a * a <= c / 2; a++) {\\n int b = sqrt(c - a * a);\\n if (a * a + b * b == c) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\nbool judgeSquareSum(int c) {\\n for (int a = 0; a * a <= c / 2; a++) {\\n int b = sqrt(c - a * a);\\n if (a * a + b * b == c) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
\\nfunc judgeSquareSum(c int) bool {\\n for a := 0; a*a <= c/2; a++ {\\n b := int(math.Sqrt(float64(c - a*a)))\\n if a*a+b*b == c {\\n return true\\n }\\n }\\n return false\\n}\\n
\\nvar judgeSquareSum = function(c) {\\n for (let a = 0; a * a * 2 <= c; a++) {\\n const b = Math.floor(Math.sqrt(c - a * a));\\n if (a * a + b * b === c) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\nimpl Solution {\\n pub fn judge_square_sum(c: i32) -> bool {\\n for a in 0.. {\\n if a * a > c / 2 {\\n break;\\n }\\n let b = ((c - a * a) as f64).sqrt() as i32;\\n if a * a + b * b == c {\\n return true;\\n }\\n }\\n false\\n }\\n}\\n
\\n设 $k = \\\\lfloor\\\\sqrt c\\\\rfloor$。本题相当于给你一个有序数组 $[0,1,4,9,16,\\\\cdots,k^2]$,判断是否存在两数之和等于 $c$。
\\n和 167. 两数之和 II - 输入有序数组 一样,使用相向双指针解决,原理请看视频【基础算法精讲 01】。
\\n注意 $a$ 和 $b$ 可以相等。比如 $c=2$,是两个 $1$ 的平方和,此时 $a=b=1$。
\\n所以下面代码的循环条件是 $a\\\\le b$,等号是必要的。
\\n对于 C++ 等部分语言,如果直接计算 $a^2+b^2$,可能会发生溢出。本来 $a^2+b^2 > c$,溢出后 $a^2+b^2$ 变成负数,$a^2+b^2 < c$ 反而成立了,导致我们移动了错误的指针。
\\n可以把判断条件改为 a * a == c - b * b
以及 a * a < c - b * b
,从而避免溢出。
问:为什么 $a^2+b^2 < c$ 时,可以把 $a$ 加一?不会错过答案吗?
\\n答:原理在视频中讲了。对于本题来说,$a^2+b^2 < c$ 成立,同时还意味着 $a^2+(b-1)^2 < c, a^2+(b-2)^2 < c,\\\\cdots$ 都成立,这意味着 $a$ 和 $[a,b]$ 中的任意整数 $b\'$ 都满足 $a^2+b\'^2 < c$,所以 $a^2$ 不可能在两数之和的答案中,移动左指针 $a$。同理,$a^2+b^2 > c$ 成立,同时还意味着 $(a+1)^2+b^2 > c, (a+2)^2+b^2 > c,\\\\cdots$ 都成立,这意味着 $b$ 和 $[a,b]$ 中的任意整数 $a\'$ 都满足 $a\'^2+b^2 > c$,所以 $b^2$ 不可能在两数之和的答案中,移动右指针 $b$。
\\nclass Solution:\\n def judgeSquareSum(self, c: int) -> bool:\\n a, b = 0, isqrt(c)\\n while a <= b:\\n s = a * a + b * b\\n if s == c:\\n return True\\n if s < c:\\n a += 1\\n else:\\n b -= 1\\n return False\\n
\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n int a = 0;\\n int b = (int) Math.sqrt(c);\\n while (a <= b) {\\n if (a * a == c - b * b) { // 避免溢出\\n return true;\\n }\\n if (a * a < c - b * b) {\\n a++;\\n } else {\\n b--;\\n }\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool judgeSquareSum(int c) {\\n int a = 0, b = sqrt(c);\\n while (a <= b) {\\n if (a * a == c - b * b) { // 避免溢出\\n return true;\\n }\\n if (a * a < c - b * b) {\\n a++;\\n } else {\\n b--;\\n }\\n }\\n return false;\\n }\\n};\\n
\\nbool judgeSquareSum(int c) {\\n int a = 0, b = sqrt(c);\\n while (a <= b) {\\n if (a * a == c - b * b) { // 避免溢出\\n return true;\\n }\\n if (a * a < c - b * b) {\\n a++;\\n } else {\\n b--;\\n }\\n }\\n return false;\\n}\\n
\\nfunc judgeSquareSum(c int) bool {\\n a, b := 0, int(math.Sqrt(float64(c)))\\n for a <= b {\\n s := a*a + b*b\\n if s == c {\\n return true\\n }\\n if s < c {\\n a++\\n } else {\\n b--\\n }\\n }\\n return false\\n}\\n
\\nvar judgeSquareSum = function(c) {\\n let a = 0, b = Math.floor(Math.sqrt(c));\\n while (a <= b) {\\n const s = a * a + b * b;\\n if (s === c) {\\n return true;\\n }\\n if (s < c) {\\n a++;\\n } else {\\n b--;\\n }\\n }\\n return false;\\n};\\n
\\nimpl Solution {\\n pub fn judge_square_sum(c: i32) -> bool {\\n let mut a = 0;\\n let mut b = (c as f64).sqrt() as i32;\\n while a <= b {\\n if a * a == c - b * b {\\n return true;\\n }\\n if a * a < c - b * b {\\n a += 1;\\n } else {\\n b -= 1;\\n }\\n }\\n false\\n }\\n}\\n
\\n更多相似题目,见下面双指针题单中的「§3.1 相向双指针」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举 枚举 $a=0,1,2,\\\\cdots$,把等式变成\\n\\n$$\\n b^2 = c - a^2\\n $$\\n\\n即\\n\\n$$\\n b = \\\\sqrt{c - a^2}\\n $$\\n\\n如果 $b$ 是整数,返回 $\\\\texttt{true}$。\\n\\n或者,设 $b\' = \\\\lfloor\\\\sqrt{c - a^2}\\\\rfloor$,如果 $a^2+b\'^2=c$ 成立,返回 $\\\\texttt{true}$。\\n\\n细节\\n\\n$a$ 最大枚举到哪?\\n\\n如果枚举到 $2a^2 > c$ 时,仍然没有找到符合等式的 $a$ 和 $b$,则停止枚举,返回 $\\\\texttt{false}$。\\n\\n为什么…","guid":"https://leetcode.cn/problems/sum-of-square-numbers//solution/liang-chong-fang-fa-mei-ju-shuang-zhi-zh-c26z","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-01T08:10:06.649Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"判断矩形的两个角落是否可达","url":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/pan-duan-ju-xing-de-liang-ge-jiao-luo-sh-ug24","content":"思路
\\n首先我们考虑 $\\\\textit{circles}$ 只有一个圆。在三种情况下,不存在这样的路径:
\\n前两种情况好理解,题目规定路径不能触碰圆的边界或者内部,因此当起点或者终点位于圆内时,肯定不存在这样的路径。第三种情况,圆作为一个单一的障碍物,既与矩形的左上边界相交,又与矩形的右下边界相交,使得不存在一条路径可以从矩形内部经过,又不碰触圆的边界或者内部。
\\n接下来,考虑 $\\\\textit{circles}$ 中有多个圆的情况。类似的,上文讨论的前两种情况仍然成立,当起点或终点位于某一个圆内时,不存在这样的路径。当有多个圆时,圆可能会在矩形内相交。当圆在矩形内相交时,这些圆可以合并成为一个障碍物,当这个合并的障碍物也满足与矩形的左上边界相交,且与矩形的右下边界相交,那么就不存在所求的路径。当圆的相交区域在矩形之外时,这些圆不能进行合并,因为相交的区域不会阻止在矩形内部的路径的生成。这样,我们可以从与矩形的左上边界相交的圆形开始深度优先搜索,然后遍历可以与当前圆合并的相邻点,重复如此,直到遍历到与矩形的右下边界相交的圆,这样的情况下,就表示不存在所求的路径。如果遍历完所有的圆后,都无法遍历到与矩形的右下边界相交的圆,那么表示存在所求的路径。
\\n特殊情况是圆的相交区域的一部分位于矩形内,另一部分位于矩形外,即圆的相交区域又和矩形的边相交。按照上述的推导,这些圆需要进行合并。在实际中,我们可以对它们进行合并,也可以不对它们进行合并。理由如下:当两个圆的相交区和矩形的边相交时,这两个圆本身就都与矩形的边相交。如果这两个圆本身就都与矩形的左上边界相交,那么我们可以不合并它们,因为它们可以各自作为起点。如果这两个圆本身就都与矩形的右下边界相交,那么我们可以不合并它们,因为它们可以各自作为终点。不存在其中一个圆仅与矩形的左上边界相交,另一个圆仅与矩形的右下边界相交的情况。这就得出了接下来的结论:判断两个相交的圆是否需要合并时,我们可以任取相交区域的一点,如果点在矩形内部,那么我们合并它们,否则不合并。
\\n接下来,我们需要将上文提到的几何关系用代码来表示。
\\n首先是判断点是否在圆内,这个我们可以用点到圆心的距离和半径进行比较。为了避免精度问题,我们可以将两边都进行平方操作。
\\n接下来是判断圆是否与矩形的左上边界相交。记圆心的坐标为 $(x, y)$,半径为 $r$。圆心需要位于图中的两个实线矩形或者圆内。在已经满足圆不包含起点或者终点的情况下,前一句话的条件已经足够。
\\n判断圆是否与右下边界相交,与上面的判断类似。
\\n最后是判断两个相交的圆内的任意一点是否与位于矩形内部。记两个圆的圆心分别为 $O_1$,$O_2$,坐标分别为 $(x_1, y_1)$,$(x_2, y_2)$,半径分别为 $r_1$,$r_2$。首先判断两圆是否相交,也可以用圆心之间的距离与半径之和进行比较。如果相交,再在线段 $O_1O_2$ 取点 $A$ 满足 $\\\\frac{|O_1A|}{|O_1O_2|}$ = $\\\\frac{r_1}{r_1+r_2}$。这样点 $A$ 一定满足在两圆的相交区域,因为点 $A$ 到两个圆心的距离均小于等于对应的半径。计算得到点 $A$ 的坐标为 $(\\\\frac{x_1\\\\times r_2+x_2\\\\times r_1}{r_1+r_2}, \\\\frac{y_1\\\\times r_2+y_2\\\\times r_1}{r_1+r_2})$。再判断这个点是否位于矩形内部即可。这样选取点 $A$,也可以避免精度带来的误差。
\\n代码
\\n###Python
\\nclass Solution:\\n def canReachCorner(self, xCorner: int, yCorner: int, circles: List[List[int]]) -> bool:\\n\\n def pointInCircle(px: int, py: int, x: int, y: int, r: int) -> bool:\\n return (x - px) ** 2 + (y - py) ** 2 <= r ** 2\\n\\n def circleIntersectsTopLeftOfRectangle(x: int, y: int, r: int, xCorner: int, yCorner: int) -> bool:\\n return (abs(x) <= r and 0 <= y <= yCorner) or (0 <= x <= xCorner and abs(y - yCorner) <= r) or pointInCircle(x, y, 0, yCorner, r)\\n \\n def circleIntersectsBottomRightOfRectangle(x: int, y: int, r: int, xCorner: int, yCorner: int) -> bool:\\n return (abs(y) <= r and 0 <= x <= xCorner) or (0 <= y <= yCorner and abs(x - xCorner) <= r) or (pointInCircle(x, y, xCorner, 0, r))\\n\\n def circlesIntersectInRectangle(x1: int, y1: int, r1: int, x2: int, y2: int, r2: int, xCorner: int, yCorner: int) -> bool:\\n return (x1 - x2) ** 2 + (y1 - y2) ** 2 <= (r1 + r2) ** 2 and x1 * r2 + x2 * r1 < (r1 + r2) * xCorner and y1 * r2 + y2 * r1 < (r1 + r2) * yCorner\\n\\n visited = [False] * len(circles)\\n def dfs(i: int) -> bool:\\n x1, y1, r1 = circles[i]\\n if circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner):\\n return True\\n visited[i] = True\\n for j, (x2, y2, r2) in enumerate(circles):\\n if (not visited[j]) and circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) and dfs(j):\\n return True\\n return False\\n\\n for i, (x, y, r) in enumerate(circles):\\n if pointInCircle(0, 0, x, y, r) or pointInCircle(xCorner, yCorner, x, y, r):\\n return False\\n if (not visited[i]) and circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) and dfs(i):\\n return False\\n return True\\n
\\n###Java
\\nclass Solution {\\n public boolean canReachCorner(int xCorner, int yCorner, int[][] circles) {\\n boolean[] visited = new boolean[circles.length];\\n for (int i = 0; i < circles.length; i++) {\\n int[] circle = circles[i];\\n int x = circle[0], y = circle[1], r = circle[2];\\n if (pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) \\n && dfs(i, circles, xCorner, yCorner, visited)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n public boolean dfs(int i, int[][] circles, int xCorner, int yCorner, boolean[] visited) {\\n int x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n if (circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = true;\\n for (int j = 0; j < circles.length; j++) {\\n if (!visited[j]) {\\n int x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n if (circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) \\n && dfs(j, circles, xCorner, yCorner, visited)) {\\n return true;\\n }\\n }\\n }\\n return false;\\n }\\n\\n public boolean pointInCircle(long px, long py, long x, long y, long r) {\\n return (x - px) * (x - px) + (y - py) * (y - py) <= r * r;\\n }\\n\\n public boolean circleIntersectsTopLeftOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (Math.abs(x) <= r && 0 <= y && y <= yCorner) ||\\n (0 <= x && x <= xCorner && Math.abs(y - yCorner) <= r) ||\\n pointInCircle(x, y, 0, yCorner, r);\\n }\\n\\n public boolean circleIntersectsBottomRightOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (Math.abs(y) <= r && 0 <= x && x <= xCorner) ||\\n (0 <= y && y <= yCorner && Math.abs(x - xCorner) <= r) ||\\n pointInCircle(x, y, xCorner, 0, r);\\n }\\n\\n public boolean circlesIntersectInRectangle(long x1, long y1, long r1, long x2, long y2, long r2, long xCorner, long yCorner) {\\n return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * xCorner &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * yCorner;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool CanReachCorner(int xCorner, int yCorner, int[][] circles) {\\n bool[] visited = new bool[circles.Length];\\n for (int i = 0; i < circles.Length; i++) {\\n int[] circle = circles[i];\\n int x = circle[0], y = circle[1], r = circle[2];\\n if (PointInCircle(0, 0, x, y, r) || PointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && CircleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) \\n && Dfs(i, circles, xCorner, yCorner, visited)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n private bool Dfs(int i, int[][] circles, int xCorner, int yCorner, bool[] visited) {\\n int x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n if (CircleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = true;\\n for (int j = 0; j < circles.Length; j++) {\\n if (!visited[j]) {\\n int x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n if (CirclesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) \\n && Dfs(j, circles, xCorner, yCorner, visited)) {\\n return true;\\n }\\n }\\n }\\n return false;\\n }\\n\\n private bool PointInCircle(long px, long py, long x, long y, long r) {\\n return (x - px) * (x - px) + (y - py) * (y - py) <= r * r;\\n }\\n\\n private bool CircleIntersectsTopLeftOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (Math.Abs(x) <= r && 0 <= y && y <= yCorner) ||\\n (0 <= x && x <= xCorner && Math.Abs(y - yCorner) <= r) ||\\n PointInCircle(x, y, 0, yCorner, r);\\n }\\n\\n private bool CircleIntersectsBottomRightOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (Math.Abs(y) <= r && 0 <= x && x <= xCorner) ||\\n (0 <= y && y <= yCorner && Math.Abs(x - xCorner) <= r) ||\\n PointInCircle(x, y, xCorner, 0, r);\\n }\\n\\n private bool CirclesIntersectInRectangle(long x1, long y1, long r1, long x2, long y2, long r2, long xCorner, long yCorner) {\\n return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * xCorner &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * yCorner;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n bool canReachCorner(int xCorner, int yCorner, vector<vector<int>>& circles) {\\n vector<bool> visited(circles.size(), false);\\n\\n function<bool(int)> dfs = [&](int i) -> bool {\\n int x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n if (circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = true;\\n for (int j = 0; j < circles.size(); ++j) {\\n int x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n if (!visited[j] && circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) && dfs(j)) {\\n return true;\\n }\\n }\\n return false;\\n };\\n\\n for (int i = 0; i < circles.size(); ++i) {\\n int x = circles[i][0], y = circles[i][1], r = circles[i][2];\\n if (pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) && dfs(i)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n bool pointInCircle(long long px, long long py, long long x, long long y, long long r) {\\n return (x - px) * (x - px) + (y - py) * (y - py) <= (long long)r * r;\\n }\\n\\n bool circleIntersectsTopLeftOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (abs(x) <= r && 0 <= y && y <= yCorner) ||\\n (0 <= x && x <= xCorner && abs(y - yCorner) <= r) ||\\n pointInCircle(x, y, 0, yCorner, r);\\n }\\n\\n bool circleIntersectsBottomRightOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (abs(y) <= r && 0 <= x && x <= xCorner) ||\\n (0 <= y && y <= yCorner && abs(x - xCorner) <= r) ||\\n pointInCircle(x, y, xCorner, 0, r);\\n }\\n\\n bool circlesIntersectInRectangle(long long x1, long long y1, long long r1, long long x2, long long y2, long long r2, long long xCorner, long long yCorner) {\\n return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * xCorner &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * yCorner;\\n }\\n};\\n
\\n###Go
\\nfunc canReachCorner(xCorner int, yCorner int, circles [][]int) bool {\\n visited := make([]bool, len(circles))\\n\\n var dfs func(i int) bool\\n dfs = func(i int) bool {\\n x1, y1, r1 := circles[i][0], circles[i][1], circles[i][2]\\n if circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner) {\\n return true\\n }\\n visited[i] = true\\n for j := 0; j < len(circles); j++ {\\n if !visited[j] && circlesIntersectInRectangle(x1, y1, r1, circles[j][0], circles[j][1], circles[j][2], xCorner, yCorner) && dfs(j) {\\n return true\\n }\\n }\\n return false\\n }\\n\\nfor i := range circles {\\nx, y, r := circles[i][0], circles[i][1], circles[i][2]\\nif pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r) {\\nreturn false\\n}\\nif !visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) && dfs(i) {\\nreturn false\\n}\\n}\\nreturn true\\n}\\n\\nfunc pointInCircle(px, py, x, y, r int) bool {\\nreturn (x - px) * (x - px) + (y - py) * (y - py) <= r * r\\n}\\n\\nfunc circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner int) bool {\\nreturn (abs(x) <= r && 0 <= y && y <= yCorner) ||\\n(0 <= x && x <= xCorner && abs(y - yCorner) <= r) ||\\npointInCircle(x, y, 0, yCorner, r)\\n}\\n\\nfunc circleIntersectsBottomRightOfRectangle(x, y, r, xCorner, yCorner int) bool {\\nreturn (abs(y) <= r && 0 <= x && x <= xCorner) ||\\n(0 <= y && y <= yCorner && abs(x - xCorner) <= r) ||\\npointInCircle(x, y, xCorner, 0, r)\\n}\\n\\nfunc circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner int) bool {\\nreturn (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\nx1 * r2 + x2 * r1 < (r1 + r2) * xCorner &&\\ny1 * r2 + y2 * r1 < (r1 + r2) * yCorner\\n}\\n\\nfunc abs(x int) int {\\nif x < 0 {\\nreturn -x\\n}\\nreturn x\\n}\\n
\\n###C
\\nbool pointInCircle(long long px, long long py, long long x, long long y, long long r) {\\n return (x - px) * (x - px) + (y - py) * (y - py) <= (long long)r * r;\\n}\\n\\nbool circleIntersectsTopLeftOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (abs(x) <= r && 0 <= y && y <= yCorner) ||\\n (0 <= x && x <= xCorner && abs(y - yCorner) <= r) ||\\n pointInCircle(x, y, 0, yCorner, r);\\n}\\n\\nbool circleIntersectsBottomRightOfRectangle(int x, int y, int r, int xCorner, int yCorner) {\\n return (abs(y) <= r && 0 <= x && x <= xCorner) ||\\n (0 <= y && y <= yCorner && abs(x - xCorner) <= r) ||\\n pointInCircle(x, y, xCorner, 0, r);\\n}\\n\\nbool circlesIntersectInRectangle(long long x1, long long y1, long long r1, long long x2, long long y2, long long r2, long long xCorner, long long yCorner) {\\n return (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * xCorner &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * yCorner;\\n}\\n\\nbool dfs(int i, int** circles, int circlesSize, int xCorner, int yCorner, int* visited) {\\n int x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n if (circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = 1;\\n for (int j = 0; j < circlesSize; ++j) {\\n int x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n if (!visited[j] && circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) \\\\\\n && dfs(j, circles, circlesSize, xCorner, yCorner, visited)) {\\n return true;\\n }\\n }\\n return false;\\n};\\n\\nbool canReachCorner(int xCorner, int yCorner, int** circles, int circlesSize, int* circlesColSize) {\\n int visited[circlesSize];\\n memset(visited, 0, sizeof(visited));\\n for (int i = 0; i < circlesSize; ++i) {\\n int x = circles[i][0], y = circles[i][1], r = circles[i][2];\\n if (pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) \\\\\\n && dfs(i, circles, circlesSize, xCorner, yCorner, visited)) {\\n return false;\\n }\\n }\\n return true;\\n}\\n
\\n###JavaScript
\\nvar canReachCorner = function(xCorner, yCorner, circles) {\\n const pointInCircle = (px, py, x, y, r) => {\\n return BigInt(x - px) ** 2n + BigInt(y - py) ** 2n <= BigInt(r) ** 2n;\\n };\\n\\n const circleIntersectsTopLeftOfRectangle = (x, y, r, xCorner, yCorner) => {\\n return (Math.abs(x) <= r && y >= 0 && y <= yCorner) ||\\n (x >= 0 && x <= xCorner && Math.abs(y - yCorner) <= r) ||\\n pointInCircle(x, y, 0, yCorner, r);\\n };\\n\\n const circleIntersectsBottomRightOfRectangle = (x, y, r, xCorner, yCorner) => {\\n return (Math.abs(y) <= r && x >= 0 && x <= xCorner) ||\\n (y >= 0 && y <= yCorner && Math.abs(x - xCorner) <= r) ||\\n pointInCircle(x, y, xCorner, 0, r);\\n };\\n\\n const circlesIntersectInRectangle = (x1, y1, r1, x2, y2, r2, xCorner, yCorner) => {\\n return BigInt(x1 - x2) ** 2n + BigInt(y1 - y2) ** 2n <= BigInt(r1 + r2) ** 2n &&\\n BigInt(x1) * BigInt(r2) + BigInt(x2) * BigInt(r1) < BigInt(r1 + r2) * BigInt(xCorner) &&\\n BigInt(y1) * BigInt(r2) + BigInt(y2) * BigInt(r1) < BigInt(r1 + r2) * BigInt(yCorner);\\n };\\n\\n const visited = new Array(circles.length).fill(false);\\n\\n const dfs = (i) => {\\n const [x1, y1, r1] = circles[i];\\n if (circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = true;\\n for (let j = 0; j < circles.length; j++) {\\n const [x2, y2, r2] = circles[j];\\n if (!visited[j] && circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) && dfs(j)) {\\n return true;\\n }\\n }\\n return false;\\n };\\n\\n for (let i = 0; i < circles.length; i++) {\\n const [x, y, r] = circles[i];\\n if (pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) && dfs(i)) {\\n return false;\\n }\\n }\\n return true;\\n};\\n
\\n###TypeScript
\\nfunction canReachCorner(xCorner: number, yCorner: number, circles: number[][]): boolean {\\n const pointInCircle = (px: number, py: number, x: number, y: number, r: number): boolean => {\\n return BigInt(x - px) ** 2n + BigInt(y - py) ** 2n <= BigInt(r) ** 2n;\\n };\\n\\n const circleIntersectsTopLeftOfRectangle = (x: number, y: number, r: number, xCorner: number, yCorner: number): boolean => {\\n return (Math.abs(x) <= r && y >= 0 && y <= yCorner) ||\\n (x >= 0 && x <= xCorner && Math.abs(y - yCorner) <= r) ||\\n pointInCircle(x, y, 0, yCorner, r);\\n };\\n\\n const circleIntersectsBottomRightOfRectangle = (x: number, y: number, r: number, xCorner: number, yCorner: number): boolean => {\\n return (Math.abs(y) <= r && x >= 0 && x <= xCorner) ||\\n (y >= 0 && y <= yCorner && Math.abs(x - xCorner) <= r) ||\\n pointInCircle(x, y, xCorner, 0, r);\\n };\\n\\n const circlesIntersectInRectangle = (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number, xCorner: number, yCorner: number): boolean => {\\n return BigInt(x1 - x2) ** 2n + BigInt(y1 - y2) ** 2n <= BigInt(r1 + r2) ** 2n &&\\n BigInt(x1) * BigInt(r2) + BigInt(x2) * BigInt(r1) < BigInt(r1 + r2) * BigInt(xCorner) &&\\n BigInt(y1) * BigInt(r2) + BigInt(y2) * BigInt(r1) < BigInt(r1 + r2) * BigInt(yCorner);\\n };\\n\\n const visited: boolean[] = new Array(circles.length).fill(false);\\n\\n const dfs = (i: number): boolean => {\\n const [x1, y1, r1] = circles[i];\\n if (circleIntersectsBottomRightOfRectangle(x1, y1, r1, xCorner, yCorner)) {\\n return true;\\n }\\n visited[i] = true;\\n for (let j = 0; j < circles.length; j++) {\\n const [x2, y2, r2] = circles[j];\\n if (!visited[j] && circlesIntersectInRectangle(x1, y1, r1, x2, y2, r2, xCorner, yCorner) && dfs(j)) {\\n return true;\\n }\\n }\\n return false;\\n };\\n\\n for (let i = 0; i < circles.length; i++) {\\n const [x, y, r] = circles[i];\\n if (pointInCircle(0, 0, x, y, r) || pointInCircle(xCorner, yCorner, x, y, r)) {\\n return false;\\n }\\n if (!visited[i] && circleIntersectsTopLeftOfRectangle(x, y, r, xCorner, yCorner) && dfs(i)) {\\n return false;\\n }\\n }\\n return true;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn can_reach_corner(x_corner: i32, y_corner: i32, circles: Vec<Vec<i32>>) -> bool {\\n fn point_in_circle(px: i32, py: i32, x: i32, y: i32, r: i32) -> bool {\\n let dx = x as i128 - px as i128;\\n let dy = y as i128 - py as i128;\\n dx * dx + dy * dy <= (r as i128) * (r as i128)\\n }\\n\\n fn circle_intersects_top_left(x: i32, y: i32, r: i32, x_corner: i32, y_corner: i32) -> bool {\\n (x.abs() <= r && y >= 0 && y <= y_corner)\\n || (x >= 0 && x <= x_corner && (y - y_corner).abs() <= r)\\n || point_in_circle(0, y_corner, x, y, r)\\n }\\n\\n fn circle_intersects_bottom_right(x: i32, y: i32, r: i32, x_corner: i32, y_corner: i32) -> bool {\\n (y.abs() <= r && x >= 0 && x <= x_corner)\\n || (y >= 0 && y <= y_corner && (x - x_corner).abs() <= r)\\n || point_in_circle(x_corner, 0, x, y, r)\\n }\\n\\n fn circles_intersect_in_rectangle(\\n x1: i32, y1: i32, r1: i32,\\n x2: i32, y2: i32, r2: i32,\\n x_corner: i32, y_corner: i32,\\n ) -> bool {\\n let dx = x1 as i128 - x2 as i128;\\n let dy = y1 as i128 - y2 as i128;\\n let r_sum = r1 as i128 + r2 as i128;\\n dx * dx + dy * dy <= r_sum * r_sum\\n && (x1 as i128 * r2 as i128 + x2 as i128 * r1 as i128) < r_sum * x_corner as i128\\n && (y1 as i128 * r2 as i128 + y2 as i128 * r1 as i128) < r_sum * y_corner as i128\\n }\\n\\n fn dfs(i: usize, circles: &Vec<Vec<i32>>, x_corner: i32, y_corner: i32, visited: &mut [bool]) -> bool {\\n let x1 = circles[i][0];\\n let y1 = circles[i][1];\\n let r1 = circles[i][2];\\n if circle_intersects_bottom_right(x1, y1, r1, x_corner, y_corner) {\\n return true;\\n }\\n visited[i] = true;\\n for (j, vec) in circles.iter().enumerate() {\\n let x2 = vec[0];\\n let y2 = vec[1];\\n let r2 = vec[2];\\n if !visited[j]\\n && circles_intersect_in_rectangle(x1, y1, r1, x2, y2, r2, x_corner, y_corner)\\n && dfs(j, circles, x_corner, y_corner, visited) {\\n return true;\\n }\\n }\\n false\\n }\\n\\n let mut visited = vec![false; circles.len()];\\n\\n for (i, vec) in circles.iter().enumerate() {\\n let x = vec[0];\\n let y = vec[1];\\n let r = vec[2];\\n if point_in_circle(0, 0, x, y, r) || point_in_circle(x_corner, y_corner, x, y, r) {\\n return false;\\n }\\n if !visited[i]\\n && circle_intersects_top_left(x, y, r, x_corner, y_corner)\\n && dfs(i, &circles, x_corner, y_corner, &mut visited) {\\n return false;\\n }\\n }\\n true\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n^2)$,其中 $n$ 是数组 $\\\\textit{circles}$ 的长度。每个圆最多会被 $\\\\textit{dfs}$ 一次,每次会遍历整个数组。
\\n空间复杂度:$O(n)$。
\\n思路与算法
\\n本题与 「3254. 长度为 K 的子数组的能量值 I」同样的问题,根据题意可知,由于子数组如果满足连续上升,此时相邻元素的差值一定为 $1$,此时我们在遍历数组的同时,用一个计数器 $\\\\textit{cnt}$ 统计以当前索引 $i$ 为结尾时连续上升的元素个数,初始化 $\\\\textit{cnt} = 0$,此时:
\\n如果满足 $i = 0$ 或者 $\\\\textit{nums}[i] - \\\\textit{nums}[i−1] = 1$ 时,此时 $\\\\textit{cnt} = \\\\textit{cnt} + 1$,即在 $\\\\textit{nums}[i−1]$ 末尾可以追加元素 $\\\\textit{nums}[i]$ 仍然满足连续上升;
\\n$\\\\textit{nums}[i] - \\\\textit{nums}[i−1] \\\\neq 1$ 时,此时 $\\\\textit{cnt}$ 重新置为 $1$,即在 $\\\\textit{nums}[i−1]$ 末尾无法追加 $\\\\textit{nums}[i]$;
\\n在计算的同时,此时如果以 $\\\\textit{nums}[i]$ 为结尾的连续上升的元素数组如果大于等于 $k$,则当前一定存在长度为 $k$ 且以 $\\\\textit{nums}[i]$ 为结尾的连续上升的子数组,此时能量值即为 $\\\\textit{nums}[i]$;如果不满足,则无法构成长度为 $k$ 且连续上升的子数组,则当前能量值为 $-1$,返回最终统计的能量值即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n int cnt = 0;\\n vector<int> ans(n - k + 1, -1);\\n for (int i = 0; i < n; i++) {\\n cnt = i == 0 || nums[i] - nums[i - 1] != 1 ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] ans = new int[n - k + 1];\\n Arrays.fill(ans, -1);\\n int cnt = 0;\\n for (int i = 0; i < n; i++) {\\n cnt = i == 0 || nums[i] - nums[i - 1] != 1 ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ResultsArray(int[] nums, int k) {\\n int n = nums.Length;\\n int[] ans = new int[n - k + 1];\\n Array.Fill(ans, -1);\\n int cnt = 0;\\n for (int i = 0; i < n; i++) {\\n cnt = (i == 0 || nums[i] - nums[i - 1] != 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc resultsArray(nums []int, k int) []int {\\n n := len(nums)\\n cnt := 0\\n ans := make([]int, n - k + 1)\\n for i := 0; i < n; i++ {\\n if i == 0 || nums[i] - nums[i - 1] != 1 {\\n cnt = 1\\n } else {\\n cnt++\\n }\\n if cnt >= k {\\n ans[i - k + 1] = nums[i]\\n } else if i - k + 1 >= 0 {\\n ans[i - k + 1] = -1\\n }\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n cnt = 0\\n ans = [-1] * (n - k + 1)\\n for i in range(n):\\n cnt = 1 if i == 0 or nums[i] - nums[i - 1] != 1 else cnt + 1\\n if cnt >= k:\\n ans[i - k + 1] = nums[i]\\n return ans\\n
\\n###C
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n *returnSize = numsSize - k + 1;\\n int* ans = (int*)malloc((*returnSize) * sizeof(int));\\n int cnt = 0;\\n for (int i = 0; i < *returnSize; i++) {\\n ans[i] = -1;\\n }\\n for (int i = 0; i < numsSize; i++) {\\n cnt = (i == 0 || nums[i] - nums[i - 1] != 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar resultsArray = function(nums, k) {\\n const n = nums.length;\\n let cnt = 0;\\n const ans = new Array(n - k + 1).fill(-1);\\n for (let i = 0; i < n; i++) {\\n cnt = (i === 0 || nums[i] - nums[i - 1] !== 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n const n = nums.length;\\n let cnt = 0;\\n const ans: number[] = Array(n - k + 1).fill(-1);\\n for (let i = 0; i < n; i++) {\\n cnt = (i === 0 || nums[i] - nums[i - 1] !== 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn results_array(nums: Vec<i32>, k: i32) -> Vec<i32> {\\n let n = nums.len();\\n let mut cnt = 0;\\n let mut ans = vec![-1; n - k as usize + 1];\\n\\n for i in 0..n {\\n cnt = if i == 0 || nums[i] - nums[i - 1] != 1 { 1 } else { cnt + 1 };\\n if cnt >= k {\\n ans[i - k as usize + 1] = nums[i];\\n }\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,$n$ 表示给定数组的长度。只需遍历一遍数组即可。
\\n空间复杂度:$O(1)$,除返回值外,不需要额外的空间。
\\n根据题意可知,我们需要找到长度为 $k$ 且满足连续上升的子数组中的最大元素,此时直接枚举数组中所有长度为 $k$ 的子数组,并判断该数组是否满足连续且上升即可。子数组满足连续上升,此时子数组中相邻元素的差值一定为 $1$,即满足 $\\\\textit{nums}[j] - \\\\textit{nums}[j-1] = 1$。如果子数组满足连续上升,此时最大值即为子数组中的最后一个元素,能量值即为子数组的最后一个元素;如果不满足,则此时能量值为 $-1$,按照上述描述依次枚举并返回能量值即可。
\\n思路与算法
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n int cnt = 0;\\n vector<int> ans(n - k + 1, -1);\\n for (int i = 0; i <= n - k; i++) {\\n bool valid = true;\\n for (int j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] != 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] ans = new int[n - k + 1];\\n Arrays.fill(ans, -1);\\n for (int i = 0; i <= n - k; i++) {\\n boolean valid = true;\\n for (int j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] != 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ResultsArray(int[] nums, int k) {\\n int n = nums.Length;\\n int[] ans = new int[n - k + 1];\\n Array.Fill(ans, -1);\\n for (int i = 0; i <= n - k; i++) {\\n bool valid = true;\\n for (int j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] != 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc resultsArray(nums []int, k int) []int {\\n n := len(nums)\\n ans := make([]int, n - k + 1)\\n for i := range ans {\\n ans[i] = -1\\n }\\n for i := 0; i <= n-k; i++ {\\n valid := true\\n for j := i + 1; j < i + k; j++ {\\n if nums[j] - nums[j - 1] != 1 {\\n valid = false\\n break\\n }\\n }\\n if valid {\\n ans[i] = nums[i + k - 1]\\n }\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n ans = [-1] * (n - k + 1)\\n for i in range(n - k + 1):\\n valid = True\\n for j in range(i + 1, i + k):\\n if nums[j] - nums[j - 1] != 1:\\n valid = False\\n break\\n if valid:\\n ans[i] = nums[i + k - 1]\\n \\n return ans\\n
\\n###C
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n int n = numsSize;\\n *returnSize = n - k + 1;\\n int* ans = (int*)malloc(*returnSize * sizeof(int));\\n for (int i = 0; i < *returnSize; i++) {\\n ans[i] = -1;\\n }\\n for (int i = 0; i <= n - k; i++) {\\n bool valid = true;\\n for (int j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] != 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar resultsArray = function(nums, k) {\\n const n = nums.length;\\n const ans = new Array(n - k + 1).fill(-1);\\n for (let i = 0; i <= n - k; i++) {\\n let valid = true;\\n for (let j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] !== 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n const n = nums.length;\\n const ans: number[] = new Array(n - k + 1).fill(-1);\\n for (let i = 0; i <= n - k; i++) {\\n let valid = true;\\n for (let j = i + 1; j < i + k; j++) {\\n if (nums[j] - nums[j - 1] !== 1) {\\n valid = false;\\n break;\\n }\\n }\\n if (valid) {\\n ans[i] = nums[i + k - 1];\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn results_array(nums: Vec<i32>, k: i32) -> Vec<i32> {\\n let n = nums.len();\\n let mut ans = vec![-1; n - k as usize + 1];\\n for i in 0..= n - k as usize {\\n let mut valid = true;\\n for j in i + 1..i + k as usize {\\n if nums[j] - nums[j - 1] != 1 {\\n valid = false;\\n break;\\n }\\n }\\n if valid {\\n ans[i] = nums[i + k as usize - 1];\\n }\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times k)$,$n$ 表示给定数组的长度,$k$ 表示给定的数字。我们需要枚举每个长度为 $k$ 的连续子数组,一共存在 $n - k + 1$ 个长度为 $k$ 的连续子数组,对于每个长度为 $k$ 的子数组都需要判断是否满足连续上升,需要的时间复杂度为 $O(k)$,总的时间复杂度为 $O(n \\\\times k)$。
\\n空间复杂度:$O(1)$,除返回值外,不需要额外的空间。
\\n思路与算法
\\n实际我们不必枚举所有的子数组,根据题意可知,由于子数组如果满足连续上升,此时相邻元素的差值一定为 $1$,此时我们在遍历数组的同时,用一个计数器 $\\\\textit{cnt}$ 统计以当前索引 $i$ 为结尾时连续上升的元素个数,初始化 $\\\\textit{cnt} = 0$,此时:
\\n如果满足 $i = 0$ 或者 $\\\\textit{nums}[i] - \\\\textit{nums}[i−1] = 1$ 时,此时 $\\\\textit{cnt} = \\\\textit{cnt} + 1$,即在 $\\\\textit{nums}[i−1]$ 末尾可以追加元素 $\\\\textit{nums}[i]$ 仍然满足连续上升;
\\n$\\\\textit{nums}[i] - \\\\textit{nums}[i−1] \\\\neq 1$ 时,此时 $\\\\textit{cnt}$ 重新置为 $1$,即在 $\\\\textit{nums}[i−1]$ 末尾无法追加 $\\\\textit{nums}[i]$;
\\n在计算的同时,此时如果以 $\\\\textit{nums}[i]$ 为结尾的连续上升的元素数组如果大于等于 $k$,则当前一定存在长度为 $k$ 且以 $\\\\textit{nums}[i]$ 为结尾的连续上升的子数组,此时能量值即为 $\\\\textit{nums}[i]$;如果不满足,则无法构成长度为 $k$ 且连续上升的子数组,则当前能量值为 $-1$,返回最终统计的能量值即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n int cnt = 0;\\n vector<int> ans(n - k + 1, -1);\\n for (int i = 0; i < n; i++) {\\n cnt = i == 0 || nums[i] - nums[i - 1] != 1 ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] ans = new int[n - k + 1];\\n Arrays.fill(ans, -1);\\n int cnt = 0;\\n for (int i = 0; i < n; i++) {\\n cnt = i == 0 || nums[i] - nums[i - 1] != 1 ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ResultsArray(int[] nums, int k) {\\n int n = nums.Length;\\n int[] ans = new int[n - k + 1];\\n Array.Fill(ans, -1);\\n int cnt = 0;\\n for (int i = 0; i < n; i++) {\\n cnt = (i == 0 || nums[i] - nums[i - 1] != 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc resultsArray(nums []int, k int) []int {\\n n := len(nums)\\n cnt := 0\\n ans := make([]int, n - k + 1)\\n for i := 0; i < n; i++ {\\n if i == 0 || nums[i] - nums[i - 1] != 1 {\\n cnt = 1\\n } else {\\n cnt++\\n }\\n if cnt >= k {\\n ans[i - k + 1] = nums[i]\\n } else if i - k + 1 >= 0 {\\n ans[i - k + 1] = -1\\n }\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n cnt = 0\\n ans = [-1] * (n - k + 1)\\n for i in range(n):\\n cnt = 1 if i == 0 or nums[i] - nums[i - 1] != 1 else cnt + 1\\n if cnt >= k:\\n ans[i - k + 1] = nums[i]\\n return ans\\n
\\n###C
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n *returnSize = numsSize - k + 1;\\n int* ans = (int*)malloc((*returnSize) * sizeof(int));\\n int cnt = 0;\\n for (int i = 0; i < *returnSize; i++) {\\n ans[i] = -1;\\n }\\n for (int i = 0; i < numsSize; i++) {\\n cnt = (i == 0 || nums[i] - nums[i - 1] != 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar resultsArray = function(nums, k) {\\n const n = nums.length;\\n let cnt = 0;\\n const ans = new Array(n - k + 1).fill(-1);\\n for (let i = 0; i < n; i++) {\\n cnt = (i === 0 || nums[i] - nums[i - 1] !== 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n const n = nums.length;\\n let cnt = 0;\\n const ans: number[] = Array(n - k + 1).fill(-1);\\n for (let i = 0; i < n; i++) {\\n cnt = (i === 0 || nums[i] - nums[i - 1] !== 1) ? 1 : cnt + 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn results_array(nums: Vec<i32>, k: i32) -> Vec<i32> {\\n let n = nums.len();\\n let mut cnt = 0;\\n let mut ans = vec![-1; n - k as usize + 1];\\n\\n for i in 0..n {\\n cnt = if i == 0 || nums[i] - nums[i - 1] != 1 { 1 } else { cnt + 1 };\\n if cnt >= k {\\n ans[i - k as usize + 1] = nums[i];\\n }\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,$n$ 表示给定数组的长度。只需遍历一遍数组即可。
\\n空间复杂度:$O(1)$,除返回值外,不需要额外的空间。
\\n我们定义 $f[i][0]$ 表示在第 $i$ 小时选择能量饮料 A 获得的最大强化能量,定义 $f[i][1]$ 表示在第 $i$ 小时选择能量饮料 B 获得的最大强化能量。初始时 $f[0][0] = \\\\textit{energyDrinkA}[0]$, $f[0][1] = \\\\textit{energyDrinkB}[0]$。答案为 $\\\\max(f[n - 1][0], f[n - 1][1])$。
\\n对于 $i > 0$,我们有以下状态转移方程:
\\n$$
\\n\\\\begin{aligned}
\\nf[i][0] & = \\\\max(f[i - 1][0] + \\\\textit{energyDrinkA}[i], f[i - 1][1]) \\\\
\\nf[i][1] & = \\\\max(f[i - 1][1] + \\\\textit{energyDrinkB}[i], f[i - 1][0])
\\n\\\\end{aligned}
\\n$$
最后返回 $\\\\max(f[n - 1][0], f[n - 1][1])$ 即可。
\\n###python
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n n = len(energyDrinkA)\\n f = [[0] * 2 for _ in range(n)]\\n f[0][0] = energyDrinkA[0]\\n f[0][1] = energyDrinkB[0]\\n for i in range(1, n):\\n f[i][0] = max(f[i - 1][0] + energyDrinkA[i], f[i - 1][1])\\n f[i][1] = max(f[i - 1][1] + energyDrinkB[i], f[i - 1][0])\\n return max(f[n - 1])\\n
\\n###java
\\nclass Solution {\\n public long maxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {\\n int n = energyDrinkA.length;\\n long[][] f = new long[n][2];\\n f[0][0] = energyDrinkA[0];\\n f[0][1] = energyDrinkB[0];\\n for (int i = 1; i < n; ++i) {\\n f[i][0] = Math.max(f[i - 1][0] + energyDrinkA[i], f[i - 1][1]);\\n f[i][1] = Math.max(f[i - 1][1] + energyDrinkB[i], f[i - 1][0]);\\n }\\n return Math.max(f[n - 1][0], f[n - 1][1]);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxEnergyBoost(vector<int>& energyDrinkA, vector<int>& energyDrinkB) {\\n int n = energyDrinkA.size();\\n vector<vector<long long>> f(n, vector<long long>(2));\\n f[0][0] = energyDrinkA[0];\\n f[0][1] = energyDrinkB[0];\\n for (int i = 1; i < n; ++i) {\\n f[i][0] = max(f[i - 1][0] + energyDrinkA[i], f[i - 1][1]);\\n f[i][1] = max(f[i - 1][1] + energyDrinkB[i], f[i - 1][0]);\\n }\\n return max(f[n - 1][0], f[n - 1][1]);\\n }\\n};\\n
\\n###go
\\nfunc maxEnergyBoost(energyDrinkA []int, energyDrinkB []int) int64 {\\nn := len(energyDrinkA)\\nf := make([][2]int64, n)\\nf[0][0] = int64(energyDrinkA[0])\\nf[0][1] = int64(energyDrinkB[0])\\nfor i := 1; i < n; i++ {\\nf[i][0] = max(f[i-1][0]+int64(energyDrinkA[i]), f[i-1][1])\\nf[i][1] = max(f[i-1][1]+int64(energyDrinkB[i]), f[i-1][0])\\n}\\nreturn max(f[n-1][0], f[n-1][1])\\n}\\n
\\n###ts
\\nfunction maxEnergyBoost(energyDrinkA: number[], energyDrinkB: number[]): number {\\n const n = energyDrinkA.length;\\n const f: number[][] = Array.from({ length: n }, () => [0, 0]);\\n f[0][0] = energyDrinkA[0];\\n f[0][1] = energyDrinkB[0];\\n for (let i = 1; i < n; i++) {\\n f[i][0] = Math.max(f[i - 1][0] + energyDrinkA[i], f[i - 1][1]);\\n f[i][1] = Math.max(f[i - 1][1] + energyDrinkB[i], f[i - 1][0]);\\n }\\n return Math.max(...f[n - 1]!);\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组的长度。
\\n我们注意到,状态 $f[i]$ 至于 $f[i - 1]$ 有关,而与 $f[i - 2]$ 无关。因此我们可以只使用两个变量 $f$ 和 $g$ 来维护状态,从而将空间复杂度优化到 $O(1)$。
\\n###python
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n f, g = energyDrinkA[0], energyDrinkB[0]\\n for a, b in zip(energyDrinkA[1:], energyDrinkB[1:]):\\n f, g = max(f + a, g), max(g + b, f)\\n return max(f, g)\\n
\\n###java
\\nclass Solution {\\n public long maxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {\\n int n = energyDrinkA.length;\\n long f = energyDrinkA[0], g = energyDrinkB[0];\\n for (int i = 1; i < n; ++i) {\\n long ff = Math.max(f + energyDrinkA[i], g);\\n g = Math.max(g + energyDrinkB[i], f);\\n f = ff;\\n }\\n return Math.max(f, g);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxEnergyBoost(vector<int>& energyDrinkA, vector<int>& energyDrinkB) {\\n int n = energyDrinkA.size();\\n long long f = energyDrinkA[0], g = energyDrinkB[0];\\n for (int i = 1; i < n; ++i) {\\n long long ff = max(f + energyDrinkA[i], g);\\n g = max(g + energyDrinkB[i], f);\\n f = ff;\\n }\\n return max(f, g);\\n }\\n};\\n
\\n###go
\\nfunc maxEnergyBoost(energyDrinkA []int, energyDrinkB []int) int64 {\\nn := len(energyDrinkA)\\nf, g := energyDrinkA[0], energyDrinkB[0]\\nfor i := 1; i < n; i++ {\\nf, g = max(f+energyDrinkA[i], g), max(g+energyDrinkB[i], f)\\n}\\nreturn int64(max(f, g))\\n}\\n
\\n###ts
\\nfunction maxEnergyBoost(energyDrinkA: number[], energyDrinkB: number[]): number {\\n const n = energyDrinkA.length;\\n let [f, g] = [energyDrinkA[0], energyDrinkB[0]];\\n for (let i = 1; i < n; ++i) {\\n [f, g] = [Math.max(f + energyDrinkA[i], g), Math.max(g + energyDrinkB[i], f)];\\n }\\n return Math.max(f, g);\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:动态规划 我们定义 $f[i][0]$ 表示在第 $i$ 小时选择能量饮料 A 获得的最大强化能量,定义 $f[i][1]$ 表示在第 $i$ 小时选择能量饮料 B 获得的最大强化能量。初始时 $f[0][0] = \\\\textit{energyDrinkA}[0]$, $f[0][1] = \\\\textit{energyDrinkB}[0]$。答案为 $\\\\max(f[n - 1][0], f[n - 1][1])$。\\n\\n对于 $i > 0$,我们有以下状态转移方程:\\n\\n$$\\n \\\\begin{aligned}\\n f[i][0] & = \\\\max(f[i…","guid":"https://leetcode.cn/problems/maximum-energy-boost-from-two-drinks//solution/python3javacgotypescript-yi-ti-yi-jie-do-ykio","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-11-01T00:23:29.971Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-超级饮料的最大强化能量🟡","url":"https://leetcode.cn/problems/maximum-energy-boost-from-two-drinks/","content":"来自未来的体育科学家给你两个整数数组 energyDrinkA
和 energyDrinkB
,数组长度都等于 n
。这两个数组分别代表 A、B 两种不同能量饮料每小时所能提供的强化能量。
你需要每小时饮用一种能量饮料来 最大化 你的总强化能量。然而,如果从一种能量饮料切换到另一种,你需要等待一小时来梳理身体的能量体系(在那个小时里你将不会获得任何强化能量)。
\\n\\n返回在接下来的 n
小时内你能获得的 最大 总强化能量。
注意 你可以选择从饮用任意一种能量饮料开始。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:energyDrinkA = [1,3,1], energyDrinkB = [3,1,1]
\\n\\n输出:5
\\n\\n解释:
\\n\\n要想获得 5 点强化能量,需要选择只饮用能量饮料 A(或者只饮用 B)。
\\n示例 2:
\\n\\n输入:energyDrinkA = [4,1,1], energyDrinkB = [1,1,3]
\\n\\n输出:7
\\n\\n解释:
\\n\\n\\n\\n
提示:
\\n\\nn == energyDrinkA.length == energyDrinkB.length
3 <= n <= 105
1 <= energyDrinkA[i], energyDrinkB[i] <= 105
根据题目描述,我们需要进行多次单点修改和区间查询,这种场景下,我们考虑使用线段树来解决。
\\n首先,我们定义一个 $\\\\textit{Node}$ 类,用于存储线段树的节点信息,包括左右端点 $l$ 和 $r$,以及四个状态值 $s_{00}$, $s_{01}$, $s_{10}$ 和 $s_{11}$。其中:
\\n接着,我们定义一个 $\\\\textit{SegmentTree}$ 类,用于构建线段树。在构建线段树的过程中,我们需要递归地构建左右子树,并根据左右子树的状态值来更新当前节点的状态值。
\\n在主函数中,我们首先根据给定的数组 $\\\\textit{nums}$ 构建线段树,并对每个查询进行处理。对于每个查询,我们首先进行单点修改,然后查询整个区间的状态值,并将结果累加到答案中。
\\n###python
\\ndef max(a: int, b: int) -> int:\\n return a if a > b else b\\n\\n\\nclass Node:\\n __slots__ = \\"l\\", \\"r\\", \\"s00\\", \\"s01\\", \\"s10\\", \\"s11\\"\\n\\n def __init__(self, l: int, r: int):\\n self.l = l\\n self.r = r\\n self.s00 = self.s01 = self.s10 = self.s11 = 0\\n\\n\\nclass SegmentTree:\\n __slots__ = \\"tr\\"\\n\\n def __init__(self, n: int):\\n self.tr: List[Node | None] = [None] * (n << 2)\\n self.build(1, 1, n)\\n\\n def build(self, u: int, l: int, r: int):\\n self.tr[u] = Node(l, r)\\n if l == r:\\n return\\n mid = (l + r) >> 1\\n self.build(u << 1, l, mid)\\n self.build(u << 1 | 1, mid + 1, r)\\n\\n def query(self, u: int, l: int, r: int) -> int:\\n if self.tr[u].l >= l and self.tr[u].r <= r:\\n return self.tr[u].s11\\n mid = (self.tr[u].l + self.tr[u].r) >> 1\\n ans = 0\\n if r <= mid:\\n ans = self.query(u << 1, l, r)\\n if l > mid:\\n ans = max(ans, self.query(u << 1 | 1, l, r))\\n return ans\\n\\n def pushup(self, u: int):\\n left, right = self.tr[u << 1], self.tr[u << 1 | 1]\\n self.tr[u].s00 = max(left.s00 + right.s10, left.s01 + right.s00)\\n self.tr[u].s01 = max(left.s00 + right.s11, left.s01 + right.s01)\\n self.tr[u].s10 = max(left.s10 + right.s10, left.s11 + right.s00)\\n self.tr[u].s11 = max(left.s10 + right.s11, left.s11 + right.s01)\\n\\n def modify(self, u: int, x: int, v: int):\\n if self.tr[u].l == self.tr[u].r:\\n self.tr[u].s11 = max(0, v)\\n return\\n mid = (self.tr[u].l + self.tr[u].r) >> 1\\n if x <= mid:\\n self.modify(u << 1, x, v)\\n else:\\n self.modify(u << 1 | 1, x, v)\\n self.pushup(u)\\n\\n\\nclass Solution:\\n def maximumSumSubsequence(self, nums: List[int], queries: List[List[int]]) -> int:\\n n = len(nums)\\n tree = SegmentTree(n)\\n for i, x in enumerate(nums, 1):\\n tree.modify(1, i, x)\\n ans = 0\\n mod = 10**9 + 7\\n for i, x in queries:\\n tree.modify(1, i + 1, x)\\n ans = (ans + tree.query(1, 1, n)) % mod\\n return ans\\n
\\n###java
\\nclass Node {\\n int l, r;\\n long s00, s01, s10, s11;\\n\\n Node(int l, int r) {\\n this.l = l;\\n this.r = r;\\n this.s00 = this.s01 = this.s10 = this.s11 = 0;\\n }\\n}\\n\\nclass SegmentTree {\\n Node[] tr;\\n\\n SegmentTree(int n) {\\n tr = new Node[n * 4];\\n build(1, 1, n);\\n }\\n\\n void build(int u, int l, int r) {\\n tr[u] = new Node(l, r);\\n if (l == r) {\\n return;\\n }\\n int mid = (l + r) >> 1;\\n build(u << 1, l, mid);\\n build(u << 1 | 1, mid + 1, r);\\n }\\n\\n long query(int u, int l, int r) {\\n if (tr[u].l >= l && tr[u].r <= r) {\\n return tr[u].s11;\\n }\\n int mid = (tr[u].l + tr[u].r) >> 1;\\n long ans = 0;\\n if (r <= mid) {\\n ans = query(u << 1, l, r);\\n }\\n if (l > mid) {\\n ans = Math.max(ans, query(u << 1 | 1, l, r));\\n }\\n return ans;\\n }\\n\\n void pushup(int u) {\\n Node left = tr[u << 1];\\n Node right = tr[u << 1 | 1];\\n tr[u].s00 = Math.max(left.s00 + right.s10, left.s01 + right.s00);\\n tr[u].s01 = Math.max(left.s00 + right.s11, left.s01 + right.s01);\\n tr[u].s10 = Math.max(left.s10 + right.s10, left.s11 + right.s00);\\n tr[u].s11 = Math.max(left.s10 + right.s11, left.s11 + right.s01);\\n }\\n\\n void modify(int u, int x, int v) {\\n if (tr[u].l == tr[u].r) {\\n tr[u].s11 = Math.max(0, v);\\n return;\\n }\\n int mid = (tr[u].l + tr[u].r) >> 1;\\n if (x <= mid) {\\n modify(u << 1, x, v);\\n } else {\\n modify(u << 1 | 1, x, v);\\n }\\n pushup(u);\\n }\\n}\\n\\nclass Solution {\\n public int maximumSumSubsequence(int[] nums, int[][] queries) {\\n int n = nums.length;\\n SegmentTree tree = new SegmentTree(n);\\n for (int i = 0; i < n; ++i) {\\n tree.modify(1, i + 1, nums[i]);\\n }\\n long ans = 0;\\n final int mod = (int) 1e9 + 7;\\n for (int[] q : queries) {\\n tree.modify(1, q[0] + 1, q[1]);\\n ans = (ans + tree.query(1, 1, n)) % mod;\\n }\\n return (int) ans;\\n }\\n}\\n
\\n###cpp
\\nclass Node {\\npublic:\\n int l, r;\\n long long s00, s01, s10, s11;\\n\\n Node(int l, int r)\\n : l(l)\\n , r(r)\\n , s00(0)\\n , s01(0)\\n , s10(0)\\n , s11(0) {}\\n};\\n\\nclass SegmentTree {\\npublic:\\n vector<Node*> tr;\\n\\n SegmentTree(int n)\\n : tr(n << 2) {\\n build(1, 1, n);\\n }\\n\\n void build(int u, int l, int r) {\\n tr[u] = new Node(l, r);\\n if (l == r) {\\n return;\\n }\\n int mid = (l + r) >> 1;\\n build(u << 1, l, mid);\\n build(u << 1 | 1, mid + 1, r);\\n }\\n\\n long long query(int u, int l, int r) {\\n if (tr[u]->l >= l && tr[u]->r <= r) {\\n return tr[u]->s11;\\n }\\n int mid = (tr[u]->l + tr[u]->r) >> 1;\\n long long ans = 0;\\n if (r <= mid) {\\n ans = query(u << 1, l, r);\\n }\\n if (l > mid) {\\n ans = max(ans, query(u << 1 | 1, l, r));\\n }\\n return ans;\\n }\\n\\n void pushup(int u) {\\n Node* left = tr[u << 1];\\n Node* right = tr[u << 1 | 1];\\n tr[u]->s00 = max(left->s00 + right->s10, left->s01 + right->s00);\\n tr[u]->s01 = max(left->s00 + right->s11, left->s01 + right->s01);\\n tr[u]->s10 = max(left->s10 + right->s10, left->s11 + right->s00);\\n tr[u]->s11 = max(left->s10 + right->s11, left->s11 + right->s01);\\n }\\n\\n void modify(int u, int x, int v) {\\n if (tr[u]->l == tr[u]->r) {\\n tr[u]->s11 = max(0LL, (long long) v);\\n return;\\n }\\n int mid = (tr[u]->l + tr[u]->r) >> 1;\\n if (x <= mid) {\\n modify(u << 1, x, v);\\n } else {\\n modify(u << 1 | 1, x, v);\\n }\\n pushup(u);\\n }\\n\\n ~SegmentTree() {\\n for (auto node : tr) {\\n delete node;\\n }\\n }\\n};\\n\\nclass Solution {\\npublic:\\n int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n SegmentTree tree(n);\\n for (int i = 0; i < n; ++i) {\\n tree.modify(1, i + 1, nums[i]);\\n }\\n long long ans = 0;\\n const int mod = 1e9 + 7;\\n for (const auto& q : queries) {\\n tree.modify(1, q[0] + 1, q[1]);\\n ans = (ans + tree.query(1, 1, n)) % mod;\\n }\\n return (int) ans;\\n }\\n};\\n
\\n###go
\\ntype Node struct {\\nl, r int\\ns00, s01, s10, s11 int\\n}\\n\\nfunc NewNode(l, r int) *Node {\\nreturn &Node{l: l, r: r, s00: 0, s01: 0, s10: 0, s11: 0}\\n}\\n\\ntype SegmentTree struct {\\ntr []*Node\\n}\\n\\nfunc NewSegmentTree(n int) *SegmentTree {\\ntr := make([]*Node, n*4)\\ntree := &SegmentTree{tr: tr}\\ntree.build(1, 1, n)\\nreturn tree\\n}\\n\\nfunc (st *SegmentTree) build(u, l, r int) {\\nst.tr[u] = NewNode(l, r)\\nif l == r {\\nreturn\\n}\\nmid := (l + r) >> 1\\nst.build(u<<1, l, mid)\\nst.build(u<<1|1, mid+1, r)\\n}\\n\\nfunc (st *SegmentTree) query(u, l, r int) int {\\nif st.tr[u].l >= l && st.tr[u].r <= r {\\nreturn st.tr[u].s11\\n}\\nmid := (st.tr[u].l + st.tr[u].r) >> 1\\nans := 0\\nif r <= mid {\\nans = st.query(u<<1, l, r)\\n}\\nif l > mid {\\nans = max(ans, st.query(u<<1|1, l, r))\\n}\\nreturn ans\\n}\\n\\nfunc (st *SegmentTree) pushup(u int) {\\nleft := st.tr[u<<1]\\nright := st.tr[u<<1|1]\\nst.tr[u].s00 = max(left.s00+right.s10, left.s01+right.s00)\\nst.tr[u].s01 = max(left.s00+right.s11, left.s01+right.s01)\\nst.tr[u].s10 = max(left.s10+right.s10, left.s11+right.s00)\\nst.tr[u].s11 = max(left.s10+right.s11, left.s11+right.s01)\\n}\\n\\nfunc (st *SegmentTree) modify(u, x, v int) {\\nif st.tr[u].l == st.tr[u].r {\\nst.tr[u].s11 = max(0, v)\\nreturn\\n}\\nmid := (st.tr[u].l + st.tr[u].r) >> 1\\nif x <= mid {\\nst.modify(u<<1, x, v)\\n} else {\\nst.modify(u<<1|1, x, v)\\n}\\nst.pushup(u)\\n}\\n\\nfunc maximumSumSubsequence(nums []int, queries [][]int) (ans int) {\\nn := len(nums)\\ntree := NewSegmentTree(n)\\nfor i, x := range nums {\\ntree.modify(1, i+1, x)\\n}\\nconst mod int = 1e9 + 7\\nfor _, q := range queries {\\ntree.modify(1, q[0]+1, q[1])\\nans = (ans + tree.query(1, 1, n)) % mod\\n}\\nreturn\\n}\\n
\\n###ts
\\nclass Node {\\n s00 = 0;\\n s01 = 0;\\n s10 = 0;\\n s11 = 0;\\n\\n constructor(\\n public l: number,\\n public r: number,\\n ) {}\\n}\\n\\nclass SegmentTree {\\n tr: Node[];\\n\\n constructor(n: number) {\\n this.tr = Array(n * 4);\\n this.build(1, 1, n);\\n }\\n\\n build(u: number, l: number, r: number) {\\n this.tr[u] = new Node(l, r);\\n if (l === r) {\\n return;\\n }\\n const mid = (l + r) >> 1;\\n this.build(u << 1, l, mid);\\n this.build((u << 1) | 1, mid + 1, r);\\n }\\n\\n query(u: number, l: number, r: number): number {\\n if (this.tr[u].l >= l && this.tr[u].r <= r) {\\n return this.tr[u].s11;\\n }\\n const mid = (this.tr[u].l + this.tr[u].r) >> 1;\\n let ans = 0;\\n if (r <= mid) {\\n ans = this.query(u << 1, l, r);\\n }\\n if (l > mid) {\\n ans = Math.max(ans, this.query((u << 1) | 1, l, r));\\n }\\n return ans;\\n }\\n\\n pushup(u: number) {\\n const left = this.tr[u << 1];\\n const right = this.tr[(u << 1) | 1];\\n this.tr[u].s00 = Math.max(left.s00 + right.s10, left.s01 + right.s00);\\n this.tr[u].s01 = Math.max(left.s00 + right.s11, left.s01 + right.s01);\\n this.tr[u].s10 = Math.max(left.s10 + right.s10, left.s11 + right.s00);\\n this.tr[u].s11 = Math.max(left.s10 + right.s11, left.s11 + right.s01);\\n }\\n\\n modify(u: number, x: number, v: number) {\\n if (this.tr[u].l === this.tr[u].r) {\\n this.tr[u].s11 = Math.max(0, v);\\n return;\\n }\\n const mid = (this.tr[u].l + this.tr[u].r) >> 1;\\n if (x <= mid) {\\n this.modify(u << 1, x, v);\\n } else {\\n this.modify((u << 1) | 1, x, v);\\n }\\n this.pushup(u);\\n }\\n}\\n\\nfunction maximumSumSubsequence(nums: number[], queries: number[][]): number {\\n const n = nums.length;\\n const tree = new SegmentTree(n);\\n for (let i = 0; i < n; i++) {\\n tree.modify(1, i + 1, nums[i]);\\n }\\n let ans = 0;\\n const mod = 1e9 + 7;\\n for (const [i, x] of queries) {\\n tree.modify(1, i + 1, x);\\n ans = (ans + tree.query(1, 1, n)) % mod;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O((n + q) \\\\times \\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 表示数组 $\\\\textit{nums}$ 的长度,而 $q$ 表示查询的次数。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:线段树 根据题目描述,我们需要进行多次单点修改和区间查询,这种场景下,我们考虑使用线段树来解决。\\n\\n首先,我们定义一个 $\\\\textit{Node}$ 类,用于存储线段树的节点信息,包括左右端点 $l$ 和 $r$,以及四个状态值 $s_{00}$, $s_{01}$, $s_{10}$ 和 $s_{11}$。其中:\\n\\n$s_{00}$ 表示不包含当前节点左右端点的子序列的最大和;\\n$s_{01}$ 表示不包含当前节点左端点的子序列的最大和;\\n$s_{10}$ 表示不包含当前节点右端点的子序列的最大和;\\n$s_{11…","guid":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements//solution/python3javacgotypescript-yi-ti-yi-jie-xi-zgmo","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-31T00:50:37.589Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-不包含相邻元素的子序列的最大和🔴","url":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements/","content":"给你一个整数数组 nums
和一个二维数组 queries
,其中 queries[i] = [posi, xi]
。
对于每个查询 i
,首先将 nums[posi]
设置为 xi
,然后计算查询 i
的答案,该答案为 nums
中 不包含相邻元素 的 子序列 的 最大 和。
返回所有查询的答案之和。
\\n\\n由于最终答案可能非常大,返回其对 109 + 7
取余 的结果。
子序列 是指从另一个数组中删除一些或不删除元素而不改变剩余元素顺序得到的数组。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [3,5,9], queries = [[1,-2],[0,-3]]
\\n\\n输出:21
\\n\\n解释:
\\n执行第 1 个查询后,nums = [3,-2,9]
,不包含相邻元素的子序列的最大和为 3 + 9 = 12
。
\\n执行第 2 个查询后,nums = [-3,-2,9]
,不包含相邻元素的子序列的最大和为 9 。
示例 2:
\\n\\n输入:nums = [0,-1], queries = [[0,-5]]
\\n\\n输出:0
\\n\\n解释:
\\n执行第 1 个查询后,nums = [-5,-1]
,不包含相邻元素的子序列的最大和为 0(选择空子序列)。
\\n\\n
提示:
\\n\\n1 <= nums.length <= 5 * 104
-105 <= nums[i] <= 105
1 <= queries.length <= 5 * 104
queries[i] == [posi, xi]
0 <= posi <= nums.length - 1
-105 <= xi <= 105
思路与算法
\\n如果没有 $\\\\textit{queries}$ 中的修改操作,那么本题就是经典的「198. 打家劫舍」,可以使用动态规划进行解决。
\\n在有修改操作的情况下,每一次操作会将 $\\\\textit{nums}[\\\\textit{pos}_i]$ 的值修改为 $x_i$。由于只修改了一个元素的值,这就提示我们可以复用动态规划中的结果,即:
\\n如果我们在答案中选择了位置 $\\\\textit{pos}i$,那么相邻的位置 $\\\\textit{pos}{i-1}$ 以及 $\\\\textit{pos}{i+1}$ 均不能被选择,左右两侧分别变成 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[\\\\textit{pos}{i-2}]$,以及 $\\\\textit{nums}[\\\\textit{pos}{i+2}]$ 到 $\\\\textit{nums}[\\\\textit{pos}{n-1}]$ 的子问题;
\\n如果我们在答案中没有选择位置 $\\\\textit{pos}i$,那么没有任何限制,左右两侧分别变成 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[\\\\textit{pos}{i-1}]$,以及 $\\\\textit{nums}[\\\\textit{pos}{i+1}]$ 到 $\\\\textit{nums}[\\\\textit{pos}{n-1}]$ 的子问题。
\\n其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。这种带修改操作的「子问题」的形式,就提示我们可以使用线段树来解决。
\\n在线段树中,对于每个节点 $t_{l, r}$,它表示的区间为 $[l, r]$,对应 $\\\\textit{nums}[l]$ 到 $\\\\textit{nums}[r]$ 这个子数组。我们需要存储四个值:$t_{l, r}(x, y)$,其中 $x, y$ 的取值为 $0$ 或 $1$:
\\n接下来我们考虑如何计算这四个值。如果 $l = r$,那么 $x = 0$ 或者 $y = 0$ 时没有元素被选择,它们的值均为 $0$;当 $x = y = 1$ 时,可以选择 $\\\\textit{nums}[l]$,也可以不选择,值为 $\\\\max{\\\\textit{nums}[l], 0}$。
\\n如果 $l \\\\neq r$,那么在线段树中该节点不是叶子结点,它的两个孩子已经存储了将这个区间从中间切开后,左右两侧的子数组分别的四个值。分别考虑 $(x, y)$ 的四种情况,以 $(x, y) = (0, 0)$ 为例,左侧只能是 $(0, 0)$ 或 $(0, 1)$。如果左侧是 $(0, 0)$,那么右侧是 $(1, 0)$(这里右侧是 $(0, 0)$ 也可以,但某个边界没有任何要求的答案,一定不会差于该边界一定没有被选择的答案,因此 $(1, 0)$ 可行时就可以忽略 $(0, 0)$,后续类似的情况不重复进行解释);如果左侧是 $(0, 1)$,那么右侧是 $(0, 0)$。
\\n同理考虑剩余的三种情况,可以得到所有的结果如下:
\\n$$
\\nt_{l, r}(0, 0) = \\\\max{ L(0, 0) + R(1, 0), L(0, 1) + R(0, 0) } \\\\
\\nt_{l, r}(0, 1) = \\\\max{ L(0, 0) + R(1, 1), L(0, 1) + R(0, 1) } \\\\
\\nt_{l, r}(1, 0) = \\\\max{ L(1, 0) + R(1, 0), L(1, 1) + R(0, 0) } \\\\
\\nt_{l, r}(1, 1) = \\\\max{ L(1, 0) + R(1, 1), L(1, 1) + R(0, 1) } \\\\
\\n$$
其中 $L(\\\\cdot, \\\\cdot)$ 和 $R(\\\\cdot, \\\\cdot)$ 分别是 $t_{l, r}$ 的左孩子和右孩子的某种情况,这样就可以在 $O(1)$ 的时间得到 $t_{l, r}$ 的四个值,因此使用单点修改的线段树,就可以在 $O(\\\\log n)$ 的时间处理每一次修改操作,随后 $O(1)$ 的时间通过根节点得到修改后的答案。
\\n代码
\\n###C++
\\nstruct SegNode {\\n SegNode() {\\n v00 = v01 = v10 = v11 = 0;\\n }\\n void set(long long v) {\\n v00 = v01 = v10 = 0;\\n v11 = max(v, 0LL);\\n }\\n long long best() {\\n return v11;\\n }\\n \\n long long v00, v01, v10, v11;\\n};\\n\\nclass SegTree {\\npublic:\\n SegTree(int n): n(n), tree(n * 4 + 1) {}\\n void init(const vector<int>& nums) {\\n internal_init(nums, 1, 1, n);\\n }\\n void update(int x, int v) {\\n internal_update(1, 1, n, x + 1, v);\\n }\\n long long query() {\\n return tree[1].best();\\n }\\n\\nprivate:\\n void internal_init(const vector<int>& nums, int x, int l, int r) {\\n if (l == r) {\\n tree[x].set(nums[l - 1]);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internal_init(nums, x * 2, l, mid);\\n internal_init(nums, x * 2 + 1, mid + 1, r);\\n pushup(x);\\n }\\n void internal_update(int x, int l, int r, int pos, int v) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l == r) {\\n tree[x].set(v);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internal_update(x * 2, l, mid, pos, v);\\n internal_update(x * 2 + 1, mid + 1, r, pos, v);\\n pushup(x);\\n }\\n void pushup(int x) {\\n int l = x * 2, r = x * 2 + 1;\\n tree[x].v00 = max(tree[l].v00 + tree[r].v10, tree[l].v01 + tree[r].v00);\\n tree[x].v01 = max(tree[l].v00 + tree[r].v11, tree[l].v01 + tree[r].v01);\\n tree[x].v10 = max(tree[l].v10 + tree[r].v10, tree[l].v11 + tree[r].v00);\\n tree[x].v11 = max(tree[l].v10 + tree[r].v11, tree[l].v11 + tree[r].v01);\\n }\\n\\nprivate:\\n int n;\\n vector<SegNode> tree;\\n};\\n\\nclass Solution {\\npublic:\\n int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n SegTree tree(n);\\n tree.init(nums);\\n \\n int ans = 0;\\n for (const auto& q: queries) {\\n tree.update(q[0], q[1]);\\n ans = ((long long)ans + tree.query()) % mod;\\n }\\n return ans;\\n }\\n\\nprivate:\\n static constexpr int mod = 1000000007;\\n};\\n
\\n###Python
\\nclass SegNode:\\n def __init__(self) -> None:\\n self.v00 = self.v01 = self.v10 = self.v11 = 0\\n \\n def set_value(self, v: int) -> None:\\n self.v00 = self.v01 = self.v10 = 0\\n self.v11 = max(v, 0)\\n \\n def best(self) -> int:\\n return self.v11\\n\\nclass SegTree:\\n def __init__(self, n: int) -> None:\\n self.n = n\\n self.tree = [SegNode() for _ in range(n * 4 + 1)]\\n \\n def init(self, nums: List[int]) -> None:\\n def internal_init(x: int, l: int, r: int) -> None:\\n if l == r:\\n self.tree[x].set_value(nums[l - 1])\\n return\\n mid = (l + r) // 2\\n internal_init(x * 2, l, mid)\\n internal_init(x * 2 + 1, mid + 1, r)\\n self.pushup(x)\\n internal_init(1, 1, self.n)\\n \\n def update(self, x: int, v: int) -> None:\\n def internal_update(x: int, l: int, r: int, pos: int, v: int) -> None:\\n if l > pos or r < pos:\\n return\\n if l == r:\\n self.tree[x].set_value(v)\\n return\\n mid = (l + r) // 2\\n internal_update(x * 2, l, mid, pos, v)\\n internal_update(x * 2 + 1, mid + 1, r, pos, v)\\n self.pushup(x)\\n internal_update(1, 1, self.n, x + 1, v)\\n \\n def query(self) -> int:\\n return self.tree[1].best()\\n\\n def pushup(self, x: int) -> None:\\n tree_ = self.tree\\n\\n l, r = x * 2, x * 2 + 1\\n tree_[x].v00 = max(tree_[l].v00 + tree_[r].v10, tree_[l].v01 + tree_[r].v00)\\n tree_[x].v01 = max(tree_[l].v00 + tree_[r].v11, tree_[l].v01 + tree_[r].v01)\\n tree_[x].v10 = max(tree_[l].v10 + tree_[r].v10, tree_[l].v11 + tree_[r].v00)\\n tree_[x].v11 = max(tree_[l].v10 + tree_[r].v11, tree_[l].v11 + tree_[r].v01)\\n\\nclass Solution:\\n def maximumSumSubsequence(self, nums: List[int], queries: List[List[int]]) -> int:\\n tree = SegTree(len(nums))\\n tree.init(nums)\\n \\n ans = 0\\n for x, v in queries:\\n tree.update(x, v)\\n ans += tree.query()\\n return ans % (10**9 + 7)\\n
\\n###Java
\\nclass SegNode {\\n long v00, v01, v10, v11;\\n SegNode() {\\n v00 = v01 = v10 = v11 = 0;\\n }\\n\\n void set(long v) {\\n v00 = v01 = v10 = 0;\\n v11 = Math.max(v, 0);\\n }\\n\\n long best() {\\n return v11;\\n }\\n}\\n\\nclass SegTree {\\n int n;\\n SegNode[] tree;\\n\\n SegTree(int n) {\\n this.n = n;\\n tree = new SegNode[n * 4 + 1];\\n for (int i = 0; i < tree.length; i++) {\\n tree[i] = new SegNode();\\n }\\n }\\n\\n void init(int[] nums) {\\n internalInit(nums, 1, 1, n);\\n }\\n\\n void update(int x, int v) {\\n internalUpdate(1, 1, n, x + 1, v);\\n }\\n\\n long query() {\\n return tree[1].best();\\n }\\n\\n private void internalInit(int[] nums, int x, int l, int r) {\\n if (l == r) {\\n tree[x].set(nums[l - 1]);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internalInit(nums, x * 2, l, mid);\\n internalInit(nums, x * 2 + 1, mid + 1, r);\\n pushup(x);\\n }\\n\\n private void internalUpdate(int x, int l, int r, int pos, int v) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l == r) {\\n tree[x].set(v);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internalUpdate(x * 2, l, mid, pos, v);\\n internalUpdate(x * 2 + 1, mid + 1, r, pos, v);\\n pushup(x);\\n }\\n\\n private void pushup(int x) {\\n int l = x * 2, r = x * 2 + 1;\\n tree[x].v00 = Math.max(tree[l].v00 + tree[r].v10, tree[l].v01 + tree[r].v00);\\n tree[x].v01 = Math.max(tree[l].v00 + tree[r].v11, tree[l].v01 + tree[r].v01);\\n tree[x].v10 = Math.max(tree[l].v10 + tree[r].v10, tree[l].v11 + tree[r].v00);\\n tree[x].v11 = Math.max(tree[l].v10 + tree[r].v11, tree[l].v11 + tree[r].v01);\\n }\\n}\\n\\nclass Solution {\\n public static final int MOD = 1000000007;\\n public int maximumSumSubsequence(int[] nums, int[][] queries) {\\n int n = nums.length;\\n SegTree tree = new SegTree(n);\\n tree.init(nums);\\n\\n long ans = 0;\\n for (int[] q : queries) {\\n tree.update(q[0], q[1]);\\n ans = (ans + tree.query()) % MOD;\\n }\\n return (int) ans;\\n }\\n}\\n
\\n###C#
\\npublic class SegNode {\\n public long v00, v01, v10, v11;\\n\\n public SegNode() {\\n v00 = v01 = v10 = v11 = 0;\\n }\\n\\n public void Set(long v) {\\n v00 = v01 = v10 = 0;\\n v11 = Math.Max(v, 0);\\n }\\n\\n public long Best() {\\n return v11;\\n }\\n}\\n\\npublic class SegTree {\\n private int n;\\n private SegNode[] tree;\\n\\n public SegTree(int n) {\\n this.n = n;\\n tree = new SegNode[n * 4 + 1];\\n for (int i = 0; i < tree.Length; i++) {\\n tree[i] = new SegNode();\\n }\\n }\\n\\n public void Init(int[] nums) {\\n InternalInit(nums, 1, 1, n);\\n }\\n\\n public void Update(int x, int v) {\\n InternalUpdate(1, 1, n, x + 1, v);\\n }\\n\\n public long Query() {\\n return tree[1].Best();\\n }\\n\\n private void InternalInit(int[] nums, int x, int l, int r) {\\n if (l == r) {\\n tree[x].Set(nums[l - 1]);\\n return;\\n }\\n int mid = (l + r) / 2;\\n InternalInit(nums, x * 2, l, mid);\\n InternalInit(nums, x * 2 + 1, mid + 1, r);\\n Pushup(x);\\n }\\n\\n private void InternalUpdate(int x, int l, int r, int pos, int v) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l == r) {\\n tree[x].Set(v);\\n return;\\n }\\n int mid = (l + r) / 2;\\n InternalUpdate(x * 2, l, mid, pos, v);\\n InternalUpdate(x * 2 + 1, mid + 1, r, pos, v);\\n Pushup(x);\\n }\\n\\n private void Pushup(int x) {\\n int l = x * 2, r = x * 2 + 1;\\n tree[x].v00 = Math.Max(tree[l].v00 + tree[r].v10, tree[l].v01 + tree[r].v00);\\n tree[x].v01 = Math.Max(tree[l].v00 + tree[r].v11, tree[l].v01 + tree[r].v01);\\n tree[x].v10 = Math.Max(tree[l].v10 + tree[r].v10, tree[l].v11 + tree[r].v00);\\n tree[x].v11 = Math.Max(tree[l].v10 + tree[r].v11, tree[l].v11 + tree[r].v01);\\n }\\n}\\n\\npublic class Solution {\\n public const int MOD = 1000000007;\\n public int MaximumSumSubsequence(int[] nums, int[][] queries) {\\n int n = nums.Length;\\n SegTree tree = new SegTree(n);\\n tree.Init(nums);\\n \\n long ans = 0;\\n foreach (var q in queries) {\\n tree.Update(q[0], q[1]);\\n ans = (ans + tree.Query()) % MOD;\\n }\\n return (int)ans;\\n }\\n}\\n
\\n###Go
\\nconst MOD = 1000000007\\n\\nfunc maximumSumSubsequence(nums []int, queries [][]int) int {\\n n := len(nums)\\n tree := NewSegTree(n)\\n tree.Init(nums)\\n\\n ans := int64(0)\\n for _, q := range queries {\\n tree.Update(q[0], q[1])\\n ans = (ans + tree.Query()) % MOD\\n }\\n return int(ans)\\n}\\n\\ntype SegNode struct {\\n v00, v01, v10, v11 int64\\n}\\n\\nfunc NewSegNode() *SegNode {\\n return &SegNode{0, 0, 0, 0}\\n}\\n\\nfunc (sn *SegNode) Set(v int64) {\\n sn.v00, sn.v01, sn.v10 = 0, 0, 0\\n sn.v11 = int64(math.Max(float64(v), 0))\\n}\\n\\nfunc (sn *SegNode) Best() int64 {\\n return sn.v11\\n}\\n\\ntype SegTree struct {\\n n int\\n tree []*SegNode\\n}\\n\\nfunc NewSegTree(n int) *SegTree {\\n tree := make([]*SegNode, n * 4 + 1)\\n for i := range tree {\\n tree[i] = NewSegNode()\\n }\\n return &SegTree{n, tree}\\n}\\n\\nfunc (st *SegTree) Init(nums []int) {\\n st.internalInit(nums, 1, 1, st.n)\\n}\\n\\nfunc (st *SegTree) Update(x, v int) {\\n st.internalUpdate(1, 1, st.n, x + 1, int64(v))\\n}\\n\\nfunc (st *SegTree) Query() int64 {\\n return st.tree[1].Best()\\n}\\n\\nfunc (st *SegTree) internalInit(nums []int, x, l, r int) {\\n if l == r {\\n st.tree[x].Set(int64(nums[l - 1]))\\n return\\n }\\n mid := (l + r) / 2\\n st.internalInit(nums, x * 2, l, mid)\\n st.internalInit(nums, x * 2 + 1, mid + 1, r)\\n st.pushup(x)\\n}\\n\\nfunc (st *SegTree) internalUpdate(x, l, r int, pos int, v int64) {\\n if l > pos || r < pos {\\n return\\n }\\n if l == r {\\n st.tree[x].Set(v)\\n return\\n }\\n mid := (l + r) / 2\\n st.internalUpdate(x * 2, l, mid, pos, v)\\n st.internalUpdate(x * 2 + 1, mid + 1, r, pos, v)\\n st.pushup(x)\\n}\\n\\nfunc (st *SegTree) pushup(x int) {\\n l, r := x * 2, x * 2 + 1\\n st.tree[x].v00 = max(st.tree[l].v00 + st.tree[r].v10, st.tree[l].v01 + st.tree[r].v00)\\n st.tree[x].v01 = max(st.tree[l].v00 + st.tree[r].v11, st.tree[l].v01 + st.tree[r].v01)\\n st.tree[x].v10 = max(st.tree[l].v10 + st.tree[r].v10, st.tree[l].v11 + st.tree[r].v00)\\n st.tree[x].v11 = max(st.tree[l].v10 + st.tree[r].v11, st.tree[l].v11 + st.tree[r].v01)\\n}\\n
\\n###C
\\ntypedef struct SegNode {\\n long long v00, v01, v10, v11;\\n} SegNode;\\n\\nSegNode* segNodeCreate() {\\n SegNode* node = (SegNode*)malloc(sizeof(SegNode));\\n node->v00 = node->v01 = node->v10 = node->v11 = 0;\\n return node;\\n}\\n\\nvoid setSegNode(SegNode* node, long long v) {\\n node->v00 = node->v01 = node->v10 = 0;\\n node->v11 = fmax(v, 0LL);\\n}\\n\\nlong long bestSegNode(SegNode* node) {\\n return node->v11;\\n}\\n\\ntypedef struct SegTree {\\n int n;\\n SegNode** tree;\\n} SegTree;\\n\\nSegTree* segTreeCreate(int n) {\\n SegTree* tree = (SegTree*)malloc(sizeof(SegTree));\\n tree->n = n;\\n tree->tree = (SegNode**)malloc((n * 4 + 1) * sizeof(SegNode*));\\n for (int i = 0; i < n * 4 + 1; i++) {\\n tree->tree[i] = segNodeCreate();\\n }\\n return tree;\\n}\\n\\nvoid freeSegTree(SegTree* tree) {\\n for (int i = 0; i <= tree->n * 4; i++) {\\n free(tree->tree[i]);\\n }\\n free(tree->tree);\\n free(tree);\\n}\\n\\nvoid initSegTree(SegTree* tree, int* nums) {\\n internalInit(tree, nums, 1, 1, tree->n);\\n}\\n\\nvoid updateSegTree(SegTree* tree, int x, int v) {\\n internalUpdate(tree, 1, 1, tree->n, x + 1, v);\\n}\\n\\nlong long querySegTree(SegTree* tree) {\\n return bestSegNode(tree->tree[1]);\\n}\\n\\nvoid internalInit(SegTree* tree, int* nums, int x, int l, int r) {\\n if (l == r) {\\n setSegNode(tree->tree[x], nums[l - 1]);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internalInit(tree, nums, x * 2, l, mid);\\n internalInit(tree, nums, x * 2 + 1, mid + 1, r);\\n pushup(tree, x);\\n}\\n\\nvoid internalUpdate(SegTree* tree, int x, int l, int r, int pos, int v) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l == r) {\\n setSegNode(tree->tree[x], v);\\n return;\\n }\\n int mid = (l + r) / 2;\\n internalUpdate(tree, x * 2, l, mid, pos, v);\\n internalUpdate(tree, x * 2 + 1, mid + 1, r, pos, v);\\n pushup(tree, x);\\n}\\n\\nvoid pushup(SegTree* tree, int x) {\\n int l = x * 2, r = x * 2 + 1;\\n tree->tree[x]->v00 = fmax(tree->tree[l]->v00 + tree->tree[r]->v10, tree->tree[l]->v01 + tree->tree[r]->v00);\\n tree->tree[x]->v01 = fmax(tree->tree[l]->v00 + tree->tree[r]->v11, tree->tree[l]->v01 + tree->tree[r]->v01);\\n tree->tree[x]->v10 = fmax(tree->tree[l]->v10 + tree->tree[r]->v10, tree->tree[l]->v11 + tree->tree[r]->v00);\\n tree->tree[x]->v11 = fmax(tree->tree[l]->v10 + tree->tree[r]->v11, tree->tree[l]->v11 + tree->tree[r]->v01);\\n}\\n\\n#define MOD 1000000007\\n\\nint maximumSumSubsequence(int* nums, int numsSize, int** queries, int queriesSize, int* queriesColSize) {\\n SegTree* tree = segTreeCreate(numsSize);\\n initSegTree(tree, nums);\\n\\n long long ans = 0;\\n for (int i = 0; i < queriesSize; i++) {\\n updateSegTree(tree, queries[i][0], queries[i][1]);\\n ans = (ans + querySegTree(tree)) % MOD;\\n }\\n freeSegTree(tree);\\n return (int)ans;\\n}\\n
\\n###JavaScript
\\nclass SegNode {\\n constructor() {\\n this.v00 = this.v01 = this.v10 = this.v11 = 0;\\n }\\n\\n set(v) {\\n this.v00 = this.v01 = this.v10 = 0;\\n this.v11 = Math.max(v, 0);\\n }\\n\\n best() {\\n return this.v11;\\n }\\n}\\n\\nclass SegTree {\\n constructor(n) {\\n this.n = n;\\n this.tree = Array.from({ length: n * 4 + 1 }, () => new SegNode());\\n }\\n\\n init(nums) {\\n this.internalInit(nums, 1, 1, this.n);\\n }\\n\\n update(x, v) {\\n this.internalUpdate(1, 1, this.n, x + 1, v);\\n }\\n\\n query() {\\n return this.tree[1].best();\\n }\\n\\n internalInit(nums, x, l, r) {\\n if (l === r) {\\n this.tree[x].set(nums[l - 1]);\\n return;\\n }\\n const mid = Math.floor((l + r) / 2);\\n this.internalInit(nums, x * 2, l, mid);\\n this.internalInit(nums, x * 2 + 1, mid + 1, r);\\n this.pushup(x);\\n }\\n\\n internalUpdate(x, l, r, pos, v) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l === r) {\\n this.tree[x].set(v);\\n return;\\n }\\n const mid = Math.floor((l + r) / 2);\\n this.internalUpdate(x * 2, l, mid, pos, v);\\n this.internalUpdate(x * 2 + 1, mid + 1, r, pos, v);\\n this.pushup(x);\\n }\\n\\n pushup(x) {\\n const l = x * 2, r = x * 2 + 1;\\n this.tree[x].v00 = Math.max(this.tree[l].v00 + this.tree[r].v10, this.tree[l].v01 + this.tree[r].v00);\\n this.tree[x].v01 = Math.max(this.tree[l].v00 + this.tree[r].v11, this.tree[l].v01 + this.tree[r].v01);\\n this.tree[x].v10 = Math.max(this.tree[l].v10 + this.tree[r].v10, this.tree[l].v11 + this.tree[r].v00);\\n this.tree[x].v11 = Math.max(this.tree[l].v10 + this.tree[r].v11, this.tree[l].v11 + this.tree[r].v01);\\n }\\n}\\n\\nconst MOD = 1000000007;\\n\\nvar maximumSumSubsequence = function(nums, queries) {\\n const n = nums.length;\\n const tree = new SegTree(n);\\n tree.init(nums);\\n\\n let ans = 0;\\n for (const q of queries) {\\n tree.update(q[0], q[1]);\\n ans = (ans + tree.query()) % MOD;\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nconst MOD = 1000000007;\\n\\nfunction maximumSumSubsequence(nums: number[], queries: number[][]): number {\\n const n = nums.length;\\n const tree = new SegTree(n);\\n tree.init(nums);\\n\\n let ans = 0;\\n for (const q of queries) {\\n tree.update(q[0], q[1]);\\n ans = (ans + tree.query()) % MOD;\\n }\\n return ans;\\n};\\n\\nclass SegNode {\\n v00: number;\\n v01: number;\\n v10: number;\\n v11: number;\\n\\n constructor() {\\n this.v00 = this.v01 = this.v10 = this.v11 = 0;\\n }\\n\\n set(v: number) {\\n this.v00 = this.v01 = this.v10 = 0;\\n this.v11 = Math.max(v, 0);\\n }\\n\\n best(): number {\\n return this.v11;\\n }\\n}\\n\\nclass SegTree {\\n n: number;\\n tree: SegNode[];\\n\\n constructor(n: number) {\\n this.n = n;\\n this.tree = Array.from({ length: n * 4 + 1 }, () => new SegNode());\\n }\\n\\n init(nums: number[]) {\\n this.internalInit(nums, 1, 1, this.n);\\n }\\n\\n update(x: number, v: number) {\\n this.internalUpdate(1, 1, this.n, x + 1, v);\\n }\\n\\n query(): number {\\n return this.tree[1].best();\\n }\\n\\n private internalInit(nums: number[], x: number, l: number, r: number) {\\n if (l === r) {\\n this.tree[x].set(nums[l - 1]);\\n return;\\n }\\n const mid = Math.floor((l + r) / 2);\\n this.internalInit(nums, x * 2, l, mid);\\n this.internalInit(nums, x * 2 + 1, mid + 1, r);\\n this.pushup(x);\\n }\\n\\n private internalUpdate(x: number, l: number, r: number, pos: number, v: number) {\\n if (l > pos || r < pos) {\\n return;\\n }\\n if (l === r) {\\n this.tree[x].set(v);\\n return;\\n }\\n const mid = Math.floor((l + r) / 2);\\n this.internalUpdate(x * 2, l, mid, pos, v);\\n this.internalUpdate(x * 2 + 1, mid + 1, r, pos, v);\\n this.pushup(x);\\n }\\n\\n private pushup(x: number) {\\n const l = x * 2, r = x * 2 + 1;\\n this.tree[x].v00 = Math.max(this.tree[l].v00 + this.tree[r].v10, this.tree[l].v01 + this.tree[r].v00);\\n this.tree[x].v01 = Math.max(this.tree[l].v00 + this.tree[r].v11, this.tree[l].v01 + this.tree[r].v01);\\n this.tree[x].v10 = Math.max(this.tree[l].v10 + this.tree[r].v10, this.tree[l].v11 + this.tree[r].v00);\\n this.tree[x].v11 = Math.max(this.tree[l].v10 + this.tree[r].v11, this.tree[l].v11 + this.tree[r].v01);\\n }\\n}\\n
\\n###Rust
\\n#[derive(Clone)]\\nstruct SegNode {\\n v00: i64,\\n v01: i64,\\n v10: i64,\\n v11: i64,\\n}\\n\\nimpl SegNode {\\n fn new() -> Self {\\n Self { v00: 0, v01: 0, v10: 0, v11: 0 }\\n }\\n\\n fn set(&mut self, v: i64) {\\n self.v00 = 0;\\n self.v01 = 0;\\n self.v10 = 0;\\n self.v11 = v.max(0);\\n }\\n\\n fn best(&self) -> i64 {\\n self.v11\\n }\\n}\\n\\nstruct SegTree {\\n n: usize,\\n tree: Vec<SegNode>,\\n}\\n\\nimpl SegTree {\\n fn new(n: usize) -> Self {\\n let tree = vec![SegNode::new(); n * 4 + 1];\\n Self { n, tree }\\n }\\n\\n fn init(&mut self, nums: &[i32]) {\\n self.internal_init(nums, 1, 1, self.n);\\n }\\n\\n fn update(&mut self, x: usize, v: i32) {\\n self.internal_update(1, 1, self.n, x + 1, v as i64);\\n }\\n\\n fn query(&self) -> i64 {\\n self.tree[1].best()\\n }\\n\\n fn internal_init(&mut self, nums: &[i32], x: usize, l: usize, r: usize) {\\n if l == r {\\n self.tree[x].set(nums[l - 1] as i64);\\n return;\\n }\\n let mid = (l + r) / 2;\\n self.internal_init(nums, x * 2, l, mid);\\n self.internal_init(nums, x * 2 + 1, mid + 1, r);\\n self.pushup(x);\\n }\\n\\n fn internal_update(&mut self, x: usize, l: usize, r: usize, pos: usize, v: i64) {\\n if l > pos || r < pos {\\n return;\\n }\\n if l == r {\\n self.tree[x].set(v);\\n return;\\n }\\n let mid = (l + r) / 2;\\n self.internal_update(x * 2, l, mid, pos, v);\\n self.internal_update(x * 2 + 1, mid + 1, r, pos, v);\\n self.pushup(x);\\n }\\n\\n fn pushup(&mut self, x: usize) {\\n let l = x * 2;\\n let r = x * 2 + 1;\\n self.tree[x].v00 = (self.tree[l].v00 + self.tree[r].v10).max(self.tree[l].v01 + self.tree[r].v00);\\n self.tree[x].v01 = (self.tree[l].v00 + self.tree[r].v11).max(self.tree[l].v01 + self.tree[r].v01);\\n self.tree[x].v10 = (self.tree[l].v10 + self.tree[r].v10).max(self.tree[l].v11 + self.tree[r].v00);\\n self.tree[x].v11 = (self.tree[l].v10 + self.tree[r].v11).max(self.tree[l].v11 + self.tree[r].v01);\\n }\\n}\\n\\nconst MOD: i64 = 1_000_000_007;\\n\\nimpl Solution {\\n pub fn maximum_sum_subsequence(nums: Vec<i32>, queries: Vec<Vec<i32>>) -> i32 {\\n let n = nums.len();\\n let mut tree = SegTree::new(n);\\n tree.init(&nums);\\n\\n let mut ans = 0;\\n for q in queries {\\n tree.update(q[0] as usize, q[1]);\\n ans = (ans + tree.query()) % MOD;\\n }\\n ans as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + q\\\\log n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$q$ 是数组 $\\\\textit{queries}$ 的长度。线段树初始化的时间为 $O(n)$,每次修改操作需要 $O(\\\\log n)$ 的时间。
\\n空间复杂度:$O(n)$,即为线段树需要使用的空间。
\\n我们可以从左到右遍历字符串 $\\\\textit{s}$,对于每一对相邻的数字,如果它们具有相同的奇偶性且前一个数字大于后一个数字,那么我们就交换这两个数字,使得字符串 $\\\\textit{s}$ 的字典序变小,然后返回交换后的字符串。
\\n遍历结束后,如果没有找到可以交换的数字对,说明字符串 $\\\\textit{s}$ 已经是字典序最小的,直接返回即可。
\\n###python
\\nclass Solution:\\n def getSmallestString(self, s: str) -> str:\\n for i, (a, b) in enumerate(pairwise(map(ord, s))):\\n if (a + b) % 2 == 0 and a > b:\\n return s[:i] + s[i + 1] + s[i] + s[i + 2 :]\\n return s\\n
\\n###java
\\nclass Solution {\\n public String getSmallestString(String s) {\\n char[] cs = s.toCharArray();\\n int n = cs.length;\\n for (int i = 1; i < n; ++i) {\\n char a = cs[i - 1], b = cs[i];\\n if (a > b && a % 2 == b % 2) {\\n cs[i] = a;\\n cs[i - 1] = b;\\n return new String(cs);\\n }\\n }\\n return s;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string getSmallestString(string s) {\\n int n = s.length();\\n for (int i = 1; i < n; ++i) {\\n char a = s[i - 1], b = s[i];\\n if (a > b && a % 2 == b % 2) {\\n s[i - 1] = b;\\n s[i] = a;\\n break;\\n }\\n }\\n return s;\\n }\\n};\\n
\\n###go
\\nfunc getSmallestString(s string) string {\\ncs := []byte(s)\\nn := len(cs)\\nfor i := 1; i < n; i++ {\\na, b := cs[i-1], cs[i]\\nif a > b && a%2 == b%2 {\\ncs[i-1], cs[i] = b, a\\nreturn string(cs)\\n}\\n}\\nreturn s\\n}\\n
\\n###ts
\\nfunction getSmallestString(s: string): string {\\n const n = s.length;\\n const cs: string[] = s.split(\'\');\\n for (let i = 1; i < n; ++i) {\\n const a = cs[i - 1];\\n const b = cs[i];\\n if (a > b && +a % 2 === +b % 2) {\\n cs[i - 1] = b;\\n cs[i] = a;\\n return cs.join(\'\');\\n }\\n }\\n return s;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为字符串 $\\\\textit{s}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:贪心 + 模拟 我们可以从左到右遍历字符串 $\\\\textit{s}$,对于每一对相邻的数字,如果它们具有相同的奇偶性且前一个数字大于后一个数字,那么我们就交换这两个数字,使得字符串 $\\\\textit{s}$ 的字典序变小,然后返回交换后的字符串。\\n\\n遍历结束后,如果没有找到可以交换的数字对,说明字符串 $\\\\textit{s}$ 已经是字典序最小的,直接返回即可。\\n\\n###python\\n\\nclass Solution:\\n def getSmallestString(self, s: str) -> str:\\n for i, (a, b…","guid":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap//solution/python3javacgotypescript-yi-ti-yi-jie-ta-i946","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-30T01:05:09.083Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-交换后字典序最小的字符串🟢","url":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap/","content":"给你一个仅由数字组成的字符串 s
,在最多交换一次 相邻 且具有相同 奇偶性 的数字后,返回可以得到的字典序最小的字符串。
如果两个数字都是奇数或都是偶数,则它们具有相同的奇偶性。例如,5 和 9、2 和 4 奇偶性相同,而 6 和 9 奇偶性不同。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: s = \\"45320\\"
\\n\\n输出: \\"43520\\"
\\n\\n解释:
\\n\\ns[1] == \'5\'
和 s[2] == \'3\'
都具有相同的奇偶性,交换它们可以得到字典序最小的字符串。
示例 2:
\\n\\n输入: s = \\"001\\"
\\n\\n输出: \\"001\\"
\\n\\n解释:
\\n\\n无需进行交换,因为 s
已经是字典序最小的。
\\n\\n
提示:
\\n\\n2 <= s.length <= 100
s
仅由数字组成。从低位到高位枚举 $n$ 和 $k$ 的二进制位,分别记为 $b_n$ 和 $b_k$:
\\n如果 $b_n = 0$ 且 $b_k = 1$,那么说明无法将 $n$ 改成 $k$,返回 $-1$。
\\n如果 $b_n = 1$ 且 $b_k = 0$,那么需要将对应的 $b_n$ 改成 $0$,并且将更改次数计入总更改次数。
\\n最后返回总更改次数。
\\n###C++
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n int res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n }\\n};\\n
\\n###C
\\nint minChanges(int n, int k) {\\n int res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n}\\n
\\n###Go
\\nfunc minChanges(n int, k int) int {\\n var res int\\n for n > 0 || k > 0 {\\n if (n & 1) == 0 && (k & 1) == 1 {\\n return -1\\n }\\n if (n & 1) == 1 && (k & 1) == 0 {\\n res++\\n }\\n n >>= 1\\n k >>= 1\\n }\\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n res = 0\\n while n > 0 or k > 0:\\n if (n & 1) == 0 and (k & 1) == 1:\\n return -1\\n if (n & 1) == 1 and (k & 1) == 0:\\n res += 1\\n n >>= 1\\n k >>= 1\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n int res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinChanges(int n, int k) {\\n int res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n }\\n}\\n
\\n###JavaScript
\\nvar minChanges = function(n, k) {\\n let res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction minChanges(n: number, k: number): number {\\n let res = 0;\\n while (n > 0 || k > 0) {\\n if ((n & 1) == 0 && (k & 1) == 1) {\\n return -1;\\n }\\n if ((n & 1) == 1 && (k & 1) == 0) {\\n res++;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_changes(n: i32, k: i32) -> i32 {\\n let mut res = 0;\\n let mut n = n;\\n let mut k = k;\\n\\n while n > 0 || k > 0 {\\n if (n & 1) == 0 && (k & 1) == 1 {\\n return -1;\\n }\\n if (n & 1) == 1 && (k & 1) == 0 {\\n res += 1;\\n }\\n n >>= 1;\\n k >>= 1;\\n }\\n\\n return res;\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(\\\\log \\\\max(n, k))$。
\\n空间复杂度:$O(1)$。
\\n如果 $n$ 将一些值为 $1$ 的二进制位改成 $0$ 得到的整数等于 $k$,那么从集合的角度来看(值为 $1$ 的二进制位为集合的元素),$k$ 是 $n$ 的子集,用位运算来表示:
\\n$$
\\nn \\\\And k = k
\\n$$
如果 $n & k \\\\neq k$,那么说明无法实现,直接返回 $-1$。我们获取存在于 $n$ 中但不存在于 $k$ 的元素集,位运算表示为 $n \\\\oplus k$,然后我们统计 $n \\\\oplus k$ 中位为 $1$ 的个数。
\\n\\n\\n大部分语言都自带统计二进制数中 $1$ 的数目的库函数,没有库函数的语言可以参考题解「1342. 将数字变成 0 的操作次数」。
\\n
###C++
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n return (n & k) == k ? __builtin_popcount(n ^ k) : -1;\\n }\\n};\\n
\\n###C
\\nint minChanges(int n, int k) {\\n return (n & k) == k ? __builtin_popcount(n ^ k) : -1;\\n}\\n
\\n###Go
\\nfunc minChanges(n int, k int) int {\\n if (n & k) == k {\\n return bits.OnesCount(uint(n ^ k))\\n } else {\\n return -1\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return (n ^ k).bit_count() if (n & k) == k else -1\\n
\\n###Java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (n & k) == k ? Integer.bitCount(n ^ k) : -1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinChanges(int n, int k) {\\n return (n & k) == k ? BitCount(n ^ k) : -1;\\n }\\n\\n public int BitCount(int x) {\\n x = (x & 0x55555555) + ((x >> 1) & 0x55555555);\\n x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\\n x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);\\n x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);\\n x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);\\n return x;\\n }\\n}\\n
\\n###JavaScript
\\nvar minChanges = function(n, k) {\\n if ((n & k) == k) {\\n return bitCount(n ^ k);\\n } else {\\n return -1;\\n }\\n};\\n\\nvar bitCount = function(x) {\\n x = (x & 0x55555555) + ((x >> 1) & 0x55555555);\\n x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\\n x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);\\n x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);\\n x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);\\n return x;\\n}\\n
\\n###TypeScript
\\nfunction minChanges(n: number, k: number): number {\\n if ((n & k) == k) {\\n return bitCount(n ^ k);\\n } else {\\n return -1;\\n }\\n};\\n\\nfunction bitCount(x: number): number {\\n x = (x & 0x55555555) + ((x >> 1) & 0x55555555);\\n x = (x & 0x33333333) + ((x >> 2) & 0x33333333);\\n x = (x & 0x0F0F0F0F) + ((x >> 4) & 0x0F0F0F0F);\\n x = (x & 0x00FF00FF) + ((x >> 8) & 0x00FF00FF);\\n x = (x & 0x0000FFFF) + ((x >> 16) & 0x0000FFFF);\\n return x;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_changes(n: i32, k: i32) -> i32 {\\n if n & k == k {\\n (n ^ k).count_ones() as i32\\n } else {\\n -1\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n题目给定了两个长度为 $n$ 的数组 $\\\\textit{energyDrinkA}$ 和 $\\\\textit{energyDrinkB}$(我们这里简称 $A$ 和 $B$),你需要从左到右依次取 $n$ 个数字,每次可以从 $A$ 取或者从 $B$ 取,但如果上一次(第 $i - 1$ 次)是从 $A$ 取的,那么第 $i$ 次要切换就必须暂停一次,接着在第 $i + 1$ 次可以从 $B$ 取。求可以取得的数字之和的最大值。
\\n可以发现每次取数所得的和只与前面一步怎么决策有关,如果上一步切换,那么计算和的时候从上两步计算,否则从上一步计算,因此我们设计 $d[i][0/1]$ 分别表示第 $i$ 步从 $A$ 或者从 $B$ 取数时,可以获得的最大值。这样一来,我们可以得到相应的转移方程:
\\n$$d[i][0] = \\\\max(d[i - 1][0], d[i - 2][1]) + A[i]$$
\\n$$d[i][1] = \\\\max(d[i - 1][1], d[i - 2][0]) + B[i]$$
注意 $i$ 在 $1$ 和 $2$ 时的特殊情况(起始下标设置为 $1$)。
\\n最终我们选择 $\\\\max(d[n][0], d[n][1])$ 作为答案。
代码
\\n###C++
\\nclass Solution {\\npublic:\\n using ll = long long;\\n long long maxEnergyBoost(vector<int>& energyDrinkA, vector<int>& energyDrinkB) {\\n int n = energyDrinkA.size();\\n vector<vector<ll>> d(n + 1, vector<ll>(2, 0));\\n for (int i = 1; i <= n; i++) {\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i][0] = max(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = max(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n }\\n }\\n return max(d[n][0], d[n][1]);\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n n = len(energyDrinkA)\\n d = [[0, 0] for _ in range(n + 1)]\\n for i in range(1, n + 1):\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if i >= 2:\\n d[i][0] = max(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = max(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n return max(d[n][0], d[n][1])\\n
\\n###Java
\\nclass Solution {\\n public long maxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {\\n int n = energyDrinkA.length;\\n long[][] d = new long[n + 1][2];\\n for (int i = 1; i <= n; i++) {\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i][0] = Math.max(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = Math.max(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n }\\n }\\n return Math.max(d[n][0], d[n][1]);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaxEnergyBoost(int[] energyDrinkA, int[] energyDrinkB) {\\n int n = energyDrinkA.Length;\\n long[,] d = new long[n + 1, 2];\\n for (int i = 1; i <= n; i++) {\\n d[i, 0] = d[i - 1, 0] + energyDrinkA[i - 1];\\n d[i, 1] = d[i - 1, 1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i, 0] = Math.Max(d[i, 0], d[i - 2, 1] + energyDrinkA[i - 1]);\\n d[i, 1] = Math.Max(d[i, 1], d[i - 2, 0] + energyDrinkB[i - 1]);\\n }\\n }\\n return Math.Max(d[n, 0], d[n, 1]);\\n }\\n}\\n
\\n###Go
\\nfunc maxEnergyBoost(energyDrinkA []int, energyDrinkB []int) int64 {\\n n := len(energyDrinkA)\\n d := make([][2]int64, n + 1)\\n for i := 1; i <= n; i++ {\\n d[i][0] = d[i-1][0] + int64(energyDrinkA[i-1])\\n d[i][1] = d[i-1][1] + int64(energyDrinkB[i-1])\\n if i >= 2 {\\n d[i][0] = max(d[i][0], d[i - 2][1] + int64(energyDrinkA[i - 1]))\\n d[i][1] = max(d[i][1], d[i - 2][0] + int64(energyDrinkB[i - 1]))\\n }\\n }\\n return max(d[n][0], d[n][1])\\n}\\n
\\n###C
\\nlong long maxEnergyBoost(int* energyDrinkA, int energyDrinkASize, int* energyDrinkB, int energyDrinkBSize) {\\n int n = energyDrinkASize;\\n long long d[n + 1][2];\\n for (int i = 0; i <= n; i++) {\\n d[i][0] = 0;\\n d[i][1] = 0;\\n }\\n for (int i = 1; i <= n; i++) {\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i][0] = fmax(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = fmax(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n }\\n }\\n return fmax(d[n][0], d[n][1]);\\n}\\n
\\n###JavaScript
\\nvar maxEnergyBoost = function(energyDrinkA, energyDrinkB) {\\n const n = energyDrinkA.length;\\n const d = Array.from({ length: n + 1 }, () => [0, 0]);\\n for (let i = 1; i <= n; i++) {\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i][0] = Math.max(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = Math.max(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n }\\n }\\n return Math.max(d[n][0], d[n][1]);\\n};\\n
\\n###TypeScript
\\nfunction maxEnergyBoost(energyDrinkA: number[], energyDrinkB: number[]): number {\\n const n = energyDrinkA.length;\\n const d: number[][] = Array.from({ length: n + 1 }, () => [0, 0]);\\n for (let i = 1; i <= n; i++) {\\n d[i][0] = d[i - 1][0] + energyDrinkA[i - 1];\\n d[i][1] = d[i - 1][1] + energyDrinkB[i - 1];\\n if (i >= 2) {\\n d[i][0] = Math.max(d[i][0], d[i - 2][1] + energyDrinkA[i - 1]);\\n d[i][1] = Math.max(d[i][1], d[i - 2][0] + energyDrinkB[i - 1]);\\n }\\n }\\n return Math.max(d[n][0], d[n][1]);\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_energy_boost(energy_drink_a: Vec<i32>, energy_drink_b: Vec<i32>) -> i64 {\\n let n = energy_drink_a.len();\\n let mut d = vec![vec![0; 2]; n + 1];\\n for i in 1..=n {\\n d[i][0] = d[i - 1][0] + energy_drink_a[i - 1] as i64;\\n d[i][1] = d[i - 1][1] + energy_drink_b[i - 1] as i64;\\n if i >= 2 {\\n d[i][0] = d[i][0].max(d[i - 2][1] + energy_drink_a[i - 1] as i64);\\n d[i][1] = d[i][1].max(d[i - 2][0] + energy_drink_b[i - 1] as i64);\\n }\\n }\\n d[n][0].max(d[n][1])\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{energyDrinkA}$ 的长度。我们共有 $O(n)$ 个状态,每个状态的转移复杂度为 $O(1)$,因此总的时间复杂度为 $O(n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{energyDrinkA}$ 的长度。维护状态的数组空间复杂度为 $O(n)$。
\\n我们可以枚举长度为 $n$ 的二进制字符串的每个位置 $i$,然后对于每个位置 $i$,我们可以枚举其可以取的值 $j$,如果 $j$ 为 $0$,那么我们需要判断其前一个位置是否为 $1$,如果为 $1$,则可以继续递归下去,否则不合法,如果 $j$ 为 $1$,则直接递归下去。
\\n###python
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n def dfs(i: int):\\n if i >= n:\\n ans.append(\\"\\".join(t))\\n return\\n for j in range(2):\\n if (j == 0 and (i == 0 or t[i - 1] == \\"1\\")) or j == 1:\\n t.append(str(j))\\n dfs(i + 1)\\n t.pop()\\n\\n ans = []\\n t = []\\n dfs(0)\\n return ans\\n
\\n###java
\\nclass Solution {\\n private List<String> ans = new ArrayList<>();\\n private StringBuilder t = new StringBuilder();\\n private int n;\\n\\n public List<String> validStrings(int n) {\\n this.n = n;\\n dfs(0);\\n return ans;\\n }\\n\\n private void dfs(int i) {\\n if (i >= n) {\\n ans.add(t.toString());\\n return;\\n }\\n for (int j = 0; j < 2; ++j) {\\n if ((j == 0 && (i == 0 || t.charAt(i - 1) == \'1\')) || j == 1) {\\n t.append(j);\\n dfs(i + 1);\\n t.deleteCharAt(t.length() - 1);\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<string> validStrings(int n) {\\n vector<string> ans;\\n string t;\\n auto dfs = [&](auto&& dfs, int i) {\\n if (i >= n) {\\n ans.emplace_back(t);\\n return;\\n }\\n for (int j = 0; j < 2; ++j) {\\n if ((j == 0 && (i == 0 || t[i - 1] == \'1\')) || j == 1) {\\n t.push_back(\'0\' + j);\\n dfs(dfs, i + 1);\\n t.pop_back();\\n }\\n }\\n };\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc validStrings(n int) (ans []string) {\\nt := []byte{}\\nvar dfs func(int)\\ndfs = func(i int) {\\nif i >= n {\\nans = append(ans, string(t))\\nreturn\\n}\\nfor j := 0; j < 2; j++ {\\nif (j == 0 && (i == 0 || t[i-1] == \'1\')) || j == 1 {\\nt = append(t, byte(\'0\'+j))\\ndfs(i + 1)\\nt = t[:len(t)-1]\\n}\\n}\\n}\\ndfs(0)\\nreturn\\n}\\n
\\n###ts
\\nfunction validStrings(n: number): string[] {\\n const ans: string[] = [];\\n const t: string[] = [];\\n const dfs = (i: number) => {\\n if (i >= n) {\\n ans.push(t.join(\'\'));\\n return;\\n }\\n for (let j = 0; j < 2; ++j) {\\n if ((j == 0 && (i == 0 || t[i - 1] == \'1\')) || j == 1) {\\n t.push(j.toString());\\n dfs(i + 1);\\n t.pop();\\n }\\n }\\n };\\n dfs(0);\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times 2^n)$,其中 $n$ 为字符串长度。忽略答案数组的空间消耗,空间复杂度 $O(n)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:DFS 我们可以枚举长度为 $n$ 的二进制字符串的每个位置 $i$,然后对于每个位置 $i$,我们可以枚举其可以取的值 $j$,如果 $j$ 为 $0$,那么我们需要判断其前一个位置是否为 $1$,如果为 $1$,则可以继续递归下去,否则不合法,如果 $j$ 为 $1$,则直接递归下去。\\n\\n###python\\n\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n def dfs(i: int):\\n if i >= n:…","guid":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros//solution/python3javacgotypescript-yi-ti-yi-jie-df-t97v","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-29T00:20:53.492Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-生成不含相邻零的二进制字符串🟡","url":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros/","content":"给你一个正整数 n
。
如果一个二进制字符串 x
的所有长度为 2 的子字符串中包含 至少 一个 \\"1\\"
,则称 x
是一个 有效 字符串。
返回所有长度为 n
的 有效 字符串,可以以任意顺序排列。
\\n\\n
示例 1:
\\n\\n输入: n = 3
\\n\\n输出: [\\"010\\",\\"011\\",\\"101\\",\\"110\\",\\"111\\"]
\\n\\n解释:
\\n\\n长度为 3 的有效字符串有:\\"010\\"
、\\"011\\"
、\\"101\\"
、\\"110\\"
和 \\"111\\"
。
示例 2:
\\n\\n输入: n = 1
\\n\\n输出: [\\"0\\",\\"1\\"]
\\n\\n解释:
\\n\\n长度为 1 的有效字符串有:\\"0\\"
和 \\"1\\"
。
\\n\\n
提示:
\\n\\n1 <= n <= 18
在本问题中,有根树指满足以下条件的 有向 图。该树只有一个根节点,所有其他节点都是该根节点的后继。该树除了根节点之外的每一个节点都有且只有一个父节点,而根节点没有父节点。
\\n\\n输入一个有向图,该图由一个有着 n
个节点(节点值不重复,从 1
到 n
)的树及一条附加的有向边构成。附加的边包含在 1
到 n
中的两个不同顶点间,这条附加的边不属于树中已存在的边。
结果图是一个以边组成的二维数组 edges
。 每个元素是一对 [ui, vi]
,用以表示 有向 图中连接顶点 ui
和顶点 vi
的边,其中 ui
是 vi
的一个父节点。
返回一条能删除的边,使得剩下的图是有 n
个节点的有根树。若有多个答案,返回最后出现在给定二维数组的答案。
\\n\\n
示例 1:
\\n输入:edges = [[1,2],[1,3],[2,3]]\\n输出:[2,3]\\n\\n\\n
示例 2:
\\n输入:edges = [[1,2],[2,3],[3,4],[4,1],[1,5]]\\n输出:[4,1]\\n\\n\\n
\\n\\n
提示:
\\n\\nn == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ui, vi <= n
\\n\\nProblem: 3335. 字符串转换后的长度 I
\\n
[TOC]
\\n遍历计算,有点暴力,能通过。
\\n执行用时分布139ms击败100.00%;消耗内存分布11.07MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def lengthAfterTransformations(self, s: str, t: int) -> int:\\n lst = [0] * 26\\n for k, v in Counter(s).items(): lst[ord(k) - 97] = v\\n for _ in range(t): lst = [lst[-1], (lst[-1] + lst[0]) % 1000000007] + lst[1: -1]\\n return sum(lst) % 1000000007\\n
\\n###C
\\nint lengthAfterTransformations(char* s, int t) {\\n int lst[26] = {0}, z, i, ans = 0;\\n while (* s) \\n ++ lst[* s ++ - \'a\'];\\n while (t --) {\\n for (i = 25, z = lst[25]; i > 1; -- i) \\n lst[i] = lst[i - 1]; \\n lst[1] = (lst[0] + z) % 1000000007;\\n lst[0] = z;\\n }\\n for (i = 0; i < 26; ++ i) \\n ans = (ans + lst[i]) % 1000000007;\\n return ans;\\n}\\n
\\n","description":"Problem: 3335. 字符串转换后的长度 I [TOC]\\n\\n遍历计算,有点暴力,能通过。\\n\\n执行用时分布139ms击败100.00%;消耗内存分布11.07MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def lengthAfterTransformations(self, s: str, t: int) -> int:\\n lst = [0] * 26\\n for k, v in Counter(s).items(): lst[ord(k) - 97] = v…","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-i//solution/shu-xue-ji-suan-bao-li-tong-guo-by-admir-4cc4","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T11:03:48.357Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"递推 & 矩阵快速幂","url":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/di-tui-ju-zhen-kuai-su-mi-by-tsreaper-ydn9","content":"递推的部分与 字符串转换后的长度 I 原理相同。维护 $f(i, c)$ 表示操作 $i$ 次以后,字符 $c$ 在字符串中出现了几次。转移方程为:
\\n$$
\\nf(i, c) = \\\\sum\\\\limits_{j = 1}^{a_c} f(i - 1, c - j)
\\n$$
不过即使用了前缀和优化,计算转移方程的复杂度还是 $\\\\mathcal{O}(n + t|\\\\Sigma|)$ 的。本题 $t$ 非常大,怎么办呢?
\\n这里就用到一个常见优化:线性递推方程,可以利用矩阵快速幂优化复杂度。没有学习过矩阵快速幂的朋友可以搜索一下,这里做一个简单的介绍。
\\n举个例子,我们先把 $|\\\\Sigma|$ 缩小到 $3$,且规定 $a_c = 2$,那么递推方程就是:
\\n$$
\\nf(i, 0) = f(i - 1, 1) + f(i - 1, 2) \\\\
\\nf(i, 1) = f(i - 1, 2) + f(i - 1, 0) \\\\
\\nf(i, 2) = f(i - 1, 0) + f(i - 1, 1)
\\n$$
如果用矩阵乘法表示以上递推方程,可以写成
\\n$$
\\n\\\\begin{bmatrix}
\\nf(i, 0) & f(i, 1) & f(i, 2)
\\n\\\\end{bmatrix}
=
\\n\\\\begin{bmatrix}
\\nf(i - 1, 0) & f(i - 1, 1) & f(i - 1, 2)
\\n\\\\end{bmatrix}
\\\\times
\\n\\\\begin{bmatrix}
\\n0 & 1 & 1 \\\\
\\n1 & 0 & 1 \\\\
\\n1 & 1 & 0
\\n\\\\end{bmatrix}
\\n$$
记 $M_i = \\\\begin{bmatrix} f(i, 0) & f(i, 1) & f(i, 2) \\\\end{bmatrix}$,把最右边的 01 矩阵记为 $K$,则上式可以写成
\\n$$
\\nM_i = M_{i - 1} \\\\times K
\\n$$
把 $M_{i - 1}$ 一直展开成初值 $M_0$,我们有
\\n$$
\\nM_i = M_0 \\\\times K^i
\\n$$
我们要求的答案就是 $M_t$ 里的元素之和,所以我们只要构造出矩阵 $K$,用快速幂求出 $K^t$ ,再用一次矩阵乘法把它和 $M_0$ 乘起来即可。本题中矩阵是 $|\\\\Sigma| \\\\times |\\\\Sigma|$ 的,所以矩阵乘法套上快速幂的复杂度为 $\\\\mathcal{O}(|\\\\Sigma|^3 \\\\log t)$,整体复杂度再加个 $n$ 即可。
\\n###cpp
\\n#define MOD ((int) 1e9 + 7)\\n\\n// 矩阵快速幂模板开始\\n\\nstruct Matrix {\\n int n, m;\\n long long A[26][26];\\n\\n Matrix(int n, int m): n(n), m(m) { memset(A, 0, sizeof(A)); }\\n\\n Matrix operator*(const Matrix &o) const {\\n Matrix r(n, o.m);\\n for (int k = 0; k < m; k++) for (int i = 0; i < n; i++) for (int j = 0; j < o.m; j++)\\n r.A[i][j] = (r.A[i][j] + A[i][k] * o.A[k][j]) % MOD;\\n return r;\\n }\\n};\\n\\nMatrix power(Matrix a, long long b) {\\n Matrix y(a.n, a.m);\\n for (int i = 0; i < y.n; i++) y.A[i][i] = 1;\\n for (; b; b >>= 1) {\\n if (b & 1) y = y * a;\\n a = a * a;\\n }\\n return y;\\n}\\n\\n// 矩阵快速幂模板结束\\n\\nclass Solution {\\npublic:\\n int lengthAfterTransformations(string s, int t, vector<int>& nums) {\\n // 构造系数矩阵\\n Matrix k(26, 26);\\n for (int i = 0; i < 26; i++) for (int j = 1; j <= nums[i]; j++) k.A[i][(i + j) % 26] = 1;\\n // 矩阵快速幂求 K^t\\n k = power(k, t);\\n Matrix v(1, 26);\\n for (char c : s) v.A[0][c - \'a\']++;\\n // 把系数矩阵和 M_0 乘起来\\n v = v * k;\\n long long ans = 0;\\n for (int i = 0; i < 26; i++) ans = (ans + v.A[0][i]) % MOD;\\n return ans;\\n }\\n};\\n
\\n","description":"解法:递推 & 矩阵快速幂 递推的部分与 字符串转换后的长度 I 原理相同。维护 $f(i, c)$ 表示操作 $i$ 次以后,字符 $c$ 在字符串中出现了几次。转移方程为:\\n\\n$$\\n f(i, c) = \\\\sum\\\\limits_{j = 1}^{a_c} f(i - 1, c - j)\\n $$\\n\\n不过即使用了前缀和优化,计算转移方程的复杂度还是 $\\\\mathcal{O}(n + t|\\\\Sigma|)$ 的。本题 $t$ 非常大,怎么办呢?\\n\\n这里就用到一个常见优化:线性递推方程,可以利用矩阵快速幂优化复杂度。没有学习过矩阵快速幂的朋友可以搜索一下…","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/di-tui-ju-zhen-kuai-su-mi-by-tsreaper-ydn9","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T04:25:01.099Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"递推","url":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-i//solution/di-tui-by-tsreaper-j54j","content":"因为我们只关心字符串最后的长度,所以字符串里每个位置具体是什么字符不关键。
\\n维护 $f(i, c)$ 表示操作 $i$ 次以后,字符 $c$ 在字符串中出现了几次。根据题意,递推方程为:
\\n$$
\\nf(i, \\\\text{a\'}) = f(i - 1, \\\\text{
z\'}) \\\\
\\nf(i, \\\\text{b\'}) = f(i - 1, \\\\text{
a\'}) + f(i - 1, \\\\text{`z\'}) \\\\
\\nf(i, c) = f(i - 1, c - 1)
\\n$$
直接计算递推方程的复杂度是 $\\\\mathcal{O}(n + t|\\\\Sigma|)$ 的,其中 $|\\\\Sigma| = 26$ 是字符集大小。
\\n###cpp
\\nclass Solution {\\npublic:\\n int lengthAfterTransformations(string s, int t) {\\n const int MOD = 1e9 + 7;\\n long long cnt[26] = {0};\\n for (char c : s) cnt[c - \'a\']++;\\n for (int i = 1; i <= t; i++) {\\n int nxt[26] = {0};\\n for (int j = 0; j < 25; j++) nxt[j + 1] = cnt[j];\\n nxt[0] = (nxt[0] + cnt[25]) % MOD;\\n nxt[1] = (nxt[1] + cnt[25]) % MOD;\\n for (int j = 0; j < 26; j++) cnt[j] = nxt[j];\\n }\\n long long ans = 0;\\n for (int i = 0; i < 26; i++) ans = (ans + cnt[i]) % MOD;\\n return ans;\\n }\\n};\\n
\\n","description":"解法:递推 因为我们只关心字符串最后的长度,所以字符串里每个位置具体是什么字符不关键。\\n\\n维护 $f(i, c)$ 表示操作 $i$ 次以后,字符 $c$ 在字符串中出现了几次。根据题意,递推方程为:\\n\\n$$\\n f(i, \\\\text{a\'}) = f(i - 1, \\\\text{z\'}) \\\\\\n f(i, \\\\text{b\'}) = f(i - 1, \\\\text{a\'}) + f(i - 1, \\\\text{`z\'}) \\\\\\n f(i, c) = f(i - 1, c - 1)\\n $$\\n\\n直接计算递推方程的复杂度是 $\\\\mathcal{O}(n + t|\\\\Sigma|)$ 的…","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-i//solution/di-tui-by-tsreaper-j54j","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T04:23:53.131Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"矩阵快速幂优化 DP(Python/NumPy/Java/C++/Go)","url":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-i//solution/zhuang-tai-ji-dp-ju-zhen-kuai-su-mi-you-6rmqh","content":"本题相当于周赛第四题中的
\\n$$
\\n\\\\textit{nums}=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2]
\\n$$
请看 我的题解。
\\n","description":"本题相当于周赛第四题中的 $$\\n \\\\textit{nums}=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2]\\n $$\\n\\n请看 我的题解。","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-i//solution/zhuang-tai-ji-dp-ju-zhen-kuai-su-mi-you-6rmqh","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T04:09:42.833Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"计数 + 矩阵快速幂","url":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/ji-shu-ju-zhen-kuai-su-mi-by-mipha-2022-kw9p","content":"计数 + 矩阵快速幂","description":"计数 + 矩阵快速幂","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/ji-shu-ju-zhen-kuai-su-mi-by-mipha-2022-kw9p","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T04:09:21.518Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"矩阵快速幂优化 DP(Python/NumPy/Java/C++/Go)","url":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/ju-zhen-kuai-su-mi-you-hua-dppythonjavac-cd2j","content":"先求出单个字母 $\\\\texttt{a},\\\\texttt{b},\\\\ldots,\\\\texttt{z}$ 替换 $t$ 次后的长度。
\\n例如字母 $\\\\texttt{a}$ 替换一次变成 $\\\\texttt{b}$ 和 $\\\\texttt{c}$,问题变成计算 $\\\\texttt{b}$ 替换 $t-1$ 次后的长度,$\\\\texttt{c}$ 替换 $t-1$ 次后的长度。二者之和即为 $\\\\texttt{a}$ 替换 $t$ 次后的长度。
\\n据此,定义 $f[i][j]$ 表示字母 $j$ 替换 $i$ 次后的长度。
\\n上面的例子,就是 $f[i][0] = f[i-1][1] + f[i-1][2]$。
\\n一般地,设 $c=\\\\textit{nums}[i]$,我们有
\\n$$
\\nf[i][j] = \\\\sum_{k=j+1}^{j+c} f[i-1][k\\\\bmod 26]
\\n$$
初始值 $f[0][j] = 1$。
\\n答案为 $\\\\sum\\\\limits_{j=0}^{25} f[t][j]\\\\cdot \\\\textit{cnt}[j]$。其中 $\\\\textit{cnt}[j]$ 为 $s$ 中的字母 $j$ 的出现次数。
\\n直接计算这个 DP 的话,时间复杂度为 $\\\\mathcal{O}(t|\\\\Sigma|)$,这可以解决周赛第二题。对于本题,还需继续优化。
\\n以示例 1 为例(也相当于周赛第二题),其中 $\\\\textit{nums}=[1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,2]$。
\\n我们有
\\n$$
\\n\\\\begin{aligned}
\\nf[i][0] &= f[i-1][1] \\\\
\\nf[i][1] &= f[i-1][2] \\\\
\\nf[i][2] &= f[i-1][3] \\\\
\\n\\\\vdots \\\\
\\nf[i][23] &= f[i-1][24] \\\\
\\nf[i][24] &= f[i-1][25] \\\\
\\nf[i][25] &= f[i-1][0] + f[i-1][1] \\\\
\\n\\\\end{aligned}
\\n$$
用矩阵乘法表示,即
\\n$$
\\n\\\\begin{bmatrix}
\\nf[i][0] \\\\
\\nf[i][1] \\\\
\\nf[i][2] \\\\
\\n\\\\vdots \\\\
\\nf[i][23] \\\\
\\nf[i][24] \\\\
\\nf[i][25] \\\\
\\n\\\\end{bmatrix}
\\n= \\\\begin{bmatrix}
\\n0 & 1 & 0 & 0 & \\\\cdots & 0 & 0 \\\\
\\n0 & 0 & 1 & 0 & \\\\cdots & 0 & 0 \\\\
\\n0 & 0 & 0 & 1 & \\\\cdots & 0 & 0 \\\\
\\n\\\\vdots & \\\\vdots & \\\\vdots & \\\\vdots & \\\\ddots & \\\\vdots & \\\\vdots \\\\
\\n0 & 0 & 0 & 0 & \\\\cdots & 1 & 0 \\\\
\\n0 & 0 & 0 & 0 & \\\\cdots & 0 & 1 \\\\
\\n1 & 1 & 0 & 0 & \\\\cdots & 0 & 0 \\\\
\\n\\\\end{bmatrix}
\\n\\\\begin{bmatrix}
\\nf[i-1][0] \\\\
\\nf[i-1][1] \\\\
\\nf[i-1][2] \\\\
\\n\\\\vdots \\\\
\\nf[i-1][23] \\\\
\\nf[i-1][24] \\\\
\\nf[i-1][25] \\\\
\\n\\\\end{bmatrix}
\\n$$
把上式中的三个矩阵分别记作 $F[i],M,F[i-1]$,即
\\n$$
\\nF[i] = M\\\\times F[i-1]
\\n$$
那么有
\\n$$
\\n\\\\begin{aligned}
\\nF[t] ={} & M\\\\times F[t-1] \\\\
\\n={} & M\\\\times M\\\\times F[t-2] \\\\
\\n={} & M\\\\times M\\\\times M\\\\times F[t-3] \\\\
\\n\\\\vdots & \\\\
\\n={} & M^t\\\\times F[0]
\\n\\\\end{aligned}
\\n$$
其中 $F[0]$ 是一个长为 $26$ 的列向量,值全为 $1$(对应着 $f$ 数组的初始值 $f[0][j] = 1$)。
\\n$M^t$ 可以用快速幂计算,原理请看【图解】一张图秒懂快速幂。
\\n根据矩阵乘法的运算法则,$f[t][j]$ 等于矩阵 $M^t$ 的第 $j$ 行与列向量 $F[0]$ 计算点积。由于 $F[0]$ 全为 $1$,所以 $f[t][j]$ 也等于 $M^t$ 第 $j$ 行的元素和。
\\n一般地,枚举 $i=0,1,\\\\ldots, 25$ 以及 $j=i+1,i+2,\\\\ldots,i+\\\\textit{nums}[i]$,初始化 $M[i][j\\\\bmod 26]=1$。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nMOD = 1_000_000_007\\n\\n# a @ b,其中 @ 是矩阵乘法\\ndef mul(a: List[List[int]], b: List[List[int]]) -> List[List[int]]:\\n return [[sum(x * y for x, y in zip(row, col)) % MOD for col in zip(*b)]\\n for row in a]\\n\\n# a^n @ f0\\ndef pow_mul(a: List[List[int]], n: int, f0: List[List[int]]) -> List[List[int]]:\\n res = f0\\n while n:\\n if n & 1:\\n res = mul(a, res)\\n a = mul(a, a)\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def lengthAfterTransformations(self, s: str, t: int, nums: List[int]) -> int:\\n SIZE = 26\\n f0 = [[1] for _ in range(SIZE)]\\n m = [[0] * SIZE for _ in range(SIZE)]\\n for i, c in enumerate(nums):\\n for j in range(i + 1, i + c + 1):\\n m[i][j % SIZE] = 1\\n mt = pow_mul(m, t, f0)\\n\\n ans = 0\\n for ch, cnt in Counter(s).items():\\n ans += mt[ord(ch) - ord(\'a\')][0] * cnt\\n return ans % MOD\\n
\\n###py
\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\n# a^n @ f0\\ndef pow_mul(a: np.ndarray, n: int, f0: np.ndarray) -> np.ndarray:\\n res = f0\\n while n:\\n if n & 1:\\n res = a @ res % MOD\\n a = a @ a % MOD\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def lengthAfterTransformations(self, s: str, t: int, nums: List[int]) -> int:\\n SIZE = 26\\n f0 = np.ones((SIZE,), dtype=object)\\n m = np.zeros((SIZE, SIZE), dtype=object)\\n for i, c in enumerate(nums):\\n for j in range(i + 1, i + c + 1):\\n m[i, j % SIZE] = 1\\n mt = pow_mul(m, t, f0)\\n\\n ans = 0\\n for ch, cnt in Counter(s).items():\\n ans += mt[ord(ch) - ord(\'a\')] * cnt\\n return ans % MOD\\n
\\n###py
\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\n# f0 @ a^n\\ndef pow_mul(f0: np.ndarray, a: np.ndarray, n: int) -> np.ndarray:\\n res = f0\\n while n:\\n if n & 1:\\n res = res @ a % MOD\\n a = a @ a % MOD\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def lengthAfterTransformations(self, s: str, t: int, nums: List[int]) -> int:\\n cnt = Counter(s)\\n f0 = np.array([cnt[c] for c in ascii_lowercase], dtype=object)\\n\\n SIZE = 26\\n m = np.zeros((SIZE, SIZE), dtype=object)\\n for i, c in enumerate(nums):\\n for j in range(i + 1, i + c + 1):\\n m[i, j % SIZE] = 1\\n\\n mt = pow_mul(f0, m, t)\\n return np.sum(mt) % MOD\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int lengthAfterTransformations(String s, int t, List<Integer> nums) {\\n final int SIZE = 26;\\n\\n int[][] f0 = new int[SIZE][1];\\n for (int i = 0; i < SIZE; i++) {\\n f0[i][0] = 1;\\n }\\n\\n int[][] m = new int[SIZE][SIZE];\\n for (int i = 0; i < SIZE; i++) {\\n int c = nums.get(i);\\n for (int j = i + 1; j <= i + c; j++) {\\n m[i][j % SIZE] = 1;\\n }\\n }\\n\\n int[][] mt = powMul(m, t, f0);\\n\\n int[] cnt = new int[SIZE];\\n for (char c : s.toCharArray()) {\\n cnt[c - \'a\']++;\\n }\\n\\n long ans = 0;\\n for (int i = 0; i < SIZE; i++) {\\n ans += (long) mt[i][0] * cnt[i];\\n }\\n return (int) (ans % MOD);\\n }\\n\\n // a^n * f0\\n private int[][] powMul(int[][] a, int n, int[][] f0) {\\n int[][] res = f0;\\n while (n > 0) {\\n if ((n & 1) > 0) {\\n res = mul(a, res);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n }\\n\\n // 返回矩阵 a 和矩阵 b 相乘的结果\\n private int[][] mul(int[][] a, int[][] b) {\\n int[][] c = new int[a.length][b[0].length];\\n for (int i = 0; i < a.length; i++) {\\n for (int k = 0; k < a[i].length; k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < b[k].length; j++) {\\n c[i][j] = (int) ((c[i][j] + (long) a[i][k] * b[k][j]) % MOD);\\n }\\n }\\n }\\n return c;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n static constexpr int MOD = 1\'000\'000\'007;\\n static constexpr int SIZE = 26;\\n\\n using Matrix = array<array<int, SIZE>, SIZE>;\\n\\n // 返回矩阵 a 和矩阵 b 相乘的结果\\n Matrix mul(Matrix& a, Matrix& b) {\\n Matrix c{};\\n for (int i = 0; i < SIZE; i++) {\\n for (int k = 0; k < SIZE; k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < SIZE; j++) {\\n c[i][j] = (c[i][j] + (long long) a[i][k] * b[k][j]) % MOD;\\n }\\n }\\n }\\n return c;\\n }\\n\\n // 返回 n 个矩阵 a 相乘的结果\\n Matrix pow(Matrix a, int n) {\\n Matrix res = {};\\n for (int i = 0; i < SIZE; i++) {\\n res[i][i] = 1; // 单位矩阵\\n }\\n while (n) {\\n if (n & 1) {\\n res = mul(res, a);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n }\\n\\npublic:\\n int lengthAfterTransformations(string s, int t, vector<int>& nums) {\\n Matrix m{};\\n for (int i = 0; i < SIZE; i++) {\\n for (int j = i + 1; j <= i + nums[i]; j++) {\\n m[i][j % SIZE] = 1;\\n }\\n }\\n Matrix mt = pow(m, t);\\n\\n int cnt[SIZE]{};\\n for (char c : s) {\\n cnt[c - \'a\']++;\\n }\\n\\n long long ans = 0;\\n for (int i = 0; i < SIZE; i++) {\\n // m 第 i 行的和就是 f[t][i]\\n ans += reduce(mt[i].begin(), mt[i].end(), 0LL) * cnt[i];\\n }\\n return ans % MOD;\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\ntype matrix [][]int\\n\\nfunc newMatrix(n, m int) matrix {\\na := make(matrix, n)\\nfor i := range a {\\na[i] = make([]int, m)\\n}\\nreturn a\\n}\\n\\nfunc (a matrix) mul(b matrix) matrix {\\nc := newMatrix(len(a), len(b[0]))\\nfor i, row := range a {\\nfor k, x := range row {\\nif x == 0 {\\ncontinue\\n}\\nfor j, y := range b[k] {\\nc[i][j] = (c[i][j] + x*y) % mod\\n}\\n}\\n}\\nreturn c\\n}\\n\\n// a^n * f0\\nfunc (a matrix) powMul(n int, f0 matrix) matrix {\\nres := f0\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = a.mul(res)\\n}\\na = a.mul(a)\\n}\\nreturn res\\n}\\n\\nfunc lengthAfterTransformations(s string, t int, nums []int) (ans int) {\\nconst size = 26\\nf0 := newMatrix(size, 1)\\nfor i := range f0 {\\nf0[i][0] = 1\\n}\\nm := newMatrix(size, size)\\nfor i, c := range nums {\\nfor j := i + 1; j <= i+c; j++ {\\nm[i][j%size] = 1\\n}\\n}\\nmt := m.powMul(t, f0)\\n\\ncnt := [26]int{}\\nfor _, c := range s {\\ncnt[c-\'a\']++\\n}\\nfor i, row := range mt {\\nans += row[0] * cnt[i]\\n}\\nreturn ans % mod\\n}\\n
\\n更多相似题目,见下面动态规划题单中的「§11.6 矩阵快速幂优化 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"先求出单个字母 $\\\\texttt{a},\\\\texttt{b},\\\\ldots,\\\\texttt{z}$ 替换 $t$ 次后的长度。 寻找子问题\\n\\n例如字母 $\\\\texttt{a}$ 替换一次变成 $\\\\texttt{b}$ 和 $\\\\texttt{c}$,问题变成计算 $\\\\texttt{b}$ 替换 $t-1$ 次后的长度,$\\\\texttt{c}$ 替换 $t-1$ 次后的长度。二者之和即为 $\\\\texttt{a}$ 替换 $t$ 次后的长度。\\n\\n状态定义和状态转移方程\\n\\n据此,定义 $f[i][j]$ 表示字母 $j$ 替换 $i$ 次后的长度。\\n\\n上面的例子,就是…","guid":"https://leetcode.cn/problems/total-characters-in-string-after-transformations-ii//solution/ju-zhen-kuai-su-mi-you-hua-dppythonjavac-cd2j","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-27T04:07:29.792Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-冗余连接🟡","url":"https://leetcode.cn/problems/redundant-connection/","content":"树可以看成是一个连通且 无环 的 无向 图。
\\n\\n给定往一棵 n
个节点 (节点值 1~n
) 的树中添加一条边后的图。添加的边的两个顶点包含在 1
到 n
中间,且这条附加的边不属于树中已存在的边。图的信息记录于长度为 n
的二维数组 edges
,edges[i] = [ai, bi]
表示图中在 ai
和 bi
之间存在一条边。
请找出一条可以删去的边,删除后可使得剩余部分是一个有着 n
个节点的树。如果有多个答案,则返回数组 edges
中最后出现的那个。
\\n\\n
示例 1:
\\n\\n输入: edges = [[1,2], [1,3], [2,3]]\\n输出: [2,3]\\n\\n\\n
示例 2:
\\n\\n输入: edges = [[1,2], [2,3], [3,4], [1,4], [1,5]]\\n输出: [1,4]\\n\\n\\n
\\n\\n
提示:
\\n\\nn == edges.length
3 <= n <= 1000
edges[i].length == 2
1 <= ai < bi <= edges.length
ai != bi
edges
中无重复元素给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 x
为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :
[0, n - 1]
中选择一个 未标记 的下标 i
。rewardValues[i]
大于 你当前的总奖励 x
,则将 rewardValues[i]
加到 x
上(即 x = x + rewardValues[i]
),并 标记 下标 i
。以整数形式返回执行最优操作能够获得的 最大 总奖励。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:rewardValues = [1,1,3,3]
\\n\\n输出:4
\\n\\n解释:
\\n\\n依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。
\\n示例 2:
\\n\\n输入:rewardValues = [1,6,4,3,2]
\\n\\n输出:11
\\n\\n解释:
\\n\\n依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。
\\n\\n\\n
提示:
\\n\\n1 <= rewardValues.length <= 5 * 104
1 <= rewardValues[i] <= 5 * 104
我们可以对奖励值数组 rewardValues
进行排序,然后使用记忆化搜索的方法求解最大总奖励。
我们定义一个函数 $\\\\textit{dfs}(x)$,表示当前总奖励为 $x$ 时,能够获得的最大总奖励。那么答案为 $\\\\textit{dfs}(0)$。
\\n函数 $\\\\textit{dfs}(x)$ 的执行过程如下:
\\nrewardValues
中第一个大于 $x$ 的元素的下标 $i$;rewardValues
中从下标 $i$ 开始的元素,对于每个元素 $v$,计算 $v + \\\\textit{dfs}(x + v)$ 的最大值。为了避免重复计算,我们使用记忆化数组 f
记录已经计算过的结果。
###python
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n @cache\\n def dfs(x: int) -> int:\\n i = bisect_right(rewardValues, x)\\n ans = 0\\n for v in rewardValues[i:]:\\n ans = max(ans, v + dfs(x + v))\\n return ans\\n\\n rewardValues.sort()\\n return dfs(0)\\n
\\n###java
\\nclass Solution {\\n private int[] nums;\\n private Integer[] f;\\n\\n public int maxTotalReward(int[] rewardValues) {\\n nums = rewardValues;\\n Arrays.sort(nums);\\n int n = nums.length;\\n f = new Integer[nums[n - 1] << 1];\\n return dfs(0);\\n }\\n\\n private int dfs(int x) {\\n if (f[x] != null) {\\n return f[x];\\n }\\n int i = Arrays.binarySearch(nums, x + 1);\\n i = i < 0 ? -i - 1 : i;\\n int ans = 0;\\n for (; i < nums.length; ++i) {\\n ans = Math.max(ans, nums[i] + dfs(x + nums[i]));\\n }\\n return f[x] = ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n sort(rewardValues.begin(), rewardValues.end());\\n int n = rewardValues.size();\\n int f[rewardValues.back() << 1];\\n memset(f, -1, sizeof(f));\\n function<int(int)> dfs = [&](int x) {\\n if (f[x] != -1) {\\n return f[x];\\n }\\n auto it = upper_bound(rewardValues.begin(), rewardValues.end(), x);\\n int ans = 0;\\n for (; it != rewardValues.end(); ++it) {\\n ans = max(ans, rewardValues[it - rewardValues.begin()] + dfs(x + *it));\\n }\\n return f[x] = ans;\\n };\\n return dfs(0);\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nsort.Ints(rewardValues)\\nn := len(rewardValues)\\nf := make([]int, rewardValues[n-1]<<1)\\nfor i := range f {\\nf[i] = -1\\n}\\nvar dfs func(int) int\\ndfs = func(x int) int {\\nif f[x] != -1 {\\nreturn f[x]\\n}\\ni := sort.SearchInts(rewardValues, x+1)\\nf[x] = 0\\nfor _, v := range rewardValues[i:] {\\nf[x] = max(f[x], v+dfs(x+v))\\n}\\nreturn f[x]\\n}\\nreturn dfs(0)\\n}\\n
\\n###ts
\\nfunction maxTotalReward(rewardValues: number[]): number {\\n rewardValues.sort((a, b) => a - b);\\n const search = (x: number): number => {\\n let [l, r] = [0, rewardValues.length];\\n while (l < r) {\\n const mid = (l + r) >> 1;\\n if (rewardValues[mid] > x) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n };\\n const f: number[] = Array(rewardValues.at(-1)! << 1).fill(-1);\\n const dfs = (x: number): number => {\\n if (f[x] !== -1) {\\n return f[x];\\n }\\n let ans = 0;\\n for (let i = search(x); i < rewardValues.length; ++i) {\\n ans = Math.max(ans, rewardValues[i] + dfs(x + rewardValues[i]));\\n }\\n return (f[x] = ans);\\n };\\n return dfs(0);\\n}\\n
\\n时间复杂度 $O(n \\\\times (\\\\log n + M))$,空间复杂度 $O(M)$。其中 $n$ 是数组 rewardValues
的长度,而 $M$ 是数组 rewardValues
中的最大值的两倍。
我们定义 $f[i][j]$ 表示用前 $i$ 个奖励值,能否得到总奖励 $j$。初始时 $f[0][0] = \\\\textit{True}$,其余值均为 $\\\\textit{False}$。
\\n我们考虑第 $i$ 个奖励值 $v$,如果我们不选择它,那么 $f[i][j] = f[i - 1][j]$;如果我们选择它,那么 $f[i][j] = f[i - 1][j - v]$,其中 $0 \\\\leq j - v \\\\lt v$。即状态转移方程为:
\\n$$
\\nf[i][j] = f[i - 1][j] \\\\vee f[i - 1][j - v]
\\n$$
最终答案为 $\\\\max{j \\\\mid f[n][j] = \\\\textit{True}}$。
\\n由于 $f[i][j]$ 只与 $f[i - 1][j]$ 和 $f[i - 1][j - v]$ 有关,我们可以优化掉第一维,只使用一个一维数组进行状态转移。
\\n###python
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n nums = sorted(set(rewardValues))\\n m = nums[-1] << 1\\n f = [False] * m\\n f[0] = True\\n for v in nums:\\n for j in range(m):\\n if 0 <= j - v < v:\\n f[j] |= f[j - v]\\n ans = m - 1\\n while not f[ans]:\\n ans -= 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int[] nums = Arrays.stream(rewardValues).distinct().sorted().toArray();\\n int n = nums.length;\\n int m = nums[n - 1] << 1;\\n boolean[] f = new boolean[m];\\n f[0] = true;\\n for (int v : nums) {\\n for (int j = 0; j < m; ++j) {\\n if (0 <= j - v && j - v < v) {\\n f[j] |= f[j - v];\\n }\\n }\\n }\\n int ans = m - 1;\\n while (!f[ans]) {\\n --ans;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n sort(rewardValues.begin(), rewardValues.end());\\n rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());\\n int n = rewardValues.size();\\n int m = rewardValues.back() << 1;\\n bool f[m];\\n memset(f, false, sizeof(f));\\n f[0] = true;\\n for (int v : rewardValues) {\\n for (int j = 1; j < m; ++j) {\\n if (0 <= j - v && j - v < v) {\\n f[j] = f[j] || f[j - v];\\n }\\n }\\n }\\n int ans = m - 1;\\n while (!f[ans]) {\\n --ans;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nslices.Sort(rewardValues)\\nnums := slices.Compact(rewardValues)\\nn := len(nums)\\nm := nums[n-1] << 1\\nf := make([]bool, m)\\nf[0] = true\\nfor _, v := range nums {\\nfor j := 1; j < m; j++ {\\nif 0 <= j-v && j-v < v {\\nf[j] = f[j] || f[j-v]\\n}\\n}\\n}\\nans := m - 1\\nfor !f[ans] {\\nans--\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction maxTotalReward(rewardValues: number[]): number {\\n const nums = Array.from(new Set(rewardValues)).sort((a, b) => a - b);\\n const n = nums.length;\\n const m = nums[n - 1] << 1;\\n const f: boolean[] = Array(m).fill(false);\\n f[0] = true;\\n for (const v of nums) {\\n for (let j = 1; j < m; ++j) {\\n if (0 <= j - v && j - v < v) {\\n f[j] = f[j] || f[j - v];\\n }\\n }\\n }\\n let ans = m - 1;\\n while (!f[ans]) {\\n --ans;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times M)$,空间复杂度 $O(M)$。其中 $n$ 是数组 rewardValues
的长度,而 $M$ 是数组 rewardValues
中的最大值的两倍。
我们可以对方法二进行优化,定义一个二进制数 $f$ 保存当前的状态,其中 $f$ 的第 $i$ 位为 $1$ 表示当前总奖励为 $i$ 是可达的。
\\n观察方法二的状态转移方程 $f[j] = f[j] \\\\vee f[j - v]$,这相当于取 $f$ 的低 $v$ 位,再左移 $v$ 位,然后与原来的 $f$ 进行或运算。
\\n那么答案为 $f$ 的最高位的位置。
\\n###python
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n nums = sorted(set(rewardValues))\\n f = 1\\n for v in nums:\\n f |= (f & ((1 << v) - 1)) << v\\n return f.bit_length() - 1\\n
\\n###java
\\nimport java.math.BigInteger;\\nimport java.util.Arrays;\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int[] nums = Arrays.stream(rewardValues).distinct().sorted().toArray();\\n BigInteger f = BigInteger.ONE;\\n for (int v : nums) {\\n BigInteger mask = BigInteger.ONE.shiftLeft(v).subtract(BigInteger.ONE);\\n BigInteger shifted = f.and(mask).shiftLeft(v);\\n f = f.or(shifted);\\n }\\n return f.bitLength() - 1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n sort(rewardValues.begin(), rewardValues.end());\\n rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());\\n bitset<100000> f{1};\\n for (int v : rewardValues) {\\n int shift = f.size() - v;\\n f |= f << shift >> (shift - v);\\n }\\n for (int i = rewardValues.back() * 2 - 1;; i--) {\\n if (f.test(i)) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues)\\none := big.NewInt(1)\\nf := big.NewInt(1)\\np := new(big.Int)\\nfor _, v := range rewardValues {\\nmask := p.Sub(p.Lsh(one, uint(v)), one)\\nf.Or(f, p.Lsh(p.And(f, mask), uint(v)))\\n}\\nreturn f.BitLen() - 1\\n}\\n
\\n时间复杂度 $O(n \\\\times M / w)$,空间复杂度 $O(n + M / w)$。其中 $n$ 是数组 rewardValues
的长度,而 $M$ 是数组 rewardValues
中的最大值的两倍。整数 $w = 32$ 或 $64$。
有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:排序 + 记忆化搜索 + 二分查找 我们可以对奖励值数组 rewardValues 进行排序,然后使用记忆化搜索的方法求解最大总奖励。\\n\\n我们定义一个函数 $\\\\textit{dfs}(x)$,表示当前总奖励为 $x$ 时,能够获得的最大总奖励。那么答案为 $\\\\textit{dfs}(0)$。\\n\\n函数 $\\\\textit{dfs}(x)$ 的执行过程如下:\\n\\n二分查找数组 rewardValues 中第一个大于 $x$ 的元素的下标 $i$;\\n遍历数组 rewardValues 中从下标 $i$ 开始的元素,对于每个元素 $v$,计算 $v…","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/python3javacgotypescript-yi-ti-san-jie-p-xzgg","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-25T00:13:53.858Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-执行操作可获得的最大总奖励 I🟡","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i/","content":"给你一个整数数组 rewardValues
,长度为 n
,代表奖励的值。
最初,你的总奖励 x
为 0,所有下标都是 未标记 的。你可以执行以下操作 任意次 :
[0, n - 1]
中选择一个 未标记 的下标 i
。rewardValues[i]
大于 你当前的总奖励 x
,则将 rewardValues[i]
加到 x
上(即 x = x + rewardValues[i]
),并 标记 下标 i
。以整数形式返回执行最优操作能够获得的 最大 总奖励。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:rewardValues = [1,1,3,3]
\\n\\n输出:4
\\n\\n解释:
\\n\\n依次标记下标 0 和 2,总奖励为 4,这是可获得的最大值。
\\n示例 2:
\\n\\n输入:rewardValues = [1,6,4,3,2]
\\n\\n输出:11
\\n\\n解释:
\\n\\n依次标记下标 0、2 和 1。总奖励为 11,这是可获得的最大值。
\\n\\n\\n
提示:
\\n\\n1 <= rewardValues.length <= 2000
1 <= rewardValues[i] <= 2000
思路
\\n字符串所有长度为 $2$ 的子字符串都包含至少一个 $1’$,则要求字符串不能有连续的 $
0’$。
设计递归函数 $\\\\textit{dfs}$,输入是表示当前的字符串,如果当前字符串长度为 $n$,那么将当前字符串加入结果数组中。否则需要考虑当前字符串末尾元素,如果末尾元素为 $0’$,则在末尾添加一个 $
0’$,并继续递归;其他情况下(字符串为空,或者末尾元素为 $1’$),则可以在末尾添加一个 $
0’$ 或者 $`1’$,并继续递归;递归的起点为空字符串。递归结束后,返回结果数组即可。在实现上,可以用数组代替递归函数输入的字符串,数组的元素是单个字符。要将字符串加入结果数组时,可以将输入的数组的元素进行连接后加入数组。需要在字符串末尾添加元素时,则需要在数组末尾添加元素,并在递归结束后删除末尾元素。递归的起点为空数组。
接下来讨论下时间复杂度。宽松地估计,时间复杂度是 $O(n\\\\times 2^n)$,因为字符串每一位可以为 $0’$ 或 $
1’$,最后拼接字符串需要消耗 $O(n)$。不过我们可以有更准确地估计。
考虑输入较小时,结果数组的长度。当 $n$ 分别为 $1$,$2$,$3$,$4$,$5$ 时,结果的长度分别为 $2$,$3$,$5$,$8$,$13$,可以猜测数组的长度呈现斐波那契数列的规律。假设 $n-2$ 时,结果中,末尾为 $0’$ 的字符串有 $a$ 个,末尾为 $
1’$ 的字符串有 $b$ 个;$n-1$ 时,结果中,末尾为 $0’$ 的字符串有 $c$ 个,末尾为 $
1’$ 的字符串有 $d$ 个。那么 $n$ 时,结果中就会有 $c+2\\\\times d$ 个字符串,而 $d = a+b$,因此结果中会有 $a+b+c+d$ 个字符串,正好是 $n-2$ 时结果中字符串个数与 $n-1$ 时结果中字符串个数之和。因此,类似于斐波那契数列的通项公式,我们可以得到结果中字符串的个数为 $\\\\frac{(\\\\frac{1+\\\\sqrt{5}}{2})^{n+2}-(\\\\frac{1-\\\\sqrt{5}}{2})^{n+2}}{\\\\sqrt{5}}$。这样的情况下,我们可以得到时间复杂度为 $O(n\\\\times(\\\\frac{1+\\\\sqrt{5}}{2})^n)$。
代码
\\n###Python
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n res = []\\n def dfs(arr: List[str]) -> None:\\n if len(arr) == n:\\n res.append(\'\'.join(arr))\\n else:\\n if arr == [] or arr[-1] == \'1\':\\n arr.append(\'0\')\\n dfs(arr)\\n arr.pop()\\n arr.append(\'1\')\\n dfs(arr)\\n arr.pop()\\n dfs([])\\n return res\\n
\\n###Java
\\nclass Solution {\\n List<String> res = new ArrayList<String>();\\n int n;\\n\\n public List<String> validStrings(int n) {\\n this.n = n;\\n dfs(new StringBuilder());\\n return res;\\n }\\n\\n public void dfs(StringBuilder sb) {\\n if (sb.length() == n) {\\n res.add(sb.toString());\\n } else {\\n if (sb.isEmpty() || sb.charAt(sb.length() - 1) == \'1\') {\\n sb.append(\'0\');\\n dfs(sb);\\n sb.setLength(sb.length() - 1);\\n }\\n sb.append(\'1\');\\n dfs(sb);\\n sb.setLength(sb.length() - 1);\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n IList<string> res = new List<string>();\\n int n;\\n\\n public IList<string> ValidStrings(int n) {\\n this.n = n;\\n DFS(new StringBuilder());\\n return res;\\n }\\n\\n public void DFS(StringBuilder sb) {\\n if (sb.Length == n) {\\n res.Add(sb.ToString());\\n } else {\\n if (sb.Length == 0 || sb[sb.Length - 1] == \'1\') {\\n sb.Append(\'0\');\\n DFS(sb);\\n sb.Length--;\\n }\\n sb.Append(\'1\');\\n DFS(sb);\\n sb.Length--;\\n }\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<string> validStrings(int n) {\\n vector<string> res;\\n string str;\\n\\n function<void(string &)> dfs = [&](string& str) {\\n if (str.size() == n) {\\n res.push_back(str);\\n return;\\n }\\n if (str.empty() || str.back() == \'1\') {\\n str.push_back(\'0\');\\n dfs(str);\\n str.pop_back();\\n }\\n str.push_back(\'1\');\\n dfs(str);\\n str.pop_back();\\n };\\n \\n dfs(str);\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc validStrings(n int) []string {\\n res := []string{}\\n var dfs func(arr []rune)\\n dfs = func(arr []rune) {\\n if len(arr) == n {\\n res = append(res, string(arr))\\n return\\n }\\n if len(arr) == 0 || arr[len(arr) - 1] == \'1\' {\\n arr = append(arr, \'0\')\\n dfs(arr)\\n arr = arr[:len(arr)-1]\\n }\\n arr = append(arr, \'1\')\\n dfs(arr)\\n arr = arr[:len(arr)-1]\\n }\\n dfs([]rune{})\\n return res\\n}\\n
\\n###C
\\nvoid dfs(char *arr, int index, int n, char **res, int *count) {\\n if (index == n) {\\n res[*count] = strdup(arr);\\n (*count)++;\\n return;\\n }\\n if (index == 0 || arr[index - 1] == \'1\') {\\n arr[index] = \'0\';\\n dfs(arr, index + 1, n, res, count);\\n }\\n arr[index] = \'1\';\\n dfs(arr, index + 1, n, res, count);\\n}\\n\\nchar** validStrings(int n, int* returnSize) {\\n char **res = malloc((1 << n) * sizeof(char *));\\n char *arr = malloc((n + 1) * sizeof(char));\\n arr[n] = \'\\\\0\';\\n int count = 0;\\n dfs(arr, 0, n, res, &count);\\n free(arr);\\n *returnSize = count;\\n return res;\\n}\\n
\\n###JavaScript
\\nvar validStrings = function(n) {\\n const res = [];\\n function dfs(arr) {\\n if (arr.length === n) {\\n res.push(arr.join(\'\'));\\n return;\\n }\\n if (arr.length === 0 || arr[arr.length - 1] === \'1\') {\\n arr.push(\'0\');\\n dfs(arr);\\n arr.pop();\\n }\\n arr.push(\'1\');\\n dfs(arr);\\n arr.pop();\\n }\\n dfs([]);\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction validStrings(n: number): string[] {\\n const res: string[] = [];\\n const dfs = (arr: string[]) => {\\n if (arr.length === n) {\\n res.push(arr.join(\'\'));\\n return;\\n }\\n if (arr.length === 0 || arr[arr.length - 1] === \'1\') {\\n arr.push(\'0\');\\n dfs(arr);\\n arr.pop();\\n }\\n arr.push(\'1\');\\n dfs(arr);\\n arr.pop();\\n };\\n dfs([]);\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn valid_strings(n: i32) -> Vec<String> {\\n let mut res = Vec::new();\\n let mut arr = Vec::new();\\n Self::dfs(&mut arr, n, &mut res);\\n res\\n }\\n\\n fn dfs(arr: &mut Vec<char>, n: i32, res: &mut Vec<String>) {\\n if arr.len() == n as usize {\\n res.push(arr.iter().collect());\\n return;\\n }\\n if arr.is_empty() || arr[arr.len() - 1] == \'1\' {\\n arr.push(\'0\');\\n Self::dfs(arr, n, res);\\n arr.pop();\\n }\\n arr.push(\'1\');\\n Self::dfs(arr, n, res);\\n arr.pop();\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n\\\\times(\\\\frac{1+\\\\sqrt{5}}{2})^n)$。
\\n空间复杂度:$O(n)$。
\\n思路
\\n用一个 $0$ 到 $2^n-1$ 之间的数字的二进制形式代表字符串,如果这个二进制没有相邻的 $0$,则代表是一个有效字符串。那么怎么判断是否有相邻的 $0$ 呢?首先将数字按位取反,记为 $t$。如果 $t ~&~ (t >> 1)$ 为 $0$,则 $t$ 中没有相邻的 $0$。
\\n代码
\\n###Python
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n res = []\\n mask = (1 << n) - 1\\n for i in range(1<<n):\\n t = mask ^ i\\n if not (t >> 1 & t):\\n res.append(f\\"{i:0{n}b}\\")\\n return res\\n
\\n###Java
\\nclass Solution {\\n public List<String> validStrings(int n) {\\n List<String> res = new ArrayList<String>();\\n int mask = (1 << n) - 1;\\n for (int i = 0; i < 1 << n; i++) {\\n int t = mask ^ i;\\n if (((t >> 1) & t) == 0) {\\n String str = Integer.toBinaryString(i);\\n StringBuilder sb = new StringBuilder();\\n for (int j = n - str.length(); j > 0; j--) {\\n sb.append(\'0\');\\n }\\n sb.append(str);\\n res.add(sb.toString());\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<string> validStrings(int n) {\\n vector<string> res;\\n int mask = (1 << n) - 1;\\n for (int i = 0; i < (1 << n); i++) {\\n int t = mask ^ i;\\n if (!(t >> 1 & t)) {\\n res.push_back(intToBinaryString(i, n));\\n }\\n }\\n return res;\\n }\\n\\n string intToBinaryString(int num, int n) {\\n string binary(n, \'0\');\\n for (int j = 0; j < n; j++) {\\n binary[n - 1 - j] = (num & (1 << j)) ? \'1\' : \'0\';\\n }\\n return binary;\\n }\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(2^n)$。
\\n空间复杂度:$O(1)$。
\\n我们注意到,每次会比较数组的前两个元素,不管结果怎么样,下一次的比较,一定是轮到了数组中的下一个元素和当前的胜者进行比较。因此,如果循环了 $n-1$ 次,那么最后的胜者一定是数组中的最大元素。否则,如果某个元素连续胜出了 $k$ 次,那么这个元素就是最后的胜者。
\\n###python
\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n n = len(skills)\\n k = min(k, n - 1)\\n i = cnt = 0\\n for j in range(1, n):\\n if skills[i] < skills[j]:\\n i = j\\n cnt = 1\\n else:\\n cnt += 1\\n if cnt == k:\\n break\\n return i\\n
\\n###java
\\nclass Solution {\\n public int findWinningPlayer(int[] skills, int k) {\\n int n = skills.length;\\n k = Math.min(k, n - 1);\\n int i = 0, cnt = 0;\\n for (int j = 1; j < n; ++j) {\\n if (skills[i] < skills[j]) {\\n i = j;\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n if (cnt == k) {\\n break;\\n }\\n }\\n return i;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector<int>& skills, int k) {\\n int n = skills.size();\\n k = min(k, n - 1);\\n int i = 0, cnt = 0;\\n for (int j = 1; j < n; ++j) {\\n if (skills[i] < skills[j]) {\\n i = j;\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n if (cnt == k) {\\n break;\\n }\\n }\\n return i;\\n }\\n};\\n
\\n###go
\\nfunc findWinningPlayer(skills []int, k int) int {\\nn := len(skills)\\nk = min(k, n-1)\\ni, cnt := 0, 0\\nfor j := 1; j < n; j++ {\\nif skills[i] < skills[j] {\\ni = j\\ncnt = 1\\n} else {\\ncnt++\\n}\\nif cnt == k {\\nbreak\\n}\\n}\\nreturn i\\n}\\n
\\n###ts
\\nfunction findWinningPlayer(skills: number[], k: number): number {\\n const n = skills.length;\\n k = Math.min(k, n - 1);\\n let [i, cnt] = [0, 0];\\n for (let j = 1; j < n; ++j) {\\n if (skills[i] < skills[j]) {\\n i = j;\\n cnt = 1;\\n } else {\\n ++cnt;\\n }\\n if (cnt === k) {\\n break;\\n }\\n }\\n return i;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:脑筋急转弯 我们注意到,每次会比较数组的前两个元素,不管结果怎么样,下一次的比较,一定是轮到了数组中的下一个元素和当前的胜者进行比较。因此,如果循环了 $n-1$ 次,那么最后的胜者一定是数组中的最大元素。否则,如果某个元素连续胜出了 $k$ 次,那么这个元素就是最后的胜者。\\n\\n###python\\n\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n n = len(skills)\\n k = min(k, n…","guid":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/python3javacgotypescript-yi-ti-yi-jie-na-49i9","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-24T00:08:31.455Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-找到连续赢 K 场比赛的第一位玩家🟡","url":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row/","content":"有 n
位玩家在进行比赛,玩家编号依次为 0
到 n - 1
。
给你一个长度为 n
的整数数组 skills
和一个 正 整数 k
,其中 skills[i]
是第 i
位玩家的技能等级。skills
中所有整数 互不相同 。
所有玩家从编号 0
到 n - 1
排成一列。
比赛进行方式如下:
\\n\\n这个比赛的赢家是 第一位连续 赢下 k
场比赛的玩家。
请你返回这个比赛的赢家编号。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:skills = [4,2,6,3,9], k = 2
\\n\\n输出:2
\\n\\n解释:
\\n\\n一开始,队列里的玩家为 [0,1,2,3,4]
。比赛过程如下:
[0,2,3,4,1]
。[2,3,4,1,0]
。[2,4,1,0,3]
。玩家 2 连续赢了 k = 2
场比赛,所以赢家是玩家 2 。
示例 2:
\\n\\n输入:skills = [2,5,4], k = 3
\\n\\n输出:1
\\n\\n解释:
\\n\\n一开始,队列里的玩家为 [0,1,2]
。比赛过程如下:
[1,2,0]
。[1,0,2]
。[1,2,0]
。玩家 1 连续赢了 k = 3
场比赛,所以赢家是玩家 1 。
\\n\\n
提示:
\\n\\nn == skills.length
2 <= n <= 105
1 <= k <= 109
1 <= skills[i] <= 106
skills
中的整数互不相同。我们可以用一个哈希表或者一个长度为 $24$ 的数组 $\\\\textit{cnt}$ 来记录每个小时数模 $24$ 的出现次数。
\\n遍历数组 $\\\\textit{hours}$,对于每个小时数 $x$,我们可以得出与 $x$ 相加为 $24$ 的倍数,且模 $24$ 之后的数为 $(24 - x \\\\bmod 24) \\\\bmod 24$。累加这个数在哈希表或者数组中的出现次数即可。然后我们将 $x$ 的模 $24$ 的出现次数加一。
\\n遍历完数组 $\\\\textit{hours}$ 后,我们就可以得到满足题意的下标对数目。
\\n###python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n cnt = Counter()\\n ans = 0\\n for x in hours:\\n ans += cnt[(24 - (x % 24)) % 24]\\n cnt[x % 24] += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long countCompleteDayPairs(int[] hours) {\\n int[] cnt = new int[24];\\n long ans = 0;\\n for (int x : hours) {\\n ans += cnt[(24 - x % 24) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countCompleteDayPairs(vector<int>& hours) {\\n int cnt[24]{};\\n long long ans = 0;\\n for (int x : hours) {\\n ans += cnt[(24 - x % 24) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countCompleteDayPairs(hours []int) (ans int64) {\\ncnt := [24]int{}\\nfor _, x := range hours {\\nans += int64(cnt[(24-x%24)%24])\\ncnt[x%24]++\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countCompleteDayPairs(hours: number[]): number {\\n const cnt: number[] = Array(24).fill(0);\\n let ans: number = 0;\\n for (const x of hours) {\\n ans += cnt[(24 - (x % 24)) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{hours}$ 的长度。空间复杂度 $O(C)$,其中 $C=24$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 我们可以用一个哈希表或者一个长度为 $24$ 的数组 $\\\\textit{cnt}$ 来记录每个小时数模 $24$ 的出现次数。\\n\\n遍历数组 $\\\\textit{hours}$,对于每个小时数 $x$,我们可以得出与 $x$ 相加为 $24$ 的倍数,且模 $24$ 之后的数为 $(24 - x \\\\bmod 24) \\\\bmod 24$。累加这个数在哈希表或者数组中的出现次数即可。然后我们将 $x$ 的模 $24$ 的出现次数加一。\\n\\n遍历完数组 $\\\\textit{hours}$ 后,我们就可以得到满足题意的下标对数目。\\n\\n###python\\n\\nclas…","guid":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-ii//solution/python3javacgotypescript-yi-ti-yi-jie-ji-gb1q","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-23T00:12:26.981Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-构成整天的下标对数目 II🟡","url":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-ii/","content":"给你一个整数数组 hours
,表示以 小时 为单位的时间,返回一个整数,表示满足 i < j
且 hours[i] + hours[j]
构成 整天 的下标对 i
, j
的数目。
整天 定义为时间持续时间是 24 小时的 整数倍 。
\\n\\n例如,1 天是 24 小时,2 天是 48 小时,3 天是 72 小时,以此类推。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: hours = [12,12,30,24,24]
\\n\\n输出: 2
\\n\\n解释:
\\n\\n构成整天的下标对分别是 (0, 1)
和 (3, 4)
。
示例 2:
\\n\\n输入: hours = [72,48,24,3]
\\n\\n输出: 3
\\n\\n解释:
\\n\\n构成整天的下标对分别是 (0, 1)
、(0, 2)
和 (1, 2)
。
\\n\\n
提示:
\\n\\n1 <= hours.length <= 5 * 105
1 <= hours[i] <= 109
我们可以用一个哈希表或者一个长度为 $24$ 的数组 $\\\\textit{cnt}$ 来记录每个小时数模 $24$ 的出现次数。
\\n遍历数组 $\\\\textit{hours}$,对于每个小时数 $x$,我们可以得出与 $x$ 相加为 $24$ 的倍数,且模 $24$ 之后的数为 $(24 - x \\\\bmod 24) \\\\bmod 24$。累加这个数在哈希表或者数组中的出现次数即可。然后我们将 $x$ 的模 $24$ 的出现次数加一。
\\n遍历完数组 $\\\\textit{hours}$ 后,我们就可以得到满足题意的下标对数目。
\\n###python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n cnt = Counter()\\n ans = 0\\n for x in hours:\\n ans += cnt[(24 - (x % 24)) % 24]\\n cnt[x % 24] += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int countCompleteDayPairs(int[] hours) {\\n int[] cnt = new int[24];\\n int ans = 0;\\n for (int x : hours) {\\n ans += cnt[(24 - x % 24) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countCompleteDayPairs(vector<int>& hours) {\\n int cnt[24]{};\\n int ans = 0;\\n for (int x : hours) {\\n ans += cnt[(24 - x % 24) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countCompleteDayPairs(hours []int) (ans int) {\\ncnt := [24]int{}\\nfor _, x := range hours {\\nans += cnt[(24-x%24)%24]\\ncnt[x%24]++\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction countCompleteDayPairs(hours: number[]): number {\\n const cnt: number[] = Array(24).fill(0);\\n let ans: number = 0;\\n for (const x of hours) {\\n ans += cnt[(24 - (x % 24)) % 24];\\n ++cnt[x % 24];\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{hours}$ 的长度。空间复杂度 $O(C)$,其中 $C=24$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 我们可以用一个哈希表或者一个长度为 $24$ 的数组 $\\\\textit{cnt}$ 来记录每个小时数模 $24$ 的出现次数。\\n\\n遍历数组 $\\\\textit{hours}$,对于每个小时数 $x$,我们可以得出与 $x$ 相加为 $24$ 的倍数,且模 $24$ 之后的数为 $(24 - x \\\\bmod 24) \\\\bmod 24$。累加这个数在哈希表或者数组中的出现次数即可。然后我们将 $x$ 的模 $24$ 的出现次数加一。\\n\\n遍历完数组 $\\\\textit{hours}$ 后,我们就可以得到满足题意的下标对数目。\\n\\n###python\\n\\nclas…","guid":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-i//solution/python3javacgotypescript-yi-ti-yi-jie-ji-0nrn","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-22T00:29:14.607Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-构成整天的下标对数目 I🟢","url":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-i/","content":"给你一个整数数组 hours
,表示以 小时 为单位的时间,返回一个整数,表示满足 i < j
且 hours[i] + hours[j]
构成 整天 的下标对 i
, j
的数目。
整天 定义为时间持续时间是 24 小时的 整数倍 。
\\n\\n例如,1 天是 24 小时,2 天是 48 小时,3 天是 72 小时,以此类推。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: hours = [12,12,30,24,24]
\\n\\n输出: 2
\\n\\n解释:
\\n\\n构成整天的下标对分别是 (0, 1)
和 (3, 4)
。
示例 2:
\\n\\n输入: hours = [72,48,24,3]
\\n\\n输出: 3
\\n\\n解释:
\\n\\n构成整天的下标对分别是 (0, 1)
、(0, 2)
和 (1, 2)
。
\\n\\n
提示:
\\n\\n1 <= hours.length <= 100
1 <= hours[i] <= 109
记 $\\\\textit{rewardValues}$ 的最大值为 $m$,因为最后一次操作前的总奖励一定小于等于 $m - 1$,所以可获得的最大总奖励小于等于 $2m - 1$。假设上一次操作选择的奖励值为 $x_1$,那么执行操作后的总奖励 $x \\\\ge x_1$,根据题意,后面任一操作选择的奖励值 $x_2$ 一定都大于 $x$,从而有 $x_2 \\\\gt x_1$,因此执行的操作是按照奖励值单调递增的。
\\n根据以上推断,首先将 $\\\\textit{rewardValues}$ 从小到大进行排序,使用 $\\\\textit{dp}[k]$ 表示总奖励 $k$ 是否可获得,初始时 $\\\\textit{dp}[0] = 1$,表示不执行任何操作获得总奖励 $0$。然后我们对 $\\\\textit{rewardValues}$ 进行遍历,令当前值为 $x$,那么对于 $k \\\\in [x, 2x - 1]$(将 $k$ 倒序枚举),将 $\\\\textit{dp}[k]$ 更新为 $\\\\textit{dp}[k - x]~|~\\\\textit{dp}[k]$(符号 $|$ 表示或操作),表示先前的操作可以获得总奖励 $k - x$,那么加上 $x$ 后,就可以获取总奖励 $k$。最后返回 $\\\\textit{dp}$ 中可以获得的最大总奖励。
\\n###C++
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n sort(rewardValues.begin(), rewardValues.end());\\n int m = rewardValues.back();\\n vector<int> dp(2 * m);\\n dp[0] = 1;\\n for (int x : rewardValues) {\\n for (int k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x]) {\\n dp[k] = 1;\\n }\\n }\\n }\\n int res = 0;\\n for (int i = 0; i < dp.size(); i++) {\\n if (dp[i]) {\\n res = i;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###C
\\nint cmp(const void *a, const void *b) {\\n return *(int *)a - *(int *)b;\\n}\\n\\nint maxTotalReward(int *rewardValues, int rewardValuesSize) {\\n qsort(rewardValues, rewardValuesSize, sizeof(int), cmp);\\n int m = rewardValues[rewardValuesSize - 1];\\n int* dp = (int*)malloc(2 * m * sizeof(int));\\n memset(dp, 0, 2 * m * sizeof(int));\\n dp[0] = 1;\\n for (int i = 0; i < rewardValuesSize; i++) {\\n int x = rewardValues[i];\\n for (int k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x] == 1) {\\n dp[k] = 1;\\n }\\n }\\n }\\n int res = 0;\\n for (int i = 0; i < 2 * m; i++) {\\n if (dp[i] == 1) {\\n res = i;\\n }\\n }\\n free(dp);\\n return res;\\n}\\n
\\n###Go
\\nfunc maxTotalReward(rewardValues []int) int {\\n sort.Ints(rewardValues)\\nm := rewardValues[len(rewardValues) - 1]\\ndp := make([]int, 2 * m)\\ndp[0] = 1\\nfor _, x := range rewardValues {\\nfor k := 2 * x - 1; k >= x; k-- {\\nif dp[k - x] == 1 {\\ndp[k] = 1\\n}\\n}\\n}\\nres := 0\\nfor i := 0; i < len(dp); i++ {\\nif dp[i] == 1 {\\nres = i\\n}\\n}\\nreturn res\\n}\\n
\\n###Python
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n rewardValues.sort()\\n m = rewardValues[-1]\\n dp = [0] * (2 * m)\\n dp[0] = 1\\n for x in rewardValues:\\n for k in range(2 * x - 1, x - 1, -1):\\n if dp[k - x] == 1:\\n dp[k] = 1\\n res = 0\\n for i in range(len(dp)):\\n if dp[i] == 1:\\n res = i\\n return res\\n
\\n###Java
\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n Arrays.sort(rewardValues);\\n int m = rewardValues[rewardValues.length - 1];\\n int[] dp = new int[2 * m];\\n dp[0] = 1;\\n for (int x : rewardValues) {\\n for (int k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x] == 1) {\\n dp[k] = 1;\\n }\\n }\\n }\\n int res = 0;\\n for (int i = 0; i < dp.length; i++) {\\n if (dp[i] == 1) {\\n res = i;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxTotalReward(int[] rewardValues) {\\n Array.Sort(rewardValues);\\n int m = rewardValues[rewardValues.Length - 1];\\n int[] dp = new int[2 * m];\\n dp[0] = 1;\\n foreach (int x in rewardValues) {\\n for (int k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x] == 1) {\\n dp[k] = 1;\\n }\\n }\\n }\\n int res = 0;\\n for (int i = 0; i < dp.Length; i++) {\\n if (dp[i] == 1) {\\n res = i;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###JavaScript
\\nvar maxTotalReward = function(rewardValues) {\\n rewardValues.sort((a, b) => a - b);\\n const m = rewardValues[rewardValues.length - 1];\\n const dp = Array(2 * m).fill(0);\\n dp[0] = 1;\\n for (let x of rewardValues) {\\n for (let k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x] === 1) {\\n dp[k] = 1;\\n }\\n }\\n }\\n let res = 0;\\n for (let i = 0; i < dp.length; i++) {\\n if (dp[i] === 1) {\\n res = i;\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction maxTotalReward(rewardValues: number[]): number {\\n rewardValues.sort((a, b) => a - b);\\n const m = rewardValues[rewardValues.length - 1];\\n const dp = Array(2 * m).fill(0);\\n dp[0] = 1;\\n for (let x of rewardValues) {\\n for (let k = 2 * x - 1; k >= x; k--) {\\n if (dp[k - x] === 1) {\\n dp[k] = 1;\\n }\\n }\\n }\\n let res = 0;\\n for (let i = 0; i < dp.length; i++) {\\n if (dp[i] === 1) {\\n res = i;\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_total_reward(reward_values: Vec<i32>) -> i32 {\\n let mut reward_values = reward_values.clone();\\n reward_values.sort();\\n let m = reward_values[reward_values.len() - 1] as usize;\\n let mut dp = vec![0; 2 * m];\\n dp[0] = 1;\\n\\n for &x in &reward_values {\\n let x = x as usize;\\n for k in (x..2 * x).rev() {\\n if dp[k - x] == 1 {\\n dp[k] = 1;\\n }\\n }\\n }\\n\\n let mut res = 0;\\n for (i, &value) in dp.iter().enumerate() {\\n if value == 1 {\\n res = i;\\n }\\n }\\n res as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n(m + \\\\log n))$,其中 $n$,$m$ 分别是 $\\\\textit{rewardValues}$ 的长度和最大值。
\\n空间复杂度:$O(m + \\\\log n)$。
\\n根据方法一的推断,一次操作前后的 $\\\\textit{dp}$ 数组(分别记为 $f_1$ 和 $f_0$)之前的递推关系为:
\\n$$
\\nf_0[k] = f_1[k - x]~|~f_0[k], k \\\\in [x, 2x - 1]
\\n$$
可以用位运算表示为 $f_0 = (\\\\text{mask}_x(f_1) \\\\ll x)~|~f_0$,其中 $\\\\text{mask}_x(f_1)$ 函数表示取 $f_1$ 的低 $x$ 位。因此我们可以使用位运算对动态规划进行优化,降低时间复杂度。
\\n###C++
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n sort(rewardValues.begin(), rewardValues.end());\\n bitset<100000> f0, f1;\\n f0[0] = 1;\\n for (int i = 0, j = 0; i < rewardValues.size(); i++) {\\n while (j < rewardValues[i]) {\\n f1[j] = f0[j];\\n j++;\\n }\\n f0 |= f1 << rewardValues[i];\\n }\\n int res = 0;\\n for (int i = 0; i < f0.size(); i++) {\\n if (f0[i]) {\\n res = i;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc maxTotalReward(rewardValues []int) int {\\n sort.Ints(rewardValues)\\n f0, f1 := big.NewInt(1), big.NewInt(0)\\n for _, x := range rewardValues {\\n mask, one := big.NewInt(0), big.NewInt(1)\\n mask.Sub(mask.Lsh(one, uint(x)), one)\\n f1.Lsh(f1.And(f0, mask), uint(x))\\n f0.Or(f0, f1)\\n }\\n return f0.BitLen() - 1\\n}\\n
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n rewardValues.sort()\\n f = 1\\n for x in rewardValues:\\n f |= (f & ((1 << x) - 1)) << x\\n return f.bit_length() - 1\\n
\\n###Java
\\nimport java.math.BigInteger;\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n Arrays.sort(rewardValues);\\n BigInteger f = BigInteger.ONE;\\n for (int x : rewardValues) {\\n BigInteger mask = BigInteger.ONE.shiftLeft(x).subtract(BigInteger.ONE);\\n f = f.or(f.and(mask).shiftLeft(x));\\n }\\n return f.bitLength() - 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxTotalReward(int[] rewardValues) {\\n Array.Sort(rewardValues);\\n BigInteger f = BigInteger.One;\\n foreach (int x in rewardValues) {\\n BigInteger mask = (BigInteger.One << x) - BigInteger.One;\\n f |= (f & mask) << x;\\n }\\n return (int)f.GetBitLength() - 1;\\n }\\n}\\n
\\n###JavaScript
\\nvar maxTotalReward = function(rewardValues) {\\n rewardValues.sort((a, b) => a - b);\\n let f = BigInt(1);\\n for (let x of rewardValues) {\\n let mask = (BigInt(1) << BigInt(x)) - BigInt(1);\\n f = f | ((f & mask) << BigInt(x));\\n }\\n return f.toString(2).length - 1;\\n};\\n
\\n###TypeScript
\\nfunction maxTotalReward(rewardValues: number[]): number {\\n rewardValues.sort((a, b) => a - b);\\n let f = BigInt(1);\\n for (let x of rewardValues) {\\n let mask = (BigInt(1) << BigInt(x)) - BigInt(1);\\n f = f | ((f & mask) << BigInt(x));\\n }\\n return f.toString(2).length - 1;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n(\\\\frac{m}{w} + \\\\log n))$,其中 $n$,$m$ 分别是 $\\\\textit{rewardValues}$ 的长度和最大值,$w$ 表示编程语言的整数位数。
\\n空间复杂度:$O(\\\\frac{m}{w} + \\\\log n)$。
\\n思路与算法
\\n由于比赛输了的玩家会排到队列末尾,因此如果前面一直没有人是赢家(连续赢下 $k$ 次),那么 $skills$ 值最高的那个玩家就是赢家。因此,我们从前到后去遍历 $i$,判断它能否连续赢下 $k$ 次。若能,则直接返回答案,否则继续遍历,直到末尾。若仍没找到,则返回 $skills$ 值最高的那个玩家。
\\n需要注意的是,如果 $i$ 不为 $0$,那么第 $i$ 个玩家最少已经赢了一次(与 $i$ 进行比赛的上一个玩家,输掉后排到末尾)。我们用 $\\\\textit{cnt}$ 来表示当前玩家赢得次数,初始值为 $0$,然后每次遍历下一个 $i$ 时,赋值为 $1$。
\\n另外,玩家 $i$ 后面那些输掉比赛的玩家一定不是赢家,因为要么玩家 $i$ 是赢家,要么玩家 $i$ 后面还有一个 $\\\\textit{skills}$ 值更高的玩家,并且与玩家 $i$ 相隔不超过 $k$(或者 $k - 1$)。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector<int>& skills, int k) {\\n int n = skills.size();\\n int cnt = 0;\\n int i = 0, last_i = 0;\\n while (i < n) {\\n int j = i + 1; \\n while (j < n && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt == k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n n = len(skills)\\n cnt = 0\\n i, last_i = 0, 0\\n while i < n:\\n j = i + 1\\n while j < n and skills[j] < skills[i] and cnt < k:\\n cnt += 1\\n j += 1\\n if cnt == k:\\n return i\\n cnt = 1\\n last_i = i\\n i = j\\n return last_i\\n
\\n###Java
\\nclass Solution {\\n public int findWinningPlayer(int[] skills, int k) {\\n int n = skills.length;\\n int cnt = 0;\\n int i = 0, last_i = 0;\\n\\n while (i < n) {\\n int j = i + 1; \\n while (j < n && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt == k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int FindWinningPlayer(int[] skills, int k) {\\n int n = skills.Length;\\n int cnt = 0;\\n int i = 0, last_i = 0;\\n\\n while (i < n) {\\n int j = i + 1; \\n while (j < n && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt == k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n }\\n}\\n
\\n###Go
\\nfunc findWinningPlayer(skills []int, k int) int {\\n n := len(skills)\\n cnt := 0\\n i, lastI := 0, 0\\n\\n for i < n {\\n j := i + 1\\n for j < n && skills[j] < skills[i] && cnt < k {\\n j++\\n cnt++\\n }\\n if cnt == k {\\n return i\\n }\\n cnt = 1\\n lastI = i\\n i = j\\n }\\n return lastI\\n}\\n
\\n###C
\\nint findWinningPlayer(int* skills, int skillsSize, int k) {\\n int cnt = 0;\\n int i = 0, last_i = 0;\\n\\n while (i < skillsSize) {\\n int j = i + 1; \\n while (j < skillsSize && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt == k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n}\\n
\\n###JavaScript
\\nvar findWinningPlayer = function(skills, k) {\\n const n = skills.length;\\n let cnt = 0;\\n let i = 0, last_i = 0;\\n\\n while (i < n) {\\n let j = i + 1; \\n while (j < n && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt === k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n};\\n
\\n###TypeScript
\\nfunction findWinningPlayer(skills: number[], k: number): number {\\n const n = skills.length;\\n let cnt = 0;\\n let i = 0, last_i = 0;\\n\\n while (i < n) {\\n let j = i + 1; \\n while (j < n && skills[j] < skills[i] && cnt < k) {\\n j++;\\n cnt++;\\n }\\n if (cnt === k) {\\n return i;\\n }\\n cnt = 1;\\n last_i = i;\\n i = j;\\n }\\n return last_i;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn find_winning_player(skills: Vec<i32>, k: i32) -> i32 {\\n let n = skills.len();\\n let mut cnt = 0;\\n let mut i = 0;\\n let mut last_i = 0;\\n\\n while i < n {\\n let mut j = i + 1;\\n while j < n && skills[j] < skills[i] && cnt < k {\\n j += 1;\\n cnt += 1;\\n }\\n if cnt == k {\\n return i as i32;\\n }\\n cnt = 1;\\n last_i = i as i32;\\n i = j;\\n }\\n last_i \\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 是 $skills$ 的长度。由于每个元素最多遍历一次,因此总体的时间复杂度为 $O(n)$。
\\n空间复杂度:$O(1)$,只使用了若干个变量。
\\n给你一个整数数组 nums
,和一个整数 k
。
对于每个下标 i
(0 <= i < nums.length
),将 nums[i]
变成 nums[i] + k
或 nums[i] - k
。
nums
的 分数 是 nums
中最大元素和最小元素的差值。
在更改每个下标对应的值之后,返回 nums
的最小 分数 。
\\n\\n
示例 1:
\\n\\n输入:nums = [1], k = 0\\n输出:0\\n解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [0,10], k = 2\\n输出:6\\n解释:将数组变为 [2, 8] 。分数 = max(nums) - min(nums) = 8 - 2 = 6 。\\n\\n\\n
示例 3:
\\n\\n输入:nums = [1,3,6], k = 3\\n输出:3\\n解释:将数组变为 [4, 6, 3] 。分数 = max(nums) - min(nums) = 6 - 3 = 3 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 104
0 <= nums[i] <= 104
0 <= k <= 104
给你一个整数数组 nums
,和一个整数 k
。
在一个操作中,您可以选择 0 <= i < nums.length
的任何索引 i
。将 nums[i]
改为 nums[i] + x
,其中 x
是一个范围为 [-k, k]
的任意整数。对于每个索引 i
,最多 只能 应用 一次 此操作。
nums
的 分数 是 nums
中最大和最小元素的差值。
在对 nums
中的每个索引最多应用一次上述操作后,返回 nums
的最低 分数 。
\\n\\n
示例 1:
\\n\\n输入:nums = [1], k = 0\\n输出:0\\n解释:分数是 max(nums) - min(nums) = 1 - 1 = 0。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [0,10], k = 2\\n输出:6\\n解释:将 nums 改为 [2,8]。分数是 max(nums) - min(nums) = 8 - 2 = 6。\\n\\n\\n
示例 3:
\\n\\n输入:nums = [1,3,6], k = 3\\n输出:0\\n解释:将 nums 改为 [4,4,4]。分数是 max(nums) - min(nums) = 4 - 4 = 0。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 104
0 <= nums[i] <= 104
0 <= k <= 104
我们注意到,每当我们将某个位置的元素变为 1 时,它的右侧的所有元素都会被反转。因此,我们可以用一个变量 $v$ 来记录当前位置及其右侧的元素是否被反转,如果被反转,那么 $v$ 的值为 1,否则为 0。
\\n我们遍历数组 $\\\\textit{nums}$,对于每个元素 $x$,我们将 $x$ 与 $v$ 进行异或运算,如果 $x$ 为 0,那么我们需要将 $x$ 变为 1,我们需要进行反转操作,我们将答案加一,并将 $v$ 取反。
\\n遍历结束后,我们就可以得到最少操作次数。
\\n###python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n ans = v = 0\\n for x in nums:\\n x ^= v\\n if x == 0:\\n ans += 1\\n v ^= 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int ans = 0, v = 0;\\n for (int x : nums) {\\n x ^= v;\\n if (x == 0) {\\n v ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int ans = 0, v = 0;\\n for (int x : nums) {\\n x ^= v;\\n if (x == 0) {\\n v ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int) (ans int) {\\nv := 0\\nfor _, x := range nums {\\nx ^= v\\nif x == 0 {\\nv ^= 1\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction minOperations(nums: number[]): number {\\n let [ans, v] = [0, 0];\\n for (let x of nums) {\\n x ^= v;\\n if (x === 0) {\\n v ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{nums}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:位运算 我们注意到,每当我们将某个位置的元素变为 1 时,它的右侧的所有元素都会被反转。因此,我们可以用一个变量 $v$ 来记录当前位置及其右侧的元素是否被反转,如果被反转,那么 $v$ 的值为 1,否则为 0。\\n\\n我们遍历数组 $\\\\textit{nums}$,对于每个元素 $x$,我们将 $x$ 与 $v$ 进行异或运算,如果 $x$ 为 0,那么我们需要将 $x$ 变为 1,我们需要进行反转操作,我们将答案加一,并将 $v$ 取反。\\n\\n遍历结束后,我们就可以得到最少操作次数。\\n\\n###python\\n\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/python3javacgotypescript-yi-ti-yi-jie-we-s8b9","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-19T00:36:04.726Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-使二进制数组全部等于 1 的最少操作次数 II🟡","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii/","content":"给你一个二进制数组 nums
。
你可以对数组执行以下操作 任意 次(也可以 0 次):
\\n\\ni
,并将从下标 i
开始一直到数组末尾 所有 元素 反转 。反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。
\\n\\n请你返回将 nums
中所有元素变为 1 的 最少 操作次数。
\\n\\n
示例 1:
\\n\\n输入:nums = [0,1,1,0,1]
\\n\\n输出:4
\\n\\n解释:
\\n我们可以执行以下操作:
i = 1
执行操作,得到 nums = [0,0,0,1,0]
。i = 0
执行操作,得到 nums = [1,1,1,0,1]
。i = 4
执行操作,得到 nums = [1,1,1,0,0]
。i = 3
执行操作,得到 nums = [1,1,1,1,1]
。示例 2:
\\n\\n输入:nums = [1,0,0,0]
\\n\\n输出:1
\\n\\n解释:
\\n我们可以执行以下操作:
i = 1
执行操作,得到 nums = [1,1,1,1]
。\\n\\n
提示:
\\n\\n1 <= nums.length <= 105
0 <= nums[i] <= 1
首先考虑没有修改怎么做,其实就是「198. 打家劫舍」的简单变形,即数组中可以出现负数。
\\n这可以用动态规划解决:记 $f(i, 0)$ 表示 $\\\\textit{nums}[0..i]$ 的最大和,并且 $\\\\textit{nums}[i]$ 没有被选择,而 $f(i, 1)$ 表示 $\\\\textit{nums}[i]$ 被选择,这样就有下面的状态转移方程:
\\n$$
\\n\\\\begin{cases}
\\nf(i, 0) = \\\\max { f(i-1, 0), f(i-1, 1) } \\\\
\\nf(i, 1) = f(i-1, 0) + \\\\textit{nums}[i]
\\n\\\\end{cases}
\\n$$
最后 $f(n-1, 0)$ 和 $f(n-1, 1)$ 中的较大值即为答案,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。
\\n现在考虑怎么快速处理修改,可以发现状态转移方程中只有 $+$ 和 $\\\\max$ 操作,因此是一种「广义矩阵乘法」,可以使用动态动态规划来维护,这里给出两个参考链接:
\\n本质上就是如果可以将状态转移方程写成「矩阵乘法」或者「广义矩阵乘法」的形式,那么问题就变为:
\\n\\n\\n初始给定 $n$ 个矩阵,每次操作修改其中的一个矩阵,并且需要返回这些矩阵依次相乘的结果。
\\n
由于矩阵乘法有结合律,因此可以使用线段树进行维护,可以在 $O(r^3 \\\\log n)$ 的时间完成一次修改操作,其中 $r$ 是矩阵的维度。
\\n回到本题,将状态转移方程转写为广义矩阵乘法的形式:
\\n$$
\\n\\\\begin{bmatrix}
\\nf(i, 0) \\\\
\\nf(i, 1)
\\n\\\\end{bmatrix} =
\\n\\\\begin{bmatrix}
\\n0 & 0 \\\\
\\n\\\\textit{nums}[i] & -\\\\infty
\\n\\\\end{bmatrix}
\\n\\\\begin{bmatrix}
\\nf(i-1, 0) \\\\
\\nf(i-1, 1)
\\n\\\\end{bmatrix}
\\n$$
然后使用线段树进行维护即可。每一次修改后的答案就是 $n$ 个矩阵相乘后里面 $4$ 个元素的最大值。
\\n###C++
\\nusing ll = long long;\\n\\nstruct SegNode {\\n SegNode() {}\\n SegNode(int x) {\\n init(x);\\n }\\n\\n void init(int x) {\\n m[0][0] = m[0][1] = 0;\\n m[1][0] = x;\\n m[1][1] = LLONG_MIN;\\n }\\n\\n void replace(int x) {\\n m[1][0] = x;\\n }\\n \\n ll m[2][2];\\n};\\n\\nclass SegTree {\\npublic:\\n SegTree(int n_): n(n_), tree(n_ * 4 + 42) {}\\n void build(const vector<int>& v) {\\n bd(1, 1, n, v);\\n }\\n\\n ll query() {\\n return max(max(tree[1].m[0][0], tree[1].m[0][1]), max(tree[1].m[1][0], tree[1].m[1][1]));\\n }\\n\\n void update(int x, int v) {\\n upd(1, 1, n, x + 1, v);\\n }\\n\\nprivate:\\n void pushup(int pos) {\\n SegNode& p = tree[pos];\\n const SegNode& l = tree[pos * 2];\\n const SegNode& r = tree[pos * 2 + 1];\\n for (int i = 0; i < 2; ++i) {\\n for (int j = 0; j < 2; ++j) {\\n p.m[i][j] = LLONG_MIN;\\n for (int k = 0; k < 2; ++k) {\\n if (l.m[i][k] != LLONG_MIN && r.m[k][j] != LLONG_MIN) {\\n p.m[i][j] = max(p.m[i][j], l.m[i][k] + r.m[k][j]);\\n }\\n }\\n }\\n }\\n }\\n\\n void bd(int pos, int l, int r, const vector<int>& v) {\\n if (l == r) {\\n tree[pos].init(v[l - 1]);\\n return;\\n }\\n int mid = (l + r) / 2;\\n bd(pos * 2, l, mid, v);\\n bd(pos * 2 + 1, mid + 1, r, v);\\n pushup(pos);\\n }\\n\\n void upd(int pos, int l, int r, int x, int v) {\\n if (l > x || r < x) {\\n return;\\n }\\n if (l == r) {\\n tree[pos].replace(v);\\n return;\\n }\\n int mid = (l + r) / 2;\\n upd(pos * 2, l, mid, x, v);\\n upd(pos * 2 + 1, mid + 1, r, x, v);\\n pushup(pos);\\n }\\n\\n int n;\\n vector<SegNode> tree;\\n};\\n\\nclass Solution {\\npublic:\\n int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n SegTree tree(n);\\n tree.build(nums);\\n\\n ll ans = 0;\\n for (const auto& q: queries) {\\n tree.update(q[0], q[1]);\\n ans += tree.query();\\n }\\n return ans % 1000000007;\\n }\\n};\\n
\\n我们注意到,数组中的第一个为 $0$ 的位置,一定需要进行一次反转操作,否则无法将其变为 $1$。因此,我们可以顺序遍历数组,每次遇到 $0$,就将其后两个元素进行反转操作,累计一次操作次数。
\\n遍历结束后,返回答案即可。
\\n###python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n ans = 0\\n for i, x in enumerate(nums):\\n if x == 0:\\n if i + 2 >= len(nums):\\n return -1\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int ans = 0;\\n int n = nums.length;\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 0) {\\n if (i + 2 >= n) {\\n return -1;\\n }\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int ans = 0;\\n int n = nums.size();\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 0) {\\n if (i + 2 >= n) {\\n return -1;\\n }\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int) (ans int) {\\nfor i, x := range nums {\\nif x == 0 {\\nif i+2 >= len(nums) {\\nreturn -1\\n}\\nnums[i+1] ^= 1\\nnums[i+2] ^= 1\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction minOperations(nums: number[]): number {\\n const n = nums.length;\\n let ans = 0;\\n for (let i = 0; i < n; ++i) {\\n if (nums[i] === 0) {\\n if (i + 2 >= n) {\\n return -1;\\n }\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ++ans;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为数组 $\\\\textit{nums}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:顺序遍历 + 模拟 我们注意到,数组中的第一个为 $0$ 的位置,一定需要进行一次反转操作,否则无法将其变为 $1$。因此,我们可以顺序遍历数组,每次遇到 $0$,就将其后两个元素进行反转操作,累计一次操作次数。\\n\\n遍历结束后,返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n ans = 0\\n for i, x in enumerate(nums):\\n if x == 0:…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/python3javacgotypescript-yi-ti-yi-jie-sh-9wfl","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-18T00:19:15.743Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-使二进制数组全部等于 1 的最少操作次数 I🟡","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i/","content":"给你一个二进制数组 nums
。
你可以对数组执行以下操作 任意 次(也可以 0 次):
\\n\\n反转 一个元素指的是将它的值从 0 变 1 ,或者从 1 变 0 。
\\n\\n请你返回将 nums
中所有元素变为 1 的 最少 操作次数。如果无法全部变成 1 ,返回 -1 。
\\n\\n
示例 1:
\\n\\n输入:nums = [0,1,1,1,0,0]
\\n\\n输出:3
\\n\\n解释:
\\n我们可以执行以下操作:
nums = [1,0,0,1,0,0]
。nums = [1,1,1,0,0,0]
。nums = [1,1,1,1,1,1]
。示例 2:
\\n\\n输入:nums = [0,1,1,1]
\\n\\n输出:-1
\\n\\n解释:
\\n无法将所有元素都变为 1 。
\\n\\n
提示:
\\n\\n3 <= nums.length <= 105
0 <= nums[i] <= 1
我们定义 $f[i][j]$ 表示 $[0..i]$ 的排列中,逆序对数量为 $j$ 的排列数。考虑下标为 $i$ 的数 $a_i$ 与前面 $i$ 个数的大小关系。如果 $a_i$ 比前面 $k$ 个数小,那么前面的 $k$ 个数与 $a_i$ 都构成了逆序对,逆序对数量为 $k$。因此,我们可以得到状态转移方程:
\\n$$
\\nf[i][j] = \\\\sum_{k=0}^{\\\\min(i, j)} f[i-1][j-k]
\\n$$
由于题目要求 $[0..\\\\textit{end}_i]$ 的逆序对数量为 $\\\\textit{cnt}_i$,因此,当我们计算 $i = \\\\textit{end}_i$ 时,我们只需要计算 $f[i][\\\\textit{cnt}_i]$ 即可。其余的 $f[i][..]$ 都为 $0$。
\\n###python
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n req = [-1] * n\\n for end, cnt in requirements:\\n req[end] = cnt\\n if req[0] > 0:\\n return 0\\n req[0] = 0\\n mod = 10**9 + 7\\n m = max(req)\\n f = [[0] * (m + 1) for _ in range(n)]\\n f[0][0] = 1\\n for i in range(1, n):\\n l, r = 0, m\\n if req[i] >= 0:\\n l = r = req[i]\\n for j in range(l, r + 1):\\n for k in range(min(i, j) + 1):\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod\\n return f[n - 1][req[n - 1]]\\n
\\n###java
\\nclass Solution {\\n public int numberOfPermutations(int n, int[][] requirements) {\\n int[] req = new int[n];\\n Arrays.fill(req, -1);\\n int m = 0;\\n for (var r : requirements) {\\n req[r[0]] = r[1];\\n m = Math.max(m, r[1]);\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n req[0] = 0;\\n final int mod = (int) 1e9 + 7;\\n int[][] f = new int[n][m + 1];\\n f[0][0] = 1;\\n for (int i = 1; i < n; ++i) {\\n int l = 0, r = m;\\n if (req[i] >= 0) {\\n l = r = req[i];\\n }\\n for (int j = l; j <= r; ++j) {\\n for (int k = 0; k <= Math.min(i, j); ++k) {\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;\\n }\\n }\\n }\\n return f[n - 1][req[n - 1]];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n vector<int> req(n, -1);\\n int m = 0;\\n for (const auto& r : requirements) {\\n req[r[0]] = r[1];\\n m = max(m, r[1]);\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n req[0] = 0;\\n const int mod = 1e9 + 7;\\n vector<vector<int>> f(n, vector<int>(m + 1, 0));\\n f[0][0] = 1;\\n for (int i = 1; i < n; ++i) {\\n int l = 0, r = m;\\n if (req[i] >= 0) {\\n l = r = req[i];\\n }\\n for (int j = l; j <= r; ++j) {\\n for (int k = 0; k <= min(i, j); ++k) {\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;\\n }\\n }\\n }\\n return f[n - 1][req[n - 1]];\\n }\\n};\\n
\\n###go
\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\nreq := make([]int, n)\\nfor i := range req {\\nreq[i] = -1\\n}\\nfor _, r := range requirements {\\nreq[r[0]] = r[1]\\n}\\nif req[0] > 0 {\\nreturn 0\\n}\\nreq[0] = 0\\nm := slices.Max(req)\\nconst mod = int(1e9 + 7)\\nf := make([][]int, n)\\nfor i := range f {\\nf[i] = make([]int, m+1)\\n}\\nf[0][0] = 1\\nfor i := 1; i < n; i++ {\\nl, r := 0, m\\nif req[i] >= 0 {\\nl, r = req[i], req[i]\\n}\\nfor j := l; j <= r; j++ {\\nfor k := 0; k <= min(i, j); k++ {\\nf[i][j] = (f[i][j] + f[i-1][j-k]) % mod\\n}\\n}\\n}\\nreturn f[n-1][req[n-1]]\\n}\\n
\\n###ts
\\nfunction numberOfPermutations(n: number, requirements: number[][]): number {\\n const req: number[] = Array(n).fill(-1);\\n for (const [end, cnt] of requirements) {\\n req[end] = cnt;\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n req[0] = 0;\\n const m = Math.max(...req);\\n const mod = 1e9 + 7;\\n const f = Array.from({ length: n }, () => Array(m + 1).fill(0));\\n f[0][0] = 1;\\n for (let i = 1; i < n; ++i) {\\n let [l, r] = [0, m];\\n if (req[i] >= 0) {\\n l = r = req[i];\\n }\\n for (let j = l; j <= r; ++j) {\\n for (let k = 0; k <= Math.min(i, j); ++k) {\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % mod;\\n }\\n }\\n }\\n return f[n - 1][req[n - 1]];\\n}\\n
\\n时间复杂度 $O(n \\\\times m \\\\times \\\\min(n, m))$,空间复杂度 $O(n \\\\times m)$。其中 $m$ 是逆序对数量的最大值。本题中 $m \\\\le 400$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:动态规划 我们定义 $f[i][j]$ 表示 $[0..i]$ 的排列中,逆序对数量为 $j$ 的排列数。考虑下标为 $i$ 的数 $a_i$ 与前面 $i$ 个数的大小关系。如果 $a_i$ 比前面 $k$ 个数小,那么前面的 $k$ 个数与 $a_i$ 都构成了逆序对,逆序对数量为 $k$。因此,我们可以得到状态转移方程:\\n\\n$$\\n f[i][j] = \\\\sum_{k=0}^{\\\\min(i, j)} f[i-1][j-k]\\n $$\\n\\n由于题目要求 $[0..\\\\textit{end}_i]$ 的逆序对数量为 $\\\\textit{cnt}_i$,因此…","guid":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/python3javacgotypescript-yi-ti-yi-jie-do-73li","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-17T00:26:51.018Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-统计逆序对的数目🔴","url":"https://leetcode.cn/problems/count-the-number-of-inversions/","content":"给你一个整数 n
和一个二维数组 requirements
,其中 requirements[i] = [endi, cnti]
表示这个要求中的末尾下标和 逆序对 的数目。
整数数组 nums
中一个下标对 (i, j)
如果满足以下条件,那么它们被称为一个 逆序对 :
i < j
且 nums[i] > nums[j]
请你返回 [0, 1, 2, ..., n - 1]
的 排列 perm
的数目,满足对 所有 的 requirements[i]
都有 perm[0..endi]
恰好有 cnti
个逆序对。
由于答案可能会很大,将它对 109 + 7
取余 后返回。
\\n\\n
示例 1:
\\n\\n输入:n = 3, requirements = [[2,2],[0,0]]
\\n\\n输出:2
\\n\\n解释:
\\n\\n两个排列为:
\\n\\n[2, 0, 1]
\\n\\n[2, 0, 1]
的逆序对为 (0, 1)
和 (0, 2)
。[2]
的逆序对数目为 0 个。[1, 2, 0]
\\n[1, 2, 0]
的逆序对为 (0, 2)
和 (1, 2)
。[1]
的逆序对数目为 0 个。示例 2:
\\n\\n输入:n = 3, requirements = [[2,2],[1,1],[0,0]]
\\n\\n输出:1
\\n\\n解释:
\\n\\n唯一满足要求的排列是 [2, 0, 1]
:
[2, 0, 1]
的逆序对为 (0, 1)
和 (0, 2)
。[2, 0]
的逆序对为 (0, 1)
。[2]
的逆序对数目为 0 。示例 3:
\\n\\n输入:n = 2, requirements = [[0,0],[1,0]]
\\n\\n输出:1
\\n\\n解释:
\\n\\n唯一满足要求的排列为 [0, 1]
:
[0]
的逆序对数目为 0 。[0, 1]
的逆序对为 (0, 1)
。\\n\\n
提示:
\\n\\n2 <= n <= 300
1 <= requirements.length <= n
requirements[i] = [endi, cnti]
0 <= endi <= n - 1
0 <= cnti <= 400
i
满足 endi == n - 1
。endi
互不相同。我们首先对数组 $\\\\textit{nums}$ 进行排序,然后从数组的两端开始取元素,分别计算两个元素的和,取最小值。最后将最小值除以 2 作为答案返回即可。
\\n###python
\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n n = len(nums)\\n return min(nums[i] + nums[-i - 1] for i in range(n // 2)) / 2\\n
\\n###java
\\nclass Solution {\\n public double minimumAverage(int[] nums) {\\n Arrays.sort(nums);\\n int n = nums.length;\\n int ans = 1 << 30;\\n for (int i = 0; i < n / 2; ++i) {\\n ans = Math.min(ans, nums[i] + nums[n - i - 1]);\\n }\\n return ans / 2.0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n double minimumAverage(vector<int>& nums) {\\n sort(nums.begin(), nums.end());\\n int ans = 1 << 30, n = nums.size();\\n for (int i = 0; i < n; ++i) {\\n ans = min(ans, nums[i] + nums[n - i - 1]);\\n }\\n return ans / 2.0;\\n }\\n};\\n
\\n###go
\\nfunc minimumAverage(nums []int) float64 {\\nsort.Ints(nums)\\nn := len(nums)\\nans := 1 << 30\\nfor i, x := range nums[:n/2] {\\nans = min(ans, x+nums[n-i-1])\\n}\\nreturn float64(ans) / 2\\n}\\n
\\n###ts
\\nfunction minimumAverage(nums: number[]): number {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n let ans = Infinity;\\n for (let i = 0; i * 2 < n; ++i) {\\n ans = Math.min(ans, nums[i] + nums[n - 1 - i]);\\n }\\n return ans / 2;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_average(mut nums: Vec<i32>) -> f64 {\\n nums.sort();\\n let n = nums.len();\\n let ans = (0..n / 2).map(|i| nums[i] + nums[n - i - 1]).min().unwrap();\\n ans as f64 / 2.0\\n }\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n)$,空间复杂度 $O(\\\\log n)$。其中 $n$ 为数组 $\\\\textit{nums}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:排序 我们首先对数组 $\\\\textit{nums}$ 进行排序,然后从数组的两端开始取元素,分别计算两个元素的和,取最小值。最后将最小值除以 2 作为答案返回即可。\\n\\n###python\\n\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n n = len(nums)\\n return min(nums[i] + nums[-i - 1] for i in range(n // 2)) / 2…","guid":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/python3javacgotypescript-yi-ti-yi-jie-pa-k7ai","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-16T00:50:57.159Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最小元素和最大元素的最小平均值🟢","url":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements/","content":"你有一个初始为空的浮点数数组 averages
。另给你一个包含 n
个整数的数组 nums
,其中 n
为偶数。
你需要重复以下步骤 n / 2
次:
nums
中移除 最小 的元素 minElement
和 最大 的元素 maxElement
。(minElement + maxElement) / 2
加入到 averages
中。返回 averages
中的 最小 元素。
\\n\\n
示例 1:
\\n\\n输入: nums = [7,8,3,4,15,13,4,1]
\\n\\n输出: 5.5
\\n\\n解释:
\\n\\n步骤 | \\nnums | \\naverages | \\n
---|---|---|
0 | \\n[7,8,3,4,15,13,4,1] | \\n[] | \\n
1 | \\n[7,8,3,4,13,4] | \\n[8] | \\n
2 | \\n[7,8,4,4] | \\n[8,8] | \\n
3 | \\n[7,4] | \\n[8,8,6] | \\n
4 | \\n[] | \\n[8,8,6,5.5] | \\n
示例 2:
\\n\\n输入: nums = [1,9,8,3,10,5]
\\n\\n输出: 5.5
\\n\\n解释:
\\n\\n步骤 | \\nnums | \\naverages | \\n
---|---|---|
0 | \\n[1,9,8,3,10,5] | \\n[] | \\n
1 | \\n[9,8,3,5] | \\n[5.5] | \\n
2 | \\n[8,5] | \\n[5.5,6] | \\n
3 | \\n[] | \\n[5.5,6,6.5] | \\n
示例 3:
\\n\\n输入: nums = [1,2,3,7,8,9]
\\n\\n输出: 5.0
\\n\\n解释:
\\n\\n步骤 | \\nnums | \\naverages | \\n
---|---|---|
0 | \\n[1,2,3,7,8,9] | \\n[] | \\n
1 | \\n[2,3,7,8] | \\n[5] | \\n
2 | \\n[3,7] | \\n[5,5] | \\n
3 | \\n[] | \\n[5,5,5] | \\n
\\n\\n
提示:
\\n\\n2 <= n == nums.length <= 50
n
为偶数。1 <= nums[i] <= 50
思路与算法
\\n由于 $\\\\textit{nums}[i]$ 只会收到 $i$ 左侧的「反转」操作影响,因此我们先从最左边的 $\\\\textit{nums}[0]$ 开始考虑:
\\n如果 $\\\\textit{nums}[0]=1$,此时不需要操作,问题则变成数组中剩余 $n−1$ 个的子问题,此时只需考虑 $\\\\textit{nums}[1,\\\\cdots,n - 1]$;
\\n如果 $\\\\textit{nums}[0]=0$,此时一定需要「反转」操作,在 $i = 0$ 处进行「反转」操作,问题变为数组中剩余 $n−1$ 个元素(在操作次数是 $1$ 的情况下)的子问题;
\\n对于剩余的元素来说,由于「反转」偶数次等于没反转,所以只需考虑操作次数的奇偶性。设遍历到 $\\\\textit{nums}[i]$ 时,之前执行了 $\\\\textit{operation}$ 次操作,分类讨论如下:
\\n如果 $\\\\textit{nums}[i] = 0$ 且已经执行了奇数次操作,即此时 $\\\\textit{operation} \\\\bmod 2 = 1$, $\\\\textit{nums}[i]$ 经过奇数次「反转」后已经变为了 $1$;如果 $\\\\textit{nums}[i] = 1$ 且已经执行了偶数次操作,此时 $\\\\textit{operation} \\\\bmod 2 = 0$,$\\\\textit{nums}[i]$ 经过偶数次「反转」后仍然为 $1$。综上可知,如果 $\\\\textit{nums}[i] \\\\neq \\\\textit{operation} \\\\bmod 2$,则不需要操作。
\\n如果 $\\\\textit{nums}[i] = 0$ 且已经执行了偶数次操作,即此时 $\\\\textit{operation} \\\\bmod 2 = 0$,$\\\\textit{nums}[i]$ 经过偶数次「反转」后仍然为 $0$,则此时一定需要再「反转」一次才能变为 $1$;如果 $\\\\textit{nums}[i] = 1$ 且已经执行了奇数次操作,此时 $\\\\textit{operation} \\\\bmod 2 = 1$,$\\\\textit{nums}[i]$ 经过奇数次「反转」后为 $0$,则此时一定需要再「反转」一次才能变为 $1$。所以如果 $\\\\textit{nums}[i] = \\\\textit{operation} \\\\bmod 2$ 时,需要再「反转」一次,此时 $\\\\textit{operation}$ 加 $1$。
\\n总的「反转」次数即为 $\\\\textit{operation}$,返回即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int operation = 0;\\n for (int num : nums) {\\n if (num == (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int operation = 0;\\n for (int num : nums) {\\n if (num == (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int operation = 0;\\n foreach (int num in nums) {\\n if (num == (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n }\\n}\\n
\\n###Go
\\nfunc minOperations(nums []int) int {\\n operation := 0\\n for _, num := range nums {\\n if num == (operation%2) {\\n operation++\\n }\\n }\\n return operation\\n}\\n
\\n###Python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n operation = 0\\n for num in nums:\\n if num == (operation % 2):\\n operation += 1\\n return operation\\n
\\n###C
\\nint minOperations(int* nums, int numsSize) {\\n int operation = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n}\\n
\\n###JavaScript
\\nvar minOperations = function(nums) {\\n let operation = 0;\\n for (let num of nums) {\\n if (num === (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n};\\n
\\n###TypeScript
\\nfunction minOperations(nums: number[]): number {\\n let operation: number = 0;\\n for (let num of nums) {\\n if (num === (operation % 2)) {\\n operation++;\\n }\\n }\\n return operation;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>) -> i32 {\\n let mut operation = 0;\\n for num in nums {\\n if num == (operation % 2) {\\n operation += 1;\\n }\\n }\\n operation\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,$n$ 表示给定的数组 $\\\\textit{nums}$ 的长度。只需遍历一遍数组即可。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n用 $n$ 表示数组 $\\\\textit{nums}$ 的长度。由于每次「反转」操作将数组 $\\\\textit{nums}$ 的一个后缀中的所有元素反转。我们换一种思路从后往前看,如果要把当前位置 $i$ 起(包含当前位置 $i$)的后缀全变成 $1$:
\\n比如我们先从最左边的 $\\\\textit{nums}[0]$ 开始考虑:
\\n综上我们考虑动态规划,设 $\\\\textit{dp}[i][j]$ 表示将数组 $\\\\textit{nums}$ 的下标范围 $[i,n−1]$ 中的所有元素变为 $j$ 的最少操作次数,$j \\\\in [0,1]$,根据上述推理可以得到如下结论:
\\n当 $j=\\\\textit{nums}[i]$ 时,下标 $i$ 不需要操作,只需要将下标范围 $[i+1,n−1]$ 中的所有元素变为 $j$,因此 $\\\\textit{dp}[i][j]= \\\\textit{dp}[i+1][j]$;
\\n当 $j \\\\neq \\\\textit{nums}[i]$ 时,下标 $i$ 需要操作,此时需要首先将下标范围 $[i+1,n−1]$ 中的所有元素变为 $j \\\\oplus 1$, $\\\\oplus$ 表示异或操作,然后将下标范围 $[i,n−1]$ 中的所有元素变为 $j$,因此 $\\\\textit{dp}[i][j]= \\\\textit{dp}[i+1][j \\\\oplus 1]+1$,其中 $\\\\oplus$ 表示异或操作;
\\n因此动态规划的状态转移方程如下:
\\n实际上述动态规划可以进一步简化,我们不需要区分将后缀变为 $1$ 或者 $0$,只需要考虑把当前位置 $i$ 起(包含当前位置 $i$)的后缀全变为与 $\\\\textit{nums}[i]$ 相同的最小「反转」次数:
\\n实际如果相邻两个元素不相等,则需要「反转」$1$ 次,才使得当前的后缀全部变为相等,最终计算可以得到使得整个数组都等于 $\\\\textit{nums}[0]$ 的最小操作次数,如果此时 $\\\\textit{nums}[0] = 0$,还需要再额外「反转」$1$ 次,才能使得数组中元素全部变为 $1$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int operation = 0;\\n for (int i = nums.size() - 2; i >= 0; i--) {\\n if (nums[i] != nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] == 1 ? operation : operation + 1;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int operation = 0;\\n for (int i = nums.length - 2; i >= 0; i--) {\\n if (nums[i] != nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] == 1 ? operation : operation + 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int operation = 0;\\n for (int i = nums.Length - 2; i >= 0; i--) {\\n if (nums[i] != nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] == 1 ? operation : operation + 1;\\n }\\n}\\n
\\n###Go
\\nfunc minOperations(nums []int) int {\\n operation := 0\\n for i := len(nums) - 2; i >= 0; i-- {\\n if nums[i] != nums[i + 1] {\\n operation++\\n }\\n }\\n if nums[0] == 1 {\\n return operation\\n }\\n return operation + 1\\n}\\n
\\n###Python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n operation = 0\\n for i in range(len(nums) - 2, -1, -1):\\n if nums[i] != nums[i + 1]:\\n operation += 1\\n return operation if nums[0] == 1 else operation + 1\\n
\\n###C
\\nint minOperations(int* nums, int numsSize) {\\n int operation = 0;\\n for (int i = numsSize - 2; i >= 0; i--) {\\n if (nums[i] != nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] == 1 ? operation : operation + 1;\\n}\\n
\\n###JavaScript
\\nvar minOperations = function(nums) {\\n let operation = 0;\\n for (let i = nums.length - 2; i >= 0; i--) {\\n if (nums[i] !== nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] === 1 ? operation : operation + 1;\\n};\\n
\\n###TypeScript
\\nfunction minOperations(nums: number[]): number {\\n let operation = 0;\\n for (let i = nums.length - 2; i >= 0; i--) {\\n if (nums[i] !== nums[i + 1]) {\\n operation++;\\n }\\n }\\n return nums[0] === 1 ? operation : operation + 1;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>) -> i32 {\\n let mut operation = 0;\\n for i in (0..nums.len() - 1).rev() {\\n if nums[i] != nums[i + 1] {\\n operation += 1;\\n }\\n }\\n if nums[0] == 1 {\\n operation\\n } else {\\n operation + 1\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,$n$ 表示给定的数组 $\\\\textit{nums}$ 的长度。只需遍历一遍数组即可。
\\n空间复杂度:$O(1)$。
\\n我们可以枚举第一行的颜色,然后模拟构造三角形,计算最大高度。
\\n###python
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n ans = 0\\n for k in range(2):\\n c = [red, blue]\\n i, j = 1, k\\n while i <= c[j]:\\n c[j] -= i\\n j ^= 1\\n ans = max(ans, i)\\n i += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n int ans = 0;\\n for (int k = 0; k < 2; ++k) {\\n int[] c = {red, blue};\\n for (int i = 1, j = k; i <= c[j]; j ^= 1, ++i) {\\n c[j] -= i;\\n ans = Math.max(ans, i);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int ans = 0;\\n for (int k = 0; k < 2; ++k) {\\n int c[2] = {red, blue};\\n for (int i = 1, j = k; i <= c[j]; j ^= 1, ++i) {\\n c[j] -= i;\\n ans = max(ans, i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxHeightOfTriangle(red int, blue int) (ans int) {\\nfor k := 0; k < 2; k++ {\\nc := [2]int{red, blue}\\nfor i, j := 1, k; i <= c[j]; i, j = i+1, j^1 {\\nc[j] -= i\\nans = max(ans, i)\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction maxHeightOfTriangle(red: number, blue: number): number {\\n let ans = 0;\\n for (let k = 0; k < 2; ++k) {\\n const c: [number, number] = [red, blue];\\n for (let i = 1, j = k; i <= c[j]; ++i, j ^= 1) {\\n c[j] -= i;\\n ans = Math.max(ans, i);\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(\\\\sqrt{n})$,其中 $n$ 为红色球和蓝色球的数量。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们可以枚举第一行的颜色,然后模拟构造三角形,计算最大高度。\\n\\n###python\\n\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n ans = 0\\n for k in range(2):\\n c = [red, blue]\\n i, j = 1, k\\n while i <= c[j]:\\n c[j] -= i…","guid":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/python3javacgotypescript-yi-ti-yi-jie-mo-97nq","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-15T00:51:02.444Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-三角形的最大高度🟢","url":"https://leetcode.cn/problems/maximum-height-of-a-triangle/","content":"给你两个整数 red
和 blue
,分别表示红色球和蓝色球的数量。你需要使用这些球来组成一个三角形,满足第 1 行有 1 个球,第 2 行有 2 个球,第 3 行有 3 个球,依此类推。
每一行的球必须是 相同 颜色,且相邻行的颜色必须 不同。
\\n\\n返回可以实现的三角形的 最大 高度。
\\n\\n\\n\\n
示例 1:
\\n\\n输入: red = 2, blue = 4
\\n\\n输出: 3
\\n\\n解释:
\\n\\n上图显示了唯一可能的排列方式。
\\n示例 2:
\\n\\n输入: red = 2, blue = 1
\\n\\n输出: 2
\\n\\n解释:
\\n\\n
\\n上图显示了唯一可能的排列方式。
示例 3:
\\n\\n输入: red = 1, blue = 1
\\n\\n输出: 1
\\n示例 4:
\\n\\n输入: red = 10, blue = 1
\\n\\n输出: 2
\\n\\n解释:
\\n\\n
\\n上图显示了唯一可能的排列方式。
\\n\\n
提示:
\\n\\n1 <= red, blue <= 100
本文接着 1884. 鸡蛋掉落-两枚鸡蛋题解 的方法二(数学做法)接着讲,请先看这篇题解,相当于本题 $k=2$ 的简单版本。
\\n反过来,如果已知答案(操作次数),$n$ 最大可以是多少?
\\n假设我们有 $i=5$ 次操作机会,有 $j=2$ 枚鸡蛋。
\\n第一次操作,你打算在几楼扔第一枚鸡蛋?
\\n你可能会想:搏一搏,单车变摩托。楼层越高越好嘛,如果鸡蛋没碎,这对我们会更有利。
\\n最高可以在几楼扔?可以在 $6$ 楼扔吗?
\\n不能。万一赌错鸡蛋碎了,后面就只能依次在 $1,2,3,4,5$ 楼扔第二枚鸡蛋,最坏情况下,总共要操作 $6$ 次。
所以第一次操作的最优做法,是在 $5$ 楼扔第一枚鸡蛋:
\\n请记住,$i=5,\\\\ j=2$ 对应的最大的 $n$ 是 $15$。
\\n假设我们有 $i=6$ 次操作机会,有 $j=3$ 枚鸡蛋。
\\n第一次操作,你打算在几楼扔第一枚鸡蛋?
\\n可以在 $17$ 楼扔吗?
\\n不能。如果鸡蛋碎了,问题就变成 $i=5,\\\\ j=2$ 的子问题(答案等于 $15$),我们无法确定(题干中的)$f$ 的值是否等于 $16$。
\\n所以第一次操作的最优做法,是在 $16$ 楼扔第一枚鸡蛋:
\\n由于这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n根据上面的讨论,定义状态为 $\\\\textit{dfs}(i,j)$,表示在有 $i$ 次操作机会和 $j$ 枚鸡蛋的情况下,可以让我们能确定 $f$ 值的最大建筑层数。
\\n在 $\\\\textit{dfs}(i-1,j-1)+1$ 楼扔第一枚鸡蛋:
\\n所以有
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\textit{dfs}(i-1,j) + \\\\textit{dfs}(i-1,j-1)+1
\\n$$
递归边界:$\\\\textit{dfs}(0,j)=\\\\textit{dfs}(i,0)=0$。无法扔鸡蛋。
\\n递归入口:枚举 $i=1,2,3,\\\\cdots$,首个满足 $\\\\textit{dfs}(i,k)\\\\ge n$ 的 $i$ 是答案。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。由于本题不会算出 $0$,所以可以把初始值设置为 $0$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\n@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\ndef dfs(i: int, j: int) -> int:\\n if i == 0 or j == 0:\\n return 0\\n return dfs(i - 1, j) + dfs(i - 1, j - 1) + 1\\n\\nclass Solution:\\n def superEggDrop(self, k: int, n: int) -> int:\\n for i in count(1):\\n if dfs(i, k) >= n:\\n return i\\n
\\n###java
\\nclass Solution {\\n public int superEggDrop(int k, int n) {\\n int[][] memo = new int[n + 1][];\\n for (int i = 1; ; i++) {\\n memo[i] = new int[k + 1]; // 动态创建 memo\\n if (dfs(i, k, memo) >= n) {\\n return i;\\n }\\n }\\n }\\n\\n private int dfs(int i, int j, int[][] memo) {\\n if (i == 0 || j == 0) {\\n return 0;\\n }\\n if (memo[i][j] != 0) { // 之前计算过\\n return memo[i][j];\\n }\\n return memo[i][j] = dfs(i - 1, j, memo) + dfs(i - 1, j - 1, memo) + 1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n vector<vector<int>> memo{{}};\\n\\n int dfs(int i, int j) {\\n if (i == 0 || j == 0) {\\n return 0;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != 0) { // 之前计算过\\n return res;\\n }\\n return res = dfs(i - 1, j) + dfs(i - 1, j - 1) + 1;\\n };\\n\\npublic:\\n int superEggDrop(int k, int n) {\\n for (int i = 1; ; i++) {\\n memo.emplace_back(k + 1); // 插入一个长为 k+1 的 vector\\n if (dfs(i, k) >= n) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc superEggDrop(k, n int) int {\\n memo := [][]int{{}}\\n var dfs func(int, int) int\\n dfs = func(i, j int) int {\\n if i == 0 || j == 0 {\\n return 0\\n }\\n p := &memo[i][j]\\n if *p != 0 { // 之前计算过\\n return *p\\n }\\n *p = dfs(i-1, j) + dfs(i-1, j-1) + 1\\n return *p\\n }\\n for i := 1; ; i++ {\\n memo = append(memo, make([]int, k+1)) // 动态创建 memo\\n if dfs(i, k) >= n {\\n return i\\n }\\n }\\n}\\n
\\n根据 这篇题解,以及阶乘的斯特林公式,我们枚举的操作次数的上限为 $\\\\mathcal{O}(k\\\\cdot\\\\sqrt[k]{\\\\sqrt{k}n})$。对于相同的 $i$,有 $\\\\mathcal{O}(k)$ 个不同的 $j$,所以状态个数为 $\\\\mathcal{O}(k^2\\\\cdot\\\\sqrt[k]{\\\\sqrt{k}n})$。
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示在有 $i$ 次操作机会和 $j$ 枚鸡蛋的情况下,可以让我们能确定 $f$ 值的最大建筑层数。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i][j] = f[i-1][j] + f[i-1][j-1]+1
\\n$$
初始值 $f[0][j]=f[i][0]=0$,翻译自递归边界 $\\\\textit{dfs}(0,j)=\\\\textit{dfs}(i,0)=0$。
\\n枚举 $i=1,2,3,\\\\cdots$,首个满足 $f[i][k]\\\\ge n$ 的 $i$ 是答案。
\\n###py
\\nclass Solution:\\n def superEggDrop(self, k: int, n: int) -> int:\\n f = [[0] * (k + 1) for _ in range(n + 1)]\\n for i in count(1):\\n for j in range(1, k + 1):\\n f[i][j] = f[i - 1][j] + f[i - 1][j - 1] + 1\\n if f[i][k] >= n:\\n return i\\n
\\n###java
\\nclass Solution {\\n public int superEggDrop(int k, int n) {\\n int[][] f = new int[n + 1][k + 1];\\n for (int i = 1; ; i++) {\\n for (int j = 1; j <= k; j++) {\\n f[i][j] = f[i - 1][j] + f[i - 1][j - 1] + 1;\\n }\\n if (f[i][k] >= n) {\\n return i;\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int superEggDrop(int k, int n) {\\n vector<vector<int>> f(n + 1, vector<int>(k + 1));\\n for (int i = 1; ; i++) {\\n for (int j = 1; j <= k; j++) {\\n f[i][j] = f[i - 1][j] + f[i - 1][j - 1] + 1;\\n }\\n if (f[i][k] >= n) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc superEggDrop(k, n int) int {\\n f := make([][]int, n+1)\\n f[0] = make([]int, k+1)\\n for i := 1; ; i++ {\\n f[i] = make([]int, k+1)\\n for j := 1; j <= k; j++ {\\n f[i][j] = f[i-1][j] + f[i-1][j-1] + 1\\n }\\n if f[i][k] >= n {\\n return i\\n }\\n }\\n}\\n
\\n观察上面的状态转移方程,在计算 $f[i]$ 时,只会用到 $f[i-1]$,不会用到比 $i-1$ 更早的状态。
\\n因此可以像 0-1 背包 那样,去掉第一个维度,把 $f[i]$ 和 $f[i-1]$ 保存到同一个数组中。注意和 0-1 背包一样,$j$ 要倒序枚举。
\\n状态转移方程改为
\\n$$
\\nf[j] = f[j] + f[j-1] + 1
\\n$$
初始值 $f[j]=0$。
\\n枚举 $i=1,2,3,\\\\cdots$,首个满足 $f[k]\\\\ge n$ 的 $i$ 是答案。
\\n###py
\\nclass Solution:\\n def superEggDrop(self, k: int, n: int) -> int:\\n f = [0] * (k + 1)\\n for i in count(1):\\n for j in range(k, 0, -1):\\n f[j] += f[j - 1] + 1\\n if f[k] >= n:\\n return i\\n
\\n###java
\\nclass Solution {\\n public int superEggDrop(int k, int n) {\\n int[] f = new int[k + 1];\\n for (int i = 1; ; i++) {\\n for (int j = k; j > 0; j--) {\\n f[j] += f[j - 1] + 1;\\n }\\n if (f[k] >= n) {\\n return i;\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int superEggDrop(int k, int n) {\\n vector<int> f(k + 1);\\n for (int i = 1; ; i++) {\\n for (int j = k; j > 0; j--) {\\n f[j] += f[j - 1] + 1;\\n }\\n if (f[k] >= n) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc superEggDrop(k, n int) int {\\n f := make([]int, k+1)\\n for i := 1; ; i++ {\\n for j := k; j > 0; j-- {\\n f[j] += f[j-1] + 1\\n }\\n if f[k] >= n {\\n return i\\n }\\n }\\n}\\n
\\n见 动态规划题单 中的「§7.5 多维 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前言 本文接着 1884. 鸡蛋掉落-两枚鸡蛋题解 的方法二(数学做法)接着讲,请先看这篇题解,相当于本题 $k=2$ 的简单版本。\\n\\n一、寻找子问题\\n\\n反过来,如果已知答案(操作次数),$n$ 最大可以是多少?\\n\\n第一个例子\\n\\n假设我们有 $i=5$ 次操作机会,有 $j=2$ 枚鸡蛋。\\n\\n第一次操作,你打算在几楼扔第一枚鸡蛋?\\n\\n你可能会想:搏一搏,单车变摩托。楼层越高越好嘛,如果鸡蛋没碎,这对我们会更有利。\\n\\n最高可以在几楼扔?可以在 $6$ 楼扔吗?\\n\\n不能。万一赌错鸡蛋碎了,后面就只能依次在 $1,2,3,4,5$ 楼扔第二枚鸡蛋,最坏情况下,总共要操作 $6…","guid":"https://leetcode.cn/problems/super-egg-drop//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-xkbx","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-14T00:45:44.031Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-鸡蛋掉落🔴","url":"https://leetcode.cn/problems/super-egg-drop/","content":"给你 k
枚相同的鸡蛋,并可以使用一栋从第 1
层到第 n
层共有 n
层楼的建筑。
已知存在楼层 f
,满足 0 <= f <= n
,任何从 高于 f
的楼层落下的鸡蛋都会碎,从 f
楼层或比它低的楼层落下的鸡蛋都不会破。
每次操作,你可以取一枚没有碎的鸡蛋并把它从任一楼层 x
扔下(满足 1 <= x <= n
)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f
确切的值 的 最小操作次数 是多少?
示例 1:
\\n\\n输入:k = 1, n = 2\\n输出:2\\n解释:\\n鸡蛋从 1 楼掉落。如果它碎了,肯定能得出 f = 0 。 \\n否则,鸡蛋从 2 楼掉落。如果它碎了,肯定能得出 f = 1 。 \\n如果它没碎,那么肯定能得出 f = 2 。 \\n因此,在最坏的情况下我们需要移动 2 次以确定 f 是多少。 \\n\\n\\n
示例 2:
\\n\\n输入:k = 2, n = 6\\n输出:3\\n\\n\\n
示例 3:
\\n\\n输入:k = 3, n = 14\\n输出:4\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= k <= 100
1 <= n <= 104
给你 2 枚相同 的鸡蛋,和一栋从第 1
层到第 n
层共有 n
层楼的建筑。
已知存在楼层 f
,满足 0 <= f <= n
,任何从 高于 f
的楼层落下的鸡蛋都 会碎 ,从 f
楼层或比它低 的楼层落下的鸡蛋都 不会碎 。
每次操作,你可以取一枚 没有碎 的鸡蛋并把它从任一楼层 x
扔下(满足 1 <= x <= n
)。如果鸡蛋碎了,你就不能再次使用它。如果某枚鸡蛋扔下后没有摔碎,则可以在之后的操作中 重复使用 这枚鸡蛋。
请你计算并返回要确定 f
确切的值 的 最小操作次数 是多少?
\\n\\n
示例 1:
\\n\\n输入:n = 2\\n输出:2\\n解释:我们可以将第一枚鸡蛋从 1 楼扔下,然后将第二枚从 2 楼扔下。\\n如果第一枚鸡蛋碎了,可知 f = 0;\\n如果第二枚鸡蛋碎了,但第一枚没碎,可知 f = 1;\\n否则,当两个鸡蛋都没碎时,可知 f = 2。\\n\\n\\n
示例 2:
\\n\\n输入:n = 100\\n输出:14\\n解释:\\n一种最优的策略是:\\n- 将第一枚鸡蛋从 9 楼扔下。如果碎了,那么 f 在 0 和 8 之间。将第二枚从 1 楼扔下,然后每扔一次上一层楼,在 8 次内找到 f 。总操作次数 = 1 + 8 = 9 。\\n- 如果第一枚鸡蛋没有碎,那么再把第一枚鸡蛋从 22 层扔下。如果碎了,那么 f 在 9 和 21 之间。将第二枚鸡蛋从 10 楼扔下,然后每扔一次上一层楼,在 12 次内找到 f 。总操作次数 = 2 + 12 = 14 。\\n- 如果第一枚鸡蛋没有再次碎掉,则按照类似的方法从 34, 45, 55, 64, 72, 79, 85, 90, 94, 97, 99 和 100 楼分别扔下第一枚鸡蛋。\\n不管结果如何,最多需要扔 14 次来确定 f 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 1000
我们定义一个数组或哈希表 $\\\\textit{cnt}$ 记录每个数字出现的次数。
\\n接下来,遍历数组 $\\\\textit{nums}$,当某个数字出现两次时,我们将其与答案进行异或运算。
\\n最后返回答案即可。
\\n###python
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n cnt = Counter(nums)\\n return reduce(xor, [x for x, v in cnt.items() if v == 2], 0)\\n
\\n###java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n int[] cnt = new int[51];\\n int ans = 0;\\n for (int x : nums) {\\n if (++cnt[x] == 2) {\\n ans ^= x;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int duplicateNumbersXOR(vector<int>& nums) {\\n int cnt[51]{};\\n int ans = 0;\\n for (int x : nums) {\\n if (++cnt[x] == 2) {\\n ans ^= x;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc duplicateNumbersXOR(nums []int) (ans int) {\\ncnt := [51]int{}\\nfor _, x := range nums {\\ncnt[x]++\\nif cnt[x] == 2 {\\nans ^= x\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction duplicateNumbersXOR(nums: number[]): number {\\n const cnt: number[] = Array(51).fill(0);\\n let ans: number = 0;\\n for (const x of nums) {\\n if (++cnt[x] === 2) {\\n ans ^= x;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(M)$。其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,而 $M$ 是数组 $\\\\textit{nums}$ 中的最大值或数组 $\\\\textit{nums}$ 不同数字的个数。
\\n由于题目中给出的数字范围是 $1 \\\\leq \\\\textit{nums}[i] \\\\leq 50$,我们可以使用一个 $64$ 位的整数来存储每个数字的出现次数。
\\n我们定义一个整数 $\\\\textit{mask}$ 来记录每个数字是否出现过。
\\n接下来,遍历数组 $\\\\textit{nums}$,当某个数字出现两次时,即 $\\\\textit{mask}$ 的二进制表示中第 $x$ 位为 $1$ 时,我们将其与答案进行异或运算。否则,我们将 $\\\\textit{mask}$ 的第 $x$ 位设置为 $1$。
\\n最后返回答案即可。
\\n###python
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n ans = mask = 0\\n for x in nums:\\n if mask >> x & 1:\\n ans ^= x\\n else:\\n mask |= 1 << x\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n int ans = 0;\\n long mask = 0;\\n for (int x : nums) {\\n if ((mask >> x & 1) == 1) {\\n ans ^= x;\\n } else {\\n mask |= 1L << x;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int duplicateNumbersXOR(vector<int>& nums) {\\n int ans = 0;\\n long long mask = 0;\\n for (int x : nums) {\\n if (mask >> x & 1) {\\n ans ^= x;\\n } else {\\n mask |= 1LL << x;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc duplicateNumbersXOR(nums []int) (ans int) {\\nmask := 0\\nfor _, x := range nums {\\nif mask>>x&1 == 1 {\\nans ^= x\\n} else {\\nmask |= 1 << x\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction duplicateNumbersXOR(nums: number[]): number {\\n let ans = 0;\\n let mask = 0n;\\n for (const x of nums) {\\n if ((mask >> BigInt(x)) & 1n) {\\n ans ^= x;\\n } else {\\n mask |= 1n << BigInt(x);\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:计数 我们定义一个数组或哈希表 $\\\\textit{cnt}$ 记录每个数字出现的次数。\\n\\n接下来,遍历数组 $\\\\textit{nums}$,当某个数字出现两次时,我们将其与答案进行异或运算。\\n\\n最后返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n cnt = Counter(nums)\\n return reduce(xor, [x for x, v in cnt.items() if v…","guid":"https://leetcode.cn/problems/find-the-xor-of-numbers-which-appear-twice//solution/python3javacgotypescript-yi-ti-shuang-ji-bvjo","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-12T00:56:39.274Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-求出出现两次数字的 XOR 值🟢","url":"https://leetcode.cn/problems/find-the-xor-of-numbers-which-appear-twice/","content":"给你一个数组 nums
,数组中的数字 要么 出现一次,要么 出现两次。
请你返回数组中所有出现两次数字的按位 XOR
值,如果没有数字出现过两次,返回 0 。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,1,3]
\\n\\n输出:1
\\n\\n解释:
\\n\\nnums
中唯一出现过两次的数字是 1 。
示例 2:
\\n\\n输入:nums = [1,2,3]
\\n\\n输出:0
\\n\\n解释:
\\n\\nnums
中没有数字出现两次。
示例 3:
\\n\\n输入:nums = [1,2,2,1]
\\n\\n输出:3
\\n\\n解释:
\\n\\n数字 1 和 2 出现过两次。1 XOR 2 == 3
。
\\n\\n
提示:
\\n\\n1 <= nums.length <= 50
1 <= nums[i] <= 50
nums
中每个数字要么出现过一次,要么出现过两次。思路
\\n首先将问题分解为子问题。考虑示例 $1$,整个排列 $\\\\textit{perm}[0,1,2]$ 恰好有 $2$ 个逆序对。分情况进行讨论:
\\n在考虑 $\\\\textit{perm}[0,1]$ 中的逆序对时,我们不需要知道排列中的元素是哪些,因为这些元素都是各异的,我们只需要知道排列的长度,以及需要构造出的逆序对数目。
\\n构造函数 $\\\\textit{dfs}(\\\\textit{end},\\\\textit{cnt})$,用来计算排列逆序对为 $\\\\textit{cnt}$ 且满足 $\\\\textit{requirements}$ 的排列 $\\\\textit{perm}[0\\\\dots end]$ 的个数。代入示例 $1$,我们可以得到 $\\\\textit{dfs}(2, 2) = \\\\textit{dfs}(1, 0) + \\\\textit{dfs}(1, 1) + \\\\textit{dfs}(1, 2)$ 的递推公式。更一般地,我们得到以下递推公式:
\\n接下来考虑边界条件。在 $\\\\textit{requirements}$ 中,当 $\\\\textit{end} = 0$ 时,如果 $\\\\textit{cnt} \\\\ne 0$,那么不存在这样的排列,直接返回 $0$。我们再将 $[\\\\textit{end}=0,\\\\textit{cnt}=0]$ 添加到 $\\\\textit{requirements}$ 中,这样,边界条件 $\\\\textit{dfs}(0,0)$ 就可以直接返回 $1$。
\\n注意需要对 $\\\\textit{dfs}$ 应用记忆化搜索,这样每个状态最多被计算一次,才能降低时间复杂度。
\\n最后返回 $\\\\textit{dfs}(n-1, \\\\textit{reqMap}[n - 1])$ 即可,其中 $\\\\textit{reqMap}$ 是将 $\\\\textit{requirements}$ 转化成的键值对,键为 $\\\\textit{end}_i$,值为 $\\\\textit{cnt}_i$。
\\n代码
\\n###Python
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n\\n mod = 10 ** 9 + 7\\n reqMap = {0: 0}\\n for end, cnt in requirements:\\n reqMap[end] = cnt\\n if reqMap[0]:\\n return 0\\n \\n @cache\\n def dfs(end: int, cnt: int) -> int:\\n if end == 0:\\n return 1\\n if end - 1 in reqMap:\\n r = reqMap[end - 1]\\n if r <= cnt <= end + r:\\n return dfs(end - 1, r)\\n else:\\n return 0\\n else:\\n sm = 0\\n for i in range(min(end, cnt) + 1):\\n sm += dfs(end - 1, cnt - i)\\n sm %= mod\\n return sm\\n \\n return dfs(n - 1, reqMap[n - 1])\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n const int MOD = 1e9 + 7;\\n unordered_map<int, int> reqMap;\\n int maxCnt = 0;\\n reqMap[0] = 0;\\n for (auto& req : requirements) {\\n reqMap[req[0]] = req[1];\\n maxCnt = max(maxCnt, req[1]);\\n }\\n if (reqMap[0]) {\\n return 0;\\n }\\n\\n vector<vector<int>> dp(n, vector<int>(maxCnt + 1, -1));\\n function<int(int, int)> dfs = [&](int end, int cnt) -> int {\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.find(end - 1) != reqMap.end()) {\\n int r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r);\\n } else {\\n return dp[end][cnt] = 0;\\n }\\n } else {\\n int sm = 0;\\n for (int i = 0; i <= min(end, cnt); ++i) {\\n sm = (sm + dfs(end - 1, cnt - i)) % MOD;\\n }\\n return dp[end][cnt] = sm;\\n }\\n };\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n static final int MOD = 1000000007;\\n Map<Integer, Integer> reqMap = new HashMap<Integer, Integer>();\\n int[][] dp;\\n\\n public int numberOfPermutations(int n, int[][] requirements) {\\n int maxCnt = 0;\\n reqMap.put(0, 0);\\n for (int[] req : requirements) {\\n reqMap.put(req[0], req[1]);\\n maxCnt = Math.max(maxCnt, req[1]);\\n }\\n if (reqMap.get(0) != 0) {\\n return 0;\\n }\\n\\n dp = new int[n][maxCnt + 1];\\n for (int i = 0; i < n; i++) {\\n Arrays.fill(dp[i], -1);\\n }\\n\\n return dfs(n - 1, reqMap.get(n - 1));\\n }\\n\\n public int dfs(int end, int cnt) {\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.containsKey(end - 1)) {\\n int r = reqMap.get(end - 1);\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r);\\n } else {\\n return dp[end][cnt] = 0;\\n }\\n } else {\\n int sm = 0;\\n for (int i = 0; i <= Math.min(end, cnt); ++i) {\\n sm = (sm + dfs(end - 1, cnt - i)) % MOD;\\n }\\n return dp[end][cnt] = sm;\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const int MOD = 1000000007;\\n IDictionary<int, int> reqDictionary = new Dictionary<int, int>();\\n int[][] dp;\\n\\n public int NumberOfPermutations(int n, int[][] requirements) {\\n int maxCnt = 0;\\n reqDictionary.Add(0, 0);\\n foreach (int[] req in requirements) {\\n if (!reqDictionary.ContainsKey(req[0])) {\\n reqDictionary.Add(req[0], req[1]);\\n } else {\\n reqDictionary[req[0]] = req[1];\\n }\\n maxCnt = Math.Max(maxCnt, req[1]);\\n }\\n if (reqDictionary[0] != 0) {\\n return 0;\\n }\\n\\n dp = new int[n][];\\n for (int i = 0; i < n; i++) {\\n dp[i] = new int[maxCnt + 1];\\n Array.Fill(dp[i], -1);\\n }\\n\\n return DFS(n - 1, reqDictionary[n - 1]);\\n }\\n\\n public int DFS(int end, int cnt) {\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqDictionary.ContainsKey(end - 1)) {\\n int r = reqDictionary[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = DFS(end - 1, r);\\n } else {\\n return dp[end][cnt] = 0;\\n }\\n } else {\\n int sm = 0;\\n for (int i = 0; i <= Math.Min(end, cnt); ++i) {\\n sm = (sm + DFS(end - 1, cnt - i)) % MOD;\\n }\\n return dp[end][cnt] = sm;\\n }\\n }\\n}\\n
\\n###Go
\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\n const MOD int = 1e9 + 7\\n reqMap := map[int]int{0: 0}\\n maxCnt := 0\\nfor _, req := range requirements {\\nreqMap[req[0]] = req[1]\\n maxCnt = max(maxCnt, req[1])\\n}\\nif reqMap[0] != 0 {\\nreturn 0\\n}\\ndp := make([][]int, n)\\nfor i := range dp {\\ndp[i] = make([]int, maxCnt + 1)\\nfor j := range dp[i] {\\ndp[i][j] = -1\\n}\\n}\\n\\n var dfs func(end, cnt int) int\\n dfs = func(end, cnt int) int {\\n if end == 0 {\\n return 1\\n }\\n if dp[end][cnt] != -1 {\\n return dp[end][cnt]\\n }\\n if r, exists := reqMap[end - 1]; exists {\\n if r <= cnt && cnt <= end + r {\\n dp[end][cnt] = dfs(end - 1, r)\\n return dp[end][cnt]\\n }\\n return 0\\n }\\n\\n totSum := 0\\n for i := 0; i <= min(end, cnt); i++ {\\n totSum = (totSum + dfs(end - 1, cnt - i)) % MOD\\n }\\n dp[end][cnt] = totSum\\n return dp[end][cnt]\\n }\\n\\nreturn dfs(n - 1, reqMap[n - 1])\\n}\\n
\\n###C
\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n hashAddItem(obj, key, val);\\n } else {\\n pEntry->val = val;\\n }\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nconst long long MOD = 1e9 + 7;\\n\\nint dfs(int end, int cnt, HashItem **reqMap, int **dp) {\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n\\n if (hashFindItem(reqMap, end - 1)) {\\n int r = hashGetItem(reqMap, end - 1, 0);\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r, reqMap, dp);\\n } else {\\n return dp[end][cnt] = 0;\\n }\\n } else {\\n long long sum = 0;\\n for (int i = 0; i <= fmin(end, cnt); ++i) {\\n sum = (sum + dfs(end - 1, cnt - i, reqMap, dp)) % MOD;\\n }\\n return dp[end][cnt] = sum;\\n }\\n}\\n\\nint numberOfPermutations(int n, int** requirements, int requirementsSize, int* requirementsColSize) {\\n HashItem *reqMap = NULL;\\n int maxCnt = 0;\\n hashAddItem(&reqMap, 0, 0);\\n for (int i = 0; i < requirementsSize; i++) {\\n int end = requirements[i][0];\\n int cnt = requirements[i][1];\\n hashSetItem(&reqMap, end, cnt);\\n maxCnt = fmax(maxCnt, cnt);\\n }\\n if (hashGetItem(&reqMap, 0, 0)) {\\n return 0;\\n }\\n int *dp[n];\\n for (int i = 0; i < n; i++) {\\n dp[i] = (int *)malloc(sizeof(int) * (maxCnt + 1));\\n memset(dp[i], 0xff, sizeof(int) * (maxCnt + 1));\\n }\\n \\n int ret = dfs(n - 1, hashGetItem(&reqMap, n - 1, 0), &reqMap, dp);\\n hashFree(&reqMap);\\n return ret;\\n}\\n
\\n###JavaScript
\\nconst MOD = 1e9 + 7;\\n\\nvar numberOfPermutations = function(n, requirements) {\\n const reqMap = {0: 0};\\n let maxCnt = 0;\\n requirements.forEach(([end, cnt]) => {\\n reqMap[end] = cnt;\\n maxCnt = Math.max(maxCnt, cnt);\\n });\\n if (reqMap[0] !== 0) {\\n return 0;\\n }\\n const dp = Array.from({ length: n }, () => Array(maxCnt + 1).fill(-1));\\n\\n function dfs(end, cnt) {\\n if (end === 0) {\\n return 1;\\n }\\n if (dp[end][cnt] !== -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.hasOwnProperty(end - 1)) {\\n const r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n dp[end][cnt] = dfs(end - 1, r);\\n return dp[end][cnt];\\n }\\n return 0;\\n }\\n\\n let totSum = 0;\\n for (let i = 0; i <= Math.min(end, cnt); i++) {\\n totSum = (totSum + dfs(end - 1, cnt - i)) % MOD;\\n }\\n dp[end][cnt] = totSum;\\n return dp[end][cnt];\\n }\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n};\\n
\\n###TypeScript
\\nconst MOD = 1e9 + 7;\\n\\nfunction numberOfPermutations(n: number, requirements: number[][]): number {\\n const reqMap: Record<number, number> = { 0: 0 };\\n let maxCnt = 0;\\n requirements.forEach(([end, cnt]) => {\\n reqMap[end] = cnt;\\n maxCnt = Math.max(maxCnt, cnt);\\n });\\n if (reqMap[0] !== 0) {\\n return 0;\\n }\\n const dp: number[][] = Array.from({ length: n }, () => Array(maxCnt + 1).fill(-1));\\n\\n const dfs = (end: number, cnt: number): number => {\\n if (end === 0) {\\n return 1;\\n }\\n if (dp[end][cnt] !== -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.hasOwnProperty(end - 1)) {\\n const r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n dp[end][cnt] = dfs(end - 1, r);\\n return dp[end][cnt];\\n }\\n return 0;\\n }\\n\\n let totSum = 0;\\n for (let i = 0; i <= Math.min(end, cnt); i++) {\\n totSum = (totSum + dfs(end - 1, cnt - i)) % MOD;\\n }\\n\\n dp[end][cnt] = totSum;\\n return dp[end][cnt];\\n };\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n};\\n
\\n###Rust
\\nuse std::collections::HashMap;\\nconst MOD: i64 = 1_000_000_007;\\n\\nimpl Solution {\\n pub fn number_of_permutations(n: i32, requirements: Vec<Vec<i32>>) -> i32 {\\n let mut req_map = HashMap::new();\\n req_map.insert(0, 0);\\n let mut max_cnt = 0;\\n for i in 0..requirements.len() {\\n let end = requirements[i][0];\\n let cnt = requirements[i][1];\\n req_map.insert(end, cnt);\\n if cnt > max_cnt {\\n max_cnt = cnt;\\n }\\n }\\n if *req_map.get(&0).unwrap() != 0 {\\n return 0;\\n }\\n let mut dp = vec![vec![-1; max_cnt as usize + 1]; n as usize];\\n\\n fn dfs(end: usize, cnt: usize, req_map: &HashMap<i32, i32>, dp: &mut Vec<Vec<i64>>) -> i64 {\\n if end == 0 {\\n return 1;\\n }\\n if dp[end][cnt] != -1 {\\n return dp[end][cnt];\\n }\\n if let Some(&r) = req_map.get(&(end as i32 - 1)) {\\n if r as usize <= cnt && cnt <= end + r as usize {\\n dp[end][cnt] = dfs(end - 1, r as usize, req_map, dp);\\n return dp[end][cnt];\\n }\\n return 0;\\n }\\n let mut tot_sum = 0;\\n for i in 0..=cnt.min(end) {\\n tot_sum = (tot_sum + dfs(end - 1, cnt - i, req_map, dp)) % MOD;\\n }\\n dp[end][cnt] = tot_sum;\\n tot_sum\\n }\\n\\n dfs(n as usize - 1, *req_map.get(&(n - 1)).unwrap() as usize, &req_map, &mut dp) as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m\\\\times n\\\\times \\\\textit{min}(m,n))$,其中 $m = \\\\textit{max}(cnt_i)$。状态一共有 $O(m\\\\times n)$ 种,每个状态消耗 $O(\\\\textit{min}(m,n))$ 时间复杂度计算。
\\n空间复杂度:$O(m\\\\times n)$。
\\n思路
\\n考虑方法一中时间复杂度的瓶颈。每个状态需要消耗 $O(\\\\textit{min}(m,n))$ 时间复杂度计算,并且都是求和的形式。考虑 $\\\\textit{dfs}(\\\\textit{end},\\\\textit{cnt})$ 和 $\\\\textit{dfs}(\\\\textit{end},\\\\textit{cnt}-1)$ 的区别。当 $\\\\textit{end}-1$ 不在 $\\\\textit{requirements}$ 体现时:
\\n同时添加一个新的边界条件,如果 $\\\\textit{cnt} \\\\le 0$,那么直接返回 $0$,其他与方法一保持一致。这样我们就降低了时间复杂度,计算每个状态的时间复杂度变为了 $O(1)$。
\\n代码
###Python
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n\\n mod = 10 ** 9 + 7\\n reqMap = {0: 0}\\n for end, cnt in requirements:\\n reqMap[end] = cnt\\n if reqMap[0]:\\n return 0\\n \\n @cache\\n def dfs(end: int, cnt: int) -> int:\\n if cnt < 0:\\n return 0\\n if end == 0:\\n return 1\\n if end - 1 in reqMap:\\n r = reqMap[end - 1]\\n if r <= cnt <= end + r:\\n return dfs(end - 1, r)\\n else:\\n return 0\\n else:\\n if cnt > end:\\n return (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt)) % mod\\n else:\\n return (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % mod\\n \\n return dfs(n - 1, reqMap[n - 1])\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n const long long MOD = 1e9 + 7;\\n unordered_map<int, int> reqMap;\\n int maxCnt = 0;\\n reqMap[0] = 0;\\n for (auto& req : requirements) {\\n reqMap[req[0]] = req[1];\\n maxCnt = max(maxCnt, req[1]);\\n }\\n if (reqMap[0]) {\\n return 0;\\n }\\n\\n vector<vector<long long>> dp(n, vector<long long>(maxCnt + 1, -1));\\n function<int(int, int)> dfs = [&](int end, int cnt) -> int {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.count(end - 1)) {\\n int r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r);\\n }\\n return 0;\\n } else {\\n if (cnt > end) {\\n return dp[end][cnt] = (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt) + MOD) % MOD;\\n } else {\\n return dp[end][cnt] = (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % MOD;\\n }\\n }\\n };\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n static final int MOD = 1000000007;\\n Map<Integer, Integer> reqMap = new HashMap<Integer, Integer>();\\n long[][] dp;\\n\\n public int numberOfPermutations(int n, int[][] requirements) {\\n int maxCnt = 0;\\n reqMap.put(0, 0);\\n for (int[] req : requirements) {\\n reqMap.put(req[0], req[1]);\\n maxCnt = Math.max(maxCnt, req[1]);\\n }\\n if (reqMap.get(0) != 0) {\\n return 0;\\n }\\n\\n dp = new long[n][maxCnt + 1];\\n for (int i = 0; i < n; i++) {\\n Arrays.fill(dp[i], -1);\\n }\\n\\n return (int) dfs(n - 1, reqMap.get(n - 1));\\n }\\n\\n public long dfs(int end, int cnt) {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.containsKey(end - 1)) {\\n int r = reqMap.get(end - 1);\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r);\\n }\\n return 0;\\n } else {\\n if (cnt > end) {\\n return dp[end][cnt] = (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt) + MOD) % MOD;\\n } else {\\n return dp[end][cnt] = (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % MOD;\\n }\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const int MOD = 1000000007;\\n IDictionary<int, int> reqDictionary = new Dictionary<int, int>();\\n long[][] dp;\\n\\n public int NumberOfPermutations(int n, int[][] requirements) {\\n int maxCnt = 0;\\n reqDictionary.Add(0, 0);\\n foreach (int[] req in requirements) {\\n if (!reqDictionary.ContainsKey(req[0])) {\\n reqDictionary.Add(req[0], req[1]);\\n } else {\\n reqDictionary[req[0]] = req[1];\\n }\\n maxCnt = Math.Max(maxCnt, req[1]);\\n }\\n if (reqDictionary[0] != 0) {\\n return 0;\\n }\\n\\n dp = new long[n][];\\n for (int i = 0; i < n; i++) {\\n dp[i] = new long[maxCnt + 1];\\n Array.Fill(dp[i], -1);\\n }\\n\\n return (int) DFS(n - 1, reqDictionary[n - 1]);\\n }\\n\\n public long DFS(int end, int cnt) {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (reqDictionary.ContainsKey(end - 1)) {\\n int r = reqDictionary[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = DFS(end - 1, r);\\n }\\n return 0;\\n } else {\\n if (cnt > end) {\\n return dp[end][cnt] = (DFS(end, cnt - 1) - DFS(end - 1, cnt - 1 - end) + DFS(end - 1, cnt) + MOD) % MOD;\\n } else {\\n return dp[end][cnt] = (DFS(end, cnt - 1) + DFS(end - 1, cnt)) % MOD;\\n }\\n }\\n }\\n}\\n
\\n###Go
\\nconst MOD int64 = 1e9 + 7\\n\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\n reqMap := make(map[int]int)\\nreqMap[0] = 0\\nmaxCnt := 0\\nfor _, req := range requirements {\\nreqMap[req[0]] = req[1]\\nif req[1] > maxCnt {\\nmaxCnt = req[1]\\n}\\n}\\nif reqMap[0] != 0 {\\nreturn 0\\n}\\n\\ndp := make([][]int64, n)\\nfor i := range dp {\\ndp[i] = make([]int64, maxCnt + 1)\\nfor j := range dp[i] {\\ndp[i][j] = -1\\n}\\n}\\n\\nvar dfs func(int, int) int64\\ndfs = func(end, cnt int) int64 {\\nif cnt < 0 {\\nreturn 0\\n}\\nif end == 0 {\\nreturn 1\\n}\\nif dp[end][cnt] != -1 {\\nreturn dp[end][cnt]\\n}\\nif r, exists := reqMap[end - 1]; exists {\\nif r <= cnt && cnt <= end + r {\\ndp[end][cnt] = dfs(end - 1, r)\\nreturn dp[end][cnt]\\n}\\nreturn 0\\n} else {\\nif cnt > end {\\ndp[end][cnt] = (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt) + MOD) % MOD\\n} else {\\ndp[end][cnt] = (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % MOD\\n}\\nreturn dp[end][cnt]\\n}\\n}\\n\\nreturn int(dfs(n - 1, reqMap[n - 1]))\\n}\\n
\\n###C
\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n hashAddItem(obj, key, val);\\n } else {\\n pEntry->val = val;\\n }\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nconst long long MOD = 1e9 + 7;\\n\\nint dfs(int end, int cnt, HashItem **reqMap, int **dp) {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end == 0) {\\n return 1;\\n }\\n if (dp[end][cnt] != -1) {\\n return dp[end][cnt];\\n }\\n if (hashFindItem(reqMap, end - 1)) {\\n int r = hashGetItem(reqMap, end - 1, 0);\\n if (r <= cnt && cnt <= end + r) {\\n return dp[end][cnt] = dfs(end - 1, r, reqMap, dp);\\n }\\n return 0;\\n } else {\\n long long sum = 0;\\n if (cnt > end) {\\n sum += dfs(end, cnt - 1, reqMap, dp) - dfs(end - 1, cnt - 1 - end, reqMap, dp) \\\\\\n + dfs(end - 1, cnt, reqMap, dp) + MOD;\\n return dp[end][cnt] = sum % MOD;\\n } else {\\n sum += dfs(end, cnt - 1, reqMap, dp) + dfs(end - 1, cnt, reqMap, dp);\\n return dp[end][cnt] = sum % MOD;\\n }\\n }\\n}\\n\\nint numberOfPermutations(int n, int** requirements, int requirementsSize, int* requirementsColSize) {\\n HashItem *reqMap = NULL;\\n int maxCnt = 0;\\n hashAddItem(&reqMap, 0, 0);\\n for (int i = 0; i < requirementsSize; i++) {\\n int end = requirements[i][0];\\n int cnt = requirements[i][1];\\n hashSetItem(&reqMap, end, cnt);\\n maxCnt = fmax(maxCnt, cnt);\\n }\\n if (hashGetItem(&reqMap, 0, 0)) {\\n return 0;\\n }\\n int *dp[n];\\n for (int i = 0; i < n; i++) {\\n dp[i] = (int *)malloc(sizeof(int) * (maxCnt + 1));\\n memset(dp[i], 0xff, sizeof(int) * (maxCnt + 1));\\n }\\n \\n int ret = dfs(n - 1, hashGetItem(&reqMap, n - 1, 0), &reqMap, dp);\\n hashFree(&reqMap);\\n return ret;\\n}\\n
\\n###JavaScript
\\nconst MOD = 1e9 + 7;\\n\\nvar numberOfPermutations = function(n, requirements) {\\n const reqMap = {0: 0};\\n let maxCnt = 0;\\n requirements.forEach(([end, cnt]) => {\\n reqMap[end] = cnt;\\n maxCnt = Math.max(maxCnt, cnt);\\n });\\n if (reqMap[0] !== 0) {\\n return 0;\\n }\\n const dp = Array.from({ length: n }, () => Array(maxCnt + 1).fill(-1));\\n\\n function dfs(end, cnt) {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end === 0) {\\n return 1;\\n }\\n if (dp[end][cnt] !== -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.hasOwnProperty(end - 1)) {\\n const r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n dp[end][cnt] = dfs(end - 1, r);\\n return dp[end][cnt];\\n }\\n return 0;\\n } else {\\n if (cnt > end) {\\n dp[end][cnt] = (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt) + MOD) % MOD;\\n } else {\\n dp[end][cnt] = (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % MOD;\\n }\\n return dp[end][cnt];\\n }\\n }\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n};\\n
\\n###TypeScript
\\nconst MOD = 1e9 + 7;\\n\\nfunction numberOfPermutations(n: number, requirements: number[][]): number {\\n const reqMap: Record<number, number> = { 0: 0 };\\n let maxCnt = 0;\\n requirements.forEach(([end, cnt]) => {\\n reqMap[end] = cnt;\\n maxCnt = Math.max(maxCnt, cnt);\\n });\\n if (reqMap[0] !== 0) {\\n return 0;\\n }\\n const dp: number[][] = Array.from({ length: n }, () => Array(maxCnt + 1).fill(-1));\\n\\n const dfs = (end: number, cnt: number): number => {\\n if (cnt < 0) {\\n return 0;\\n }\\n if (end === 0) {\\n return 1;\\n }\\n if (dp[end][cnt] !== -1) {\\n return dp[end][cnt];\\n }\\n if (reqMap.hasOwnProperty(end - 1)) {\\n const r = reqMap[end - 1];\\n if (r <= cnt && cnt <= end + r) {\\n dp[end][cnt] = dfs(end - 1, r);\\n return dp[end][cnt];\\n }\\n return 0;\\n } else {\\n if (cnt > end) {\\n dp[end][cnt] = (dfs(end, cnt - 1) - dfs(end - 1, cnt - 1 - end) + dfs(end - 1, cnt) + MOD) % MOD;\\n } else {\\n dp[end][cnt] = (dfs(end, cnt - 1) + dfs(end - 1, cnt)) % MOD;\\n }\\n return dp[end][cnt];\\n }\\n };\\n\\n return dfs(n - 1, reqMap[n - 1]);\\n};\\n
\\n###Rust
\\nuse std::collections::HashMap;\\nconst MOD: i64 = 1_000_000_007;\\n\\nimpl Solution {\\n pub fn number_of_permutations(n: i32, requirements: Vec<Vec<i32>>) -> i32 {\\n let mut req_map: HashMap<i32, i32> = HashMap::new();\\n req_map.insert(0, 0);\\n let mut max_cnt = 0;\\n for i in 0..requirements.len() {\\n let end = requirements[i][0];\\n let cnt = requirements[i][1];\\n req_map.insert(end, cnt);\\n max_cnt = max_cnt.max(cnt);\\n }\\n if req_map[&0] != 0 {\\n return 0;\\n }\\n let mut dp = vec![vec![-1; (max_cnt + 1) as usize]; n as usize];\\n\\n fn dfs(end: usize, cnt: i32, req_map: &HashMap<i32, i32>, dp: &mut Vec<Vec<i64>>) -> i64 {\\n if cnt < 0 {\\n return 0;\\n }\\n if end == 0 {\\n return 1;\\n }\\n if dp[end][cnt as usize] != -1 {\\n return dp[end][cnt as usize];\\n }\\n if let Some(&r) = req_map.get(&(end as i32 - 1)) {\\n if r <= cnt && cnt <= (end as i32 + r) {\\n dp[end][cnt as usize] = dfs(end - 1, r, req_map, dp);\\n return dp[end][cnt as usize];\\n }\\n return 0;\\n } else {\\n if cnt > end as i32 {\\n dp[end][cnt as usize] = (dfs(end, cnt - 1, req_map, dp)\\n - dfs(end - 1, cnt - 1 - end as i32, req_map, dp)\\n + dfs(end - 1, cnt, req_map, dp)\\n + MOD) % MOD;\\n } else {\\n dp[end][cnt as usize] = (dfs(end, cnt - 1, req_map, dp)\\n + dfs(end - 1, cnt, req_map, dp)) % MOD;\\n }\\n return dp[end][cnt as usize];\\n }\\n }\\n\\n dfs(n as usize - 1, *req_map.get(&(n - 1)).unwrap(), &req_map, &mut dp) as i32\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(m\\\\times n)$,其中 $m = \\\\textit{max}(cnt_i)$。
\\n空间复杂度:$O(m\\\\times n)$。
\\n思路与算法
\\n我们可以递增地枚举三角形的高度,在第 $i$ 行时,如果对应的颜色的剩余球数大于等于 $i$ 个,那么就可以组成第 $i$ 行,否则不能,三角形的最大高度为 $i-1$。
\\n三角形的颜色布局有两种可能:即红蓝交替(第一行为红色)或者蓝红交替(第一行为蓝色),我们分别枚举这两种情况,并取二者高度的较大值即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n auto maxHeight = [](int x, int y) -> int {\\n for (int i = 1;; ++i) {\\n if (i % 2 == 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n }\\n else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n }\\n };\\n return max(maxHeight(red, blue), maxHeight(blue, red));\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n def maxHeight(x: int, y: int) -> int:\\n i = 1\\n while True:\\n if i % 2 == 1:\\n x -= i\\n if x < 0:\\n return i - 1\\n else:\\n y -= i\\n if y < 0:\\n return i - 1\\n i += 1\\n \\n return max(maxHeight(red, blue), maxHeight(blue, red))\\n
\\n###Java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n }\\n\\n public int maxHeight(int x, int y) {\\n int i = 1;\\n do {\\n if (i % 2 == 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n i++;\\n } while (true);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxHeightOfTriangle(int red, int blue) {\\n return Math.Max(MaxHeight(red, blue), MaxHeight(blue, red));\\n }\\n\\n public int MaxHeight(int x, int y) {\\n int i = 1;\\n do {\\n if (i % 2 == 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n i++;\\n } while (true);\\n }\\n}\\n
\\n###Go
\\nfunc maxHeightOfTriangle(red int, blue int) int {\\n return max(maxHeight(red, blue), maxHeight(blue, red))\\n}\\n\\nfunc maxHeight(x, y int) int {\\nfor i := 1; ; i++ {\\nif i%2 == 1 {\\nx -= i\\nif x < 0 {\\nreturn i - 1\\n}\\n} else {\\ny -= i\\nif y < 0 {\\nreturn i - 1\\n}\\n}\\n}\\n}\\n
\\n###C
\\nint maxHeight(int x, int y) {\\n for (int i = 1;; ++i) {\\n if (i % 2 == 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n }\\n}\\n\\nint maxHeightOfTriangle(int red, int blue) {\\n return fmax(maxHeight(red, blue), maxHeight(blue, red));\\n}\\n
\\n###JavaScript
\\nvar maxHeightOfTriangle = function(red, blue) {\\n const maxHeight = (x, y) => {\\n for (let i = 1;; i++) {\\n if (i % 2 === 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n }\\n }\\n\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n};\\n
\\n###TypeScript
\\nfunction maxHeightOfTriangle(red: number, blue: number): number {\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n};\\n\\nfunction maxHeight(x: number, y: number): number {\\n for (let i = 1;; i++) {\\n if (i % 2 === 1) {\\n x -= i;\\n if (x < 0) {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if (y < 0) {\\n return i - 1;\\n }\\n }\\n }\\n}\\n
\\n###Rust
\\nuse std::cmp::max;\\n\\nimpl Solution {\\n pub fn max_height_of_triangle(red: i32, blue: i32) -> i32 {\\n fn max_height(mut x: i32, mut y: i32) -> i32 {\\n let mut i = 1;\\n loop {\\n if i % 2 == 1 {\\n x -= i;\\n if x < 0 {\\n return i - 1;\\n }\\n } else {\\n y -= i;\\n if y < 0 {\\n return i - 1;\\n }\\n }\\n i += 1;\\n }\\n }\\n max(max_height(red, blue), max_height(blue, red))\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(\\\\sqrt{n})$,其中 $n$ 是题目中给定的 $\\\\textit{red}$ 和 $\\\\textit{blue}$ 的范围。同一种颜色的球形成一个公差为 $2$ 的等差数列,那么球的数量是三角形高度的平方级别,因此高度是数量的平方根级别,即 $O(\\\\sqrt{n})$。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n我们也可以使用等差数列公式直接计算出高度。
\\n对于从第一行开始的情况,球的个数依次为 $1, 3, 5, \\\\cdots, 2k-1$,其中 $2k-1$ 是最后一行,那么总计个数为:
\\n$$
\\n1 + 3 + 5 + \\\\cdots + (2k-1) = \\\\frac{(1+(2k-1)) \\\\times k}{2} = k^2
\\n$$
那么 $k$ 的最大值即为 $\\\\lfloor \\\\sqrt{n_o} \\\\rfloor$,其中 $n_o$ 是提供给奇数行的球的数量,$\\\\lfloor \\\\cdot \\\\rfloor$ 表示向下取整。
\\n同理,对于从第二行开始的情况,有:
\\n$$
\\n2+4+6+\\\\cdots+2k = \\\\frac{(2+2k) \\\\times k}{2} = k^2 + k
\\n$$
解方程可得 $k$ 的最大值为 $\\\\lfloor \\\\dfrac{-1+\\\\sqrt{1+4n_e}}{2} \\\\rfloor$,其中 $n_e$ 是提供给偶数行的球的数量。
\\n因此最后一个奇数行为 $2\\\\lfloor \\\\sqrt{n_o} \\\\rfloor - 1$,最后一个偶数行为 $2\\\\lfloor \\\\dfrac{-1+\\\\sqrt{1+4n_e}}{2} \\\\rfloor$,最终的答案即为其中的较小值加 $1$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n auto maxHeight = [](int x, int y) -> int {\\n int odd = 2 * int(sqrt(x)) - 1;\\n int even = 2 * int((-1 + sqrt(1 + 4 * y)) / 2);\\n return min(odd, even) + 1;\\n };\\n return max(maxHeight(red, blue), maxHeight(blue, red));\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n def maxHeight(x: int, y: int) -> int:\\n odd = 2 * int(sqrt(x)) - 1\\n even = 2 * int((-1 + sqrt(1 + 4 * y)) / 2)\\n return min(odd, even) + 1\\n \\n return max(maxHeight(red, blue), maxHeight(blue, red))\\n
\\n###Java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n }\\n\\n public int maxHeight(int x, int y) {\\n int odd = 2 * (int)(Math.sqrt(x)) - 1;\\n int even = 2 * (int)((-1 + Math.sqrt(1 + 4 * y)) / 2);\\n return Math.min(odd, even) + 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxHeightOfTriangle(int red, int blue) {\\n return Math.Max(MaxHeight(red, blue), MaxHeight(blue, red));\\n }\\n\\n public int MaxHeight(int x, int y) {\\n int odd = 2 * (int)(Math.Sqrt(x)) - 1;\\n int even = 2 * (int)((-1 + Math.Sqrt(1 + 4 * y)) / 2);\\n return Math.Min(odd, even) + 1;\\n }\\n}\\n
\\n###Go
\\nfunc maxHeightOfTriangle(red int, blue int) int {\\n return max(maxHeight(red, blue), maxHeight(blue, red))\\n}\\n\\nfunc maxHeight(x, y int) int {\\nodd := 2 * int(math.Sqrt(float64(x))) - 1\\neven := 2 * int((-1 + math.Sqrt(1 + 4 * float64(y))) / 2)\\nreturn min(odd, even) + 1\\n}\\n
\\n###C
\\nint maxHeight(int x, int y) {\\n int odd = 2 * (int)sqrt((double)x) - 1;\\n int even = 2 * (int)((-1 + sqrt(1 + 4.0 * (double)y)) / 2);\\n return fmin(odd, even) + 1;\\n}\\n\\nint maxHeightOfTriangle(int red, int blue) {\\n return fmax(maxHeight(red, blue), maxHeight(blue, red));\\n}\\n
\\n###JavaScript
\\nvar maxHeightOfTriangle = function(red, blue) {\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n};\\n\\nfunction maxHeight(x, y) {\\n const odd = 2 * Math.floor(Math.sqrt(x)) - 1;\\n const even = 2 * Math.floor(Math.floor(-1 + Math.sqrt(1 + 4 * y)) / 2);\\n return Math.min(odd, even) + 1;\\n}\\n
\\n###TypeScript
\\nfunction maxHeightOfTriangle(red: number, blue: number): number {\\n return Math.max(maxHeight(red, blue), maxHeight(blue, red));\\n};\\n\\nfunction maxHeight(x: number, y: number): number {\\n const odd = 2 * Math.floor(Math.sqrt(x)) - 1;\\n const even = 2 * Math.floor((-1 + Math.sqrt(1 + 4 * y)) / 2);\\n return Math.min(odd, even) + 1;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_height_of_triangle(red: i32, blue: i32) -> i32 {\\n fn maxHeight(x: i32, y: i32) -> i32 {\\n let odd = 2 * ((x as f64).sqrt() as i32) - 1;\\n let even = 2 * (((-1.0 + (1.0 + 4.0 * (y as f64)).sqrt()) / 2.0) as i32);\\n odd.min(even) + 1\\n }\\n std::cmp::max(maxHeight(red, blue), maxHeight(blue, red))\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n我们用一个哈希表 $\\\\textit{cnt1}$ 记录数组 $\\\\textit{nums1}$ 中每个数除以 $k$ 的商的出现次数,用一个哈希表 $\\\\textit{cnt2}$ 记录数组 $\\\\textit{nums2}$ 中每个数的出现次数。
\\n接下来,我们枚举数组 $\\\\textit{nums2}$ 中的每个数 $x$,对于每个数 $x$,我们枚举 $x$ 的倍数 $y$,其中 $y$ 的范围是 $[x, \\\\textit{mx}]$,其中 $\\\\textit{mx}$ 是 $\\\\textit{cnt1}$ 中的最大键值,然后我们统计 $\\\\textit{cnt1}[y]$ 的和,记为 $s$,最后我们将 $s \\\\times v$ 累加到答案中,其中 $v$ 是 $\\\\textit{cnt2}[x]$。
\\n###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt1 = Counter(x // k for x in nums1 if x % k == 0)\\n if not cnt1:\\n return 0\\n cnt2 = Counter(nums2)\\n ans = 0\\n mx = max(cnt1)\\n for x, v in cnt2.items():\\n s = sum(cnt1[y] for y in range(x, mx + 1, x))\\n ans += s * v\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> cnt1 = new HashMap<>();\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1.merge(x / k, 1, Integer::sum);\\n }\\n }\\n if (cnt1.isEmpty()) {\\n return 0;\\n }\\n Map<Integer, Integer> cnt2 = new HashMap<>();\\n for (int x : nums2) {\\n cnt2.merge(x, 1, Integer::sum);\\n }\\n long ans = 0;\\n int mx = Collections.max(cnt1.keySet());\\n for (var e : cnt2.entrySet()) {\\n int x = e.getKey(), v = e.getValue();\\n int s = 0;\\n for (int y = x; y <= mx; y += x) {\\n s += cnt1.getOrDefault(y, 0);\\n }\\n ans += 1L * s * v;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> cnt1;\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1[x / k]++;\\n }\\n }\\n if (cnt1.empty()) {\\n return 0;\\n }\\n unordered_map<int, int> cnt2;\\n for (int x : nums2) {\\n ++cnt2[x];\\n }\\n int mx = 0;\\n for (auto& [x, _] : cnt1) {\\n mx = max(mx, x);\\n }\\n long long ans = 0;\\n for (auto& [x, v] : cnt2) {\\n long long s = 0;\\n for (int y = x; y <= mx; y += x) {\\n s += cnt1[y];\\n }\\n ans += s * v;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) (ans int64) {\\ncnt1 := map[int]int{}\\nfor _, x := range nums1 {\\nif x%k == 0 {\\ncnt1[x/k]++\\n}\\n}\\nif len(cnt1) == 0 {\\nreturn 0\\n}\\ncnt2 := map[int]int{}\\nfor _, x := range nums2 {\\ncnt2[x]++\\n}\\nmx := 0\\nfor x := range cnt1 {\\nmx = max(mx, x)\\n}\\nfor x, v := range cnt2 {\\ns := 0\\nfor y := x; y <= mx; y += x {\\ns += cnt1[y]\\n}\\nans += int64(s) * int64(v)\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n const cnt1: Map<number, number> = new Map();\\n for (const x of nums1) {\\n if (x % k === 0) {\\n cnt1.set((x / k) | 0, (cnt1.get((x / k) | 0) || 0) + 1);\\n }\\n }\\n if (cnt1.size === 0) {\\n return 0;\\n }\\n const cnt2: Map<number, number> = new Map();\\n for (const x of nums2) {\\n cnt2.set(x, (cnt2.get(x) || 0) + 1);\\n }\\n const mx = Math.max(...cnt1.keys());\\n let ans = 0;\\n for (const [x, v] of cnt2) {\\n let s = 0;\\n for (let y = x; y <= mx; y += x) {\\n s += cnt1.get(y) || 0;\\n }\\n ans += s * v;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n + m + (M / k) \\\\times \\\\log m)$,空间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums1}$ 和 $\\\\textit{nums2}$ 的长度,而 $M$ 是数组 $\\\\textit{nums1}$ 中的最大值。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:哈希表 + 枚举倍数 我们用一个哈希表 $\\\\textit{cnt1}$ 记录数组 $\\\\textit{nums1}$ 中每个数除以 $k$ 的商的出现次数,用一个哈希表 $\\\\textit{cnt2}$ 记录数组 $\\\\textit{nums2}$ 中每个数的出现次数。\\n\\n接下来,我们枚举数组 $\\\\textit{nums2}$ 中的每个数 $x$,对于每个数 $x$,我们枚举 $x$ 的倍数 $y$,其中 $y$ 的范围是 $[x, \\\\textit{mx}]$,其中 $\\\\textit{mx}$ 是 $\\\\textit{cnt1}$ 中的最大键值…","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/python3javacgotypescript-yi-ti-yi-jie-ha-jltt","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-11T00:18:10.060Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"哈希表tv之面对空间复杂度哈希表不敌二进制掩码被一拳揍成哈基米","url":"https://leetcode.cn/problems/find-the-xor-of-numbers-which-appear-twice//solution/ha-xi-biao-tvzhi-mian-dui-kong-jian-fu-z-mrmy","content":"具体来说,我们使用 $result$ 来记录结果值,使用哈希表 $theHash$ 来记录 $nums$ 中出现的数字。
\\n随后我们遍历 $nums$ 数组。对于当前的 $num$ ,有:
\\n最终返回 $result$ 即可。
\\n###C#
\\npublic class Solution {\\n public int DuplicateNumbersXOR(int[] nums) {\\n var theHash = new HashSet<int>();\\n return nums.Where(num => !theHash.Add(num)).Aggregate(0, (curr, num) => curr ^ num);\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static int duplicateNumbersXOR(const std::vector<int>& nums) {\\n auto theHash = std::unordered_set<int>{};\\n auto result = 0;\\n\\n for (const auto& num : nums) {\\n if (theHash.insert(num).second) continue;\\n result ^= num;\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n var theHash = new HashSet<Integer>();\\n var result = 0;\\n\\n for (var num : nums) {\\n if (theHash.add(num)) continue;\\n result ^= num;\\n }\\n\\n return result;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n theHash = set()\\n result = 0\\n\\n for num in nums:\\n if num in theHash:\\n result ^= num\\n else:\\n theHash.add(num)\\n\\n return result\\n\\n
\\n###Golang
\\nfunc duplicateNumbersXOR(nums []int) int {\\n theHash := make(map[int]bool)\\n result := 0\\n\\n for _, num := range nums {\\n if _, exists := theHash[num]; exists {\\n result ^= num\\n } else {\\n theHash[num] = true\\n }\\n }\\n\\n return result\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func duplicateNumbersXOR(nums: Array<Int64>): Int64 {\\n let theHash = HashSet<Int64>()\\n var result = 0\\n\\n for (num in nums) {\\n if (theHash.put(num)) { continue }\\n result ^= num\\n }\\n\\n return result\\n }\\n}\\n
\\n注意到题目中有 $1≤nums[i]≤50$ ,因此可以考虑使用 $64$ 位二进制整数取代哈希表。对于某一个 $nums[i]$ 记其为 $x$ ,此时我们可以根据二进制数中第 $x$ 位的值,来判断其是否出现过,若第 $x$ 位的值为 $1$ 则代表出现过,反之则否。
\\n具体来说,我们使用一个 $64$ 位二进制整数 $numHash$ 来取代哈希表,使用变量 $result$ 来记录结果值。
\\n随后我们遍历 $nums$ 数组,对于当前的 $x$ ,我们可以通过将 $numHash$ 右移 $x$ 位,随后再与 $1$ 相与,便能知其 $x$ 位上的值是 $0$ 还是 $1$,既 $numHash>>x;&;1$ ,同样:
\\n最终返回 $result$ 即可。
\\n###C#
\\npublic class Solution {\\n public int DuplicateNumbersXOR(int[] nums) {\\n var result = 0;\\n var numHash = 0L; \\n \\n foreach (var num in nums) {\\n if ((numHash >> num & 1) != 0) {\\n result ^= num;\\n } else {\\n numHash |= 1L << num;\\n }\\n }\\n\\n return result;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n static int duplicateNumbersXOR(const std::vector<int>& nums) {\\n auto result = 0;\\n auto numHash = 0L; \\n \\n for (const auto& num : nums) {\\n if ((numHash >> num & 1) != 0) {\\n result ^= num;\\n } else {\\n numHash |= 1L << num;\\n }\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n var result = 0;\\n var numHash = 0L; \\n \\n for (var num : nums) {\\n if ((numHash >> num & 1) != 0) {\\n result ^= num;\\n } else {\\n numHash |= 1L << num;\\n }\\n }\\n\\n return result;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n result = num_hash = 0\\n \\n for num in nums:\\n if (num_hash >> num & 1) != 0:\\n result ^= num\\n else:\\n num_hash |= 1 << num\\n\\n return result\\n\\n
\\n###Golang
\\nfunc duplicateNumbersXOR(nums []int) int {\\n result := 0\\n var numHash int64 = 0\\n \\n for _, num := range nums {\\n if (numHash>>num)&1 != 0 {\\n result ^= num\\n } else {\\n numHash |= 1 << uint(num)\\n }\\n }\\n\\n return result\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func duplicateNumbersXOR(nums: Array<Int64>): Int64 {\\n var result = 0\\n var numHash = 0\\n\\n for (num in nums) {\\n if ((numHash >> num & 1) != 0) {\\n result ^= num;\\n } else {\\n numHash |= 1 << num;\\n }\\n }\\n\\n return result\\n }\\n}\\n
\\n给你两个整数数组 nums1
和 nums2
,长度分别为 n
和 m
。同时给你一个正整数 k
。
如果 nums1[i]
可以被 nums2[j] * k
整除,则称数对 (i, j)
为 优质数对(0 <= i <= n - 1
, 0 <= j <= m - 1
)。
返回 优质数对 的总数。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums1 = [1,3,4], nums2 = [1,3,4], k = 1
\\n\\n输出:5
\\n\\n解释:
\\n\\n5个优质数对分别是 (0, 0)
, (1, 0)
, (1, 1)
, (2, 0)
, 和 (2, 2)
。
示例 2:
\\n\\n输入:nums1 = [1,2,4,12], nums2 = [2,4], k = 3
\\n\\n输出:2
\\n\\n解释:
\\n\\n2个优质数对分别是 (3, 0)
和 (3, 1)
。
\\n\\n
提示:
\\n\\n1 <= n, m <= 105
1 <= nums1[i], nums2[j] <= 106
1 <= k <= 103
假设 $n=10$。
\\n如果第一枚鸡蛋在 $4$ 楼扔下,分类讨论:
\\n这两种情况取最大值,因为在扔之前,我们不知道鸡蛋是否会碎。为了保证无论在何种情况下,我们都可以确定 $f$ 的值,所以要取最大值。
\\n一般地,可以枚举第一枚鸡蛋在 $1,2,3,\\\\cdots,10$ 楼扔下,分别计算每种情况需要操作多少次,取其中最小值,作为最终的答案。
\\n比如第一枚鸡蛋在 $4$ 楼扔下,到最终确定 $f$,需要操作 $4$ 次。而第一枚鸡蛋在 $2$ 楼扔下,到最终确定 $f$,需要操作 $5$ 次。那么相比在 $2$ 楼扔,肯定是在 $4$ 楼扔更优。
\\n由于我们会遇到和原问题相似的、规模更小的子问题,所以可以用递归解决。
\\n根据上面的讨论,定义状态为 $\\\\textit{dfs}(i)$,表示在一栋有 $i$ 层楼的建筑中扔鸡蛋,无论 $f$ 等于多少,我们都能确定 $f$ 的最小操作次数。
\\n枚举第一枚鸡蛋在 $j=1,2,3,\\\\cdots,i$ 楼扔下,分类讨论:
\\n这两种情况取最大值,即为在第 $j$ 楼扔下第一枚鸡蛋,到最终确定 $f$,所需要的最小操作次数,即
\\n$$
\\n\\\\max(j, \\\\textit{dfs}(i-j)+1)
\\n$$
对 $j=1,2,3,\\\\cdots,i$ 的上式取最小值,得
\\n$$
\\n\\\\textit{dfs}(i) = \\\\min_{j=1}^i \\\\max(j, \\\\textit{dfs}(i-j)+1)
\\n$$
递归边界:$\\\\textit{dfs}(0)=0$。此时 $f$ 一定等于 $0$,无需扔鸡蛋。
\\n递归入口:$\\\\textit{dfs}(n)$,也就是答案。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$,但本题除了 $\\\\textit{dfs}(0)$,其余 $\\\\textit{dfs}(i)$ 都是正数,所以初始化成 $0$ 也可以。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\n@cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\ndef dfs(i: int) -> int:\\n if i == 0:\\n return 0\\n return min(max(j, dfs(i - j) + 1) for j in range(1, i + 1))\\n\\nclass Solution:\\n def twoEggDrop(self, n: int) -> int:\\n return dfs(n)\\n
\\n###py
\\nclass Solution:\\n @cache\\n def twoEggDrop(self, n: int) -> int:\\n if n == 0:\\n return 0\\n return min(max(j, self.twoEggDrop(n - j) + 1) for j in range(1, n + 1))\\n
\\n###java
\\nclass Solution {\\n private static final int[] memo = new int[1001];\\n\\n public int twoEggDrop(int n) {\\n if (n == 0) {\\n return 0;\\n }\\n if (memo[n] > 0) { // 之前计算过\\n return memo[n];\\n }\\n int res = Integer.MAX_VALUE;\\n for (int j = 1; j <= n; j++) {\\n res = Math.min(res, Math.max(j, twoEggDrop(n - j) + 1));\\n }\\n return memo[n] = res; // 记忆化\\n }\\n}\\n
\\n###cpp
\\nint memo[1001];\\n\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n if (n == 0) {\\n return 0;\\n }\\n int& res = memo[n]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n res = INT_MAX;\\n for (int j = 1; j <= n; j++) {\\n res = min(res, max(j, twoEggDrop(n - j) + 1));\\n }\\n return res;\\n }\\n};\\n
\\n###go
\\nvar memo [1001]int\\n\\nfunc twoEggDrop(n int) int {\\n if n == 0 {\\n return 0\\n }\\n p := &memo[n]\\n if *p > 0 { // 之前计算过\\n return *p\\n }\\n res := math.MaxInt\\n for j := 1; j <= n; j++ {\\n res = min(res, max(j, twoEggDrop(n-j)+1))\\n }\\n *p = res // 记忆化\\n return res\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i]$ 的定义和 $\\\\textit{dfs}(i)$ 的定义是一样的,都表示在一栋有 $i$ 层楼的建筑中扔鸡蛋,无论 $f$ 等于多少,我们都能确定 $f$ 的最小操作次数。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i] = \\\\min_{j=1}^i \\\\max(j, f[i-j]+1)
\\n$$
初始值 $f[0]=0$,翻译自递归边界 $\\\\textit{dfs}(0)=0$。
\\n答案为 $f[n]$,翻译自递归入口 $\\\\textit{dfs}(n)$。
\\n代码实现时,可以把 $f$ 数组的计算写在外面(预处理),这样无需每个测试数据都重新计算一遍。
\\n###py
\\nf = [0] * 1001\\nfor i in range(1, len(f)):\\n f[i] = min(max(j, f[i - j] + 1) for j in range(1, i + 1))\\n\\nclass Solution:\\n def twoEggDrop(self, n: int) -> int:\\n return f[n]\\n
\\n###java
\\nclass Solution {\\n private static final int[] f = new int[1001];\\n\\n static {\\n for (int i = 1; i < f.length; i++) {\\n f[i] = Integer.MAX_VALUE;\\n for (int j = 1; j <= i; j++) {\\n f[i] = Math.min(f[i], Math.max(j, f[i - j] + 1));\\n }\\n }\\n }\\n\\n public int twoEggDrop(int n) {\\n return f[n];\\n }\\n}\\n
\\n###cpp
\\nconst int MX = 1000;\\nint f[MX + 1];\\n\\nauto init = [] {\\n for (int i = 1; i <= MX; i++) {\\n f[i] = INT_MAX;\\n for (int j = 1; j <= i; j++) {\\n f[i] = min(f[i], max(j, f[i - j] + 1));\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n return f[n];\\n }\\n};\\n
\\n###go
\\nvar f [1001]int\\n\\nfunc init() {\\n for i := 1; i < len(f); i++ {\\n f[i] = math.MaxInt\\n for j := 1; j <= i; j++ {\\n f[i] = min(f[i], max(j, f[i-j]+1))\\n }\\n }\\n}\\n\\nfunc twoEggDrop(n int) int {\\n return f[n]\\n}\\n
\\n反过来,如果已知答案(操作次数),$n$ 最大可以是多少?
\\n假设答案是 $5$,也就是我们只能操作 $5$ 次。第一次操作,你打算在几楼扔第一枚鸡蛋?
\\n你可能会想:搏一搏,单车变摩托。楼层越高越好嘛,如果鸡蛋没碎,这对我们会更有利。
\\n最高可以在几楼扔?可以在 $6$ 楼扔吗?
\\n不能。万一赌错鸡蛋碎了,后面就只能依次在 $1,2,3,4,5$ 楼扔第二枚鸡蛋,最坏情况下,总共要操作 $6$ 次。
所以第一次操作的最优做法,是在 $5$ 楼扔第一枚鸡蛋:
\\n同理,在 $5+4=9$ 楼扔第一枚鸡蛋:
\\n同理,在 $5+4+3=12$ 楼扔第一枚鸡蛋:
\\n同理,在 $5+4+3+2=14$ 楼扔第一枚鸡蛋:
\\n综上所述,如果答案是 $5$,也就是可以扔 $5$ 次鸡蛋,那么 $n$ 最大是 $5+4+3+2+1=15$。对于更大的 $n$,比如 $n=16$,我们无法保证只用 $5$ 次操作就能确定 $f$(至少要 $6$ 次操作才能保证可以确定 $f$)。
\\n一般地,设答案为 $x$,必须满足
\\n$$
\\nx+(x-1)+(x-2) + \\\\cdots + 2 + 1 \\\\ge n
\\n$$
即
\\n$$
\\n\\\\dfrac{x(x+1)}{2} \\\\ge n
\\n$$
解一元二次不等式,得
\\n$$
\\nx \\\\ge \\\\dfrac{\\\\sqrt{8n+1}-1}{2}
\\n$$
由于 $x$ 是整数,所以最小的 $x$ 为
\\n$$
\\n\\\\left\\\\lceil\\\\dfrac{\\\\sqrt{8n+1}-1}{2}\\\\right\\\\rceil
\\n$$
###py
\\nclass Solution:\\n def twoEggDrop(self, n: int) -> int:\\n return ceil((sqrt(n * 8 + 1) - 1) / 2)\\n
\\n###java
\\nclass Solution {\\n public int twoEggDrop(int n) {\\n return (int) Math.ceil((Math.sqrt(n * 8 + 1) - 1) / 2);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n return ceil((sqrt(n * 8 + 1) - 1) / 2);\\n }\\n};\\n
\\n###go
\\nfunc twoEggDrop(n int) int {\\n return int(math.Ceil((math.Sqrt(float64(n*8+1)) - 1) / 2))\\n}\\n
\\n在 $a$ 是整数,$b$ 是正整数的前提下,有如下恒等式
\\n$$
\\n\\\\left\\\\lceil\\\\dfrac{x+a}{b}\\\\right\\\\rceil = \\\\left\\\\lceil\\\\dfrac{\\\\lceil x \\\\rceil + a}{b}\\\\right\\\\rceil
\\n$$
证明方法和 题解 复杂度分析中的证明是一样的。
\\n关于上取整的计算,在 $c$ 是整数,$d$ 是正整数的前提下,有如下恒等式
\\n$$
\\n\\\\left\\\\lceil\\\\dfrac{c}{d}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{c+d-1}{d}\\\\right\\\\rfloor
\\n$$
讨论 $c$ 被 $d$ 整除,和不被 $d$ 整除两种情况,可以证明上式的正确性。
\\n结合上面两个恒等式,我们有
\\n$$
\\n\\\\left\\\\lceil\\\\dfrac{\\\\sqrt{8n+1}-1}{2}\\\\right\\\\rceil = \\\\left\\\\lceil\\\\dfrac{\\\\lceil \\\\sqrt{8n+1} \\\\rceil -1}{2}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{\\\\lceil \\\\sqrt{8n+1} \\\\rceil}{2}\\\\right\\\\rfloor
\\n$$
对于本题来说,这个优化其实无所谓,但在 3296. 移山所需的最少秒数 中,这个优化可以减少运行时间。例如 Java 可以从 16 ms 优化到 10 ms。
\\n###py
\\nclass Solution:\\n def twoEggDrop(self, n: int) -> int:\\n return ceil(sqrt(n * 8 + 1)) // 2\\n
\\n###java
\\nclass Solution {\\n public int twoEggDrop(int n) {\\n return (int) Math.ceil(Math.sqrt(n * 8 + 1)) / 2;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n return (int) ceil(sqrt(n * 8 + 1)) / 2;\\n }\\n};\\n
\\n###go
\\nfunc twoEggDrop(n int) int {\\n return int(math.Ceil(math.Sqrt(float64(n*8+1)))) / 2\\n}\\n
\\n如果有 $3$ 枚鸡蛋呢?$k$ 枚鸡蛋呢?DP 代码要如何修改?
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:动态规划 一、寻找子问题\\n\\n假设 $n=10$。\\n\\n如果第一枚鸡蛋在 $4$ 楼扔下,分类讨论:\\n\\n如果鸡蛋碎了,那么接下来只能依次在 $1,2,3$ 楼扔第二枚鸡蛋,最坏情况下,总共要操作 $1+3=4$ 次。\\n如果鸡蛋没碎,那么接下来可以在 $5$ 到 $10$ 楼中继续扔第一枚鸡蛋,这等价于在一栋有 $10-4=6$ 层楼的建筑中扔鸡蛋的子问题。这个子问题的结果加上 $1$,就是原问题 $n=10$ 的答案。\\n\\n这两种情况取最大值,因为在扔之前,我们不知道鸡蛋是否会碎。为了保证无论在何种情况下,我们都可以确定 $f$ 的值,所以要取最大值。\\n\\n一…","guid":"https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors//solution/liang-chong-fang-fa-dong-tai-gui-hua-shu-hd4i","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-10T11:03:00.386Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题双解:暴力枚举 & 哈希表+枚举倍数(清晰题解)","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i//solution/python3javacgotypescript-yi-ti-shuang-ji-6e04","content":"我们直接枚举所有的数位 $(x, y)$,判断是否满足 $x \\\\bmod (y \\\\times k) = 0$,如果满足则答案加一。
\\n枚举结束后,返回答案即可。
\\n###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n return sum(x % (y * k) == 0 for x in nums1 for y in nums2)\\n
\\n###java
\\nclass Solution {\\n public int numberOfPairs(int[] nums1, int[] nums2, int k) {\\n int ans = 0;\\n for (int x : nums1) {\\n for (int y : nums2) {\\n if (x % (y * k) == 0) {\\n ++ans;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n int ans = 0;\\n for (int x : nums1) {\\n for (int y : nums2) {\\n if (x % (y * k) == 0) {\\n ++ans;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) (ans int) {\\nfor _, x := range nums1 {\\nfor _, y := range nums2 {\\nif x%(y*k) == 0 {\\nans++\\n}\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n let ans = 0;\\n for (const x of nums1) {\\n for (const y of nums2) {\\n if (x % (y * k) === 0) {\\n ++ans;\\n }\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(m \\\\times n)$,其中 $m$ 和 $n$ 分别是数组 $\\\\textit{nums1}$ 和 $\\\\textit{nums2}$ 的长度。空间复杂度 $O(1)$。
\\n我们用一个哈希表 $\\\\textit{cnt1}$ 记录数组 $\\\\textit{nums1}$ 中每个数除以 $k$ 的商的出现次数,用一个哈希表 $\\\\textit{cnt2}$ 记录数组 $\\\\textit{nums2}$ 中每个数的出现次数。
\\n接下来,我们枚举数组 $\\\\textit{nums2}$ 中的每个数 $x$,对于每个数 $x$,我们枚举 $x$ 的倍数 $y$,其中 $y$ 的范围是 $[x, \\\\textit{mx}]$,其中 $\\\\textit{mx}$ 是 $\\\\textit{cnt1}$ 中的最大键值,然后我们统计 $\\\\textit{cnt1}[y]$ 的和,记为 $s$,最后我们将 $s \\\\times v$ 累加到答案中,其中 $v$ 是 $\\\\textit{cnt2}[x]$。
\\n###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt1 = Counter(x // k for x in nums1 if x % k == 0)\\n if not cnt1:\\n return 0\\n cnt2 = Counter(nums2)\\n ans = 0\\n mx = max(cnt1)\\n for x, v in cnt2.items():\\n s = sum(cnt1[y] for y in range(x, mx + 1, x))\\n ans += s * v\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> cnt1 = new HashMap<>();\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1.merge(x / k, 1, Integer::sum);\\n }\\n }\\n if (cnt1.isEmpty()) {\\n return 0;\\n }\\n Map<Integer, Integer> cnt2 = new HashMap<>();\\n for (int x : nums2) {\\n cnt2.merge(x, 1, Integer::sum);\\n }\\n int ans = 0;\\n int mx = Collections.max(cnt1.keySet());\\n for (var e : cnt2.entrySet()) {\\n int x = e.getKey(), v = e.getValue();\\n int s = 0;\\n for (int y = x; y <= mx; y += x) {\\n s += cnt1.getOrDefault(y, 0);\\n }\\n ans += s * v;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> cnt1;\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1[x / k]++;\\n }\\n }\\n if (cnt1.empty()) {\\n return 0;\\n }\\n unordered_map<int, int> cnt2;\\n for (int x : nums2) {\\n ++cnt2[x];\\n }\\n int mx = 0;\\n for (auto& [x, _] : cnt1) {\\n mx = max(mx, x);\\n }\\n int ans = 0;\\n for (auto& [x, v] : cnt2) {\\n int s = 0;\\n for (int y = x; y <= mx; y += x) {\\n s += cnt1[y];\\n }\\n ans += s * v;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) (ans int) {\\ncnt1 := map[int]int{}\\nfor _, x := range nums1 {\\nif x%k == 0 {\\ncnt1[x/k]++\\n}\\n}\\nif len(cnt1) == 0 {\\nreturn 0\\n}\\ncnt2 := map[int]int{}\\nfor _, x := range nums2 {\\ncnt2[x]++\\n}\\nmx := 0\\nfor x := range cnt1 {\\nmx = max(mx, x)\\n}\\nfor x, v := range cnt2 {\\ns := 0\\nfor y := x; y <= mx; y += x {\\ns += cnt1[y]\\n}\\nans += s * v\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n const cnt1: Map<number, number> = new Map();\\n for (const x of nums1) {\\n if (x % k === 0) {\\n cnt1.set((x / k) | 0, (cnt1.get((x / k) | 0) || 0) + 1);\\n }\\n }\\n if (cnt1.size === 0) {\\n return 0;\\n }\\n const cnt2: Map<number, number> = new Map();\\n for (const x of nums2) {\\n cnt2.set(x, (cnt2.get(x) || 0) + 1);\\n }\\n const mx = Math.max(...cnt1.keys());\\n let ans = 0;\\n for (const [x, v] of cnt2) {\\n let s = 0;\\n for (let y = x; y <= mx; y += x) {\\n s += cnt1.get(y) || 0;\\n }\\n ans += s * v;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n + m + (M / k) \\\\times \\\\log m)$,空间复杂度 $O(n + m)$,其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums1}$ 和 $\\\\textit{nums2}$ 的长度,而 $M$ 是数组 $\\\\textit{nums1}$ 中的最大值。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:暴力枚举 我们直接枚举所有的数位 $(x, y)$,判断是否满足 $x \\\\bmod (y \\\\times k) = 0$,如果满足则答案加一。\\n\\n枚举结束后,返回答案即可。\\n\\n###python\\n\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n return sum(x % (y * k) == 0 for x in nums1 for y in nums2)\\n\\n\\n###java\\n\\nclass…","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i//solution/python3javacgotypescript-yi-ti-shuang-ji-6e04","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-10T00:22:52.582Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-优质数对的总数 I🟢","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i/","content":"给你两个整数数组 nums1
和 nums2
,长度分别为 n
和 m
。同时给你一个正整数 k
。
如果 nums1[i]
可以被 nums2[j] * k
整除,则称数对 (i, j)
为 优质数对(0 <= i <= n - 1
, 0 <= j <= m - 1
)。
返回 优质数对 的总数。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums1 = [1,3,4], nums2 = [1,3,4], k = 1
\\n\\n输出:5
\\n\\n解释:
\\n\\n5个优质数对分别是 (0, 0)
, (1, 0)
, (1, 1)
, (2, 0)
, 和 (2, 2)
。
示例 2:
\\n\\n输入:nums1 = [1,2,4,12], nums2 = [2,4], k = 3
\\n\\n输出:2
\\n\\n解释:
\\n\\n2个优质数对分别是 (3, 0)
和 (3, 1)
。
\\n\\n
提示:
\\n\\n1 <= n, m <= 50
1 <= nums1[i], nums2[j] <= 50
1 <= k <= 50
根据题目描述,我们需要求出数组 $\\\\textit{nums}$ 下标 $l$ 到 $r$ 的元素的按位或运算的结果,即 $\\\\textit{nums}[l] \\\\lor \\\\textit{nums}[l + 1] \\\\lor \\\\cdots \\\\lor \\\\textit{nums}[r]$。其中 $\\\\lor$ 表示按位或运算。
\\n如果我们每次固定右端点 $r$,那么左端点 $l$ 的范围是 $[0, r]$。每次移动右端点 $r$,按位或的结果只会变大,我们用一个变量 $s$ 记录当前的按位或的结果,如果 $s$ 大于 $k$,我们就将左端点 $l$ 向右移动,直到 $s$ 小于等于 $k$。在移动左端点 $l$ 的过程中,我们需要维护一个数组 $cnt$,记录当前区间内每个二进制位上 $0$ 的个数,当 $cnt[h]$ 为 $0$ 时,说明当前区间内的元素在第 $h$ 位上都为 $1$,我们就可以将 $s$ 的第 $h$ 位设置为 $0$。
\\n###python
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n m = max(nums).bit_length()\\n cnt = [0] * m\\n s = i = 0\\n ans = inf\\n for j, x in enumerate(nums):\\n s |= x\\n ans = min(ans, abs(s - k))\\n for h in range(m):\\n if x >> h & 1:\\n cnt[h] += 1\\n while i < j and s > k:\\n y = nums[i]\\n for h in range(m):\\n if y >> h & 1:\\n cnt[h] -= 1\\n if cnt[h] == 0:\\n s ^= 1 << h\\n i += 1\\n ans = min(ans, abs(s - k))\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minimumDifference(int[] nums, int k) {\\n int mx = 0;\\n for (int x : nums) {\\n mx = Math.max(mx, x);\\n }\\n int m = 32 - Integer.numberOfLeadingZeros(mx);\\n int[] cnt = new int[m];\\n int n = nums.length;\\n int ans = Integer.MAX_VALUE;\\n for (int i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n ans = Math.min(ans, Math.abs(s - k));\\n for (int h = 0; h < m; ++h) {\\n if ((nums[j] >> h & 1) == 1) {\\n ++cnt[h];\\n }\\n }\\n while (i < j && s > k) {\\n for (int h = 0; h < m; ++h) {\\n if ((nums[i] >> h & 1) == 1 && --cnt[h] == 0) {\\n s ^= 1 << h;\\n }\\n }\\n ++i;\\n ans = Math.min(ans, Math.abs(s - k));\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int mx = *max_element(nums.begin(), nums.end());\\n int m = 32 - __builtin_clz(mx);\\n int n = nums.size();\\n int ans = INT_MAX;\\n vector<int> cnt(m);\\n for (int i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n ans = min(ans, abs(s - k));\\n for (int h = 0; h < m; ++h) {\\n if (nums[j] >> h & 1) {\\n ++cnt[h];\\n }\\n }\\n while (i < j && s > k) {\\n for (int h = 0; h < m; ++h) {\\n if (nums[i] >> h & 1 && --cnt[h] == 0) {\\n s ^= 1 << h;\\n }\\n }\\n ans = min(ans, abs(s - k));\\n ++i;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minimumDifference(nums []int, k int) int {\\nm := bits.Len(uint(slices.Max(nums)))\\ncnt := make([]int, m)\\nans := math.MaxInt32\\ns, i := 0, 0\\nfor j, x := range nums {\\ns |= x\\nans = min(ans, abs(s-k))\\nfor h := 0; h < m; h++ {\\nif x>>h&1 == 1 {\\ncnt[h]++\\n}\\n}\\nfor i < j && s > k {\\ny := nums[i]\\nfor h := 0; h < m; h++ {\\nif y>>h&1 == 1 {\\ncnt[h]--\\nif cnt[h] == 0 {\\ns ^= 1 << h\\n}\\n}\\n}\\nans = min(ans, abs(s-k))\\ni++\\n}\\n}\\nreturn ans\\n}\\n\\nfunc abs(x int) int {\\nif x < 0 {\\nreturn -x\\n}\\nreturn x\\n}\\n
\\n###ts
\\nfunction minimumDifference(nums: number[], k: number): number {\\n const m = Math.max(...nums).toString(2).length;\\n const n = nums.length;\\n const cnt: number[] = Array(m).fill(0);\\n let ans = Infinity;\\n for (let i = 0, j = 0, s = 0; j < n; ++j) {\\n s |= nums[j];\\n ans = Math.min(ans, Math.abs(s - k));\\n for (let h = 0; h < m; ++h) {\\n if ((nums[j] >> h) & 1) {\\n ++cnt[h];\\n }\\n }\\n while (i < j && s > k) {\\n for (let h = 0; h < m; ++h) {\\n if ((nums[i] >> h) & 1 && --cnt[h] === 0) {\\n s ^= 1 << h;\\n }\\n }\\n ans = Math.min(ans, Math.abs(s - k));\\n ++i;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log M)$,空间复杂度 $O(\\\\log M)$。其中 $n$ 和 $M$ 分别是数组 $\\\\textit{nums}$ 的长度和数组 $\\\\textit{nums}$ 中元素的最大值。
\\n根据题目描述,我们需要求出数组 $nums$ 下标 $l$ 到 $r$ 的元素的按位或运算的结果,即 $nums[l] \\\\lor nums[l + 1] \\\\lor \\\\cdots \\\\lor nums[r]$。其中 $\\\\lor$ 表示按位或运算。
\\n如果我们每次固定右端点 $r$,那么左端点 $l$ 的范围是 $[0, r]$。由于按位或之和随着 $l$ 的减小而单调递增,并且 $\\\\textit{nums}[i]$ 的值不超过 $10^9$,因此区间 $[0, r]$ 最多只有 $30$ 种不同的值。因此,我们可以用一个集合来维护所有的 $nums[l] \\\\lor nums[l + 1] \\\\lor \\\\cdots \\\\lor nums[r]$ 的值。当我们从 $r$ 遍历到 $r+1$ 时,以 $r+1$ 为右端点的值,就是集合中每个值与 $nums[r + 1]$ 进行按位或运算得到的值,再加上 $nums[r + 1]$ 本身。因此,我们只需要枚举集合中的每个值,与 $nums[r]$ 进行按位或运算,就可以得到以 $r$ 为右端点的所有值,将每个值与 $k$ 相减后取绝对值,就可以得到以 $r$ 为右端点的所有值与 $k$ 的差的绝对值,其中的最小值就是答案。
\\n###python
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n ans = inf\\n s = set()\\n for x in nums:\\n s = {x | y for y in s} | {x}\\n ans = min(ans, min(abs(y - k) for y in s))\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minimumDifference(int[] nums, int k) {\\n int ans = Integer.MAX_VALUE;\\n Set<Integer> pre = new HashSet<>();\\n for (int x : nums) {\\n Set<Integer> cur = new HashSet<>();\\n for (int y : pre) {\\n cur.add(x | y);\\n }\\n cur.add(x);\\n for (int y : cur) {\\n ans = Math.min(ans, Math.abs(y - k));\\n }\\n pre = cur;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int ans = INT_MAX;\\n unordered_set<int> pre;\\n for (int x : nums) {\\n unordered_set<int> cur;\\n cur.insert(x);\\n for (int y : pre) {\\n cur.insert(x | y);\\n }\\n for (int y : cur) {\\n ans = min(ans, abs(y - k));\\n }\\n pre = move(cur);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minimumDifference(nums []int, k int) int {\\nans := math.MaxInt32\\npre := map[int]bool{}\\nfor _, x := range nums {\\ncur := map[int]bool{x: true}\\nfor y := range pre {\\ncur[x|y] = true\\n}\\nfor y := range cur {\\nans = min(ans, max(y-k, k-y))\\n}\\npre = cur\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction minimumDifference(nums: number[], k: number): number {\\n let ans = Infinity;\\n let pre = new Set<number>();\\n for (const x of nums) {\\n const cur = new Set<number>();\\n cur.add(x);\\n for (const y of pre) {\\n cur.add(x | y);\\n }\\n for (const y of cur) {\\n ans = Math.min(ans, Math.abs(y - k));\\n }\\n pre = cur;\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log M)$,空间复杂度 $O(\\\\log M)$。其中 $n$ 和 $M$ 分别是数组 $nums$ 的长度和数组 $nums$ 中的最大值。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:双指针 + 位运算 根据题目描述,我们需要求出数组 $\\\\textit{nums}$ 下标 $l$ 到 $r$ 的元素的按位或运算的结果,即 $\\\\textit{nums}[l] \\\\lor \\\\textit{nums}[l + 1] \\\\lor \\\\cdots \\\\lor \\\\textit{nums}[r]$。其中 $\\\\lor$ 表示按位或运算。\\n\\n如果我们每次固定右端点 $r$,那么左端点 $l$ 的范围是 $[0, r]$。每次移动右端点 $r$,按位或的结果只会变大,我们用一个变量 $s$ 记录当前的按位或的结果,如果 $s$ 大于 $k$,我们就将左端点…","guid":"https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k//solution/python3javacgotypescript-yi-ti-shuang-ji-gsix","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-09T00:21:42.205Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-找到按位或最接近 K 的子数组🔴","url":"https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k/","content":"给你一个数组 nums
和一个整数 k
。你需要找到 nums
的一个 子数组 ,满足子数组中所有元素按位或运算 OR
的值与 k
的 绝对差 尽可能 小 。换言之,你需要选择一个子数组 nums[l..r]
满足 |k - (nums[l] OR nums[l + 1] ... OR nums[r])|
最小。
请你返回 最小 的绝对差值。
\\n\\n子数组 是数组中连续的 非空 元素序列。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [1,2,4,5], k = 3
\\n\\n输出:0
\\n\\n解释:
\\n\\n子数组 nums[0..1]
的按位 OR
运算值为 3 ,得到最小差值 |3 - 3| = 0
。
示例 2:
\\n\\n输入:nums = [1,3,1,3], k = 2
\\n\\n输出:1
\\n\\n解释:
\\n\\n子数组 nums[1..1]
的按位 OR
运算值为 3 ,得到最小差值 |3 - 2| = 1
。
示例 3:
\\n\\n输入:nums = [1], k = 10
\\n\\n输出:9
\\n\\n解释:
\\n\\n只有一个子数组,按位 OR
运算值为 1 ,得到最小差值 |10 - 1| = 9
。
\\n\\n
提示:
\\n\\n1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= k <= 109
思路与算法
\\n由于题目求数组中所有出现两次数字的按位 $\\\\text{XOR}$ 值,此时我们用哈希表检测当前数字出现的次数是否超过 $1$ 次,依次将出现次数超过 $1$ 次的数字进行异或即可得到结果。
\\n由于数组中每个元素的大小范围为 $[1,50]$,实际可以使用一个 $64$ 位的二进制掩码 $\\\\text{mask}$ 代替哈希表。如果 $x$ 已经出现过,则将对应掩码 $\\\\text{mask}$ 的第 $x$ 位置为 $1$,如果未出现过,则掩码 $\\\\text{mask}$ 的第 $x$ 位置为 $0$。使用二进制掩码可以有效节省空间。
代码
\\n###C++
\\nclass Solution {\\npublic:\\n int duplicateNumbersXOR(vector<int>& nums) {\\n unordered_set<int> cnt;\\n int res = 0;\\n for (int num : nums) {\\n if (cnt.find(num) != cnt.end()) {\\n res ^= num;\\n } else {\\n cnt.emplace(num);\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n Set<Integer> cnt = new HashSet<>();\\n int res = 0;\\n for (int num : nums) {\\n if (cnt.contains(num)) {\\n res ^= num;\\n } else {\\n cnt.add(num);\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int DuplicateNumbersXOR(int[] nums) {\\n HashSet<int> cnt = new HashSet<int>();\\n int res = 0;\\n foreach (int num in nums) {\\n if (cnt.Contains(num)) {\\n res ^= num;\\n } else {\\n cnt.Add(num);\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Go
\\nfunc duplicateNumbersXOR(nums []int) int {\\n cnt := make(map[int]bool)\\n res := 0\\n for _, num := range nums {\\n if _, found := cnt[num]; found {\\n res ^= num\\n } else {\\n cnt[num] = true\\n }\\n } \\n return res\\n}\\n
\\n###Python
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n cnt = set()\\n res = 0 \\n for num in nums:\\n if num in cnt:\\n res ^= num\\n else:\\n cnt.add(num)\\n return res\\n
\\n###C
\\n#define MAX_NUM 64\\n\\nint duplicateNumbersXOR(int* nums, int numsSize) {\\n int cnt[MAX_NUM] = {0};\\n int res = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n if (cnt[nums[i]] == 1) {\\n res ^= nums[i];\\n } else {\\n cnt[nums[i]] = 1;\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar duplicateNumbersXOR = function(nums) {\\n const cnt = new Set();\\n let res = 0;\\n for (const num of nums) {\\n if (cnt.has(num)) {\\n res ^= num;\\n } else {\\n cnt.add(num);\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction duplicateNumbersXOR(nums: number[]): number {\\n const cnt = new Set<number>();\\n let res = 0;\\n for (const num of nums) {\\n if (cnt.has(num)) {\\n res ^= num;\\n } else {\\n cnt.add(num);\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn duplicate_numbers_xor(nums: Vec<i32>) -> i32 {\\n let mut cnt = HashSet::new();\\n let mut res = 0;\\n \\n for num in nums {\\n if cnt.contains(&num) {\\n res ^= num;\\n } else {\\n cnt.insert(num);\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 表示给定数组的长度。只需要遍历一遍数组即可。
\\n空间复杂度:$O(n)$,其中 $n$ 表示给定数组的长度。需要用哈希表记录数组中出现的数字,需要的空间为 $O(n)$。
\\n思路与算法
\\n题目给定一个数组 $\\\\textit{nums}$ 和一个整数 $k$。我们需要找到 $\\\\textit{nums}$ 的一个非空子数组,要求其所有元素的或运算结果与 $k$ 值尽可能接近。
\\n根据或运算的性质,当我们固定子数组的右端点,不断地向左延伸左端点时,子数组或运算结果逐渐增加,并且结果种类数不超过 $\\\\log (\\\\max(\\\\textit{nums})) + 1$ 种。因为或运算结果每次增加对应于二进制表示中某些位上由 $0$ 变为 $1$,因此增加的种类数受到数值范围的限制。
\\n因此,我们从左到右遍历 $\\\\textit{nums}$,并在过程中记录每个二进制上的 $1$ 出现的最晚的位置。这些位置用于我们在延伸左端点时,遍历所有种类的或运算结果。具体的,我们用 $\\\\textit{bits_max_pos}[j]$ 来表示第 $j$ 个二进制为 $1$ 出现的最晚位置,在固定右端点后,将所有的二元组 $(\\\\textit{bits_max_pos}[j], j)$ 从大到小排序,然后依次遍历这些二元组。遍历时将或运算结果与 $2^{j}$ 做或运算,得到新的结果,并计算其与 $k$ 的差值。最终答案取所有差值的最小值。
\\n需要注意的是,对于不同的 $j$,其 $\\\\textit{bits_max_pos}[j]$ 可能相同,在计算区间或运算结果时需要将它们都考虑到,因此这里需要双指针去更新或运算结果。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int n = nums.size();\\n vector<int> bits_max_pos(31, -1);\\n vector<pair<int, int>> pos_to_bit;\\n int res = INT_MAX;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 30; j++) {\\n if (nums[i] >> j & 1) {\\n bits_max_pos[j] = i;\\n }\\n }\\n pos_to_bit.clear();\\n for (int j = 0; j <= 30; j++) {\\n if (bits_max_pos[j] != -1) {\\n pos_to_bit.push_back(make_pair(bits_max_pos[j], j));\\n }\\n }\\n sort(pos_to_bit.begin(), pos_to_bit.end(), greater<pair<int, int>>());\\n int val = 0;\\n for (int j = 0, p = 0; j < pos_to_bit.size(); ) {\\n while (j < pos_to_bit.size() && pos_to_bit[j].first == pos_to_bit[p].first) {\\n val |= 1 << pos_to_bit[j].second;\\n j++;\\n }\\n res = min(res, abs(val - k));\\n p = j;\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minimumDifference(int[] nums, int k) {\\n int n = nums.length;\\n int[] bitsMaxPos = new int[31];\\n Arrays.fill(bitsMaxPos, -1);\\n List<int[]> posToBit = new ArrayList<int[]>();\\n int res = Integer.MAX_VALUE;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 30; j++) {\\n if ((nums[i] >> j & 1) != 0) {\\n bitsMaxPos[j] = i;\\n }\\n }\\n posToBit.clear();\\n for (int j = 0; j <= 30; j++) {\\n if (bitsMaxPos[j] != -1) {\\n posToBit.add(new int[]{bitsMaxPos[j], j});\\n }\\n }\\n Collections.sort(posToBit, (a, b) -> a[0] != b[0] ? b[0] - a[0] : b[1] - a[1]);\\n int val = 0;\\n for (int j = 0, p = 0; j < posToBit.size(); ) {\\n while (j < posToBit.size() && posToBit.get(j)[0] == posToBit.get(p)[0]) {\\n val |= 1 << posToBit.get(j)[1];\\n j++;\\n }\\n res = Math.min(res, Math.abs(val - k));\\n p = j;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinimumDifference(int[] nums, int k) {\\n int n = nums.Length;\\n int[] bitsMaxPos = new int[31];\\n Array.Fill(bitsMaxPos, -1);\\n IList<Tuple<int, int>> posToBit = new List<Tuple<int, int>>();\\n int res = int.MaxValue;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j <= 30; j++) {\\n if ((nums[i] >> j & 1) != 0) {\\n bitsMaxPos[j] = i;\\n }\\n }\\n posToBit.Clear();\\n for (int j = 0; j <= 30; j++) {\\n if (bitsMaxPos[j] != -1) {\\n posToBit.Add(new Tuple<int, int>(bitsMaxPos[j], j));\\n }\\n }\\n ((List<Tuple<int, int>>) posToBit).Sort((a, b) => a.Item1 != b.Item1 ? b.Item1 - a.Item1 : b.Item2 - a.Item2);\\n int val = 0;\\n for (int j = 0, p = 0; j < posToBit.Count; ) {\\n while (j < posToBit.Count && posToBit[j].Item1 == posToBit[p].Item1) {\\n val |= 1 << posToBit[j].Item2;\\n j++;\\n }\\n res = Math.Min(res, Math.Abs(val - k));\\n p = j;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n bits_max_pos = [-1] * 31\\n res = inf\\n \\n for i in range(n):\\n for j in range(31):\\n if nums[i] >> j & 1:\\n bits_max_pos[j] = i\\n pos_to_bit = [(bits_max_pos[j], j) for j in range(31) if bits_max_pos[j] != -1]\\n pos_to_bit.sort(reverse = True, key = lambda x: x[0])\\n \\n j, val = 0, 0\\n while j < len(pos_to_bit):\\n p = j\\n while j < len(pos_to_bit) and pos_to_bit[j][0] == pos_to_bit[p][0]:\\n val |= 1 << pos_to_bit[j][1]\\n j += 1\\n res = min(res, abs(val - k))\\n \\n return res\\n
\\n###Go
\\nfunc minimumDifference(nums []int, k int) int {\\n n := len(nums)\\nbitsMaxPos := make([]int, 31)\\nfor i := range bitsMaxPos {\\nbitsMaxPos[i] = -1\\n}\\n\\nres := math.MaxInt\\nfor i := 0; i < n; i++ {\\nfor j := 0; j <= 30; j++ {\\nif nums[i]>>j & 1 == 1 {\\nbitsMaxPos[j] = i\\n}\\n}\\n\\nposToBit := make([][2]int, 0)\\nfor j := 0; j <= 30; j++ {\\nif bitsMaxPos[j] != -1 {\\nposToBit = append(posToBit, [2]int{bitsMaxPos[j], j})\\n}\\n}\\nsort.Slice(posToBit, func(a, b int) bool {\\nreturn posToBit[a][0] > posToBit[b][0]\\n})\\n\\nval := 0\\nfor j, p := 0, 0; j < len(posToBit); p = j {\\nfor j < len(posToBit) && posToBit[j][0] == posToBit[p][0] {\\nval |= 1 << posToBit[j][1]\\nj++\\n}\\n res = min(res, int(math.Abs(float64(val - k))))\\n}\\n}\\nreturn res\\n}\\n
\\n###C
\\nstatic int cmp(const void *a, const void *b) {\\n return ((int*)b)[0] - ((int *)a)[0];\\n}\\n\\nint minimumDifference(int* nums, int numsSize, int k) {\\n int bits_max_pos[31];\\n for (int i = 0; i < 31; i++) {\\n bits_max_pos[i] = -1;\\n }\\n\\n int res = INT_MAX;\\n for (int i = 0; i < numsSize; i++) {\\n for (int j = 0; j <= 30; j++) {\\n if (nums[i] >> j & 1) {\\n bits_max_pos[j] = i;\\n }\\n }\\n \\n int pos_to_bit[31][2];\\n int size = 0;\\n for (int j = 0; j <= 30; j++) {\\n if (bits_max_pos[j] != -1) {\\n pos_to_bit[size][0] = bits_max_pos[j];\\n pos_to_bit[size][1] = j;\\n size++;\\n }\\n }\\n \\n qsort(pos_to_bit, size, sizeof(pos_to_bit[0]), cmp); \\n int val = 0;\\n for (int j = 0, p = 0; j < size; p = j) {\\n while (j < size && pos_to_bit[j][0] == pos_to_bit[p][0]) {\\n val |= 1 << pos_to_bit[j][1];\\n j++;\\n }\\n res = fmin(res, abs(val - k));\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar minimumDifference = function(nums, k) {\\n const n = nums.length;\\n const bitsMaxPos = new Array(31).fill(-1);\\n let res = Number.MAX_SAFE_INTEGER;\\n\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j <= 30; j++) {\\n if (nums[i] >> j & 1) {\\n bitsMaxPos[j] = i;\\n }\\n }\\n \\n const posToBit = [];\\n for (let j = 0; j <= 30; j++) {\\n if (bitsMaxPos[j] !== -1) {\\n posToBit.push([bitsMaxPos[j], j]);\\n }\\n }\\n \\n posToBit.sort((a, b) => b[0] - a[0]);\\n let val = 0, j = 0;\\n for (let j = 0, p = 0; j < posToBit.length; p = j) {\\n while (j < posToBit.length && posToBit[j][0] === posToBit[p][0]) {\\n val |= 1 << posToBit[j][1];\\n j++;\\n }\\n res = Math.min(res, Math.abs(val - k));\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction minimumDifference(nums: number[], k: number): number {\\n const n = nums.length;\\n const bitsMaxPos = new Array(31).fill(-1);\\n let res = Number.MAX_SAFE_INTEGER;\\n\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j <= 30; j++) {\\n if (nums[i] >> j & 1) {\\n bitsMaxPos[j] = i;\\n }\\n }\\n const posToBit: [number, number][] = [];\\n for (let j = 0; j <= 30; j++) {\\n if (bitsMaxPos[j] !== -1) {\\n posToBit.push([bitsMaxPos[j], j]);\\n }\\n } \\n posToBit.sort((a, b) => b[0] - a[0]);\\n let val = 0;\\n for (let j = 0, p = 0; j < posToBit.length; p = j) {\\n while (j < posToBit.length && posToBit[j][0] === posToBit[p][0]) {\\n val |= 1 << posToBit[j][1];\\n j++;\\n }\\n res = Math.min(res, Math.abs(val - k));\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn minimum_difference(nums: Vec<i32>, k: i32) -> i32 {\\n let n = nums.len();\\n let mut bits_max_pos = vec![-1; 31];\\n let mut res = i32::MAX;\\n\\n for i in 0..n {\\n for j in 0..=30 {\\n if nums[i] >> j & 1 == 1 {\\n bits_max_pos[j] = i as i32;\\n }\\n }\\n \\n let mut pos_to_bit = Vec::new();\\n for j in 0..=30 {\\n if bits_max_pos[j] != -1 {\\n pos_to_bit.push((bits_max_pos[j], j as i32));\\n }\\n }\\n pos_to_bit.sort_by(|a, b| b.0.cmp(&a.0));\\n let mut val = 0;\\n let mut j = 0;\\n while j < pos_to_bit.len() {\\n let p = j;\\n while j < pos_to_bit.len() && pos_to_bit[j].0 == pos_to_bit[p].0 {\\n val |= 1 << pos_to_bit[j].1;\\n j += 1;\\n }\\n res = std::cmp::min(res, (val - k).abs());\\n }\\n }\\n res\\n }\\n}\\n
\\n###Cangjie
\\nimport std.collection.*\\nimport std.sort.*\\nimport std.math.*\\n\\nclass Solution {\\n func minimumDifference(nums: Array<Int64>, k: Int64): Int64 {\\n let n = nums.size\\n let bits_max_pos = Array<Int>(31, { _ => -1 })\\n let pos_to_bit = ArrayList<(Int, Int)>()\\n var res = Int.Max\\n for (i in 0..n) {\\n for (j in 0..=30) {\\n if ((nums[i] >> j & 1) == 1) {\\n bits_max_pos[j] = i\\n }\\n }\\n pos_to_bit.clear()\\n for (j in 0..=30) {\\n if (bits_max_pos[j] != -1) {\\n pos_to_bit.append((bits_max_pos[j], j))\\n }\\n }\\n pos_to_bit.sortBy(stable: true){ x: (Int, Int), y: (Int, Int) =>\\n if (x[0] == y[0]) {\\n if(x[1] < y[1]){\\n return Ordering.GT\\n } else if(x[1] > y[1]){\\n return Ordering.LT\\n } else{\\n return Ordering.EQ\\n }\\n } else if (x[0] < y[0]){\\n return Ordering.GT\\n } else {\\n return Ordering.LT\\n }\\n }\\n var val = 0\\n var j = 0\\n while (j < pos_to_bit.size) {\\n let p = j\\n while (j < pos_to_bit.size && pos_to_bit[j][0] == pos_to_bit[p][0]) {\\n val |= 1 << pos_to_bit[j][1]\\n j++\\n }\\n res = min(res, abs(val - k))\\n }\\n }\\n return res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n\\\\log N \\\\log \\\\log N)$, 其中 $n$ 是 $\\\\textit{nums}$ 的长度, $N$ 为 $\\\\textit{nums}$ 中的最大值。每次固定右端点后需要对所有的二元组进行排序,排序的复杂度为 $\\\\log N \\\\log \\\\log N$,因此总体时间复杂度为 $O(n\\\\log N \\\\log \\\\log N)$。
\\n空间复杂度:$O(\\\\log N)$。
\\n给你一份旅游线路图,该线路图中的旅行线路用数组 paths
表示,其中 paths[i] = [cityAi, cityBi]
表示该线路将会从 cityAi
直接前往 cityBi
。请你找出这次旅行的终点站,即没有任何可以通往其他城市的线路的城市。
题目数据保证线路图会形成一条不存在循环的线路,因此恰有一个旅行终点站。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:paths = [[\\"London\\",\\"New York\\"],[\\"New York\\",\\"Lima\\"],[\\"Lima\\",\\"Sao Paulo\\"]]\\n输出:\\"Sao Paulo\\" \\n解释:从 \\"London\\" 出发,最后抵达终点站 \\"Sao Paulo\\" 。本次旅行的路线是 \\"London\\" -> \\"New York\\" -> \\"Lima\\" -> \\"Sao Paulo\\" 。\\n\\n\\n
示例 2:
\\n\\n输入:paths = [[\\"B\\",\\"C\\"],[\\"D\\",\\"B\\"],[\\"C\\",\\"A\\"]]\\n输出:\\"A\\"\\n解释:所有可能的线路是:\\n\\"D\\" -> \\"B\\" -> \\"C\\" -> \\"A\\". \\n\\"B\\" -> \\"C\\" -> \\"A\\". \\n\\"C\\" -> \\"A\\". \\n\\"A\\". \\n显然,旅行终点站是 \\"A\\" 。\\n\\n\\n
示例 3:
\\n\\n输入:paths = [[\\"A\\",\\"Z\\"]]\\n输出:\\"Z\\"\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= paths.length <= 100
paths[i].length == 2
1 <= cityAi.length, cityBi.length <= 10
cityAi != cityBi
汽车从起点出发驶向目的地,该目的地位于出发位置东面 target
英里处。
沿途有加油站,用数组 stations
表示。其中 stations[i] = [positioni, fueli]
表示第 i
个加油站位于出发位置东面 positioni
英里处,并且有 fueli
升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel
升燃料。它每行驶 1 英里就会用掉 1 升汽油。当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1
。
注意:如果汽车到达加油站时剩余燃料为 0
,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0
,仍然认为它已经到达目的地。
\\n\\n
示例 1:
\\n\\n输入:target = 1, startFuel = 1, stations = []\\n输出:0\\n解释:可以在不加油的情况下到达目的地。\\n\\n\\n
示例 2:
\\n\\n输入:target = 100, startFuel = 1, stations = [[10,100]]\\n输出:-1\\n解释:无法抵达目的地,甚至无法到达第一个加油站。\\n\\n\\n
示例 3:
\\n\\n输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]\\n输出:2\\n解释:\\n出发时有 10 升燃料。\\n开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。\\n然后,从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),\\n并将汽油从 10 升加到 50 升。然后开车抵达目的地。\\n沿途在两个加油站停靠,所以返回 2 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= target, startFuel <= 109
0 <= stations.length <= 500
1 <= positioni < positioni+1 < target
1 <= fueli < 109
具体来说,一个简单朴素的思路是对于 $hours$ 中的每个时间 $hours[i]$ ,我们遍历 $hours$ 的 $[0,;i)$ 中的每个时间 $hours[j]$ 并判断是否有 $(hours[i]+hours[j])%24=0$ ,统计返回即可。
\\n###C#
\\npublic class Solution {\\n public int CountCompleteDayPairs(int[] hours) {\\n return Enumerable.Range(1, hours.Length - 1).Sum(i => Enumerable.Range(0, i).Count(j => (hours[i] + hours[j]) % 24 == 0));\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int countCompleteDayPairs(const std::vector<int>& hours) {\\n auto result = 0;\\n\\n for (auto i = 1; i < hours.size(); i++) {\\n result += std::count_if(hours.begin(), hours.begin() + i,\\n [&](const int h) { return (hours[i] + h) % 24 == 0; }\\n );\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public int countCompleteDayPairs(int[] hours) {\\n return IntStream.range(1, hours.length).map(i -> (int) IntStream.range(0, i).filter(j -> (hours[i] + hours[j]) % 24 == 0).count()).sum();\\n }\\n}\\n\\n
\\n###Python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n return sum(\\n sum(1 for j in range(i) if (hours[i] + hours[j]) % 24 == 0)\\n for i in range(1, len(hours))\\n )\\n
\\n###Golang
\\nfunc countCompleteDayPairs(hours []int) int {\\nresult := 0\\n\\nfor i := 1; i < len(hours); i++ {\\nfor j := 0; j < i; j++ {\\nif (hours[i]+hours[j])%24 != 0 { continue }\\nresult++\\n}\\n}\\n\\nreturn result\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func countCompleteDayPairs(hours: Array<Int64>): Int64 {\\n var result = 0\\n\\n for (i in 1..hours.size) {\\n for (j in 0..i) {\\n if ((hours[i] + hours[j]) % 24 != 0) { continue }\\n result++\\n }\\n }\\n\\n return result\\n }\\n}\\n
\\n我们观察能够组成整天的数对 $(a,;b)$ ,根据 $(a+b)%24=0$ ,我们能够推算出 $a%24+b%24=24$ ,移项过后我们便得到「$a%24=24-b%24$」。
\\n因此我们可以考虑,使用哈希表来记录 $hours$ 中所有 $a%24$ 的出现次数。随后,我们从左到右遍历 $hours$ 。对于当前的小时 $b$ ,我们使用 $hash[24-b%24]$ ,根据以上公式,其等价于 $hash[a%24]$ ,因此我们便访问到了其对应能组成整天的小时的个数。
\\n特殊地,当有 $b$ 整除 $24$ ,即 $b%24==0$ 时,其能够与所有其他同样能整除 $24$ 的小时组成整天。但是对于这种情况,会出我们对哈希表存储和访问方式的冲突。
\\n不妨考虑对于 $hour=24$ ,我们存储时会有 $hour%24=0$ ,但我们访问时会有 $24-hour%24=24$。因此,我们在访问时可以再套上一层对 $24$ 取余,即 $hash[(24;-;b;%;24);%;24]$ 。
\\n具体来说,我们使用一个长度为 $24$ 的数组 $hoursCount$ 来模拟哈希表,使用 $result$ 来记录最终结果。随后我们从左至右遍历 $hours$ 。
\\n对于当前的小时 $hour$ ,我们在哈希表中查询有多少个能与其配对的小时,即 $hoursCount[(24;-;b;%;24);%;24]$ ,将其加入到 $result$ 中。
\\n随后,我们再将哈希表中当前小时对 $24$ 取余的结果加一,即 $hoursCount[hour;%; 24]++$ 。
\\n遍历结束后,最终我们返回 $result$ 即为所求。
\\n###C#
\\npublic class Solution {\\n public int CountCompleteDayPairs(int[] hours) {\\n var hoursCount = new int[24];\\n var result = 0;\\n\\n foreach (var hour in hours) {\\n result += hoursCount[(24 - hour % 24) % 24];\\n hoursCount[hour % 24]++;\\n }\\n\\n return result;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int countCompleteDayPairs(std::vector<int> &hours) {\\n auto hoursCount = std::array<int, 24>();\\n auto result = 0;\\n\\n for (const auto &hour: hours) {\\n result += hoursCount[(24 - hour % 24) % 24];\\n hoursCount[hour % 24]++;\\n }\\n\\n return result;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public int countCompleteDayPairs(int[] hours) {\\n var hoursCount = new int[24];\\n var result = 0;\\n\\n for (var hour : hours) {\\n result += hoursCount[(24 - hour % 24) % 24];\\n hoursCount[hour % 24]++;\\n }\\n\\n return result;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n hours_count = [0] * 24\\n result = 0\\n\\n for hour in hours:\\n result += hours_count[(24 - hour % 24) % 24]\\n hours_count[hour % 24] += 1\\n\\n return result\\n\\n
\\n###Golang
\\nfunc countCompleteDayPairs(hours []int) int {\\nhoursCount := make([]int, 24)\\nvar result = 0\\n\\nfor _, hour := range hours {\\nresult += hoursCount[(24-hour%24)%24]\\nhoursCount[hour%24]++\\n}\\n\\nreturn result\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func countCompleteDayPairs(hours: Array<Int64>): Int64 {\\n let hoursCount = Array<Int64>(24, item: 0)\\n var result = 0\\n\\n for (hour in hours) {\\n result += hoursCount[(24 - hour % 24) % 24]\\n hoursCount[hour % 24]++\\n }\\n\\n return result\\n }\\n}\\n
\\n一个直观的思路是我们从左至右遍历 $nums$ ,如果遇到 $nums[i]=0$ ,则对下标 $i,;i+1,;i+2$ 三者都执行反转操作。
\\n事实上这确实是一个最优策略,通过反转 $i$ 及其右侧的两个元素,我们可以确保当前位 $i$ 被反转为 $1$ ,并且对右侧的元素也有可能产生积极的影响,如由 $[...0,;0,;0,;1,...]$ 反转为 $[...1,;1,;1,;1,...]$ 等。
\\n反过来说,不妨假定存在一种更优的策略。当我们遇到某个 $0$ 时选择不立即执行反转,而是等待后续再回来处理。
\\n而这会导致后面某个时刻仍需处理这个 $0$ ,甚至可能还会导致其他更多的 $0$ 出现,未处理的 $0$ 将增多,最终结果就是可能需要更多的反转操作,不符合我们的目的,因此概括来说,就是「不要让后续的操作影响之前的结果」
\\n具体来说,我们使用变量 $result$ 来记录结果,随后我们从左至右遍历 $nums$ 数组。对于当前的 $nums[i]$ 有:
\\n最终我们需要特别注意的是,对于直到 $i=nums.Length-3$ 时,我们无法继续对 $nums$ 的倒数两个元素 $nums[-2],;nums[-1]$ 进行操作,因此在我们返回 $result$ 之前,需要进行判断。
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n var result = 0;\\n\\n for (var i = 0; i + 3 <= nums.Length; i++) {\\n if (nums[i] != 0) continue;\\n (nums[i], nums[i + 1], nums[i + 2]) = (nums[i] ^ 1, nums[i + 1] ^ 1, nums[i + 2] ^ 1);\\n result++;\\n }\\n\\n return (nums[^1] & nums[^2]) == 1 ? result : -1;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int minOperations(std::vector<int> &nums) {\\n auto result = 0;\\n\\n for (auto i = 0; i + 3 <= nums.size(); i++) {\\n if (nums[i] != 0) continue;\\n nums[i] ^= 1, nums[i + 1] ^= 1, nums[i + 2] ^= 1;\\n result++;\\n }\\n\\n return (nums.back() & nums[nums.size() - 2]) == 1 ? result : -1;\\n }\\n};\\n
\\n###Java
\\npublic class Solution {\\n public int minOperations(int[] nums) {\\n var result = 0;\\n\\n for (var i = 0; i + 3 <= nums.length; i++) {\\n if (nums[i] != 0) continue;\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n result++;\\n }\\n\\n return (nums[nums.length - 1] & nums[nums.length - 2]) == 1 ? result : -1;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n result = 0\\n\\n for i in range(len(nums) - 2):\\n if nums[i] != 0: continue\\n nums[i] ^= 1; nums[i + 1] ^= 1; nums[i + 2] ^= 1\\n result += 1\\n\\n return result if (nums[-1] & nums[-2]) == 1 else -1\\n\\n\\n
\\n###Golang
\\nfunc minOperations(nums []int) int {\\nresult := 0\\n\\nfor i := 0; i+3 <= len(nums); i++ {\\nif nums[i] != 0 { continue }\\nnums[i], nums[i+1], nums[i+2] = nums[i]^1, nums[i+1]^1, nums[i+2]^1\\nresult++\\n}\\n\\nif (nums[len(nums)-1] & nums[len(nums)-2]) == 1 { return result }\\nreturn -1\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func minOperations(nums: Array<Int64>): Int64 {\\n var result = 0\\n\\n for (i in 0..nums.size - 2) {\\n if (nums[i] != 0) { continue }\\n nums[i] ^= 1\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n result += 1\\n }\\n\\n return if ((nums[nums.size - 1] & nums[nums.size - 2]) == 1) { result } else { -1 }\\n }\\n}\\n
\\n在一条环路上有 n
个加油站,其中第 i
个加油站有汽油 gas[i]
升。
你有一辆油箱容量无限的的汽车,从第 i
个加油站开往第 i+1
个加油站需要消耗汽油 cost[i]
升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 gas
和 cost
,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1
。如果存在解,则 保证 它是 唯一 的。
\\n\\n
示例 1:
\\n\\n输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]\\n输出: 3\\n解释:\\n从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油\\n开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油\\n开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油\\n开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油\\n开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油\\n开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。\\n因此,3 可为起始索引。\\n\\n
示例 2:
\\n\\n输入: gas = [2,3,4], cost = [3,4,3]\\n输出: -1\\n解释:\\n你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。\\n我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油\\n开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油\\n开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油\\n你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。\\n因此,无论怎样,你都不可能绕环路行驶一周。\\n\\n
\\n\\n
提示:
\\n\\ngas.length == n
cost.length == n
1 <= n <= 105
0 <= gas[i], cost[i] <= 104
给你一个数组 time
,其中 time[i]
表示第 i
辆公交车完成 一趟旅途 所需要花费的时间。
每辆公交车可以 连续 完成多趟旅途,也就是说,一辆公交车当前旅途完成后,可以 立马开始 下一趟旅途。每辆公交车 独立 运行,也就是说可以同时有多辆公交车在运行且互不影响。
\\n\\n给你一个整数 totalTrips
,表示所有公交车 总共 需要完成的旅途数目。请你返回完成 至少 totalTrips
趟旅途需要花费的 最少 时间。
\\n\\n
示例 1:
\\n\\n输入:time = [1,2,3], totalTrips = 5\\n输出:3\\n解释:\\n- 时刻 t = 1 ,每辆公交车完成的旅途数分别为 [1,0,0] 。\\n 已完成的总旅途数为 1 + 0 + 0 = 1 。\\n- 时刻 t = 2 ,每辆公交车完成的旅途数分别为 [2,1,0] 。\\n 已完成的总旅途数为 2 + 1 + 0 = 3 。\\n- 时刻 t = 3 ,每辆公交车完成的旅途数分别为 [3,1,1] 。\\n 已完成的总旅途数为 3 + 1 + 1 = 5 。\\n所以总共完成至少 5 趟旅途的最少时间为 3 。\\n\\n\\n
示例 2:
\\n\\n输入:time = [2], totalTrips = 1\\n输出:2\\n解释:\\n只有一辆公交车,它将在时刻 t = 2 完成第一趟旅途。\\n所以完成 1 趟旅途的最少时间为 2 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= time.length <= 105
1 <= time[i], totalTrips <= 107
有 n
位乘客即将登机,飞机正好有 n
个座位。第一位乘客的票丢了,他随便选了一个座位坐下。
剩下的乘客将会:
\\n\\n如果他们自己的座位还空着,就坐到自己的座位上,
\\n第 n
位乘客坐在自己的座位上的概率是多少?
\\n\\n
示例 1:
\\n\\n输入:n = 1\\n输出:1.00000\\n解释:第一个人只会坐在自己的位置上。\\n\\n
示例 2:
\\n\\n输入: n = 2\\n输出: 0.50000\\n解释:在第一个人选好座位坐下后,第二个人坐在自己的座位上的概率是 0.5。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 10^5
一个国家有 n
个城市,城市编号为 0
到 n - 1
,题目保证 所有城市 都由双向道路 连接在一起 。道路由二维整数数组 edges
表示,其中 edges[i] = [xi, yi, timei]
表示城市 xi
和 yi
之间有一条双向道路,耗费时间为 timei
分钟。两个城市之间可能会有多条耗费时间不同的道路,但是不会有道路两头连接着同一座城市。
每次经过一个城市时,你需要付通行费。通行费用一个长度为 n
且下标从 0 开始的整数数组 passingFees
表示,其中 passingFees[j]
是你经过城市 j
需要支付的费用。
一开始,你在城市 0
,你想要在 maxTime
分钟以内 (包含 maxTime
分钟)到达城市 n - 1
。旅行的 费用 为你经过的所有城市 通行费之和 (包括 起点和终点城市的通行费)。
给你 maxTime
,edges
和 passingFees
,请你返回完成旅行的 最小费用 ,如果无法在 maxTime
分钟以内完成旅行,请你返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:maxTime = 30, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]\\n输出:11\\n解释:最优路径为 0 -> 1 -> 2 -> 5 ,总共需要耗费 30 分钟,需要支付 11 的通行费。\\n\\n\\n
示例 2:
\\n\\n输入:maxTime = 29, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]\\n输出:48\\n解释:最优路径为 0 -> 3 -> 4 -> 5 ,总共需要耗费 26 分钟,需要支付 48 的通行费。\\n你不能选择路径 0 -> 1 -> 2 -> 5 ,因为这条路径耗费的时间太长。\\n\\n\\n
示例 3:
\\n\\n输入:maxTime = 25, edges = [[0,1,10],[1,2,10],[2,5,10],[0,3,1],[3,4,10],[4,5,15]], passingFees = [5,1,2,20,20,3]\\n输出:-1\\n解释:无法在 25 分钟以内从城市 0 到达城市 5 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= maxTime <= 1000
n == passingFees.length
2 <= n <= 1000
n - 1 <= edges.length <= 1000
0 <= xi, yi <= n - 1
1 <= timei <= 1000
1 <= passingFees[j] <= 1000
题目相当于给你一条链上的有向边,你需要找到终点,也就是没有出边的点。
\\n既然没有出边,那么终点必然不在 $\\\\textit{cityA}_i$ 中。
\\nclass Solution:\\n def destCity(self, paths: List[List[str]]) -> str:\\n set_a = set(p[0] for p in paths)\\n return next(p[1] for p in paths if p[1] not in set_a)\\n
\\nclass Solution {\\n public String destCity(List<List<String>> paths) {\\n Set<String> setA = new HashSet<>(paths.size()); // 预分配空间\\n for (List<String> p : paths) {\\n setA.add(p.get(0));\\n }\\n\\n for (List<String> p : paths) {\\n if (!setA.contains(p.get(1))) {\\n return p.get(1);\\n }\\n }\\n return \\"\\";\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string destCity(vector<vector<string>>& paths) {\\n unordered_set<string> set_a;\\n for (auto& p : paths) {\\n set_a.insert(p[0]);\\n }\\n\\n for (auto& p : paths) {\\n if (!set_a.contains(p[1])) {\\n return p[1];\\n }\\n }\\n return \\"\\";\\n }\\n};\\n
\\nfunc destCity(paths [][]string) string {\\n setA := make(map[string]struct{}, len(paths)) // 预分配空间\\n for _, p := range paths {\\n setA[p[0]] = struct{}{}\\n }\\n\\n for _, p := range paths {\\n if _, ok := setA[p[1]]; !ok {\\n return p[1]\\n }\\n }\\n return \\"\\"\\n}\\n
\\nvar destCity = function(paths) {\\n const setA = new Set();\\n for (const p of paths) {\\n setA.add(p[0]);\\n }\\n\\n for (const p of paths) {\\n if (!setA.has(p[1])) {\\n return p[1];\\n }\\n }\\n return \\"\\";\\n};\\n
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn dest_city(paths: Vec<Vec<String>>) -> String {\\n let set_a = paths.iter().map(|p| p[0].clone()).collect::<HashSet<_>>();\\n paths.into_iter().find(|p| !set_a.contains(&p[1])).unwrap()[1].clone()\\n }\\n}\\n
\\n在方法一的基础上,额外维护一个哈希集合 $\\\\textit{setB}$,用来存储那些「可能是答案」的 $\\\\textit{cityB}_i$:
\\n最后 $\\\\textit{setB}$ 必然恰好剩下一个元素(题目保证),返回这个元素。
\\nclass Solution:\\n def destCity(self, paths: List[List[str]]) -> str:\\n set_a = set()\\n set_b = set()\\n for a, b in paths:\\n set_b.discard(a) # a 一定不是答案\\n if b not in set_a: # b 有可能是答案\\n set_b.add(b)\\n set_a.add(a)\\n return set_b.pop()\\n
\\nclass Solution {\\n public String destCity(List<List<String>> paths) {\\n Set<String> setA = new HashSet<>(paths.size());\\n Set<String> setB = new HashSet<>();\\n for (List<String> p : paths) {\\n String a = p.get(0);\\n String b = p.get(1);\\n setB.remove(a); // a 一定不是答案\\n if (!setA.contains(b)) { // b 有可能是答案\\n setB.add(b);\\n }\\n setA.add(a);\\n }\\n return setB.iterator().next();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string destCity(vector<vector<string>>& paths) {\\n set<string> set_a, set_b;\\n for (auto& p : paths) {\\n auto& a = p[0];\\n auto& b = p[1];\\n set_b.erase(a); // a 一定不是答案\\n if (!set_a.contains(b)) { // b 有可能是答案\\n set_b.insert(b);\\n }\\n set_a.insert(a);\\n }\\n return *set_b.begin();\\n }\\n};\\n
\\nfunc destCity(paths [][]string) string {\\n setA := make(map[string]struct{}, len(paths))\\n setB := map[string]struct{}{}\\n for _, p := range paths {\\n a, b := p[0], p[1]\\n delete(setB, a) // a 一定不是答案\\n if _, ok := setA[b]; !ok { // b 有可能是答案\\n setB[b] = struct{}{}\\n }\\n setA[a] = struct{}{}\\n }\\n for b := range setB {\\n return b\\n }\\n return \\"\\"\\n}\\n
\\nvar destCity = function(paths) {\\n const setA = new Set();\\n const setB = new Set();\\n for (const [a, b] of paths) {\\n setB.delete(a); // a 一定不是答案\\n if (!setA.has(b)) { // b 有可能是答案\\n setB.add(b);\\n }\\n setA.add(a);\\n }\\n return [...setB][0];\\n};\\n
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn dest_city(paths: Vec<Vec<String>>) -> String {\\n let mut set_a = HashSet::with_capacity(paths.len());\\n let mut set_b = HashSet::new();\\n for p in paths {\\n let a = &p[0];\\n let b = &p[1];\\n set_b.remove(a); // a 一定不是答案\\n if !set_a.contains(b) { // b 有可能是答案\\n set_b.insert(b.clone());\\n }\\n set_a.insert(a.clone());\\n }\\n set_b.into_iter().next().unwrap()\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 题目相当于给你一条链上的有向边,你需要找到终点,也就是没有出边的点。\\n\\n既然没有出边,那么终点必然不在 $\\\\textit{cityA}_i$ 中。\\n\\n方法一:两次遍历\\n遍历 $\\\\textit{paths}$,把所有 $\\\\textit{cityA}_i$ 保存到一个哈希集合 $\\\\textit{setA}$ 中。\\n再次遍历 $\\\\textit{paths}$,如果发现 $\\\\textit{cityB}_i$ 不在 $\\\\textit{setA}$ 中,则立刻返回 $\\\\textit{cityB}_i$。\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/destination-city//solution/jian-dan-ti-jian-dan-zuo-pythonjavacgojs-81og","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-02T02:04:03.546Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-准时到达的列车最小时速🟡","url":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time/","content":"给你一个浮点数 hour
,表示你到达办公室可用的总通勤时间。要到达办公室,你必须按给定次序乘坐 n
趟列车。另给你一个长度为 n
的整数数组 dist
,其中 dist[i]
表示第 i
趟列车的行驶距离(单位是千米)。
每趟列车均只能在整点发车,所以你可能需要在两趟列车之间等待一段时间。
\\n\\n1
趟列车需要 1.5
小时,那你必须再等待 0.5
小时,搭乘在第 2 小时发车的第 2
趟列车。返回能满足你准时到达办公室所要求全部列车的 最小正整数 时速(单位:千米每小时),如果无法准时到达,则返回 -1
。
生成的测试用例保证答案不超过 107
,且 hour
的 小数点后最多存在两位数字 。
\\n\\n
示例 1:
\\n\\n输入:dist = [1,3,2], hour = 6\\n输出:1\\n解释:速度为 1 时:\\n- 第 1 趟列车运行需要 1/1 = 1 小时。\\n- 由于是在整数时间到达,可以立即换乘在第 1 小时发车的列车。第 2 趟列车运行需要 3/1 = 3 小时。\\n- 由于是在整数时间到达,可以立即换乘在第 4 小时发车的列车。第 3 趟列车运行需要 2/1 = 2 小时。\\n- 你将会恰好在第 6 小时到达。\\n\\n\\n
示例 2:
\\n\\n输入:dist = [1,3,2], hour = 2.7\\n输出:3\\n解释:速度为 3 时:\\n- 第 1 趟列车运行需要 1/3 = 0.33333 小时。\\n- 由于不是在整数时间到达,故需要等待至第 1 小时才能搭乘列车。第 2 趟列车运行需要 3/3 = 1 小时。\\n- 由于是在整数时间到达,可以立即换乘在第 2 小时发车的列车。第 3 趟列车运行需要 2/3 = 0.66667 小时。\\n- 你将会在第 2.66667 小时到达。\\n\\n
示例 3:
\\n\\n输入:dist = [1,3,2], hour = 1.9\\n输出:-1\\n解释:不可能准时到达,因为第 3 趟列车最早是在第 2 小时发车。\\n\\n
\\n\\n
提示:
\\n\\nn == dist.length
1 <= n <= 105
1 <= dist[i] <= 105
1 <= hour <= 109
hours
中,小数点后最多存在两位数字假设第 $100$ 天是旅行的最后一天,分类讨论:
\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n\\n\\n注 1:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。
\\n注 2:动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。在做题时,可根据题目要求,选择适合题目的一种来思考。本题用到的是「枚举选哪个」。
\\n
根据上面的讨论,定义 $\\\\textit{dfs}(i)$ 表示 $1$ 到 $i$ 天的最小花费。
\\n如果第 $i$ 天不在 $\\\\textit{days}$ 中,那么问题变成 $1$ 到 $i-1$ 天的最小花费,即
\\n$$
\\n\\\\textit{dfs}(i) = \\\\textit{dfs}(i-1)
\\n$$
如果第 $i$ 天在 $\\\\textit{days}$ 中,分类讨论:
\\n这三种情况取最小值,就得到了 $\\\\textit{dfs}(i)$,即
\\n$$
\\n\\\\textit{dfs}(i) = \\\\min(\\\\textit{dfs}(i-1)+\\\\textit{costs}[0],\\\\textit{dfs}(i-7)+\\\\textit{costs}[1],\\\\textit{dfs}(i-30)+\\\\textit{costs}[2])
\\n$$
递归边界:$\\\\textit{dfs}(i)=0$,其中 $i\\\\le 0$。此时没有要旅行的天数。
\\n递归入口:$\\\\textit{dfs}(D)$,其中 $D=\\\\textit{days}[n-1]$ 是最后一天。为了方便翻译成递推,我们从最后一天开始思考。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。
\\n本题由于 $\\\\textit{costs}[i]$ 均为正数,不会算出 $0$,把初始值设置为 $0$ 也可以。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n last_day = days[-1]\\n days = set(days)\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int) -> int:\\n if i <= 0:\\n return 0\\n if i not in days:\\n return dfs(i - 1)\\n return min(dfs(i - 1) + costs[0], dfs(i - 7) + costs[1], dfs(i - 30) + costs[2])\\n return dfs(last_day)\\n
\\n###java
\\nclass Solution {\\n public int mincostTickets(int[] days, int[] costs) {\\n int lastDay = days[days.length - 1];\\n boolean[] isTravel = new boolean[lastDay + 1];\\n for (int d : days) {\\n isTravel[d] = true;\\n }\\n int[] memo = new int[lastDay + 1];\\n return dfs(lastDay, isTravel, costs, memo);\\n }\\n\\n private int dfs(int i, boolean[] isTravel, int[] costs, int[] memo) {\\n if (i <= 0) {\\n return 0;\\n }\\n if (memo[i] > 0) { // 之前计算过\\n return memo[i];\\n }\\n if (!isTravel[i]) {\\n return memo[i] = dfs(i - 1, isTravel, costs, memo);\\n }\\n return memo[i] = Math.min(dfs(i - 1, isTravel, costs, memo) + costs[0],\\n Math.min(dfs(i - 7, isTravel, costs, memo) + costs[1],\\n dfs(i - 30, isTravel, costs, memo) + costs[2]));\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int mincostTickets(vector<int>& days, vector<int>& costs) {\\n int last_day = days.back();\\n unordered_set<int> day_set(days.begin(), days.end());\\n vector<int> memo(last_day + 1);\\n auto dfs = [&](auto&& dfs, int i) -> int {\\n if (i <= 0) {\\n return 0;\\n }\\n int& res = memo[i]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n if (!day_set.count(i)) {\\n return res = dfs(dfs, i - 1);\\n }\\n return res = min({dfs(dfs, i - 1) + costs[0],\\n dfs(dfs, i - 7) + costs[1],\\n dfs(dfs, i - 30) + costs[2]});\\n };\\n return dfs(dfs, last_day);\\n }\\n};\\n
\\n###go
\\nfunc mincostTickets(days, costs []int) int {\\n lastDay := days[len(days)-1]\\n isTravel := make([]bool, lastDay+1)\\n for _, day := range days {\\n isTravel[day] = true\\n }\\n memo := make([]int, lastDay+1)\\n var dfs func(int) int\\n dfs = func(i int) (res int) {\\n if i <= 0 {\\n return\\n }\\n p := &memo[i]\\n if *p > 0 { // 之前计算过\\n return *p\\n }\\n defer func() { *p = res }() // 记忆化\\n if !isTravel[i] {\\n return dfs(i - 1)\\n }\\n return min(dfs(i-1)+costs[0], dfs(i-7)+costs[1], dfs(i-30)+costs[2])\\n }\\n return dfs(lastDay)\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i]$ 的定义和 $\\\\textit{dfs}(i)$ 的定义是一样的,都表示 $1$ 到 $i$ 天的最小花费。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i] = \\\\min(f[i-1]+\\\\textit{costs}[0],f[i-7]+\\\\textit{costs}[1],f[i-30]+\\\\textit{costs}[2])
\\n$$
由于 $f[0]=0$ 且负数 $i$ 的状态值也为 $0$,我们可以把负数 $i$ 视作 $0$,上式等价于
\\n$$
\\nf[i] = \\\\min(f[i-1]+\\\\textit{costs}[0],f[\\\\max(i-7,0)]+\\\\textit{costs}[1],f[\\\\max(i-30,0)]+\\\\textit{costs}[2])
\\n$$
初始值 $f[0]=0$,翻译自递归边界 $\\\\textit{dfs}(0)=0$。
\\n答案为 $f[D]$,翻译自递归入口 $\\\\textit{dfs}(D)$。
\\n###py
\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n last_day = days[-1]\\n days = set(days)\\n f = [0] * (last_day + 1)\\n for i in range(1, last_day + 1):\\n if i not in days:\\n f[i] = f[i - 1]\\n else:\\n f[i] = min(f[i - 1] + costs[0], \\n f[max(i - 7, 0)] + costs[1],\\n f[max(i - 30, 0)] + costs[2])\\n return f[-1]\\n
\\n###java
\\nclass Solution {\\n public int mincostTickets(int[] days, int[] costs) {\\n int lastDay = days[days.length - 1];\\n boolean[] isTravel = new boolean[lastDay + 1];\\n for (int d : days) {\\n isTravel[d] = true;\\n }\\n int[] f = new int[lastDay + 1];\\n for (int i = 1; i <= lastDay; i++) {\\n if (!isTravel[i]) {\\n f[i] = f[i - 1];\\n } else {\\n f[i] = Math.min(f[i - 1] + costs[0],\\n Math.min(f[Math.max(i - 7, 0)] + costs[1],\\n f[Math.max(i - 30, 0)] + costs[2]));\\n }\\n }\\n return f[lastDay];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int mincostTickets(vector<int>& days, vector<int>& costs) {\\n int last_day = days.back();\\n unordered_set<int> day_set(days.begin(), days.end());\\n vector<int> f(last_day + 1);\\n for (int i = 1; i <= last_day; i++) {\\n if (!day_set.contains(i)) {\\n f[i] = f[i - 1];\\n } else {\\n f[i] = min({f[i - 1] + costs[0],\\n f[max(i - 7, 0)] + costs[1],\\n f[max(i - 30, 0)] + costs[2]});\\n }\\n }\\n return f[last_day];\\n }\\n};\\n
\\n###go
\\nfunc mincostTickets(days, costs []int) int {\\n lastDay := days[len(days)-1]\\n isTravel := make([]bool, lastDay+1)\\n for _, day := range days {\\n isTravel[day] = true\\n }\\n f := make([]int, lastDay+1)\\n for i := 1; i <= lastDay; i++ {\\n if !isTravel[i] {\\n f[i] = f[i-1]\\n } else {\\n f[i] = min(f[i-1]+costs[0], f[max(i-7, 0)]+costs[1], f[max(i-30, 0)]+costs[2])\\n }\\n }\\n return f[lastDay]\\n}\\n
\\n如果把数据范围修改为 $\\\\textit{days}[i]\\\\le 10^9$,上面的做法就不行了。
\\n能不能做到时间复杂度和 $D$ 无关呢?比如只和 $\\\\textit{days}$ 的长度 $n$ 有关?
\\n既然要做到只和 $n$ 有关,那么仿照上面的状态定义,我们定义 $f[i]$ 表示旅行了 $i$ 天的最小花费:
\\n一般地,$f[i+1]$ 表示完成 $\\\\textit{days}[0]$ 到 $\\\\textit{days}[i]$ 的最小花费。
\\n分类讨论:
\\n这三种情况取最小值,就得到了 $f[i+1]$,即
\\n$$
\\nf[i+1] = \\\\min(f[i]+\\\\textit{costs}[0],f[j]+\\\\textit{costs}[1],f[k]+\\\\textit{costs}[2])
\\n$$
其中:
\\n由于 $\\\\textit{days}$ 是有序数组,计算 $j$ 和 $k$ 可以用双指针(三指针)算法。
\\n初始值 $f[0]=0,\\\\ j=0,\\\\ k=0$。
\\n答案为 $f[n]$。
\\n###py
\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n f = [0] * (len(days) + 1)\\n j = k = 0\\n for i, d in enumerate(days):\\n while days[j] <= d - 7:\\n j += 1\\n while days[k] <= d - 30:\\n k += 1\\n f[i + 1] = min(f[i] + costs[0], f[j] + costs[1], f[k] + costs[2])\\n return f[-1]\\n
\\n###java
\\nclass Solution {\\n public int mincostTickets(int[] days, int[] costs) {\\n int n = days.length;\\n int[] f = new int[n + 1];\\n int j = 0;\\n int k = 0;\\n for (int i = 0; i < n; i++) {\\n int d = days[i];\\n while (days[j] <= d - 7) {\\n j++;\\n }\\n while (days[k] <= d - 30) {\\n k++;\\n }\\n f[i + 1] = Math.min(f[i] + costs[0], Math.min(f[j] + costs[1], f[k] + costs[2]));\\n }\\n return f[n];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int mincostTickets(vector<int>& days, vector<int>& costs) {\\n int n = days.size();\\n vector<int> f(n + 1);\\n int j = 0, k = 0;\\n for (int i = 0; i < n; i++) {\\n int d = days[i];\\n while (days[j] <= d - 7) {\\n j++;\\n }\\n while (days[k] <= d - 30) {\\n k++;\\n }\\n f[i + 1] = min({f[i] + costs[0], f[j] + costs[1], f[k] + costs[2]});\\n }\\n return f[n];\\n }\\n};\\n
\\n###go
\\nfunc mincostTickets(days, costs []int) int {\\n n := len(days)\\n f := make([]int, n+1)\\n j, k := 0, 0\\n for i, d := range days {\\n for days[j] <= d-7 {\\n j++\\n }\\n for days[k] <= d-30 {\\n k++\\n }\\n f[i+1] = min(f[i]+costs[0], f[j]+costs[1], f[k]+costs[2])\\n }\\n return f[n]\\n}\\n
\\n见 动态规划题单 中的「§7.1 一维线性 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 假设第 $100$ 天是旅行的最后一天,分类讨论:\\n\\n在第 $100$ 天购买为期 $1$ 天的通行证,接下来需要解决的问题为:$1$ 到 $99$ 天的最小花费。\\n在第 $94$ 天购买为期 $7$ 天的通行证,接下来需要解决的问题为:$1$ 到 $93$ 天的最小花费。\\n在第 $71$ 天购买为期 $30$ 天的通行证,接下来需要解决的问题为:$1$ 到 $70$ 天的最小花费。\\n\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。\\n\\n注 1:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。\\n\\n注 2…","guid":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-tkw4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-10-01T01:05:26.382Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最低票价🟡","url":"https://leetcode.cn/problems/minimum-cost-for-tickets/","content":"在一个火车旅行很受欢迎的国度,你提前一年计划了一些火车旅行。在接下来的一年里,你要旅行的日子将以一个名为 days
的数组给出。每一项是一个从 1
到 365
的整数。
火车票有 三种不同的销售方式 :
\\n\\ncosts[0]
美元;costs[1]
美元;costs[2]
美元。通行证允许数天无限制的旅行。 例如,如果我们在第 2
天获得一张 为期 7 天 的通行证,那么我们可以连着旅行 7 天:第 2
天、第 3
天、第 4
天、第 5
天、第 6
天、第 7
天和第 8
天。
返回 你想要完成在给定的列表 days
中列出的每一天的旅行所需要的最低消费 。
\\n\\n
示例 1:
\\n\\n输入:days = [1,4,6,7,8,20], costs = [2,7,15]\\n输出:11\\n解释: \\n例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划:\\n在第 1 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 1 天生效。\\n在第 3 天,你花了 costs[1] = $7 买了一张为期 7 天的通行证,它将在第 3, 4, ..., 9 天生效。\\n在第 20 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 20 天生效。\\n你总共花了 $11,并完成了你计划的每一天旅行。\\n\\n\\n
示例 2:
\\n\\n输入:days = [1,2,3,4,5,6,7,8,9,10,30,31], costs = [2,7,15]\\n输出:17\\n解释:\\n例如,这里有一种购买通行证的方法,可以让你完成你的旅行计划: \\n在第 1 天,你花了 costs[2] = $15 买了一张为期 30 天的通行证,它将在第 1, 2, ..., 30 天生效。\\n在第 31 天,你花了 costs[0] = $2 买了一张为期 1 天的通行证,它将在第 31 天生效。 \\n你总共花了 $17,并完成了你计划的每一天旅行。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= days.length <= 365
1 <= days[i] <= 365
days
按顺序严格递增costs.length == 3
1 <= costs[i] <= 1000
思路与算法
\\n根据题意进行模拟,遍历所有的数对,判断是否优质,返回优质数对的个数。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n int res = 0;\\n for (int a : nums1) {\\n for (int b : nums2) {\\n if (a % (b * k) == 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int numberOfPairs(int[] nums1, int[] nums2, int k) {\\n int res = 0;\\n for (int a : nums1) {\\n for (int b : nums2) {\\n if (a % (b * k) == 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n res = 0\\n for a in nums1:\\n for b in nums2:\\n if a % (b * k) == 0:\\n res += 1\\n return res\\n
\\n###JavaScript
\\nvar numberOfPairs = function(nums1, nums2, k) {\\n let res = 0;\\n for (let a of nums1) {\\n for (let b of nums2) {\\n if (a % (b * k) === 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n let res = 0;\\n for (let a of nums1) {\\n for (let b of nums2) {\\n if (a % (b * k) === 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) int {\\n res := 0\\n for _, a := range nums1 {\\n for _, b := range nums2 {\\n if a % (b * k) == 0 {\\n res++\\n }\\n }\\n }\\n return res\\n}\\n\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfPairs(int[] nums1, int[] nums2, int k) {\\n int res = 0;\\n foreach (int a in nums1) {\\n foreach (int b in nums2) {\\n if (a % (b * k) == 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C
\\nint numberOfPairs(int* nums1, int nums1Size, int* nums2, int nums2Size, int k) {\\n int res = 0;\\n for (int i = 0; i < nums1Size; i++) {\\n for (int j = 0; j < nums2Size; j++) {\\n if (nums1[i] % (nums2[j] * k) == 0) {\\n res++;\\n }\\n }\\n }\\n return res;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn number_of_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> i32 {\\n let mut res = 0;\\n for &a in &nums1 {\\n for &b in &nums2 {\\n if a % (b * k) == 0 {\\n res += 1;\\n }\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\times m)$,其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n分别统计 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的频数。
\\n遍历 $\\\\textit{nums}_2$ 出现过的数 $a$,枚举 $a \\\\times k$ 的倍数,如果在 $\\\\textit{nums}_1$ 出现过就可以组成优质数对,更新结果。
\\n返回优质数对的总数。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> count, count2;\\n int max1 = 0;\\n for (int num : nums1) {\\n count[num]++;\\n max1 = max(max1, num);\\n }\\n for (int num : nums2) {\\n count2[num]++;\\n }\\n long long res = 0;\\n for (const auto& pair : count2) {\\n int a = pair.first, cnt = pair.second;\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.count(b) > 0) {\\n res += 1L * count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> count = new HashMap<>();\\n Map<Integer, Integer> count2 = new HashMap<>();\\n int max1 = 0;\\n for (int num : nums1) {\\n count.put(num, count.getOrDefault(num, 0) + 1);\\n max1 = Math.max(max1, num);\\n }\\n for (int num : nums2) {\\n count2.put(num, count2.getOrDefault(num, 0) + 1);\\n }\\n long res = 0;\\n for (int a : count2.keySet()) {\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.containsKey(b)) {\\n res += 1L * count.get(b) * count2.get(a);\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n count = Counter(nums1)\\n max1 = max(count)\\n res = 0\\n for a, cnt in Counter(nums2).items():\\n for b in range(a * k, max1 + 1, a * k):\\n if b in count:\\n res += count[b] * cnt\\n return res\\n
\\n###JavaScript
\\nvar numberOfPairs = function(nums1, nums2, k) {\\n const count = {};\\n const count2 = {};\\n let res = 0, max1 = 0;\\n for (let num of nums1) {\\n count[num] = (count[num] || 0) + 1;\\n max1 = Math.max(max1, num);\\n }\\n for (let num of nums2) {\\n count2[num] = (count2[num] || 0) + 1;\\n }\\n for (let a in count2) {\\n let cnt = count2[a];\\n for (let b = a * k; b <= max1; b += a * k) {\\n if (b in count) {\\n res += count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n const count = {};\\n const count2 = {};\\n let res = 0, max1 = 0;\\n for (let num of nums1) {\\n count[num] = (count[num] || 0) + 1;\\n max1 = Math.max(max1, num);\\n }\\n for (let num of nums2) {\\n count2[num] = (count2[num] || 0) + 1;\\n }\\n for (let a in count2) {\\n let cnt = count2[a];\\n for (let b = Number(a) * k; b <= max1; b += Number(a) * k) {\\n if (b in count) {\\n res += count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) int64 {\\n count := make(map[int]int)\\n count2 := make(map[int]int)\\n max1 := 0\\n for _, num := range nums1 {\\n count[num]++\\n if num > max1 {\\n max1 = num\\n }\\n }\\n for _, num := range nums2 {\\n count2[num]++\\n }\\n var res int64\\n for a, cnt := range count2 {\\n for b := a * k; b <= max1; b += a * k {\\n if _, ok := count[b]; ok {\\n res += int64(count[b] * cnt)\\n }\\n }\\n }\\n return res\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long NumberOfPairs(int[] nums1, int[] nums2, int k) {\\n Dictionary<int, int> count = new Dictionary<int, int>();\\n Dictionary<int, int> count2 = new Dictionary<int, int>();\\n int max1 = 0;\\n foreach (int num in nums1) {\\n if (count.ContainsKey(num)) {\\n count[num]++;\\n } else {\\n count[num] = 1;\\n }\\n max1 = Math.Max(max1, num);\\n }\\n foreach (int num in nums2) {\\n if (count2.ContainsKey(num)) {\\n count2[num]++;\\n } else {\\n count2[num] = 1;\\n }\\n }\\n long res = 0;\\n foreach (int a in count2.Keys) {\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.ContainsKey(b)) {\\n res += 1l * count[b] * count2[a];\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn number_of_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> i32 {\\n let mut count: HashMap<i32, i32> = HashMap::new();\\n let mut count2: HashMap<i32, i32> = HashMap::new();\\n let mut res = 0;\\n let mut max1 = 0;\\n for &num in &nums1 {\\n *count.entry(num).or_insert(0) += 1;\\n max1 = std::cmp::max(max1, num);\\n }\\n for &num in &nums2 {\\n *count2.entry(num).or_insert(0) += 1;\\n }\\n for (&a, &cnt) in &count2 {\\n for b in (a * k..=max1).step_by((a * k) as usize) {\\n if let Some(&value) = count.get(&b) {\\n res += value * cnt;\\n }\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + m + \\\\dfrac{v}{k} \\\\times \\\\log m)$,其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度,$k$ 是给定的正整数,$v$ 是数组 $\\\\textit{nums}_1$ 最大值,$\\\\log m$ 是「调和级数」求和的结果。
\\n空间复杂度:$O(n + m)$。
\\n\\n\\nProblem: 3305. 元音辅音字符串计数 I
\\n
[TOC]
\\n前缀和字符处理
\\n执行用时分布606ms击败100.00%;消耗内存分布16.65MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def countOfSubstrings(self, word: str, k: int) -> int:\\n n, pre, ans = len(word) + 1, list(accumulate([c if c in \'aeiou\' else \'_\' for c in word], func = lambda cnt, c : cnt + Counter(c), initial = Counter())), 0\\n for l in range(n - 5 - k):\\n for r in range(l + 5 + k, n):\\n cnt = pre[r] - pre[l]\\n if cnt[\'_\'] < k: continue\\n if cnt[\'_\'] == k and all(cnt[c] for c in \'aeiou\'): ans += 1\\n if cnt[\'_\'] > k: break\\n return ans\\n
\\n","description":"Problem: 3305. 元音辅音字符串计数 I [TOC]\\n\\n前缀和字符处理\\n\\n执行用时分布606ms击败100.00%;消耗内存分布16.65MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def countOfSubstrings(self, word: str, k: int) -> int:\\n n, pre, ans = len(word) + 1, list(accumulate([c if c in \'aeiou\' else \'_\' for c in word], func…","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-i//solution/qian-zhui-he-chu-li-by-admiring-meninsky-ein3","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-30T06:41:27.867Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-座位预约管理系统🟡","url":"https://leetcode.cn/problems/seat-reservation-manager/","content":"请你设计一个管理 n
个座位预约的系统,座位编号从 1
到 n
。
请你实现 SeatManager
类:
SeatManager(int n)
初始化一个 SeatManager
对象,它管理从 1
到 n
编号的 n
个座位。所有座位初始都是可预约的。int reserve()
返回可以预约座位的 最小编号 ,此座位变为不可预约。void unreserve(int seatNumber)
将给定编号 seatNumber
对应的座位变成可以预约。\\n\\n
示例 1:
\\n\\n输入:\\n[\\"SeatManager\\", \\"reserve\\", \\"reserve\\", \\"unreserve\\", \\"reserve\\", \\"reserve\\", \\"reserve\\", \\"reserve\\", \\"unreserve\\"]\\n[[5], [], [], [2], [], [], [], [], [5]]\\n输出:\\n[null, 1, 2, null, 2, 3, 4, 5, null]\\n\\n解释:\\nSeatManager seatManager = new SeatManager(5); // 初始化 SeatManager ,有 5 个座位。\\nseatManager.reserve(); // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。\\nseatManager.reserve(); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。\\nseatManager.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。\\nseatManager.reserve(); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。\\nseatManager.reserve(); // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。\\nseatManager.reserve(); // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。\\nseatManager.reserve(); // 唯一可以预约的是座位 5 ,所以返回 5 。\\nseatManager.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 105
1 <= seatNumber <= n
reserve
的调用,题目保证至少存在一个可以预约的座位。unreserve
的调用,题目保证 seatNumber
在调用函数前都是被预约状态。reserve
和 unreserve
的调用 总共 不超过 105
次。为方便表述,将每个元音字母至少出现一次且恰好包含 $k$ 个辅音字母的子字符串称为符合要求的子字符串。
\\n最直观的思路是遍历字符串 $\\\\textit{word}$ 的每个子字符串并判断是否子字符串是否符合要求。可以固定子字符串的起始下标,从左到右依次遍历从起始下标到末尾的每个字符,使用哈希集合存储子字符串中的元音字母,并维护子字符串中的辅音字母个数,如果哈希集合中的元音字母个数等于 $5$ 且子字符串中的辅音字母个数等于 $k$,则得到一个符合要求的子字符串。
\\n实现方面可以优化遍历条件。对于每个起始下标,当遍历到的辅音字母个数大于 $k$ 时,该起始下标对应的更长的子字符串中的辅音字母一定大于 $k$,因此不需要继续遍历该起始下标对应的更长的子字符串。
\\n###Java
\\nclass Solution {\\n public int countOfSubstrings(String word, int k) {\\n int totalCount = 0;\\n int n = word.length();\\n for (int i = 0; i < n; i++) {\\n Set<Character> vowels = new HashSet<Character>();\\n int consonants = 0;\\n for (int j = i; j < n && consonants <= k; j++) {\\n char c = word.charAt(j);\\n if (isVowel(c)) {\\n vowels.add(c);\\n } else {\\n consonants++;\\n }\\n if (vowels.size() == 5 && consonants == k) {\\n totalCount++;\\n }\\n }\\n }\\n return totalCount;\\n }\\n\\n public boolean isVowel(char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountOfSubstrings(string word, int k) {\\n int totalCount = 0;\\n int n = word.Length;\\n for (int i = 0; i < n; i++) {\\n ISet<char> vowels = new HashSet<char>();\\n int consonants = 0;\\n for (int j = i; j < n && consonants <= k; j++) {\\n char c = word[j];\\n if (IsVowel(c)) {\\n vowels.Add(c);\\n } else {\\n consonants++;\\n }\\n if (vowels.Count == 5 && consonants == k) {\\n totalCount++;\\n }\\n }\\n }\\n return totalCount;\\n }\\n\\n public bool IsVowel(char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n }\\n}\\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是字符串 $\\\\textit{word}$ 的长度。需要遍历的子字符串的个数是 $O(n^2)$。
\\n空间复杂度:$O(|\\\\Sigma|)$,其中 $\\\\Sigma$ 是字符集,这道题中字符集为所有元音字母,$|\\\\Sigma| = 5$。哈希集合的空间是 $O(|\\\\Sigma|)$。
\\n可以使用滑动窗口降低时间复杂度。具体见「3306. 元音辅音字符串计数 II」。
\\n###Java
\\nclass Solution {\\n public int countOfSubstrings(String word, int k) {\\n int totalCount = 0;\\n Map<Character, Integer> vowelsCounts1 = new HashMap<Character, Integer>();\\n Map<Character, Integer> vowelsCounts2 = new HashMap<Character, Integer>();\\n int consonants1 = 0, consonants2 = 0;\\n int n = word.length();\\n int start1 = 0, start2 = 0, end = 0;\\n while (end < n) {\\n char curr = word.charAt(end);\\n if (isVowel(curr)) {\\n vowelsCounts1.put(curr, vowelsCounts1.getOrDefault(curr, 0) + 1);\\n vowelsCounts2.put(curr, vowelsCounts2.getOrDefault(curr, 0) + 1);\\n } else {\\n consonants1++;\\n consonants2++;\\n }\\n while (vowelsCounts1.size() == 5 && consonants1 > k) {\\n char prev = word.charAt(start1);\\n if (isVowel(prev)) {\\n vowelsCounts1.put(prev, vowelsCounts1.get(prev) - 1);\\n if (vowelsCounts1.get(prev) == 0) {\\n vowelsCounts1.remove(prev);\\n }\\n } else {\\n consonants1--;\\n }\\n start1++;\\n }\\n while (vowelsCounts2.size() == 5 && consonants2 >= k) {\\n char prev = word.charAt(start2);\\n if (isVowel(prev)) {\\n vowelsCounts2.put(prev, vowelsCounts2.get(prev) - 1);\\n if (vowelsCounts2.get(prev) == 0) {\\n vowelsCounts2.remove(prev);\\n }\\n } else {\\n consonants2--;\\n }\\n start2++;\\n }\\n totalCount += Math.max(start2 - start1, 0);\\n end++;\\n }\\n return totalCount;\\n }\\n\\n public boolean isVowel(char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountOfSubstrings(string word, int k) {\\n int totalCount = 0;\\n IDictionary<char, int> vowelsCounts1 = new Dictionary<char, int>();\\n IDictionary<char, int> vowelsCounts2 = new Dictionary<char, int>();\\n int consonants1 = 0, consonants2 = 0;\\n int n = word.Length;\\n int start1 = 0, start2 = 0, end = 0;\\n while (end < n) {\\n char curr = word[end];\\n if (IsVowel(curr)) {\\n vowelsCounts1.TryAdd(curr, 0);\\n vowelsCounts1[curr]++;\\n vowelsCounts2.TryAdd(curr, 0);\\n vowelsCounts2[curr]++;\\n } else {\\n consonants1++;\\n consonants2++;\\n }\\n while (vowelsCounts1.Count == 5 && consonants1 > k) {\\n char prev = word[start1];\\n if (IsVowel(prev)) {\\n vowelsCounts1[prev]--;\\n if (vowelsCounts1[prev] == 0) {\\n vowelsCounts1.Remove(prev);\\n }\\n } else {\\n consonants1--;\\n }\\n start1++;\\n }\\n while (vowelsCounts2.Count == 5 && consonants2 >= k) {\\n char prev = word[start2];\\n if (IsVowel(prev)) {\\n vowelsCounts2[prev]--;\\n if (vowelsCounts2[prev] == 0) {\\n vowelsCounts2.Remove(prev);\\n }\\n } else {\\n consonants2--;\\n }\\n start2++;\\n }\\n totalCount += Math.Max(start2 - start1, 0);\\n end++;\\n }\\n return totalCount;\\n }\\n\\n public bool IsVowel(char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $\\\\textit{word}$ 的长度。每个滑动窗口的左右端点最多各遍历字符串 $\\\\textit{word}$ 一次。
\\n空间复杂度:$O(|\\\\Sigma|)$,其中 $\\\\Sigma$ 是字符集,这道题中字符集为所有元音字母,$|\\\\Sigma| = 5$。哈希表的空间是 $O(|\\\\Sigma|)$。
\\n本题和周赛第三题是一样的,请看 我的题解。
\\n","description":"本题和周赛第三题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-i//solution/on-liang-ci-hua-dong-chuang-kou-pythonja-q93p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T04:12:24.398Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"恰好型滑动窗口:转换成两个至少型滑动窗口(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/liang-ci-hua-chuang-pythonjavacgo-by-end-2lpz","content":"问:某班有 $10$ 个人至少 $20$ 岁,$3$ 个人至少 $21$ 岁,那么恰好 $20$ 岁的人有多少个?
\\n答:「至少 $20$ 岁」可以分成「恰好 $20$ 岁」和「至少 $21$ 岁」,所以「至少 $20$ 岁」的人数减去「至少 $21$ 岁」的人数,就是「恰好 $20$ 岁」的人数,即 $10-3=7$。
\\n根据这个思路,本题等价于如下两个问题:
\\n二者相减,所表达的含义就是恰好包含 $k$ 个辅音字母了,所以答案为 $f_k - f_{k+1}$。
\\n对于每个问题,由于子串越长,越满足要求,有单调性,所以可以用滑动窗口解决。如果你不了解滑动窗口,可以看视频【基础算法精讲 03】。
\\n如果你之前没有做过统计子串/子数组个数的滑动窗口,推荐先完成 2962. 统计最大元素出现至少 K 次的子数组(我的题解),这也是一道至少+统计个数的问题,且比本题要简单许多。
\\n问:能不能把 $f_k$ 定义成「至多」?
\\n答:至多和前面的「每个元音字母至少出现一次」冲突,「至少」要求子串越长越好,而「至多」要求子串越短越好,这样必须分开求解(总共要计算四个滑动窗口),相比下面代码的直接求解要麻烦许多。
\\n问:代码中的 ans += left
是什么意思?
答:滑动窗口的内层循环结束时,右端点固定在 $\\\\textit{right}$,左端点在 $0,1,2,\\\\cdots,\\\\textit{left}-1$ 的所有子串都是合法的,这一共有 $\\\\textit{left}$ 个。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def f(self, word: str, k: int) -> int:\\n cnt1 = defaultdict(int) # 每种元音的个数\\n ans = cnt2 = left = 0 # cnt2 维护辅音个数\\n for b in word:\\n if b in \\"aeiou\\":\\n cnt1[b] += 1\\n else:\\n cnt2 += 1\\n while len(cnt1) == 5 and cnt2 >= k:\\n out = word[left]\\n if out in \\"aeiou\\":\\n cnt1[out] -= 1\\n if cnt1[out] == 0:\\n del cnt1[out]\\n else:\\n cnt2 -= 1\\n left += 1\\n ans += left\\n return ans\\n\\n def countOfSubstrings(self, word: str, k: int) -> int:\\n return self.f(word, k) - self.f(word, k + 1)\\n
\\n###java
\\nclass Solution {\\n public long countOfSubstrings(String word, int k) {\\n char[] s = word.toCharArray();\\n return f(s, k) - f(s, k + 1);\\n }\\n\\n private long f(char[] word, int k) {\\n long ans = 0;\\n // 这里用哈希表实现,替换成数组会更快\\n HashMap<Character, Integer> cnt1 = new HashMap<>(); // 每种元音的个数\\n int cnt2 = 0; // 辅音个数\\n int left = 0;\\n for (char b : word) {\\n if (\\"aeiou\\".indexOf(b) >= 0) {\\n cnt1.merge(b, 1, Integer::sum); // ++cnt1[b]\\n } else {\\n cnt2++;\\n }\\n while (cnt1.size() == 5 && cnt2 >= k) {\\n char out = word[left];\\n if (\\"aeiou\\".indexOf(out) >= 0) {\\n if (cnt1.merge(out, -1, Integer::sum) == 0) { // --cnt1[out] == 0\\n cnt1.remove(out);\\n }\\n } else {\\n cnt2--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const string VOWEL = \\"aeiou\\";\\n\\n long long f(string& word, int k) {\\n long long ans = 0;\\n // 这里用哈希表实现,替换成数组会更快\\n unordered_map<char, int> cnt1; // 每种元音的个数\\n int cnt2 = 0; // 辅音个数\\n int left = 0;\\n for (char b : word) {\\n if (VOWEL.find(b) != string::npos) {\\n cnt1[b]++;\\n } else {\\n cnt2++;\\n }\\n while (cnt1.size() == 5 && cnt2 >= k) {\\n char out = word[left];\\n if (VOWEL.find(out) != string::npos) {\\n if (--cnt1[out] == 0) {\\n cnt1.erase(out);\\n }\\n } else {\\n cnt2--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n\\npublic:\\n long long countOfSubstrings(string word, int k) {\\n return f(word, k) - f(word, k + 1);\\n }\\n};\\n
\\n###go
\\nfunc f(word string, k int) (ans int64) {\\n// 这里用哈希表实现,替换成数组会更快\\ncnt1 := map[byte]int{} // 每种元音的个数\\ncnt2 := 0 // 辅音个数\\nleft := 0\\nfor _, b := range word {\\nif strings.ContainsRune(\\"aeiou\\", b) {\\ncnt1[byte(b)]++\\n} else {\\ncnt2++\\n}\\nfor len(cnt1) == 5 && cnt2 >= k {\\nout := word[left]\\nif strings.ContainsRune(\\"aeiou\\", rune(out)) {\\ncnt1[out]--\\nif cnt1[out] == 0 {\\ndelete(cnt1, out)\\n}\\n} else {\\ncnt2--\\n}\\nleft++\\n}\\nans += int64(left)\\n}\\nreturn\\n}\\n\\nfunc countOfSubstrings(word string, k int) int64 {\\nreturn f(word, k) - f(word, k+1)\\n}\\n
\\n(1065233 >> b & 1) > 0
可以判断字母 $b$ 是否为元音。\\n\\nPython 代码无需优化,保持原样就行。
\\n
###java
\\nclass Solution {\\n public long countOfSubstrings(String word, int k) {\\n char[] s = word.toCharArray();\\n return f(s, k) - f(s, k + 1);\\n }\\n\\n private long f(char[] word, int k) {\\n final int VOWEL_MASK = 1065233;\\n long ans = 0;\\n int[] cnt1 = new int[\'u\' - \'a\' + 1];\\n int size1 = 0; // 元音种类数\\n int cnt2 = 0;\\n int left = 0;\\n for (char b : word) {\\n b -= \'a\';\\n if ((VOWEL_MASK >> b & 1) > 0) {\\n if (cnt1[b]++ == 0) {\\n size1++;\\n }\\n } else {\\n cnt2++;\\n }\\n while (size1 == 5 && cnt2 >= k) {\\n int out = word[left] - \'a\';\\n if ((VOWEL_MASK >> out & 1) > 0) {\\n if (--cnt1[out] == 0) {\\n size1--;\\n }\\n } else {\\n cnt2--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int VOWEL_MASK = 1065233;\\n\\n long long f(string& word, int k) {\\n long long ans = 0;\\n int cnt1[\'u\' - \'a\' + 1]{};\\n int size1 = 0; // 元音种类数\\n int cnt2 = 0;\\n int left = 0;\\n for (char b : word) {\\n b -= \'a\';\\n if (VOWEL_MASK >> b & 1) {\\n if (cnt1[b]++ == 0) {\\n size1++;\\n }\\n } else {\\n cnt2++;\\n }\\n while (size1 == 5 && cnt2 >= k) {\\n char out = word[left] - \'a\';\\n if (VOWEL_MASK >> out & 1) {\\n if (--cnt1[out] == 0) {\\n size1--;\\n }\\n } else {\\n cnt2--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n\\npublic:\\n long long countOfSubstrings(string word, int k) {\\n return f(word, k) - f(word, k + 1);\\n }\\n};\\n
\\n###go
\\nfunc f(word string, k int) (ans int64) {\\nconst vowelMask = 1065233\\ncnt1 := [\'u\' - \'a\' + 1]int{}\\nsize1 := 0 // 元音种类数\\ncnt2 := 0\\nleft := 0\\nfor _, b := range word {\\nb -= \'a\'\\nif vowelMask>>b&1 > 0 {\\nif cnt1[b] == 0 {\\nsize1++\\n}\\ncnt1[b]++\\n} else {\\ncnt2++\\n}\\nfor size1 == 5 && cnt2 >= k {\\nout := word[left] - \'a\'\\nif vowelMask>>out&1 > 0 {\\ncnt1[out]--\\nif cnt1[out] == 0 {\\nsize1--\\n}\\n} else {\\ncnt2--\\n}\\nleft++\\n}\\nans += int64(left)\\n}\\nreturn\\n}\\n\\nfunc countOfSubstrings(word string, k int) int64 {\\nreturn f(word, k) - f(word, k+1)\\n}\\n
\\n把两个滑动窗口合并成一个。我一般把这种滑窗叫做三指针滑窗。
\\n###py
\\nclass Solution:\\n def countOfSubstrings(self, word: str, k: int) -> int:\\n cnt_vowel1 = defaultdict(int)\\n cnt_vowel2 = defaultdict(int)\\n cnt_consonant1 = cnt_consonant2 = 0\\n ans = left1 = left2 = 0\\n for b in word:\\n if b in \\"aeiou\\":\\n cnt_vowel1[b] += 1\\n cnt_vowel2[b] += 1\\n else:\\n cnt_consonant1 += 1\\n cnt_consonant2 += 1\\n\\n while len(cnt_vowel1) == 5 and cnt_consonant1 >= k:\\n out = word[left1]\\n if out in \\"aeiou\\":\\n cnt_vowel1[out] -= 1\\n if cnt_vowel1[out] == 0:\\n del cnt_vowel1[out]\\n else:\\n cnt_consonant1 -= 1\\n left1 += 1\\n\\n while len(cnt_vowel2) == 5 and cnt_consonant2 > k:\\n out = word[left2]\\n if out in \\"aeiou\\":\\n cnt_vowel2[out] -= 1\\n if cnt_vowel2[out] == 0:\\n del cnt_vowel2[out]\\n else:\\n cnt_consonant2 -= 1\\n left2 += 1\\n\\n ans += left1 - left2\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long countOfSubstrings(String word, int k) {\\n final int VOWEL_MASK = 1065233;\\n char[] s = word.toCharArray();\\n long ans = 0;\\n int[] cntVowel1 = new int[\'u\' - \'a\' + 1], cntVowel2 = new int[\'u\' - \'a\' + 1];\\n int sizeVowel1 = 0, sizeVowel2 = 0; // 元音种类数\\n int cntConsonant1 = 0, cntConsonant2 = 0;\\n int left1 = 0, left2 = 0;\\n for (char b : s) {\\n b -= \'a\';\\n if ((VOWEL_MASK >> b & 1) > 0) {\\n if (cntVowel1[b]++ == 0) {\\n sizeVowel1++;\\n }\\n if (cntVowel2[b]++ == 0) {\\n sizeVowel2++;\\n }\\n } else {\\n cntConsonant1++;\\n cntConsonant2++;\\n }\\n\\n while (sizeVowel1 == 5 && cntConsonant1 >= k) {\\n int out = s[left1] - \'a\';\\n if ((VOWEL_MASK >> out & 1) > 0) {\\n if (--cntVowel1[out] == 0) {\\n sizeVowel1--;\\n }\\n } else {\\n cntConsonant1--;\\n }\\n left1++;\\n }\\n\\n while (sizeVowel2 == 5 && cntConsonant2 > k) {\\n int out = s[left2] - \'a\';\\n if ((VOWEL_MASK >> out & 1) > 0) {\\n if (--cntVowel2[out] == 0) {\\n sizeVowel2--;\\n }\\n } else {\\n cntConsonant2--;\\n }\\n left2++;\\n }\\n\\n ans += left1 - left2;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countOfSubstrings(string word, int k) {\\n const int VOWEL_MASK = 1065233;\\n long long ans = 0;\\n int cnt_vowel1[\'u\' - \'a\' + 1]{}, cnt_vowel2[\'u\' - \'a\' + 1]{};\\n int size_vowel1 = 0, size_vowel2 = 0; // 元音种类数\\n int cnt_consonant1 = 0, cnt_consonant2 = 0;\\n int left1 = 0, left2 = 0;\\n for (int b : word) {\\n b -= \'a\';\\n if (VOWEL_MASK >> b & 1) {\\n if (cnt_vowel1[b]++ == 0) {\\n size_vowel1++;\\n }\\n if (cnt_vowel2[b]++ == 0) {\\n size_vowel2++;\\n }\\n } else {\\n cnt_consonant1++;\\n cnt_consonant2++;\\n }\\n\\n while (size_vowel1 == 5 && cnt_consonant1 >= k) {\\n char out = word[left1] - \'a\';\\n if (VOWEL_MASK >> out & 1) {\\n if (--cnt_vowel1[out] == 0) {\\n size_vowel1--;\\n }\\n } else {\\n cnt_consonant1--;\\n }\\n left1++;\\n }\\n\\n while (size_vowel2 == 5 && cnt_consonant2 > k) {\\n char out = word[left2] - \'a\';\\n if (VOWEL_MASK >> out & 1) {\\n if (--cnt_vowel2[out] == 0) {\\n size_vowel2--;\\n }\\n } else {\\n cnt_consonant2--;\\n }\\n left2++;\\n }\\n\\n ans += left1 - left2;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countOfSubstrings(word string, k int) (ans int64) {\\nconst vowelMask = 1065233\\nvar cntVowel1, cntVowel2 [\'u\' - \'a\' + 1]int\\nsizeVowel1, sizeVowel2 := 0, 0 // 元音种类数\\ncntConsonant1, cntConsonant2 := 0, 0\\nleft1, left2 := 0, 0\\nfor _, b := range word {\\nb -= \'a\'\\nif vowelMask>>b&1 > 0 {\\nif cntVowel1[b] == 0 {\\nsizeVowel1++\\n}\\ncntVowel1[b]++\\nif cntVowel2[b] == 0 {\\nsizeVowel2++\\n}\\ncntVowel2[b]++\\n} else {\\ncntConsonant1++\\ncntConsonant2++\\n}\\n\\nfor sizeVowel1 == 5 && cntConsonant1 >= k {\\nout := word[left1] - \'a\'\\nif vowelMask>>out&1 > 0 {\\ncntVowel1[out]--\\nif cntVowel1[out] == 0 {\\nsizeVowel1--\\n}\\n} else {\\ncntConsonant1--\\n}\\nleft1++\\n}\\n\\nfor sizeVowel2 == 5 && cntConsonant2 > k {\\nout := word[left2] - \'a\'\\nif vowelMask>>out&1 > 0 {\\ncntVowel2[out]--\\nif cntVowel2[out] == 0 {\\nsizeVowel2--\\n}\\n} else {\\ncntConsonant2--\\n}\\nleft2++\\n}\\n\\nans += int64(left1 - left2)\\n}\\nreturn\\n}\\n
\\n更多相似题目,见下面滑动窗口题单中的「§2.3.3 恰好型滑动窗口」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"问:某班有 $10$ 个人至少 $20$ 岁,$3$ 个人至少 $21$ 岁,那么恰好 $20$ 岁的人有多少个? 答:「至少 $20$ 岁」可以分成「恰好 $20$ 岁」和「至少 $21$ 岁」,所以「至少 $20$ 岁」的人数减去「至少 $21$ 岁」的人数,就是「恰好 $20$ 岁」的人数,即 $10-3=7$。\\n\\n根据这个思路,本题等价于如下两个问题:\\n\\n每个元音字母至少出现一次,并且至少包含 $k$ 个辅音字母的子串个数。记作 $f_k$。\\n每个元音字母至少出现一次,并且至少包含 $k+1$ 个辅音字母的子串个数。记作 $f_{k+1}$。\\n\\n二…","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/liang-ci-hua-chuang-pythonjavacgo-by-end-2lpz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T04:10:14.441Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针","url":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-i//solution/shuang-zhi-zhen-by-tsreaper-d0a7","content":"常见转换:“恰好包含 $k$ 个的答案”,变为“至少包含 $k$ 个的答案,减去至少包含 $(k + 1)$ 个的答案”。
\\n如果一个子串含有所有元音字母,而且至少含有 $k$ 个辅音字母,那么包含它的字符串也能满足条件,因此可以使用双指针求解。
\\n复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int countOfSubstrings(string word, int K) {\\n int n = word.size();\\n\\n // 判断字母是否为元音\\n auto check = [&](char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n };\\n\\n // 包含所有元音,且至少包含 K 个辅音的子串有几个\\n auto calc = [&](int K) {\\n long long ret = 0;\\n // cnt[a]:元音字母 a 出现次数\\n // good:元音种数\\n // x:辅音个数\\n int cnt[26] = {0}, good = 0, x = 0;\\n for (int i = 0, j = 0; i < n; i++) {\\n while (j < n && (good < 5 || x < K)) {\\n if (check(word[j])) {\\n int t = cnt[word[j] - \'a\']++;\\n if (t == 0) good++;\\n } else {\\n x++;\\n }\\n j++;\\n }\\n if (good == 5 && x >= K) ret += n - j + 1;\\n if (check(word[i])) {\\n int t = --cnt[word[i] - \'a\'];\\n if (t == 0) good--;\\n } else {\\n x--;\\n }\\n }\\n return ret;\\n };\\n\\n // 恰好包含 K 个 -> 至少包含 K 个,减去至少包含 (K + 1) 个\\n return calc(K) - calc(K + 1);\\n }\\n};\\n
\\n","description":"解法:双指针 常见转换:“恰好包含 $k$ 个的答案”,变为“至少包含 $k$ 个的答案,减去至少包含 $(k + 1)$ 个的答案”。\\n\\n如果一个子串含有所有元音字母,而且至少含有 $k$ 个辅音字母,那么包含它的字符串也能满足条件,因此可以使用双指针求解。\\n\\n复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int countOfSubstrings(string word, int K) {\\n int n = word.size();…","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-i//solution/shuang-zhi-zhen-by-tsreaper-d0a7","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T04:10:13.547Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针","url":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/shuang-zhi-zhen-by-tsreaper-770z","content":"常见转换:“恰好包含 $k$ 个的答案”,变为“至少包含 $k$ 个的答案,减去至少包含 $(k + 1)$ 个的答案”。
\\n如果一个子串含有所有元音字母,而且至少含有 $k$ 个辅音字母,那么包含它的字符串也能满足条件,因此可以使用双指针求解。
\\n复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countOfSubstrings(string word, int K) {\\n int n = word.size();\\n\\n // 判断字母是否为元音\\n auto check = [&](char c) {\\n return c == \'a\' || c == \'e\' || c == \'i\' || c == \'o\' || c == \'u\';\\n };\\n\\n // 包含所有元音,且至少包含 K 个辅音的子串有几个\\n auto calc = [&](int K) {\\n long long ret = 0;\\n // cnt[a]:元音字母 a 出现次数\\n // good:元音种数\\n // x:辅音个数\\n int cnt[26] = {0}, good = 0, x = 0;\\n for (int i = 0, j = 0; i < n; i++) {\\n while (j < n && (good < 5 || x < K)) {\\n if (check(word[j])) {\\n int t = cnt[word[j] - \'a\']++;\\n if (t == 0) good++;\\n } else {\\n x++;\\n }\\n j++;\\n }\\n if (good == 5 && x >= K) ret += n - j + 1;\\n if (check(word[i])) {\\n int t = --cnt[word[i] - \'a\'];\\n if (t == 0) good--;\\n } else {\\n x--;\\n }\\n }\\n return ret;\\n };\\n\\n // 恰好包含 K 个 -> 至少包含 K 个,减去至少包含 (K + 1) 个\\n return calc(K) - calc(K + 1);\\n }\\n};\\n
\\n","description":"解法:双指针 常见转换:“恰好包含 $k$ 个的答案”,变为“至少包含 $k$ 个的答案,减去至少包含 $(k + 1)$ 个的答案”。\\n\\n如果一个子串含有所有元音字母,而且至少含有 $k$ 个辅音字母,那么包含它的字符串也能满足条件,因此可以使用双指针求解。\\n\\n复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n long long countOfSubstrings(string word, int K) {\\n int n = word.size();…","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/shuang-zhi-zhen-by-tsreaper-770z","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T04:09:58.932Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两次双指针","url":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/liang-ci-shuang-zhi-zhen-by-mipha-2022-h86c","content":"两次双指针","description":"两次双指针","guid":"https://leetcode.cn/problems/count-of-substrings-containing-every-vowel-and-k-consonants-ii//solution/liang-ci-shuang-zhi-zhen-by-mipha-2022-h86c","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T04:02:24.747Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:一次遍历(清晰题解)","url":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/python3javacgotypescript-yi-ti-yi-jie-yi-1qub","content":"根据题目描述,当第 $k$ 个人完成购票时,在第 $k$ 个人前面的所有人,购买的票数都不会超过第 $k$ 个人购买的票数,而在第 $k$ 个人后面的所有人,购买的票数都不会超过第 $k$ 个人购买的票数减 $1$。
\\n因此,我们可以遍历整个队伍,对于第 $i$ 个人,如果 $i \\\\leq k$,购票时间为 $\\\\min(\\\\textit{tickets}[i], \\\\textit{tickets}[k])$,否则购票时间为 $\\\\min(\\\\textit{tickets}[i], \\\\textit{tickets}[k] - 1)$。我们将所有人的购票时间相加即可。
\\n###python
\\nclass Solution:\\n def timeRequiredToBuy(self, tickets: List[int], k: int) -> int:\\n ans = 0\\n for i, x in enumerate(tickets):\\n ans += min(x, tickets[k] if i <= k else tickets[k] - 1)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int timeRequiredToBuy(int[] tickets, int k) {\\n int ans = 0;\\n for (int i = 0; i < tickets.length; ++i) {\\n ans += Math.min(tickets[i], i <= k ? tickets[k] : tickets[k] - 1);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int timeRequiredToBuy(vector<int>& tickets, int k) {\\n int ans = 0;\\n for (int i = 0; i < tickets.size(); ++i) {\\n ans += min(tickets[i], i <= k ? tickets[k] : tickets[k] - 1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc timeRequiredToBuy(tickets []int, k int) (ans int) {\\nfor i, x := range tickets {\\nt := tickets[k]\\nif i > k {\\nt--\\n}\\nans += min(x, t)\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction timeRequiredToBuy(tickets: number[], k: number): number {\\n let ans = 0;\\n const n = tickets.length;\\n for (let i = 0; i < n; ++i) {\\n ans += Math.min(tickets[i], i <= k ? tickets[k] : tickets[k] - 1);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为队伍的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 根据题目描述,当第 $k$ 个人完成购票时,在第 $k$ 个人前面的所有人,购买的票数都不会超过第 $k$ 个人购买的票数,而在第 $k$ 个人后面的所有人,购买的票数都不会超过第 $k$ 个人购买的票数减 $1$。\\n\\n因此,我们可以遍历整个队伍,对于第 $i$ 个人,如果 $i \\\\leq k$,购票时间为 $\\\\min(\\\\textit{tickets}[i], \\\\textit{tickets}[k])$,否则购票时间为 $\\\\min(\\\\textit{tickets}[i], \\\\textit{tickets}[k] - 1…","guid":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/python3javacgotypescript-yi-ti-yi-jie-yi-1qub","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-29T00:58:15.903Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-买票需要的时间🟢","url":"https://leetcode.cn/problems/time-needed-to-buy-tickets/","content":"有 n
个人前来排队买票,其中第 0
人站在队伍 最前方 ,第 (n - 1)
人站在队伍 最后方 。
给你一个下标从 0 开始的整数数组 tickets
,数组长度为 n
,其中第 i
人想要购买的票数为 tickets[i]
。
每个人买票都需要用掉 恰好 1 秒 。一个人 一次只能买一张票 ,如果需要购买更多票,他必须走到 队尾 重新排队(瞬间 发生,不计时间)。如果一个人没有剩下需要买的票,那他将会 离开 队伍。
\\n\\n返回位于位置 k
(下标从 0 开始)的人完成买票需要的时间(以秒为单位)。
\\n\\n
示例 1:
\\n\\n示例 2:
\\n\\n\\n\\n
提示:
\\n\\nn == tickets.length
1 <= n <= 100
1 <= tickets[i] <= 100
0 <= k < n
来看看示例 1 是怎么算的(点击下面播放按钮):
\\n<,
\\n,
\\n,
\\n,
\\n,
\\n,
\\n,
\\n>
\\n\\n注:把数组复制一份,意思是 $\\\\textit{gas}=\\\\textit{gas}+\\\\textit{gas}=[1,2,3,4,5,1,2,3,4,5]$。
\\n
对于示例 2,由于 $\\\\textit{gas}$ 元素和小于 $\\\\textit{cost}$ 元素和,油量不够我们跑一圈,答案一定是 $-1$。
\\n如果 $\\\\textit{gas}$ 元素和大于等于 $\\\\textit{cost}$ 元素和,答案是否一定存在?如何找到答案?
\\n从示例 1 的计算过程可以发现,我们可以先计算从 $0$ 号加油站出发的油量变化,然后从中找到油量最低时所处的加油站($3$ 号加油站),即为答案。
\\n有没有可能从 $3$ 号加油站出发,某个时刻油量变成负数呢?
\\n这是不会的,请看下图:
\\n问:下面的代码,是否有可能算出 $\\\\textit{ans}=n$?
\\n答:不会,如果最后一轮循环 $\\\\textit{s} < \\\\textit{minS}$,那么 $s$ 必然小于 $0$,这会导致最终返回 $-1$。
\\n###py
\\nclass Solution:\\n def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:\\n ans = min_s = s = 0 # s 表示油量,min_s 表示最小油量\\n for i, (g, c) in enumerate(zip(gas, cost)):\\n s += g - c # 在 i 处加油,然后从 i 到 i+1\\n if s < min_s:\\n min_s = s # 更新最小油量\\n ans = i + 1 # 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n # 循环结束后,s 即为 gas 之和减去 cost 之和\\n return -1 if s < 0 else ans\\n
\\n###java
\\nclass Solution {\\n public int canCompleteCircuit(int[] gas, int[] cost) {\\n int ans = 0;\\n int minS = 0; // 最小油量\\n int s = 0; // 油量\\n for (int i = 0; i < gas.length; i++) {\\n s += gas[i] - cost[i]; // 在 i 处加油,然后从 i 到 i+1\\n if (s < minS) {\\n minS = s; // 更新最小油量\\n ans = i + 1; // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n return s < 0 ? -1 : ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {\\n int ans = 0, min_s = 0, s = 0; // s 表示油量,min_s 表示最小油量\\n for (int i = 0; i < gas.size(); i++) {\\n s += gas[i] - cost[i]; // 在 i 处加油,然后从 i 到 i+1\\n if (s < min_s) {\\n min_s = s; // 更新最小油量\\n ans = i + 1; // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n return s < 0 ? -1 : ans;\\n }\\n};\\n
\\n###c
\\nint canCompleteCircuit(int* gas, int gasSize, int* cost, int costSize) {\\n int ans = 0, min_s = 0, s = 0; // s 表示油量,min_s 表示最小油量\\n for (int i = 0; i < gasSize; i++) {\\n s += gas[i] - cost[i]; // 在 i 处加油,然后从 i 到 i+1\\n if (s < min_s) {\\n min_s = s; // 更新最小油量\\n ans = i + 1; // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n return s < 0 ? -1 : ans;\\n}\\n
\\n###go
\\nfunc canCompleteCircuit(gas, cost []int) int {\\n var ans, minS, s int // s 表示油量,minS 表示最小油量\\n for i, g := range gas {\\n s += g - cost[i] // 在 i 处加油,然后从 i 到 i+1\\n if s < minS {\\n minS = s // 更新最小油量\\n ans = i + 1 // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n if s < 0 {\\n return -1\\n }\\n return ans\\n}\\n
\\n###js
\\nvar canCompleteCircuit = function(gas, cost) {\\n let ans = 0, minS = 0, s = 0; // s 表示油量,minS 表示最小油量\\n for (let i = 0; i < gas.length; i++) {\\n s += gas[i] - cost[i]; // 在 i 处加油,然后从 i 到 i+1\\n if (s < minS) {\\n minS = s; // 更新最小油量\\n ans = i + 1; // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n return s < 0 ? -1 : ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_complete_circuit(gas: Vec<i32>, cost: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut min_s = 0; // 最小油量\\n let mut s = 0; // 油量\\n for (i, (g, c)) in gas.iter().zip(cost.iter()).enumerate() {\\n s += g - c; // 在 i 处加油,然后从 i 到 i+1\\n if s < min_s {\\n min_s = s; // 更新最小油量\\n ans = i + 1; // 注意 s 减去 c 之后,汽车在 i+1 而不是 i\\n }\\n }\\n // 循环结束后,s 即为 gas 之和减去 cost 之和\\n if s < 0 { -1 } else { ans as _ }\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"来看看示例 1 是怎么算的(点击下面播放按钮): <,\\n ,\\n ,\\n ,\\n ,\\n ,\\n ,\\n >\\n\\n注:把数组复制一份,意思是 $\\\\textit{gas}=\\\\textit{gas}+\\\\textit{gas}=[1,2,3,4,5,1,2,3,4,5]$。\\n\\n对于示例 2,由于 $\\\\textit{gas}$ 元素和小于 $\\\\textit{cost}$ 元素和,油量不够我们跑一圈,答案一定是 $-1$。\\n\\n如果 $\\\\textit{gas}$ 元素和大于等于 $\\\\textit{cost}$ 元素和,答案是否一定存在?如何找到答案?\\n\\n从示例 1 的计算过程可以发现…","guid":"https://leetcode.cn/problems/gas-station//solution/yong-zhe-xian-tu-zhi-guan-li-jie-pythonj-qccr","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-28T03:38:50.357Z","media":[{"url":"https://pic.leetcode.cn/1727488541-mHKzak-lc134-1.png","type":"photo","width":1948,"height":1552,"blurhash":"LGSPU;~qxuaf-;t7oft7_3M_WB%M"},{"url":"https://pic.leetcode.cn/1727488541-InbYGL-lc134-2.png","type":"photo","width":1948,"height":1552,"blurhash":"LASidK~pof~W%Moft7oft2kCt7oz"},{"url":"https://pic.leetcode.cn/1727488541-fjFxav-lc134-start-0.png","type":"photo","width":1948,"height":1552,"blurhash":"L9Ss50%NjE~p-;ayxuM|WAt7xuNG"},{"url":"https://pic.leetcode.cn/1727488541-tPqzSK-lc134-start-1.png","type":"photo","width":1948,"height":1552,"blurhash":"L8Ss50-=t5~p?bj[%MM{t4t6xuNG"},{"url":"https://pic.leetcode.cn/1727488541-ItsdhT-lc134-start-2.png","type":"photo","width":1948,"height":1552,"blurhash":"L8Ss1]?bxt~W_3oK%MM{R$jYt7S3"},{"url":"https://pic.leetcode.cn/1727488541-ghnmVS-lc134-start-3-c.png","type":"photo","width":1948,"height":1552,"blurhash":"L7Ss1]_N%0~V~qad%MNGITRQt8kW"},{"url":"https://pic.leetcode.cn/1727488999-LKAKIT-lc134-start-4.png","type":"photo","width":1948,"height":1552,"blurhash":"L8Ss50~qoc?b?bRj-:WBobM{t8oz"},{"url":"https://pic.leetcode.cn/1727489143-abCTer-lc134-final-c.png","type":"photo","width":1948,"height":1552,"blurhash":"L7Ss1]~qxr^+~qn%x]M|E0V[ogX8"},{"url":"https://pic.leetcode.cn/1727493196-XpIKIO-lc134-prove-c.png","type":"photo","width":1991,"height":4452,"blurhash":"LBR:HI_4?b-;~qj[xuj[~VaeM{t7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-以组为单位订音乐会的门票🔴","url":"https://leetcode.cn/problems/booking-concert-tickets-in-groups/","content":"一个音乐会总共有 n
排座位,编号从 0
到 n - 1
,每一排有 m
个座椅,编号为 0
到 m - 1
。你需要设计一个买票系统,针对以下情况进行座位安排:
k
位观众坐在 同一排座位,且座位连续 。k
位观众中 每一位 都有座位坐,但他们 不一定 坐在一起。由于观众非常挑剔,所以:
\\n\\nmaxRow
,这个组才能订座位。每一组的 maxRow
可能 不同 。请你实现 BookMyShow
类:
BookMyShow(int n, int m)
,初始化对象,n
是排数,m
是每一排的座位数。int[] gather(int k, int maxRow)
返回长度为 2
的数组,表示 k
个成员中 第一个座位 的排数和座位编号,这 k
位成员必须坐在 同一排座位,且座位连续 。换言之,返回最小可能的 r
和 c
满足第 r
排中 [c, c + k - 1]
的座位都是空的,且 r <= maxRow
。如果 无法 安排座位,返回 []
。boolean scatter(int k, int maxRow)
如果组里所有 k
个成员 不一定 要坐在一起的前提下,都能在第 0
排到第 maxRow
排之间找到座位,那么请返回 true
。这种情况下,每个成员都优先找排数 最小 ,然后是座位编号最小的座位。如果不能安排所有 k
个成员的座位,请返回 false
。\\n\\n
示例 1:
\\n\\n输入:\\n[\\"BookMyShow\\", \\"gather\\", \\"gather\\", \\"scatter\\", \\"scatter\\"]\\n[[2, 5], [4, 0], [2, 0], [5, 1], [5, 1]]\\n输出:\\n[null, [0, 0], [], true, false]\\n\\n解释:\\nBookMyShow bms = new BookMyShow(2, 5); // 总共有 2 排,每排 5 个座位。\\nbms.gather(4, 0); // 返回 [0, 0]\\n // 这一组安排第 0 排 [0, 3] 的座位。\\nbms.gather(2, 0); // 返回 []\\n // 第 0 排只剩下 1 个座位。\\n // 所以无法安排 2 个连续座位。\\nbms.scatter(5, 1); // 返回 True\\n // 这一组安排第 0 排第 4 个座位和第 1 排 [0, 3] 的座位。\\nbms.scatter(5, 1); // 返回 False\\n // 总共只剩下 1 个座位。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 5 * 104
1 <= m, k <= 109
0 <= maxRow <= n - 1
gather
和 scatter
总 调用次数不超过 5 * 104
次。给你一个由字符 \'a\'
、\'b\'
、\'c\'
组成的字符串 s
和一个非负整数 k
。每分钟,你可以选择取走 s
最左侧 还是 最右侧 的那个字符。
你必须取走每种字符 至少 k
个,返回需要的 最少 分钟数;如果无法取到,则返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:s = \\"aabaaaacaabc\\", k = 2\\n输出:8\\n解释:\\n从 s 的左侧取三个字符,现在共取到两个字符 \'a\' 、一个字符 \'b\' 。\\n从 s 的右侧取五个字符,现在共取到四个字符 \'a\' 、两个字符 \'b\' 和两个字符 \'c\' 。\\n共需要 3 + 5 = 8 分钟。\\n可以证明需要的最少分钟数是 8 。\\n\\n\\n
示例 2:
\\n\\n输入:s = \\"a\\", k = 1\\n输出:-1\\n解释:无法取到一个字符 \'b\' 或者 \'c\',所以返回 -1 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s
仅由字母 \'a\'
、\'b\'
、\'c\'
组成0 <= k <= s.length
我们遍历数组 $\\\\textit{nums}$,计算元素和 $x$ 和数字和 $y$,最后返回 $|x - y|$ 即可。由于 $x$ 一定大于等于 $y$,所以我们也可以直接返回 $x - y$。
\\n###python
\\nclass Solution:\\n def differenceOfSum(self, nums: List[int]) -> int:\\n x = y = 0\\n for v in nums:\\n x += v\\n while v:\\n y += v % 10\\n v //= 10\\n return x - y\\n
\\n###java
\\nclass Solution {\\n public int differenceOfSum(int[] nums) {\\n int x = 0, y = 0;\\n for (int v : nums) {\\n x += v;\\n for (; v > 0; v /= 10) {\\n y += v % 10;\\n }\\n }\\n return x - y;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int differenceOfSum(vector<int>& nums) {\\n int x = 0, y = 0;\\n for (int v : nums) {\\n x += v;\\n for (; v; v /= 10) {\\n y += v % 10;\\n }\\n }\\n return x - y;\\n }\\n};\\n
\\n###go
\\nfunc differenceOfSum(nums []int) int {\\nvar x, y int\\nfor _, v := range nums {\\nx += v\\nfor ; v > 0; v /= 10 {\\ny += v % 10\\n}\\n}\\nreturn x - y\\n}\\n
\\n###ts
\\nfunction differenceOfSum(nums: number[]): number {\\n let [x, y] = [0, 0];\\n for (let v of nums) {\\n x += v;\\n for (; v; v = Math.floor(v / 10)) {\\n y += v % 10;\\n }\\n }\\n return x - y;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn difference_of_sum(nums: Vec<i32>) -> i32 {\\n let mut x = 0;\\n let mut y = 0;\\n\\n for &v in &nums {\\n x += v;\\n let mut num = v;\\n while num > 0 {\\n y += num % 10;\\n num /= 10;\\n }\\n }\\n\\n x - y\\n }\\n}\\n
\\n###c
\\nint differenceOfSum(int* nums, int numsSize) {\\n int x = 0, y = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int v = nums[i];\\n x += v;\\n while (v > 0) {\\n y += v % 10;\\n v /= 10;\\n }\\n }\\n return x - y;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log_{10} M)$,其中 $n$ 和 $M$ 分别是数组 $\\\\textit{nums}$ 的长度和数组中元素的最大值。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:模拟 我们遍历数组 $\\\\textit{nums}$,计算元素和 $x$ 和数字和 $y$,最后返回 $|x - y|$ 即可。由于 $x$ 一定大于等于 $y$,所以我们也可以直接返回 $x - y$。\\n\\n###python\\n\\nclass Solution:\\n def differenceOfSum(self, nums: List[int]) -> int:\\n x = y = 0\\n for v in nums:\\n x += v\\n while v:…","guid":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/python3javacgotypescriptrust-yi-ti-yi-ji-8os8","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-26T00:34:50.383Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-数组元素和与数字和的绝对差🟢","url":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array/","content":"给你一个正整数数组 nums
。
nums
中的所有元素相加求和。nums
中每一个元素的每一数位(重复数位需多次求和)相加求和。返回 元素和 与 数字和 的绝对差。
\\n\\n注意:两个整数 x
和 y
的绝对差定义为 |x - y|
。
\\n\\n
示例 1:
\\n\\n输入:nums = [1,15,6,3]\\n输出:9\\n解释:\\nnums 的元素和是 1 + 15 + 6 + 3 = 25 。\\nnums 的数字和是 1 + 1 + 5 + 6 + 3 = 16 。\\n元素和与数字和的绝对差是 |25 - 16| = 9 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [1,2,3,4]\\n输出:0\\n解释:\\nnums 的元素和是 1 + 2 + 3 + 4 = 10 。\\nnums 的数字和是 1 + 2 + 3 + 4 = 10 。\\n元素和与数字和的绝对差是 |10 - 10| = 0 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 2000
1 <= nums[i] <= 2000
给你一个字符串数组 ideas
表示在公司命名过程中使用的名字列表。公司命名流程如下:
ideas
中选择 2 个 不同 名字,称为 ideaA
和 ideaB
。ideaA
和 ideaB
的首字母。ideas
中,那么 ideaA ideaB
(串联 ideaA
和 ideaB
,中间用一个空格分隔)是一个有效的公司名字。返回 不同 且有效的公司名字的数目。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:ideas = [\\"coffee\\",\\"donuts\\",\\"time\\",\\"toffee\\"]\\n输出:6\\n解释:下面列出一些有效的选择方案:\\n- (\\"coffee\\", \\"donuts\\"):对应的公司名字是 \\"doffee conuts\\" 。\\n- (\\"donuts\\", \\"coffee\\"):对应的公司名字是 \\"conuts doffee\\" 。\\n- (\\"donuts\\", \\"time\\"):对应的公司名字是 \\"tonuts dime\\" 。\\n- (\\"donuts\\", \\"toffee\\"):对应的公司名字是 \\"tonuts doffee\\" 。\\n- (\\"time\\", \\"donuts\\"):对应的公司名字是 \\"dime tonuts\\" 。\\n- (\\"toffee\\", \\"donuts\\"):对应的公司名字是 \\"doffee tonuts\\" 。\\n因此,总共有 6 个不同的公司名字。\\n\\n下面列出一些无效的选择方案:\\n- (\\"coffee\\", \\"time\\"):在原数组中存在交换后形成的名字 \\"toffee\\" 。\\n- (\\"time\\", \\"toffee\\"):在原数组中存在交换后形成的两个名字。\\n- (\\"coffee\\", \\"toffee\\"):在原数组中存在交换后形成的两个名字。\\n\\n\\n
示例 2:
\\n\\n输入:ideas = [\\"lack\\",\\"back\\"]\\n输出:0\\n解释:不存在有效的选择方案。因此,返回 0 。\\n\\n\\n
\\n\\n
提示:
\\n\\n2 <= ideas.length <= 5 * 104
1 <= ideas[i].length <= 10
ideas[i]
由小写英文字母组成ideas
中的所有字符串 互不相同class Solution:\\n def smallestRangeI(self, nums: List[int], k: int) -> int:\\n return max(max(nums) - min(nums) - k * 2, 0)\\n
\\nclass Solution {\\n public int smallestRangeI(int[] nums, int k) {\\n int mn = nums[0];\\n int mx = nums[0];\\n for (int x : nums) {\\n mn = Math.min(mn, x);\\n mx = Math.max(mx, x);\\n }\\n return Math.max(mx - mn - 2 * k, 0);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int smallestRangeI(vector<int>& nums, int k) {\\n auto [m, M] = ranges::minmax(nums);\\n return max(M - m - k * 2, 0);\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint smallestRangeI(int* nums, int numsSize, int k) {\\n int mn = nums[0], mx = nums[0];\\n for (int i = 1; i < numsSize; i++) {\\n mn = MIN(mn, nums[i]);\\n mx = MAX(mx, nums[i]);\\n }\\n return MAX(mx - mn - 2 * k, 0);\\n}\\n
\\nfunc smallestRangeI(nums []int, k int) int {\\n return max(slices.Max(nums)-slices.Min(nums)-k*2, 0)\\n}\\n
\\nvar smallestRangeI = function(nums, k) {\\n const mn = Math.min(...nums);\\n const mx = Math.max(...nums);\\n return Math.max(mx - mn - 2 * k, 0);\\n};\\n
\\nimpl Solution {\\n pub fn smallest_range_i(nums: Vec<i32>, k: i32) -> i32 {\\n let mn = *nums.iter().min().unwrap();\\n let mx = *nums.iter().max().unwrap();\\n (mx - mn - 2 * k).max(0)\\n }\\n}\\n
\\n额外输入一个整数 $m$。如果限制你至多操作 $m$ 次,答案是多少?
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"class Solution: def smallestRangeI(self, nums: List[int], k: int) -> int:\\n return max(max(nums) - min(nums) - k * 2, 0)\\n\\nclass Solution {\\n public int smallestRangeI(int[] nums, int k) {\\n int mn = nums[0];\\n int mx = nums[0];\\n for (int x : nums)…","guid":"https://leetcode.cn/problems/smallest-range-i//solution/tu-fen-lei-tao-lun-jian-ji-xie-fa-python-9phy","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-24T09:08:07.301Z","media":[{"url":"https://pic.leetcode.cn/1727168375-dPcHXR-lc908-c.png","type":"photo","width":2897,"height":3700,"blurhash":"LLR{x#?b?cxu?dtRV?Rj$_M_WGxu"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图】小的变大,大的变小(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/smallest-range-ii//solution/xiao-de-bian-da-da-de-bian-xiao-pythonja-8fnp","content":"为了方便枚举,先把 $\\\\textit{nums}$ 从小到大排序。
\\n答案初始化为 $\\\\textit{nums}[n-1] - \\\\textit{nums}[0]$,对应着把所有数都变大或者变小的情况。
\\n考虑把 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[i-1]$ 都变大 $k$,把 $\\\\textit{nums}[i]$ 到 $\\\\textit{nums}[n-1]$ 都变小 $k$。
\\n如此变化后,数组的最大值要么是 $\\\\textit{nums}[i-1]+k$,要么是 $\\\\textit{nums}[n-1]-k$,即
\\n$$
\\n\\\\max(\\\\textit{nums}[i-1]+k,\\\\textit{nums}[n-1]-k)
\\n$$
数组的最小值要么是 $\\\\textit{nums}[0]+k$,要么是 $\\\\textit{nums}[i]-k$,即
\\n$$
\\n\\\\min(\\\\textit{nums}[0]+k,\\\\textit{nums}[i]-k)
\\n$$
最大值最小值之差为
\\n$$
\\n\\\\max(\\\\textit{nums}[i-1]+k,\\\\textit{nums}[n-1]-k) - \\\\min(\\\\textit{nums}[0]+k,\\\\textit{nums}[i]-k)
\\n$$
枚举 $i=1,2,3,\\\\cdots,n-1$,用上式更新答案的最小值。
\\nclass Solution:\\n def smallestRangeII(self, nums: List[int], k: int) -> int:\\n nums.sort()\\n ans = nums[-1] - nums[0]\\n for x, y in pairwise(nums):\\n mx = max(x + k, nums[-1] - k)\\n mn = min(nums[0] + k, y - k)\\n ans = min(ans, mx - mn)\\n return ans\\n
\\nclass Solution {\\n public int smallestRangeII(int[] nums, int k) {\\n Arrays.sort(nums);\\n int n = nums.length;\\n int ans = nums[n - 1] - nums[0];\\n for (int i = 1; i < n; i++) {\\n int mx = Math.max(nums[i - 1] + k, nums[n - 1] - k);\\n int mn = Math.min(nums[0] + k, nums[i] - k);\\n ans = Math.min(ans, mx - mn);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int smallestRangeII(vector<int>& nums, int k) {\\n ranges::sort(nums);\\n int ans = nums.back() - nums[0];\\n for (int i = 1; i < nums.size(); i++) {\\n int mx = max(nums[i - 1] + k, nums.back() - k);\\n int mn = min(nums[0] + k, nums[i] - k);\\n ans = min(ans, mx - mn);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nint smallestRangeII(int* nums, int n, int k) {\\n qsort(nums, n, sizeof(int), cmp);\\n int ans = nums[n - 1] - nums[0];\\n for (int i = 1; i < n; i++) {\\n int mx = MAX(nums[i - 1] + k, nums[n - 1] - k);\\n int mn = MIN(nums[0] + k, nums[i] - k);\\n ans = MIN(ans, mx - mn);\\n }\\n return ans;\\n}\\n
\\nfunc smallestRangeII(nums []int, k int) int {\\n slices.Sort(nums)\\n n := len(nums)\\n ans := nums[n-1] - nums[0]\\n for i := 1; i < n; i++ {\\n mx := max(nums[i-1]+k, nums[n-1]-k)\\n mn := min(nums[0]+k, nums[i]-k)\\n ans = min(ans, mx-mn)\\n }\\n return ans\\n}\\n
\\nvar smallestRangeII = function(nums, k) {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n let ans = nums[n - 1] - nums[0];\\n for (let i = 1; i < n; i++) {\\n const mx = Math.max(nums[i - 1] + k, nums[n - 1] - k);\\n const mn = Math.min(nums[0] + k, nums[i] - k);\\n ans = Math.min(ans, mx - mn);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn smallest_range_ii(mut nums: Vec<i32>, k: i32) -> i32 {\\n nums.sort_unstable();\\n let n = nums.len();\\n let mut ans = nums[n - 1] - nums[0];\\n for i in 1..n {\\n let mx = (nums[i - 1] + k).max(nums[n - 1] - k);\\n let mn = (nums[0] + k).min(nums[i] - k);\\n ans = ans.min(mx - mn);\\n }\\n ans\\n }\\n}\\n
\\n额外输入一个整数 $m$。如果要求恰好修改 $m$ 个元素,答案是多少?本题相当于恰好修改 $n$ 个元素。
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"为了方便枚举,先把 $\\\\textit{nums}$ 从小到大排序。 答案初始化为 $\\\\textit{nums}[n-1] - \\\\textit{nums}[0]$,对应着把所有数都变大或者变小的情况。\\n\\n考虑把 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[i-1]$ 都变大 $k$,把 $\\\\textit{nums}[i]$ 到 $\\\\textit{nums}[n-1]$ 都变小 $k$。\\n\\n如此变化后,数组的最大值要么是 $\\\\textit{nums}[i-1]+k$,要么是 $\\\\textit{nums}[n-1]-k$,即\\n\\n$$\\n \\\\…","guid":"https://leetcode.cn/problems/smallest-range-ii//solution/xiao-de-bian-da-da-de-bian-xiao-pythonja-8fnp","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-24T08:19:38.424Z","media":[{"url":"https://pic.leetcode.cn/1727165884-AiuZzx-lc910-c.png","type":"photo","width":2952,"height":7110,"blurhash":"UQSY~y~qxu_3?bWBayt7M{ofofWB?bayWBt7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"使二进制数组全部等于 1 的最少操作次数 I","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/shi-er-jin-zhi-shu-zu-quan-bu-deng-yu-1-lx2nx","content":"思路与算法
\\n给定的数组中的元素要么为 $0$,要么为 $1$,题目要求将数组中所有元素都变为 $1$,由于每次可以反转的元素为连续 $3$ 个元素,此时只能从左到右或者从右到左依次遍历数组中的每个元素,遇到 $0$ 时,则将后续的连续 $3$ 个相邻的元素进行反转即可,任意连续的 $3$ 个元素最多只会反转 $1$ 次,且所有的反转操作都是必要的。
\\n实际操作过程如下:
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int n = nums.size();\\n int ans = 0;\\n \\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 0) {\\n if (i > n - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int n = nums.length;\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 0) {\\n if (i > n - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int n = nums.Length;\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 0) {\\n if (i > n - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc minOperations(nums []int) int {\\n n := len(nums)\\n ans := 0\\n for i := 0; i < n; i++ {\\n if nums[i] == 0 {\\n if i > n - 3 {\\n return -1\\n }\\n nums[i] ^= 1\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n ans++\\n }\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n n = len(nums)\\n ans = 0\\n for i in range(n):\\n if nums[i] == 0:\\n if i > n - 3:\\n return -1\\n nums[i] ^= 1\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n ans += 1\\n return ans\\n
\\n###C
\\nint minOperations(int* nums, int numsSize) {\\n int ans = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == 0) {\\n if (i > numsSize - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar minOperations = function(nums) {\\n let n = nums.length;\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n if (nums[i] === 0) {\\n if (i > n - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction minOperations(nums: number[]): number {\\n const n = nums.length;\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n if (nums[i] === 0) {\\n if (i > n - 3) {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let mut ans = 0;\\n for i in 0..n {\\n if nums[i] == 0 {\\n if i > n - 3 {\\n return -1;\\n }\\n nums[i] ^= 1;\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans += 1;\\n }\\n }\\n\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 表示给定的数组 $\\\\textit{nums}$ 的长度。只需遍历一遍数组即可。
\\n空间复杂度:$O(1)$。
\\n思路与算法
\\n$\\\\textit{hours}[i] + \\\\textit{hours}[j]$ 能够被 $24$ 整除,只需 $\\\\textit{hours}[i]$ 除以 $24$ 的余数与 $\\\\textit{hours}[j]$ 除以 $24$ 的余数之和能够被 $24$ 整除。
\\n我们可以枚举 $\\\\textit{hours}[i]$,每一个 $\\\\textit{hours}[i]$ 对答案的贡献就是能与其成对的 $\\\\textit{hours}[j]$ 的数量。如果暴力查找能够成对的 $\\\\textit{hours}[j]$,则每次都需要遍历一遍 $\\\\textit{hours}$ 数组中剩余的元素。我们可以使用一个长度为 $24$ 的数组 $cnt$ 记录每个余数的出现次数,从而快速查询能够与 $\\\\textit{hours}[i]$ 成对的元素数量。
\\n注意,哈希表记录的是位于我们当前枚举的 $\\\\textit{hours}[i]$ 左边的元素,也就是说我们是在枚举右边值的同时维护左边的元素。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long countCompleteDayPairs(vector<int>& hours) {\\n long long ans = 0;\\n vector<int> cnt(24);\\n for (int hour : hours) {\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long countCompleteDayPairs(int[] hours) {\\n long ans = 0;\\n int[] cnt = new int[24];\\n for (int hour : hours) {\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n }\\n}\\n
\\n###C
\\nlong long countCompleteDayPairs(int* hours, int hoursSize) {\\n long long ans = 0;\\n int cnt[24] = {0};\\n for (int i = 0; i < hoursSize; i++) {\\n int hour = hours[i];\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n}\\n
\\n###Go
\\nfunc countCompleteDayPairs(hours []int) int64 {\\n var ans int64 = 0\\n cnt := make([]int, 24)\\n for _, hour := range hours {\\n ans += int64(cnt[(24 - hour % 24) % 24])\\n cnt[hour % 24]++\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n ans = 0\\n cnt = [0] * 24\\n for hour in hours:\\n ans += cnt[(24 - hour % 24) % 24]\\n cnt[hour % 24] += 1\\n return ans\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_complete_day_pairs(hours: Vec<i32>) -> i64 {\\n let mut ans: i64 = 0;\\n let mut cnt = vec![0; 24];\\n for hour in hours {\\n ans += cnt[(24 - hour % 24) as usize % 24] as i64;\\n cnt[hour as usize % 24] += 1;\\n }\\n ans\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long CountCompleteDayPairs(int[] hours) {\\n long ans = 0;\\n int[] cnt = new int[24];\\n foreach (int hour in hours) {\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n }\\n}\\n
\\n###JavaScript
\\nvar countCompleteDayPairs = function(hours) {\\n let ans = 0;\\n let cnt = new Array(24).fill(0);\\n for (let hour of hours) {\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction countCompleteDayPairs(hours: number[]): number {\\n let ans = 0;\\n let cnt = new Array(24).fill(0);\\n for (let hour of hours) {\\n ans += cnt[(24 - hour % 24) % 24];\\n cnt[hour % 24]++;\\n }\\n return ans;\\n};\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 是 $\\\\textit{hours}$ 数组的长度。
\\n空间复杂度:$O(24)$,哈希表的大小只需要包含 $\\\\textit{hours}[i]$ 除以 $24$ 余数的取值范围。
\\n思路与算法
\\n我们可以枚举每一个 $\\\\textit{hours}[i]$,然后遍历剩下的 $\\\\textit{hours}[j]$,如果 $\\\\textit{hours}[i] + \\\\textit{hours}[j]$ 能够被 $24$ 整除,就对答案进行累加。
\\n注意,枚举的方向可以从左往右,也可以从右往左,即对于每一个 $\\\\textit{hours}[i]$,可以查看 $j > i$ 或查看 $j < i$ 时的 $\\\\textit{hours}[j]$ 能否与 $\\\\textit{hours}[i]$ 成对,只需要保证枚举不会重复即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int countCompleteDayPairs(vector<int>& hours) {\\n int ans = 0;\\n for (int i = 1; i < hours.size(); i++) {\\n for (int j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 == 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int countCompleteDayPairs(int[] hours) {\\n int ans = 0;\\n for (int i = 1; i < hours.length; i++) {\\n for (int j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 == 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc countCompleteDayPairs(hours []int) int {\\n ans := 0\\n for i := 1; i < len(hours); i++ {\\n for j := 0; j < i; j++ {\\n if (hours[i]+hours[j])%24 == 0 {\\n ans++\\n }\\n }\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n ans = 0\\n for i in range(1, len(hours)):\\n for j in range(i):\\n if (hours[i] + hours[j]) % 24 == 0:\\n ans += 1\\n return ans\\n
\\n###C#
\\npublic class Solution {\\n public int CountCompleteDayPairs(int[] hours) {\\n int ans = 0;\\n for (int i = 1; i < hours.Length; i++) {\\n for (int j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 == 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn count_complete_day_pairs(hours: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n for i in 1..hours.len() {\\n for j in 0..i {\\n if (hours[i] + hours[j]) % 24 == 0 {\\n ans += 1;\\n }\\n }\\n }\\n ans\\n }\\n}\\n
\\n###JavaScript
\\nvar countCompleteDayPairs = function(hours) {\\n let ans = 0;\\n for (let i = 1; i < hours.length; i++) {\\n for (let j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 === 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction countCompleteDayPairs(hours: number[]): number {\\n let ans = 0;\\n for (let i = 1; i < hours.length; i++) {\\n for (let j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 === 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n};\\n
\\n###C
\\nint countCompleteDayPairs(int* hours, int hoursSize) {\\n int ans = 0;\\n for (int i = 1; i < hoursSize; i++) {\\n for (int j = 0; j < i; j++) {\\n if ((hours[i] + hours[j]) % 24 == 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n^2)$,其中 $n$ 是 hours
数组的长度。
空间复杂度:$O(1)$。
\\n思路与算法
\\n分别统计 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的频数。
\\n遍历 $\\\\textit{nums}_2$ 出现过的数 $a$,枚举 $a \\\\times k$ 的倍数,如果在 $\\\\textit{nums}_1$ 出现过就可以组成优质数对,更新结果。
\\n返回优质数对的总数。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> count, count2;\\n int max1 = 0;\\n for (int num : nums1) {\\n count[num]++;\\n max1 = max(max1, num);\\n }\\n for (int num : nums2) {\\n count2[num]++;\\n }\\n long long res = 0;\\n for (const auto& pair : count2) {\\n int a = pair.first, cnt = pair.second;\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.count(b) > 0) {\\n res += 1L * count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> count = new HashMap<>();\\n Map<Integer, Integer> count2 = new HashMap<>();\\n int max1 = 0;\\n for (int num : nums1) {\\n count.put(num, count.getOrDefault(num, 0) + 1);\\n max1 = Math.max(max1, num);\\n }\\n for (int num : nums2) {\\n count2.put(num, count2.getOrDefault(num, 0) + 1);\\n }\\n long res = 0;\\n for (int a : count2.keySet()) {\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.containsKey(b)) {\\n res += 1L * count.get(b) * count2.get(a);\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n count = Counter(nums1)\\n max1 = max(count)\\n res = 0\\n for a, cnt in Counter(nums2).items():\\n for b in range(a * k, max1 + 1, a * k):\\n if b in count:\\n res += count[b] * cnt\\n return res\\n
\\n###JavaScript
\\nvar numberOfPairs = function(nums1, nums2, k) {\\n const count = {};\\n const count2 = {};\\n let res = 0, max1 = 0;\\n for (let num of nums1) {\\n count[num] = (count[num] || 0) + 1;\\n max1 = Math.max(max1, num);\\n }\\n for (let num of nums2) {\\n count2[num] = (count2[num] || 0) + 1;\\n }\\n for (let a in count2) {\\n let cnt = count2[a];\\n for (let b = a * k; b <= max1; b += a * k) {\\n if (b in count) {\\n res += count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction numberOfPairs(nums1: number[], nums2: number[], k: number): number {\\n const count = {};\\n const count2 = {};\\n let res = 0, max1 = 0;\\n for (let num of nums1) {\\n count[num] = (count[num] || 0) + 1;\\n max1 = Math.max(max1, num);\\n }\\n for (let num of nums2) {\\n count2[num] = (count2[num] || 0) + 1;\\n }\\n for (let a in count2) {\\n let cnt = count2[a];\\n for (let b = Number(a) * k; b <= max1; b += Number(a) * k) {\\n if (b in count) {\\n res += count[b] * cnt;\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###Go
\\nfunc numberOfPairs(nums1 []int, nums2 []int, k int) int64 {\\n count := make(map[int]int)\\n count2 := make(map[int]int)\\n max1 := 0\\n for _, num := range nums1 {\\n count[num]++\\n if num > max1 {\\n max1 = num\\n }\\n }\\n for _, num := range nums2 {\\n count2[num]++\\n }\\n var res int64\\n for a, cnt := range count2 {\\n for b := a * k; b <= max1; b += a * k {\\n if _, ok := count[b]; ok {\\n res += int64(count[b] * cnt)\\n }\\n }\\n }\\n return res\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long NumberOfPairs(int[] nums1, int[] nums2, int k) {\\n Dictionary<int, int> count = new Dictionary<int, int>();\\n Dictionary<int, int> count2 = new Dictionary<int, int>();\\n int max1 = 0;\\n foreach (int num in nums1) {\\n if (count.ContainsKey(num)) {\\n count[num]++;\\n } else {\\n count[num] = 1;\\n }\\n max1 = Math.Max(max1, num);\\n }\\n foreach (int num in nums2) {\\n if (count2.ContainsKey(num)) {\\n count2[num]++;\\n } else {\\n count2[num] = 1;\\n }\\n }\\n long res = 0;\\n foreach (int a in count2.Keys) {\\n for (int b = a * k; b <= max1; b += a * k) {\\n if (count.ContainsKey(b)) {\\n res += 1l * count[b] * count2[a];\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn number_of_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> i32 {\\n let mut count: HashMap<i32, i32> = HashMap::new();\\n let mut count2: HashMap<i32, i32> = HashMap::new();\\n let mut res = 0;\\n let mut max1 = 0;\\n for &num in &nums1 {\\n *count.entry(num).or_insert(0) += 1;\\n max1 = std::cmp::max(max1, num);\\n }\\n for &num in &nums2 {\\n *count2.entry(num).or_insert(0) += 1;\\n }\\n for (&a, &cnt) in &count2 {\\n for b in (a * k..=max1).step_by((a * k) as usize) {\\n if let Some(&value) = count.get(&b) {\\n res += value * cnt;\\n }\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + m + \\\\dfrac{v}{k} \\\\times \\\\log m)$,其中 $n$ 和 $m$ 分别是数组 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度,$k$ 是给定的正整数,$v$ 是数组 $\\\\textit{nums}_1$ 最大值,$\\\\log m$ 是「调和级数」求和的结果。
\\n空间复杂度:$O(n + m)$。
\\n我们可以使用两个变量 $x$ 和 $y$ 分别记录当前字符串中 $\\\\textit{pattern}[0]$ 和 $\\\\textit{pattern}[1]$ 出现的次数。
\\n然后遍历字符串 $\\\\textit{text}$,对于当前遍历到的字符 $c$:
\\n遍历结束后,由于我们可以插入一个字符,因此,如果我们在字符串开头加上 $\\\\textit{pattern}[0]$,那么可以得到 $y$ 个 $\\\\textit{pattern}$ 子序列;如果我们在字符串结尾加上 $\\\\textit{pattern}[1]$,那么可以得到 $x$ 个 $\\\\textit{pattern}$ 子序列。因此,我们将答案加上 $x$ 和 $y$ 中的较大值即可。
\\n###python
\\nclass Solution:\\n def maximumSubsequenceCount(self, text: str, pattern: str) -> int:\\n ans = x = y = 0\\n for c in text:\\n if c == pattern[1]:\\n y += 1\\n ans += x\\n if c == pattern[0]:\\n x += 1\\n ans += max(x, y)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long maximumSubsequenceCount(String text, String pattern) {\\n long ans = 0;\\n int x = 0, y = 0;\\n for (int i = 0; i < text.length(); ++i) {\\n if (text.charAt(i) == pattern.charAt(1)) {\\n ++y;\\n ans += x;\\n }\\n if (text.charAt(i) == pattern.charAt(0)) {\\n ++x;\\n }\\n }\\n ans += Math.max(x, y);\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maximumSubsequenceCount(string text, string pattern) {\\n long long ans = 0;\\n int x = 0, y = 0;\\n for (char& c : text) {\\n if (c == pattern[1]) {\\n ++y;\\n ans += x;\\n }\\n if (c == pattern[0]) {\\n ++x;\\n }\\n }\\n ans += max(x, y);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maximumSubsequenceCount(text string, pattern string) (ans int64) {\\nx, y := 0, 0\\nfor _, c := range text {\\nif byte(c) == pattern[1] {\\ny++\\nans += int64(x)\\n}\\nif byte(c) == pattern[0] {\\nx++\\n}\\n}\\nans += int64(max(x, y))\\nreturn\\n}\\n
\\n###ts
\\nfunction maximumSubsequenceCount(text: string, pattern: string): number {\\n let ans = 0;\\n let [x, y] = [0, 0];\\n for (const c of text) {\\n if (c === pattern[1]) {\\n ++y;\\n ans += x;\\n }\\n if (c === pattern[0]) {\\n ++x;\\n }\\n }\\n ans += Math.max(x, y);\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为字符串 $\\\\textit{text}$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:遍历 + 计数 我们可以使用两个变量 $x$ 和 $y$ 分别记录当前字符串中 $\\\\textit{pattern}[0]$ 和 $\\\\textit{pattern}[1]$ 出现的次数。\\n\\n然后遍历字符串 $\\\\textit{text}$,对于当前遍历到的字符 $c$:\\n\\n如果 $c$ 等于 $\\\\textit{pattern}[1]$,我们将 $y$ 加一,此时之前出现过的所有 $\\\\textit{pattern}[0]$ 都可以和当前的 $c$ 组成一个 $\\\\textit{pattern}$ 子序列,因此答案加上 $x$;\\n如果 $c$ 等于…","guid":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/python3javacgotypescript-yi-ti-yi-jie-bi-d9vf","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-24T00:20:26.530Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-字符串中最多数目的子序列🟡","url":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string/","content":"给你一个下标从 0 开始的字符串 text
和另一个下标从 0 开始且长度为 2
的字符串 pattern
,两者都只包含小写英文字母。
你可以在 text
中任意位置插入 一个 字符,这个插入的字符必须是 pattern[0]
或者 pattern[1]
。注意,这个字符可以插入在 text
开头或者结尾的位置。
请你返回插入一个字符后,text
中最多包含多少个等于 pattern
的 子序列 。
子序列 指的是将一个字符串删除若干个字符后(也可以不删除),剩余字符保持原本顺序得到的字符串。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:text = \\"abdcdbc\\", pattern = \\"ac\\"\\n输出:4\\n解释:\\n如果我们在 text[1] 和 text[2] 之间添加 pattern[0] = \'a\' ,那么我们得到 \\"abadcdbc\\" 。那么 \\"ac\\" 作为子序列出现 4 次。\\n其他得到 4 个 \\"ac\\" 子序列的方案还有 \\"aabdcdbc\\" 和 \\"abdacdbc\\" 。\\n但是,\\"abdcadbc\\" ,\\"abdccdbc\\" 和 \\"abdcdbcc\\" 这些字符串虽然是可行的插入方案,但是只出现了 3 次 \\"ac\\" 子序列,所以不是最优解。\\n可以证明插入一个字符后,无法得到超过 4 个 \\"ac\\" 子序列。\\n\\n\\n
示例 2:
\\n\\n输入:text = \\"aabb\\", pattern = \\"ab\\"\\n输出:6\\n解释:\\n可以得到 6 个 \\"ab\\" 子序列的部分方案为 \\"aaabb\\" ,\\"aaabb\\" 和 \\"aabbb\\" 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= text.length <= 105
pattern.length == 2
text
和 pattern
都只包含小写英文字母。问:为什么代码只需要遍历到 $n-2$?
\\n答:当 $i=n-2$ 时,如果 $i<\\\\textit{curRight}$,说明可以到达 $n-1$;如果 $i=\\\\textit{curRight}$,我们会造桥,这样也可以到达 $n-1$。所以无论是何种情况,都只需要遍历到 $n-2$。或者说,$n-1$ 已经是终点了,你总不能在终点还打算造桥吧?
\\n问:我能想出这题的思路,就是代码实现总是写不对,有没有什么建议?
\\n答:清晰的变量名以及一些必要的注释,会对理清代码逻辑有帮助。在出现错误时,可以用一些小数据去运行你的代码,通过 print 或者打断点的方式,查看这些关键变量的值,看看是否与预期结果一致。
\\n问:如果题目没有保证一定能到达 $n-1$,代码要怎么改?
\\n答:见 1326. 灌溉花园的最少水龙头数目,我的题解。
\\n###py
\\nclass Solution:\\n def jump(self, nums: List[int]) -> int:\\n ans = 0\\n cur_right = 0 # 已建造的桥的右端点\\n next_right = 0 # 下一座桥的右端点的最大值\\n for i in range(len(nums) - 1):\\n next_right = max(next_right, i + nums[i])\\n if i == cur_right: # 到达已建造的桥的右端点\\n cur_right = next_right # 造一座桥\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int jump(int[] nums) {\\n int ans = 0;\\n int curRight = 0; // 已建造的桥的右端点\\n int nextRight = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < nums.length - 1; i++) {\\n nextRight = Math.max(nextRight, i + nums[i]);\\n if (i == curRight) { // 到达已建造的桥的右端点\\n curRight = nextRight; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int jump(vector<int>& nums) {\\n int ans = 0;\\n int cur_right = 0; // 已建造的桥的右端点\\n int next_right = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i + 1 < nums.size(); i++) {\\n next_right = max(next_right, i + nums[i]);\\n if (i == cur_right) { // 到达已建造的桥的右端点\\n cur_right = next_right; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint jump(int* nums, int numsSize) {\\n int ans = 0;\\n int cur_right = 0; // 已建造的桥的右端点\\n int next_right = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < numsSize - 1; i++) {\\n next_right = MAX(next_right, i + nums[i]);\\n if (i == cur_right) { // 到达已建造的桥的右端点\\n cur_right = next_right; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc jump(nums []int) (ans int) {\\n curRight := 0 // 已建造的桥的右端点\\n nextRight := 0 // 下一座桥的右端点的最大值\\n for i, num := range nums[:len(nums)-1] {\\n nextRight = max(nextRight, i+num)\\n if i == curRight { // 到达已建造的桥的右端点\\n curRight = nextRight // 造一座桥\\n ans++\\n }\\n }\\n return\\n}\\n
\\n###js
\\nvar jump = function(nums) {\\n let ans = 0;\\n let curRight = 0; // 已建造的桥的右端点\\n let nextRight = 0; // 下一座桥的右端点的最大值\\n for (let i = 0; i < nums.length - 1; i++) {\\n nextRight = Math.max(nextRight, i + nums[i]);\\n if (i === curRight) { // 到达已建造的桥的右端点\\n curRight = nextRight; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn jump(nums: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut cur_right = 0; // 已建造的桥的右端点\\n let mut next_right = 0; // 下一座桥的右端点的最大值\\n for i in 0..nums.len()-1 {\\n next_right = next_right.max(i as i32 + nums[i]);\\n if i as i32 == cur_right { // 到达已建造的桥的右端点\\n cur_right = next_right; // 造一座桥\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面贪心题单中的「区间贪心」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"答疑 问:为什么代码只需要遍历到 $n-2$?\\n\\n答:当 $i=n-2$ 时,如果 $i<\\\\textit{curRight}$,说明可以到达 $n-1$;如果 $i=\\\\textit{curRight}$,我们会造桥,这样也可以到达 $n-1$。所以无论是何种情况,都只需要遍历到 $n-2$。或者说,$n-1$ 已经是终点了,你总不能在终点还打算造桥吧?\\n\\n问:我能想出这题的思路,就是代码实现总是写不对,有没有什么建议?\\n\\n答:清晰的变量名以及一些必要的注释,会对理清代码逻辑有帮助。在出现错误时,可以用一些小数据去运行你的代码,通过 print…","guid":"https://leetcode.cn/problems/jump-game-ii//solution/tu-jie-yi-zhang-tu-miao-dong-tiao-yue-yo-h2d4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-23T02:16:35.477Z","media":[{"url":"https://pic.leetcode.cn/1727057167-mGElnQ-45-c.png","type":"photo","width":2562,"height":5514,"blurhash":"LFR:NYXTxt?b~q%Mjsj[$JngWBoe"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最佳观光组合🟡","url":"https://leetcode.cn/problems/best-sightseeing-pair/","content":"给你一个正整数数组 values
,其中 values[i]
表示第 i
个观光景点的评分,并且两个景点 i
和 j
之间的 距离 为 j - i
。
一对景点(i < j
)组成的观光组合的得分为 values[i] + values[j] + i - j
,也就是景点的评分之和 减去 它们两者之间的距离。
返回一对观光景点能取得的最高分。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:values = [8,1,5,2,6]\\n输出:11\\n解释:i = 0, j = 2, values[i] + values[j] + i - j = 8 + 5 + 0 - 2 = 11\\n\\n\\n
示例 2:
\\n\\n输入:values = [1,2]\\n输出:2\\n\\n\\n
\\n\\n
提示:
\\n\\n2 <= values.length <= 5 * 104
1 <= values[i] <= 1000
可以将一个字符串重新排列后使字符串 $\\\\textit{word}_2$ 是重排字符串的前缀,等价于原始字符串覆盖字符串 $\\\\textit{word}_2$ 的所有字符,因此问题等价于计算字符串 $\\\\textit{word}_1$ 的覆盖字符串 $\\\\textit{word}_2$ 的所有字符的子字符串数目。以下将覆盖字符串 $\\\\textit{word}_2$ 中的每个字符的子字符串称为「覆盖子字符串」。
\\n如果字符串 $\\\\textit{word}_1$ 的下标范围 $[\\\\textit{start}, \\\\textit{end}]$ 的子字符串覆盖字符串 $\\\\textit{word}_2$ 中的每个字符,则将子字符串的左端点向左移动或将子字符串的右端点向右移动之后,得到更长的子字符串,同样覆盖字符串 $\\\\textit{word}_2$ 中的每个字符。考虑两个不同下标 $\\\\textit{end}_1$ 和 $\\\\textit{end}_2$,其中 $\\\\textit{end}_1 < \\\\textit{end}_2$,分别以这两个下标作为结束下标,寻找最小覆盖子字符串,将这两个最小覆盖子字符串的起始下标分别记为 $\\\\textit{start}_1$ 和 $\\\\textit{start}_2$,则必有 $\\\\textit{start}_1 \\\\le \\\\textit{start}_2$。因此,需要对每个下标寻找以该下标作为结束下标的最小覆盖子字符串,当子字符串的结束下标确定时,只要子字符串的起始下标小于等于覆盖子字符串的起始下标,就能确保子字符串是覆盖子字符串,由此可以计算以每个下标作为结束下标的覆盖子字符串数目。
\\n判断一个子字符串是否为覆盖子字符串时,需要使用哈希表记录每个字符在子字符串中的出现次数与在 $\\\\textit{word}_2$ 中的出现次数之差,哈希表只记录在 $\\\\textit{word}_2$ 中出现的字符,如果每个字符的出现次数之差都非负则子字符串是覆盖子字符串,否则子字符串不是覆盖子字符串。
\\n可以使用变长滑动窗口寻找字符串 $\\\\textit{word}_1$ 中的以每个下标作为结束下标的最小覆盖子字符串。用 $[\\\\textit{start}, \\\\textit{end}]$ 表示滑动窗口,初始时 $\\\\textit{start} = \\\\textit{end} = 0$。将滑动窗口的右端点 $\\\\textit{end}$ 向右移动,移动过程中维护滑动窗口的左端点 $\\\\textit{start}$,对于每个 $\\\\textit{end}$ 寻找最小覆盖子字符串。每次移动滑动窗口的一个端点之后,都会有一个字符移入滑动窗口或移出滑动窗口,使用变化的字符更新哈希表,然后判断滑动窗口中的子字符串是否为覆盖子字符串。
\\n具体做法是,首先遍历字符串 $\\\\textit{word}_2$,将 $\\\\textit{word}_2$ 中的每个字符在哈希表中的出现次数减少 $1$,然后使用滑动窗口遍历字符串 $\\\\textit{word}_1$,对于每个右端点 $\\\\textit{end}$,执行如下操作。
\\n如果 $\\\\textit{word}_1[\\\\textit{end}]$ 在哈希表中,则将该字符在哈希表中的次数增加 $1$。
\\n如果滑动窗口 $[\\\\textit{start}, \\\\textit{end}]$ 中的子字符串是覆盖子字符串,执行如下操作,直到滑动窗口 $[\\\\textit{start}, \\\\textit{end}]$ 中的子字符串不是覆盖子字符串。
\\n如果 $\\\\textit{word}_1[\\\\textit{start}]$ 在哈希表中,则将该字符在哈希表中的次数减少 $1$。
\\n将 $\\\\textit{start}$ 向右移动一位。
\\n此时的滑动窗口 $[\\\\textit{start}, \\\\textit{end}]$ 中的子字符串为以 $\\\\textit{end}$ 作为结束下标的最大非覆盖子字符串。如果 $\\\\textit{start} > 0$,则以 $\\\\textit{end}$ 作为结束下标的最小覆盖子字符串为下标范围 $[\\\\textit{start} - 1, \\\\textit{end}]$ 的子字符串。因此以 $\\\\textit{end}$ 作为结束下标的覆盖子字符串的起始下标范围是 $[0, \\\\textit{start} - 1]$,数目是 $\\\\textit{start}$,当 $\\\\textit{start} = 0$ 时也适用。
\\n遍历结束之后,即可得到字符串 $\\\\textit{word}_1$ 中的覆盖子字符串数目。
\\n###Java
\\nclass Solution {\\n public long validSubstringCount(String word1, String word2) {\\n long validCount = 0;\\n Map<Character, Integer> counts = new HashMap<Character, Integer>();\\n int m = word1.length(), n = word2.length();\\n for (int i = 0; i < n; i++) {\\n char c = word2.charAt(i);\\n counts.put(c, counts.getOrDefault(c, 0) - 1);\\n }\\n int total = counts.size();\\n int meets = 0;\\n int start = 0, end = 0;\\n while (end < m) {\\n char curr = word1.charAt(end);\\n if (counts.containsKey(curr)) {\\n counts.put(curr, counts.get(curr) + 1);\\n if (counts.get(curr) == 0) {\\n meets++;\\n }\\n }\\n while (meets == total) {\\n char prev = word1.charAt(start);\\n if (counts.containsKey(prev)) {\\n counts.put(prev, counts.get(prev) - 1);\\n if (counts.get(prev) < 0) {\\n meets--;\\n }\\n }\\n start++;\\n }\\n validCount += start;\\n end++;\\n }\\n return validCount;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long ValidSubstringCount(string word1, string word2) {\\n long validCount = 0;\\n IDictionary<char, int> counts = new Dictionary<char, int>();\\n int m = word1.Length;\\n foreach (char c in word2) {\\n counts.TryAdd(c, 0);\\n counts[c]--;\\n }\\n int total = counts.Count;\\n int meets = 0;\\n int start = 0, end = 0;\\n while (end < m) {\\n char curr = word1[end];\\n if (counts.ContainsKey(curr)) {\\n counts[curr]++;\\n if (counts[curr] == 0) {\\n meets++;\\n }\\n }\\n while (meets == total) {\\n char prev = word1[start];\\n if (counts.ContainsKey(prev)) {\\n counts[prev]--;\\n if (counts[prev] < 0) {\\n meets--;\\n }\\n }\\n start++;\\n }\\n validCount += start;\\n end++;\\n }\\n return validCount;\\n }\\n}\\n
\\n时间复杂度:$O(m + n)$,其中 $m$ 和 $n$ 分别是字符串 $\\\\textit{word}_1$ 和 $\\\\textit{word}_2$ 的长度。需要首先遍历字符串 $\\\\textit{word}_2$ 计算每个字符的次数,然后使用滑动窗口遍历字符串 $\\\\textit{word}_1$,滑动窗口的左右端点最多各遍历字符串 $\\\\textit{word}_1$ 一次,每次移动之后判断当前子字符串是否为覆盖子字符串的时间是 $O(1)$,因此时间复杂度是 $O(m + n)$。
\\n空间复杂度:$O(\\\\min(n, |\\\\Sigma|))$,其中 $n$ 是字符串 $\\\\textit{word}_2$ 的长度,$\\\\Sigma$ 是字符集,这道题中 $\\\\Sigma$ 是全部小写英语字母,$|\\\\Sigma| = 26$。哈希表的空间是 $O(\\\\min(n, |\\\\Sigma|))$。
\\n本题和周赛第四题是一样的,请看 我的题解。
\\n","description":"本题和周赛第四题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-i//solution/on-xian-xing-zuo-fa-pythonjavacgo-by-end-o4ft","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-22T04:04:03.869Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针 + 计数","url":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii//solution/shuang-zhi-zhen-ji-shu-by-mipha-2022-vqcx","content":"双指针 + 计数","description":"双指针 + 计数","guid":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii//solution/shuang-zhi-zhen-ji-shu-by-mipha-2022-vqcx","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-22T04:03:14.253Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 滑动窗口求个数(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii//solution/on-hua-dong-chuang-kou-qiu-ge-shu-python-0x7a","content":"下文把 $\\\\textit{word}_1$ 和 $\\\\textit{word}_2$ 简称为 $s$ 和 $t$。
\\n由于子串可以重排,只要子串可以涵盖(见 76 题)字符串 $t$,那么子串就可以通过重排,使得 $t$ 是子串的前缀。
\\n所以本题是 76. 最小覆盖子串 的求个数版本,做法都是滑动窗口,请看 我的题解。
\\n滑动窗口的内层循环结束时,右端点固定在 $\\\\textit{right}$,左端点在 $0,1,2,\\\\ldots,\\\\textit{left}-1$ 的所有子串都是合法的,这一共有 $\\\\textit{left}$ 个,把 $\\\\textit{left}$ 加入答案。
\\n具体请看 视频讲解 第三+四题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def validSubstringCount(self, s: str, t: str) -> int:\\n if len(s) < len(t):\\n return 0\\n\\n # t 的字母出现次数与 s 的字母出现次数之差\\n diff = defaultdict(int) # 也可以用 Counter(t),但是会慢很多\\n for c in t:\\n diff[c] += 1\\n\\n # 窗口内有 less 个字母的出现次数比 t 的少\\n less = len(diff)\\n\\n ans = left = 0\\n for c in s:\\n diff[c] -= 1\\n if diff[c] == 0:\\n # c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\n less -= 1\\n while less == 0: # 窗口符合要求\\n if diff[s[left]] == 0:\\n # s[left] 移出窗口之前,检查出现次数,\\n # 如果窗口内 s[left] 的出现次数和 t 的一样,\\n # 那么 s[left] 移出窗口后,窗口内 s[left] 的出现次数比 t 的少\\n less += 1\\n diff[s[left]] += 1\\n left += 1\\n ans += left\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long validSubstringCount(String S, String T) {\\n if (S.length() < T.length()) {\\n return 0;\\n }\\n\\n char[] s = S.toCharArray();\\n char[] t = T.toCharArray();\\n int[] diff = new int[26]; // t 的字母出现次数与 s 的字母出现次数之差\\n for (char c : t) {\\n diff[c - \'a\']++;\\n }\\n\\n // 统计窗口内有多少个字母的出现次数比 t 的少\\n int less = 0;\\n for (int d : diff) {\\n if (d > 0) {\\n less++;\\n }\\n }\\n\\n long ans = 0;\\n int left = 0;\\n for (char c : s) {\\n diff[c - \'a\']--;\\n if (diff[c - \'a\'] == 0) {\\n // c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\n less--;\\n }\\n while (less == 0) { // 窗口符合要求\\n char outChar = s[left++]; // 准备移出窗口的字母\\n if (diff[outChar - \'a\'] == 0) {\\n // outChar 移出窗口之前检查出现次数,\\n // 如果窗口内 outChar 的出现次数和 t 的一样,\\n // 那么 outChar 移出窗口后,窗口内 outChar 的出现次数比 t 的少\\n less++;\\n }\\n diff[outChar - \'a\']++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long validSubstringCount(string s, string t) {\\n if (s.length() < t.length()) {\\n return 0;\\n }\\n\\n int diff[26]{}; // t 的字母出现次数与 s 的字母出现次数之差\\n for (char c : t) {\\n diff[c - \'a\']++;\\n }\\n\\n // 统计窗口内有多少个字母的出现次数比 t 的少\\n int less = 0;\\n for (int d : diff) {\\n if (d > 0) {\\n less++;\\n }\\n }\\n\\n long long ans = 0;\\n int left = 0;\\n for (char c : s) {\\n diff[c - \'a\']--;\\n if (diff[c - \'a\'] == 0) {\\n // c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\n less--;\\n }\\n while (less == 0) { // 窗口符合要求\\n char out_char = s[left++] - \'a\'; // 准备移出窗口的字母\\n if (diff[out_char] == 0) {\\n // out_char 移出窗口之前,检查出现次数,\\n // 如果窗口内 out_char 的出现次数和 t 的一样,\\n // 那么 out_char 移出窗口后,窗口内 out_char 的出现次数比 t 的少\\n less++;\\n }\\n diff[out_char]++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nlong long validSubstringCount(char* s, char* t) {\\n int diff[26] = {}; // t 的字母出现次数与 s 的字母出现次数之差\\n for (int i = 0; t[i]; i++) {\\n diff[t[i] - \'a\']++;\\n }\\n\\n // 统计窗口内有多少个字母的出现次数比 t 的少\\n int less = 0;\\n for (int i = 0; i < 26; i++) {\\n if (diff[i] > 0) {\\n less++;\\n }\\n }\\n\\n long long ans = 0;\\n int left = 0;\\n for (int i = 0; s[i]; i++) {\\n diff[s[i] - \'a\']--;\\n if (diff[s[i] - \'a\'] == 0) {\\n // s[i] 移入窗口后,窗口内 s[i] 的出现次数和 t 的一样\\n less--;\\n }\\n while (less == 0) { // 窗口符合要求\\n char out_char = s[left++] - \'a\'; // 准备移出窗口的字母\\n if (diff[out_char] == 0) {\\n // out_char 移出窗口之前,检查出现次数,\\n // 如果窗口内 out_char 的出现次数和 t 的一样,\\n // 那么 out_char 移出窗口后,窗口内 out_char 的出现次数比 t 的少\\n less++;\\n }\\n diff[out_char]++;\\n }\\n ans += left;\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc validSubstringCount(s, t string) (ans int64) {\\nif len(s) < len(t) {\\nreturn 0\\n}\\n\\ndiff := [26]int{} // t 的字母出现次数与 s 的字母出现次数之差\\nfor _, c := range t {\\ndiff[c-\'a\']++\\n}\\n\\n// 统计窗口内有多少个字母的出现次数比 t 的少\\nless := 0\\nfor _, d := range diff {\\nif d > 0 {\\nless++\\n}\\n}\\n\\nleft := 0\\nfor _, c := range s {\\ndiff[c-\'a\']--\\nif diff[c-\'a\'] == 0 {\\n// c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\nless--\\n}\\nfor less == 0 { // 窗口符合要求\\nif diff[s[left]-\'a\'] == 0 {\\n // s[left] 移出窗口之前,检查出现次数,\\n // 如果窗口内 s[left] 的出现次数和 t 的一样,\\n // 那么 s[left] 移出窗口后,窗口内 s[left] 的出现次数比 t 的少\\nless++\\n}\\ndiff[s[left]-\'a\']++\\nleft++\\n}\\nans += int64(left)\\n}\\nreturn\\n}\\n
\\n###js
\\nvar validSubstringCount = function(s, t) {\\n if (s.length < t.length) {\\n return 0;\\n }\\n\\n const diff = Array(26).fill(0); // t 的字母出现次数与 s 的字母出现次数之差\\n for (const c of t) {\\n diff[c.charCodeAt(0) - \'a\'.charCodeAt(0)]++;\\n }\\n\\n // 统计窗口内有多少个字母的出现次数比 t 的少\\n let less = 0;\\n for (const d of diff) {\\n if (d > 0) {\\n less++;\\n }\\n }\\n\\n let ans = 0;\\n let left = 0;\\n for (const ch of s) {\\n const c = ch.charCodeAt(0) - \'a\'.charCodeAt(0);\\n diff[c]--;\\n if (diff[c] === 0) {\\n // c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\n less--;\\n }\\n while (less === 0) { // 窗口符合要求\\n const outChar = s[left++].charCodeAt(0) - \'a\'.charCodeAt(0);\\n if (diff[outChar] === 0) {\\n // outChar 移出窗口之前,检查出现次数,\\n // 如果窗口内 outChar 的出现次数和 t 的一样,\\n // 那么 outChar 移出窗口后,窗口内 outChar 的出现次数比 t 的少\\n less++;\\n }\\n diff[outChar]++;\\n }\\n ans += left;\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn valid_substring_count(s: String, t: String) -> i64 {\\n if s.len() < t.len() {\\n return 0;\\n }\\n\\n let mut diff = vec![0; 26]; // t 的字母出现次数与 s 的字母出现次数之差\\n for c in t.bytes() {\\n diff[(c - b\'a\') as usize] += 1;\\n }\\n\\n // 统计窗口内有多少个字母的出现次数比 t 的少\\n let mut less = diff.iter().filter(|&&d| d > 0).count() as i32;\\n\\n let mut ans = 0;\\n let mut left = 0;\\n let s = s.as_bytes();\\n for c in s {\\n let c = (c - b\'a\') as usize;\\n diff[c] -= 1;\\n if diff[c] == 0 {\\n // c 移入窗口后,窗口内 c 的出现次数和 t 的一样\\n less -= 1;\\n }\\n while less == 0 { // 窗口符合要求\\n let out_char = (s[left] - b\'a\') as usize; // 准备移出窗口的字母\\n if diff[out_char] == 0 {\\n // out_char 移出窗口之前,检查出现次数,\\n // 如果窗口内 out_char 的出现次数和 t 的一样,\\n // 那么 out_char 移出窗口后,窗口内 out_char 的出现次数比 t 的少\\n less += 1;\\n }\\n diff[out_char] += 1;\\n left += 1;\\n }\\n ans += left;\\n }\\n ans as _\\n }\\n}\\n
\\n更多相似题目,见下面滑动窗口题单中的「§2.3 求子数组个数」,例如 2962. 统计最大元素出现至少 K 次的子数组 等。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"下文把 $\\\\textit{word}_1$ 和 $\\\\textit{word}_2$ 简称为 $s$ 和 $t$。 由于子串可以重排,只要子串可以涵盖(见 76 题)字符串 $t$,那么子串就可以通过重排,使得 $t$ 是子串的前缀。\\n\\n所以本题是 76. 最小覆盖子串 的求个数版本,做法都是滑动窗口,请看 我的题解。\\n\\n滑动窗口的内层循环结束时,右端点固定在 $\\\\textit{right}$,左端点在 $0,1,2,\\\\ldots,\\\\textit{left}-1$ 的所有子串都是合法的,这一共有 $\\\\textit{left}$ 个,把 $\\\\textit…","guid":"https://leetcode.cn/problems/count-substrings-that-can-be-rearranged-to-contain-a-string-ii//solution/on-hua-dong-chuang-kou-qiu-ge-shu-python-0x7a","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-22T04:03:04.468Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-找到小镇的法官🟢","url":"https://leetcode.cn/problems/find-the-town-judge/","content":"小镇里有 n
个人,按从 1
到 n
的顺序编号。传言称,这些人中有一个暗地里是小镇法官。
如果小镇法官真的存在,那么:
\\n\\n给你一个数组 trust
,其中 trust[i] = [ai, bi]
表示编号为 ai
的人信任编号为 bi
的人。
如果小镇法官存在并且可以确定他的身份,请返回该法官的编号;否则,返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:n = 2, trust = [[1,2]]\\n输出:2\\n\\n\\n
示例 2:
\\n\\n输入:n = 3, trust = [[1,3],[2,3]]\\n输出:3\\n\\n\\n
示例 3:
\\n\\n输入:n = 3, trust = [[1,3],[2,3],[3,1]]\\n输出:-1\\n\\n \\n\\n
提示:
\\n\\n1 <= n <= 1000
0 <= trust.length <= 104
trust[i].length == 2
trust
中的所有trust[i] = [ai, bi]
互不相同ai != bi
1 <= ai, bi <= n
我们定义一个长度为 $n$ 的数组 $\\\\textit{cnt}$,其中 $\\\\textit{cnt}[i]$ 表示节点 $i$ 的边积分,初始时所有元素均为 $0$。定义一个答案变量 $\\\\textit{ans}$,初始时为 $0$。
\\n接下来,我们遍历数组 $\\\\textit{edges}$,对于每个节点 $i$,以及它的出边节点 $j$,我们更新 $\\\\textit{cnt}[j]$ 为 $\\\\textit{cnt}[j] + i$。如果 $\\\\textit{cnt}[\\\\textit{ans}] < \\\\textit{cnt}[j]$ 或者 $\\\\textit{cnt}[\\\\textit{ans}] = \\\\textit{cnt}[j]$ 且 $j < \\\\textit{ans}$,我们更新 $\\\\textit{ans}$ 为 $j$。
\\n最后,返回 $\\\\textit{ans}$ 即可。
\\n###python
\\nclass Solution:\\n def edgeScore(self, edges: List[int]) -> int:\\n ans = 0\\n cnt = [0] * len(edges)\\n for i, j in enumerate(edges):\\n cnt[j] += i\\n if cnt[ans] < cnt[j] or (cnt[ans] == cnt[j] and j < ans):\\n ans = j\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int edgeScore(int[] edges) {\\n int n = edges.length;\\n long[] cnt = new long[n];\\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n int j = edges[i];\\n cnt[j] += i;\\n if (cnt[ans] < cnt[j] || (cnt[ans] == cnt[j] && j < ans)) {\\n ans = j;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int edgeScore(vector<int>& edges) {\\n int n = edges.size();\\n vector<long long> cnt(n);\\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n int j = edges[i];\\n cnt[j] += i;\\n if (cnt[ans] < cnt[j] || (cnt[ans] == cnt[j] && j < ans)) {\\n ans = j;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc edgeScore(edges []int) (ans int) {\\ncnt := make([]int, len(edges))\\nfor i, j := range edges {\\ncnt[j] += i\\nif cnt[ans] < cnt[j] || (cnt[ans] == cnt[j] && j < ans) {\\nans = j\\n}\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction edgeScore(edges: number[]): number {\\n const n = edges.length;\\n const cnt: number[] = Array(n).fill(0);\\n let ans: number = 0;\\n for (let i = 0; i < n; ++i) {\\n const j = edges[i];\\n cnt[j] += i;\\n if (cnt[ans] < cnt[j] || (cnt[ans] === cnt[j] && j < ans)) {\\n ans = j;\\n }\\n }\\n return ans;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn edge_score(edges: Vec<i32>) -> i32 {\\n let n = edges.len();\\n let mut cnt = vec![0_i64; n];\\n let mut ans = 0;\\n\\n for (i, &j) in edges.iter().enumerate() {\\n let j = j as usize;\\n cnt[j] += i as i64;\\n if cnt[ans] < cnt[j] || (cnt[ans] == cnt[j] && j < ans) {\\n ans = j;\\n }\\n }\\n\\n ans as i32\\n }\\n}\\n
\\n时间复杂度 $O(n)$,空间复杂度 $O(n)$。其中 $n$ 为数组 $\\\\textit{edges}$ 的长度。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们定义一个长度为 $n$ 的数组 $\\\\textit{cnt}$,其中 $\\\\textit{cnt}[i]$ 表示节点 $i$ 的边积分,初始时所有元素均为 $0$。定义一个答案变量 $\\\\textit{ans}$,初始时为 $0$。\\n\\n接下来,我们遍历数组 $\\\\textit{edges}$,对于每个节点 $i$,以及它的出边节点 $j$,我们更新 $\\\\textit{cnt}[j]$ 为 $\\\\textit{cnt}[j] + i$。如果 $\\\\textit{cnt}[\\\\textit{ans}] < \\\\textit{cnt}[j]$ 或者…","guid":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/python3javacgotypescript-yi-ti-yi-jie-yi-660g","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-21T00:45:20.173Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-边积分最高的节点🟡","url":"https://leetcode.cn/problems/node-with-highest-edge-score/","content":"给你一个有向图,图中有 n
个节点,节点编号从 0
到 n - 1
,其中每个节点都 恰有一条 出边。
图由一个下标从 0 开始、长度为 n
的整数数组 edges
表示,其中 edges[i]
表示存在一条从节点 i
到节点 edges[i]
的 有向 边。
节点 i
的 边积分 定义为:所有存在一条指向节点 i
的边的节点的 编号 总和。
返回 边积分 最高的节点。如果多个节点的 边积分 相同,返回编号 最小 的那个。
\\n\\n\\n\\n
示例 1:
\\n输入:edges = [1,0,0,0,0,7,7,5]\\n输出:7\\n解释:\\n- 节点 1、2、3 和 4 都有指向节点 0 的边,节点 0 的边积分等于 1 + 2 + 3 + 4 = 10 。\\n- 节点 0 有一条指向节点 1 的边,节点 1 的边积分等于 0 。\\n- 节点 7 有一条指向节点 5 的边,节点 5 的边积分等于 7 。\\n- 节点 5 和 6 都有指向节点 7 的边,节点 7 的边积分等于 5 + 6 = 11 。\\n节点 7 的边积分最高,所以返回 7 。\\n\\n\\n
示例 2:
\\n输入:edges = [2,0,0,2]\\n输出:0\\n解释:\\n- 节点 1 和 2 都有指向节点 0 的边,节点 0 的边积分等于 1 + 2 = 3 。\\n- 节点 0 和 3 都有指向节点 2 的边,节点 2 的边积分等于 0 + 3 = 3 。\\n节点 0 和 2 的边积分都是 3 。由于节点 0 的编号更小,返回 0 。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == edges.length
2 <= n <= 105
0 <= edges[i] < n
edges[i] != i
如果一个正整数每一个数位都是 互不相同 的,我们称它是 特殊整数 。
\\n\\n给你一个 正 整数 n
,请你返回区间 [1, n]
之间特殊整数的数目。
\\n\\n
示例 1:
\\n\\n输入:n = 20\\n输出:19\\n解释:1 到 20 之间所有整数除了 11 以外都是特殊整数。所以总共有 19 个特殊整数。\\n\\n\\n
示例 2:
\\n\\n输入:n = 5\\n输出:5\\n解释:1 到 5 所有整数都是特殊整数。\\n\\n\\n
示例 3:
\\n\\n输入:n = 135\\n输出:110\\n解释:从 1 到 135 总共有 110 个整数是特殊整数。\\n不特殊的部分数字为:22 ,114 和 131 。\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 2 * 109
我们可以遍历字符串 $s$,用一个变量 $\\\\textit{ans}$ 记录最长的字母序连续子字符串的长度,用另一个变量 $\\\\textit{cnt}$ 记录当前连续子字符串的长度。初始时 $\\\\textit{ans} = \\\\textit{cnt} = 1$。
\\n接下来,我们从下标为 $1$ 的字符开始遍历字符串 $s$,对于每个字符 $s[i]$,如果 $s[i] - s[i - 1] = 1$,则说明当前字符和前一个字符是连续的,此时 $\\\\textit{cnt} = \\\\textit{cnt} + 1$,并更新 $\\\\textit{ans} = \\\\max(\\\\textit{ans}, \\\\textit{cnt})$;否则,说明当前字符和前一个字符不连续,此时 $\\\\textit{cnt} = 1$。
\\n最终返回 $\\\\textit{ans}$ 即可。
\\n###python
\\nclass Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n ans = cnt = 1\\n for x, y in pairwise(map(ord, s)):\\n if y - x == 1:\\n cnt += 1\\n ans = max(ans, cnt)\\n else:\\n cnt = 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int longestContinuousSubstring(String s) {\\n int ans = 1, cnt = 1;\\n for (int i = 1; i < s.length(); ++i) {\\n if (s.charAt(i) - s.charAt(i - 1) == 1) {\\n ans = Math.max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int longestContinuousSubstring(string s) {\\n int ans = 1, cnt = 1;\\n for (int i = 1; i < s.size(); ++i) {\\n if (s[i] - s[i - 1] == 1) {\\n ans = max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc longestContinuousSubstring(s string) int {\\nans, cnt := 1, 1\\nfor i := range s[1:] {\\nif s[i+1]-s[i] == 1 {\\ncnt++\\nans = max(ans, cnt)\\n} else {\\ncnt = 1\\n}\\n}\\nreturn ans\\n}\\n
\\n###ts
\\nfunction longestContinuousSubstring(s: string): number {\\n let [ans, cnt] = [1, 1];\\n for (let i = 1; i < s.length; ++i) {\\n if (s.charCodeAt(i) - s.charCodeAt(i - 1) === 1) {\\n ans = Math.max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn longest_continuous_substring(s: String) -> i32 {\\n let mut ans = 1;\\n let mut cnt = 1;\\n let s = s.as_bytes();\\n for i in 1..s.len() {\\n if s[i] - s[i - 1] == 1 {\\n cnt += 1;\\n ans = ans.max(cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n###c
\\n#define max(a, b) (((a) > (b)) ? (a) : (b))\\n\\nint longestContinuousSubstring(char* s) {\\n int n = strlen(s);\\n int ans = 1, cnt = 1;\\n for (int i = 1; i < n; ++i) {\\n if (s[i] - s[i - 1] == 1) {\\n ++cnt;\\n ans = max(ans, cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n)$,其中 $n$ 为字符串 $s$ 的长度。空间复杂度 $O(1)$。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:一次遍历 我们可以遍历字符串 $s$,用一个变量 $\\\\textit{ans}$ 记录最长的字母序连续子字符串的长度,用另一个变量 $\\\\textit{cnt}$ 记录当前连续子字符串的长度。初始时 $\\\\textit{ans} = \\\\textit{cnt} = 1$。\\n\\n接下来,我们从下标为 $1$ 的字符开始遍历字符串 $s$,对于每个字符 $s[i]$,如果 $s[i] - s[i - 1] = 1$,则说明当前字符和前一个字符是连续的,此时 $\\\\textit{cnt} = \\\\textit{cnt} + 1$,并更新 $\\\\textit{ans} =…","guid":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/python3javacgotypescript-yi-ti-yi-jie-yi-em1b","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-19T00:31:15.210Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-最长的字母序连续子字符串的长度🟡","url":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring/","content":"字母序连续字符串 是由字母表中连续字母组成的字符串。换句话说,字符串 \\"abcdefghijklmnopqrstuvwxyz\\"
的任意子字符串都是 字母序连续字符串 。
\\"abc\\"
是一个字母序连续字符串,而 \\"acb\\"
和 \\"za\\"
不是。给你一个仅由小写英文字母组成的字符串 s
,返回其 最长 的 字母序连续子字符串 的长度。
\\n\\n
示例 1:
\\n\\n输入:s = \\"abacaba\\"\\n输出:2\\n解释:共有 4 个不同的字母序连续子字符串 \\"a\\"、\\"b\\"、\\"c\\" 和 \\"ab\\" 。\\n\\"ab\\" 是最长的字母序连续子字符串。\\n\\n\\n
示例 2:
\\n\\n输入:s = \\"abcde\\"\\n输出:5\\n解释:\\"abcde\\" 是最长的字母序连续子字符串。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s
由小写英文字母组成当汽车行驶到第 $i$ 个加油站时,视作获取了一个装有 $\\\\textit{fuel}_i$ 升汽油的油桶。
\\n在后续的行驶过程中,可以在没油时,把油桶中的油加到汽车中。
\\n选哪个(哪些)油桶?
\\n为了让加油次数尽量少,贪心地选油量多的油桶。
\\n由于有添加和删除操作,用最大堆维护这些油桶。
\\n可以把终点 $\\\\textit{target}$ 视作一个虚拟加油站,加到 $\\\\textit{stations}$ 末尾,这样可以用同样的代码处理最后一段路程(从最后一个加油站到终点)。
\\n可以提前判断 $\\\\textit{startFuel}\\\\ge \\\\textit{target}$ 的情况,油量足以把车开到终点,直接返回 $0$。或者,在循环中判断 $\\\\textit{curFuel}\\\\ge \\\\textit{target} - \\\\textit{position}_i$ 的情况,此时可以退出循环。不过考虑到对实际运行时间没有影响,代码中没有写这些优化。
\\nclass Solution:\\n def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:\\n stations.append((target, 0))\\n ans = pre_position = 0\\n cur_fuel = startFuel\\n fuel_heap = [] # 下面把堆中元素取反,当作最大堆用\\n for position, fuel in stations:\\n cur_fuel -= position - pre_position # 每行驶 1 英里用掉 1 升汽油\\n while fuel_heap and cur_fuel < 0: # 没油了\\n cur_fuel -= heappop(fuel_heap) # 选油量最多的油桶\\n ans += 1\\n if cur_fuel < 0: # 无法到达\\n return -1\\n heappush(fuel_heap, -fuel) # 留着后面加油\\n pre_position = position\\n return ans\\n
\\nclass Solution {\\n public int minRefuelStops(int target, int startFuel, int[][] stations) {\\n int n = stations.length;\\n int ans = 0;\\n int prePosition = 0;\\n int curFuel = startFuel;\\n PriorityQueue<Integer> fuelHeap = new PriorityQueue<>((a, b) -> b - a); // 最大堆\\n for (int i = 0; i <= n; i++) {\\n int position = i < n ? stations[i][0] : target;\\n curFuel -= position - prePosition; // 每行驶 1 英里用掉 1 升汽油\\n while (!fuelHeap.isEmpty() && curFuel < 0) { // 没油了\\n curFuel += fuelHeap.poll(); // 选油量最多的油桶\\n ans++;\\n }\\n if (curFuel < 0) { // 无法到达\\n return -1;\\n }\\n fuelHeap.offer(i < n ? stations[i][1] : 0); // 留着后面加油\\n prePosition = position;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {\\n stations.push_back({target, 0});\\n int ans = 0, pre_position = 0, cur_fuel = startFuel;\\n priority_queue<int> fuel_heap;\\n for (auto& station : stations) {\\n int position = station[0];\\n cur_fuel -= position - pre_position; // 每行驶 1 英里用掉 1 升汽油\\n while (!fuel_heap.empty() && cur_fuel < 0) { // 没油了\\n cur_fuel += fuel_heap.top(); // 选油量最多的油桶\\n fuel_heap.pop();\\n ans++;\\n }\\n if (cur_fuel < 0) { // 无法到达\\n return -1;\\n }\\n fuel_heap.push(station[1]); // 留着后面加油\\n pre_position = position;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc minRefuelStops(target, startFuel int, stations [][]int) (ans int) {\\n stations = append(stations, []int{target, 0})\\n prePosition, curFuel := 0, startFuel\\n fuelHeap := &hp{}\\n for _, station := range stations {\\n position, fuel := station[0], station[1]\\n curFuel -= position - prePosition // 每行驶 1 英里用掉 1 升汽油\\n for fuelHeap.Len() > 0 && curFuel < 0 { // 没油了\\n curFuel += heap.Pop(fuelHeap).(int) // 选油量最多的油桶\\n ans++\\n }\\n if curFuel < 0 { // 无法到达\\n return -1\\n }\\n heap.Push(fuelHeap, fuel) // 留着后面加油\\n prePosition = position\\n }\\n return\\n}\\n\\ntype hp struct{ sort.IntSlice }\\nfunc (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] } // 最大堆\\nfunc (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }\\nfunc (h *hp) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }\\n
\\nvar minRefuelStops = function(target, startFuel, stations) {\\n stations.push([target, 0]);\\n let ans = 0, prePosition = 0, curFuel = startFuel;\\n const fuelHeap = new MaxPriorityQueue();\\n for (const [position, fuel] of stations) {\\n curFuel -= position - prePosition; // 每行驶 1 英里用掉 1 升汽油\\n while (!fuelHeap.isEmpty() && curFuel < 0) { // 没油了\\n curFuel += fuelHeap.dequeue().element; // 选油量最多的油桶\\n ans++;\\n }\\n if (curFuel < 0) { // 无法到达\\n return -1;\\n }\\n fuelHeap.enqueue(fuel); // 留着后面加油\\n prePosition = position;\\n }\\n return ans;\\n};\\n
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn min_refuel_stops(target: i32, start_fuel: i32, mut stations: Vec<Vec<i32>>) -> i32 {\\n stations.push(vec![target, 0]);\\n let mut ans = 0;\\n let mut pre_position = 0;\\n let mut cur_fuel = start_fuel; // 当前油量\\n let mut fuel_heap = BinaryHeap::new(); // 最大堆\\n for station in stations {\\n let position = station[0];\\n cur_fuel -= position - pre_position; // 每行驶 1 英里用掉 1 升汽油\\n while cur_fuel < 0 && !fuel_heap.is_empty() { // 没油了\\n cur_fuel += fuel_heap.pop().unwrap(); // 选油量最多的油桶\\n ans += 1;\\n }\\n if cur_fuel < 0 { // 无法到达\\n return -1;\\n }\\n fuel_heap.push(station[1]); // 留着后面加油\\n pre_position = position;\\n }\\n ans\\n }\\n}\\n
\\n定义 $\\\\textit{miles}$ 表示可以行驶的距离,初始值为 $\\\\textit{startFuel}$。
\\n如果循环中发现 $\\\\textit{miles} < \\\\textit{position}_i$,就从把油桶中的油加到 $\\\\textit{miles}$ 中,直到 $\\\\textit{miles} \\\\ge \\\\textit{position}_i$。
\\nclass Solution:\\n def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:\\n stations.append((target, 0))\\n ans, miles = 0, startFuel\\n fuel_heap = [] # 下面把堆中元素取反,当作最大堆用\\n for position, fuel in stations:\\n while fuel_heap and miles < position: # 没有足够的油到达 position\\n miles -= heappop(fuel_heap) # 选油量最多的油桶\\n ans += 1\\n if miles < position: # 无法到达\\n return -1\\n heappush(fuel_heap, -fuel) # 留着后面加油\\n return ans\\n
\\nclass Solution {\\n public int minRefuelStops(int target, int startFuel, int[][] stations) {\\n int n = stations.length;\\n int ans = 0;\\n int miles = startFuel;\\n PriorityQueue<Integer> fuelHeap = new PriorityQueue<>((a, b) -> b - a); // 最大堆\\n for (int i = 0; i <= n; i++) {\\n int position = i < n ? stations[i][0] : target;\\n while (!fuelHeap.isEmpty() && miles < position) { // 没有足够的油到达 position\\n miles += fuelHeap.poll(); // 选油量最多的油桶\\n ans++;\\n }\\n if (miles < position) { // 无法到达\\n return -1;\\n }\\n fuelHeap.offer(i < n ? stations[i][1] : 0); // 留着后面加油\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {\\n stations.push_back({target, 0});\\n int ans = 0, miles = startFuel;\\n priority_queue<int> fuel_heap;\\n for (auto& station : stations) {\\n int position = station[0];\\n while (!fuel_heap.empty() && miles < position) { // 没有足够的油到达 position\\n miles += fuel_heap.top(); // 选油量最多的油桶\\n fuel_heap.pop();\\n ans++;\\n }\\n if (miles < position) { // 无法到达\\n return -1;\\n }\\n fuel_heap.push(station[1]); // 留着后面加油\\n }\\n return ans;\\n }\\n};\\n
\\nfunc minRefuelStops(target, startFuel int, stations [][]int) (ans int) {\\n stations = append(stations, []int{target, 0})\\n miles := startFuel\\n fuelHeap := &hp{}\\n for _, station := range stations {\\n position, fuel := station[0], station[1]\\n for fuelHeap.Len() > 0 && miles < position { // 没有足够的油到达 position\\n miles += heap.Pop(fuelHeap).(int) // 选油量最多的油桶\\n ans++\\n }\\n if miles < position { // 无法到达\\n return -1\\n }\\n heap.Push(fuelHeap, fuel) // 留着后面加油\\n }\\n return\\n}\\n\\ntype hp struct{ sort.IntSlice }\\nfunc (h hp) Less(i, j int) bool { return h.IntSlice[i] > h.IntSlice[j] } // 最大堆\\nfunc (h *hp) Push(v any) { h.IntSlice = append(h.IntSlice, v.(int)) }\\nfunc (h *hp) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }\\n
\\nvar minRefuelStops = function(target, startFuel, stations) {\\n stations.push([target, 0]);\\n let ans = 0, miles = startFuel;\\n const fuelHeap = new MaxPriorityQueue();\\n for (const [position, fuel] of stations) {\\n while (!fuelHeap.isEmpty() && miles < position) { // 没有足够的油到达 position\\n miles += fuelHeap.dequeue().element; // 选油量最多的油桶\\n ans++;\\n }\\n if (miles < position) { // 无法到达\\n return -1;\\n }\\n fuelHeap.enqueue(fuel); // 留着后面加油\\n }\\n return ans;\\n};\\n
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn min_refuel_stops(target: i32, start_fuel: i32, mut stations: Vec<Vec<i32>>) -> i32 {\\n stations.push(vec![target, 0]);\\n let mut ans = 0;\\n let mut miles = start_fuel;\\n let mut fuel_heap = BinaryHeap::new();\\n for station in stations {\\n let position = station[0];\\n while !fuel_heap.is_empty() && miles < position { // 没有足够的油到达 position\\n miles += fuel_heap.pop().unwrap(); // 选油量最多的油桶\\n ans += 1;\\n }\\n if miles < position { // 无法到达\\n return -1;\\n }\\n fuel_heap.push(station[1]); // 留着后面加油\\n }\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面贪心题单中的「§1.9 反悔贪心」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 当汽车行驶到第 $i$ 个加油站时,视作获取了一个装有 $\\\\textit{fuel}_i$ 升汽油的油桶。\\n\\n在后续的行驶过程中,可以在没油时,把油桶中的油加到汽车中。\\n\\n选哪个(哪些)油桶?\\n\\n为了让加油次数尽量少,贪心地选油量多的油桶。\\n\\n由于有添加和删除操作,用最大堆维护这些油桶。\\n\\n细节\\n\\n可以把终点 $\\\\textit{target}$ 视作一个虚拟加油站,加到 $\\\\textit{stations}$ 末尾,这样可以用同样的代码处理最后一段路程(从最后一个加油站到终点)。\\n\\n可以提前判断 $\\\\textit{startFuel}\\\\ge \\\\textit…","guid":"https://leetcode.cn/problems/minimum-number-of-refueling-stops//solution/zui-da-dui-tan-xin-pythonjavacgojsrust-b-yldp","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-18T06:55:29.090Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-坐上公交的最晚时间🟡","url":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus/","content":"给你一个下标从 0 开始长度为 n
的整数数组 buses
,其中 buses[i]
表示第 i
辆公交车的出发时间。同时给你一个下标从 0 开始长度为 m
的整数数组 passengers
,其中 passengers[j]
表示第 j
位乘客的到达时间。所有公交车出发的时间互不相同,所有乘客到达的时间也互不相同。
给你一个整数 capacity
,表示每辆公交车 最多 能容纳的乘客数目。
每位乘客都会搭乘下一辆有座位的公交车。如果你在 y
时刻到达,公交在 x
时刻出发,满足 y <= x
且公交没有满,那么你可以搭乘这一辆公交。最早 到达的乘客优先上车。
返回你可以搭乘公交车的最晚到达公交站时间。你 不能 跟别的乘客同时刻到达。
\\n\\n注意:数组 buses
和 passengers
不一定是有序的。
\\n\\n
示例 1:
\\n\\n输入:buses = [10,20], passengers = [2,17,18,19], capacity = 2\\n输出:16\\n解释:\\n第 1 辆公交车载着第 1 位乘客。\\n第 2 辆公交车载着你和第 2 位乘客。\\n注意你不能跟其他乘客同一时间到达,所以你必须在第二位乘客之前到达。\\n\\n
示例 2:
\\n\\n输入:buses = [20,30,10], passengers = [19,13,26,4,25,11,21], capacity = 2\\n输出:20\\n解释:\\n第 1 辆公交车载着第 4 位乘客。\\n第 2 辆公交车载着第 6 位和第 2 位乘客。\\n第 3 辆公交车载着第 1 位乘客和你。\\n\\n\\n
\\n\\n
提示:
\\n\\nn == buses.length
m == passengers.length
1 <= n, m, capacity <= 105
2 <= buses[i], passengers[i] <= 109
buses
中的元素 互不相同 。passengers
中的元素 互不相同 。给你一个数组 routes
,表示一系列公交线路,其中每个 routes[i]
表示一条公交线路,第 i
辆公交车将会在上面循环行驶。
routes[0] = [1, 5, 7]
表示第 0
辆公交车会一直按序列 1 -> 5 -> 7 -> 1 -> 5 -> 7 -> 1 -> ...
这样的车站路线行驶。现在从 source
车站出发(初始时不在公交车上),要前往 target
车站。 期间仅可乘坐公交车。
求出 最少乘坐的公交车数量 。如果不可能到达终点车站,返回 -1
。
\\n\\n
示例 1:
\\n\\n输入:routes = [[1,2,7],[3,6,7]], source = 1, target = 6\\n输出:2\\n解释:最优策略是先乘坐第一辆公交车到达车站 7 , 然后换乘第二辆公交车到车站 6 。 \\n\\n\\n
示例 2:
\\n\\n输入:routes = [[7,12],[4,5,15],[6],[15,19],[9,12,13]], source = 15, target = 12\\n输出:-1\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= routes.length <= 500
.1 <= routes[i].length <= 105
routes[i]
中的所有值 互不相同sum(routes[i].length) <= 105
0 <= routes[i][j] < 106
0 <= source, target < 106
\\n\\nProblem: 3287. 求出数组中最大序列值
\\n
###Python3
\\nclass Solution:\\n def maxValue(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n mx = reduce(or_, nums)\\n prefix = [[[False] * (mx + 1) for i in range(n + 1)] for j in range(k + 1)]\\n ans = -inf\\n # 1. prefix or for k\\n for i in range(n):\\n prefix[0][i + 1] = prefix[0][i].copy()\\n prefix[0][i + 1][nums[i]] = True\\n for i in range(1, k):\\n for j in range(i, n - k + 1):\\n x = nums[j]\\n # unselect\\n prefix[i][j + 1] = prefix[i][j].copy()\\n # select\\n for h in range(0, mx + 1):\\n if prefix[i - 1][j][h]:\\n prefix[i][j + 1][h | x] = True\\n # print( f[i])\\n # 2. suffix or for k\\n suffix = [[[False] * (mx + 1) for i in range(n + 1)] for j in range(k + 1)]\\n for i in range(n - 1, -1, -1):\\n suffix[0][i] = suffix[0][i + 1].copy()\\n suffix[0][i][nums[i]] = True\\n\\n for i in range(1, k):\\n for j in range(n - 1, k - 1, -1):\\n x = nums[j]\\n # unselect\\n suffix[i][j] = suffix[i][j + 1].copy()\\n # select\\n for h in range(0, mx + 1):\\n if suffix[i - 1][j + 1][h]:\\n suffix[i][j][h | x] = True\\n\\n # 3. 枚举 所有 prefix xor suffix\\n ans = -inf\\n for i in range(k - 1, n - k + 1): # [0, i] [i+1, i+k]\\n pre = prefix[k - 1][i + 1]\\n for j in range(1, mx + 1):\\n if not pre[j]:\\n continue\\n post = suffix[k - 1][i + 1]\\n for h in range(1, mx + 1):\\n if post[h]:\\n ans = max(ans, j ^ h)\\n return ans\\n
\\n环形公交路线上有 n
个站,按次序从 0
到 n - 1
进行编号。我们已知每一对相邻公交站之间的距离,distance[i]
表示编号为 i
的车站和编号为 (i + 1) % n
的车站之间的距离。
环线上的公交车都可以按顺时针和逆时针的方向行驶。
\\n\\n返回乘客从出发点 start
到目的地 destination
之间的最短距离。
\\n\\n
示例 1:
\\n\\n输入:distance = [1,2,3,4], start = 0, destination = 1\\n输出:1\\n解释:公交站 0 和 1 之间的距离是 1 或 9,最小值是 1。\\n\\n
\\n\\n
示例 2:
\\n\\n输入:distance = [1,2,3,4], start = 0, destination = 2\\n输出:3\\n解释:公交站 0 和 2 之间的距离是 3 或 7,最小值是 3。\\n\\n\\n
\\n\\n
示例 3:
\\n\\n输入:distance = [1,2,3,4], start = 0, destination = 3\\n输出:4\\n解释:公交站 0 和 3 之间的距离是 6 或 4,最小值是 4。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= n <= 10^4
distance.length == n
0 <= start, destination < n
0 <= distance[i] <= 10^4
\\n\\nProblem: 3285. 找到稳定山的下标
\\n
[TOC]
\\n遍历比较
\\n执行用时分布4ms击败100.00%;消耗内存分布10.92MB击败100.00%
\\n###C
\\nint* stableMountains(int* height, int heightSize, int threshold, int* returnSize) {\\n int * ans = (int *)calloc(heightSize, sizeof(int)), i = 0; \\n for (* returnSize = 0, -- heightSize; i < heightSize; )\\n if (height[i ++] > threshold) \\n ans[(* returnSize) ++] = i;\\n return ans;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i + 1 for i, x in enumerate(height[ : - 1]) if x > threshold]\\n
\\n","description":"Problem: 3285. 找到稳定山的下标 [TOC]\\n\\n遍历比较\\n\\n执行用时分布4ms击败100.00%;消耗内存分布10.92MB击败100.00%\\n\\n###C\\n\\nint* stableMountains(int* height, int heightSize, int threshold, int* returnSize) {\\n int * ans = (int *)calloc(heightSize, sizeof(int)), i = 0; \\n for (* returnSize = 0, -- heightSize; i…","guid":"https://leetcode.cn/problems/find-indices-of-stable-mountains//solution/bian-li-bi-jiao-by-admiring-meninskyuli-a96q","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T06:24:10.134Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"三种方法:Z 函数 / 字符串哈希+二分 / AC 自动机(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/ac-zi-dong-ji-you-hua-dppythonjavacgo-by-49jt","content":"本题和周赛第四题是一样的,请看 我的题解。
\\n","description":"本题和周赛第四题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/ac-zi-dong-ji-you-hua-dppythonjavacgo-by-49jt","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T05:07:28.795Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"三种方法:Z 函数 / 字符串哈希 / AC 自动机(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/ac-zi-dong-ji-pythonjavacgo-by-endlessch-hcqk","content":"把 $\\\\textit{target}$ 划分成若干段,要求每一段都是某个 $\\\\textit{words}[i]$ 的前缀。
\\n返回最小划分成多少段。如果无法划分,返回 $-1$。
\\n示例 1 的 $\\\\textit{words}=[\\\\texttt{abc},\\\\texttt{aaaaa},\\\\texttt{bcdef}]$,$\\\\textit{target}=\\\\texttt{aabcdabc}$。
\\n要让划分的段数尽量小,那么每一段的长度要尽量长?
\\n虽然还不知道要怎么划分,但这启发我们考虑如下内容:
\\n注意 $\\\\textit{words}[i]$ 前缀的前缀还是 $\\\\textit{words}[i]$ 的前缀。$\\\\texttt{bcd}$ 是 $\\\\texttt{bcdef}$ 的前缀,意味着 $\\\\texttt{b}$ 和 $\\\\texttt{bc}$ 也是 $\\\\texttt{bcdef}$ 的前缀。如果从 $\\\\textit{target}[2]$ 开始的段,最长可以是 $3$,那么这个段的长度也可以是 $1$ 或者 $2$。
\\n如果我们算出了上面这些最长长度 $2,3,3,\\\\cdots$,那么问题就变成:
\\n示例 1 的 $\\\\textit{maxJumps} = [2, 3, 3, 0, 0, 3, 2, 0]$,跳法是 $0\\\\to 2\\\\to 5\\\\to 8$。
\\n现在剩下的问题是,如何计算 $\\\\textit{maxJumps}$ 数组?
\\n对于字符串 $s$,定义 $z[i]$ 表示后缀 $s[i:]$ 与 $s$ 的 LCP(最长公共前缀)长度,其中 $s[i:]$ 表示从 $s[i]$ 到 $s[n-1]$ 的子串。
\\n遍历 $\\\\textit{words}$,对于 $\\\\textit{word} =\\\\textit{words}[i]$,构造字符串
\\n$$
\\ns = \\\\textit{word} + \\\\texttt{#} + \\\\textit{target}
\\n$$
中间插入 $\\\\texttt{#}$ 目的是避免 $z[i]$ 超过 $\\\\textit{word}$ 的长度。
\\n计算 $s$ 的 $z$ 数组。设 $m$ 为 $\\\\textit{word}$ 的长度加一,那么 $\\\\textit{target}[i:]$ 与 $\\\\textit{word}$ 的最长公共前缀,就是 $z[m+i]$。用 $z[m+i]$ 更新 $\\\\textit{maxJumps}[i]$ 的最大值。
\\n###py
\\nclass Solution:\\n def calc_z(self, s: str) -> list[int]:\\n n = len(s)\\n z = [0] * n\\n box_l = box_r = 0 # z-box 左右边界(闭区间)\\n for i in range(1, n):\\n if i <= box_r:\\n z[i] = min(z[i - box_l], box_r - i + 1)\\n while i + z[i] < n and s[z[i]] == s[i + z[i]]:\\n box_l, box_r = i, i + z[i]\\n z[i] += 1\\n return z\\n\\n # 桥的概念,见我在 45 或 1326 题下的题解\\n def jump(self, max_jumps: List[int]) -> int:\\n ans = 0\\n cur_r = 0 # 已建造的桥的右端点\\n nxt_r = 0 # 下一座桥的右端点的最大值\\n for i, max_jump in enumerate(max_jumps): # 如果走到 n-1 时没有返回 -1,那么必然可以到达 n\\n nxt_r = max(nxt_r, i + max_jump)\\n if i == cur_r: # 到达已建造的桥的右端点\\n if i == nxt_r: # 无论怎么造桥,都无法从 i 到 i+1\\n return -1\\n cur_r = nxt_r # 造一座桥\\n ans += 1\\n return ans\\n\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n n = len(target)\\n max_jumps = [0] * n\\n for word in words:\\n z = self.calc_z(word + \\"#\\" + target)\\n m = len(word) + 1\\n for i in range(n):\\n max_jumps[i] = max(max_jumps[i], z[m + i])\\n return self.jump(max_jumps)\\n
\\n###py
\\nclass Solution:\\n def calc_z(self, s: str) -> list[int]:\\n n = len(s)\\n z = [0] * n\\n box_l = box_r = 0 # z-box 左右边界(闭区间)\\n for i in range(1, n):\\n if i <= box_r:\\n # 手动 min,加快速度\\n x = z[i - box_l]\\n y = box_r - i + 1\\n z[i] = x if x < y else y\\n while i + z[i] < n and s[z[i]] == s[i + z[i]]:\\n box_l, box_r = i, i + z[i]\\n z[i] += 1\\n return z\\n\\n # 桥的概念,见我在 45 或 1326 题下的题解\\n def jump(self, max_jumps: List[int]) -> int:\\n ans = 0\\n cur_r = 0 # 已建造的桥的右端点\\n nxt_r = 0 # 下一座桥的右端点的最大值\\n for i, max_jump in enumerate(max_jumps): # 如果走到 n-1 时没有返回 -1,那么必然可以到达 n\\n nxt_r = max(nxt_r, i + max_jump)\\n if i == cur_r: # 到达已建造的桥的右端点\\n if i == nxt_r: # 无论怎么造桥,都无法从 i 到 i+1\\n return -1\\n cur_r = nxt_r # 造一座桥\\n ans += 1\\n return ans\\n\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n n = len(target)\\n max_jumps = [0] * n\\n for word in words:\\n z = self.calc_z(word + \\"#\\" + target)\\n m = len(word) + 1\\n for i in range(n):\\n # 手动 max,加快速度\\n if z[m + i] > max_jumps[i]:\\n max_jumps[i] = z[m + i]\\n return self.jump(max_jumps)\\n
\\n###java
\\nclass Solution {\\n public int minValidStrings(String[] words, String target) {\\n int n = target.length();\\n int[] maxJumps = new int[n];\\n for (String word : words) {\\n int[] z = calcZ(word + \\"#\\" + target);\\n int m = word.length() + 1;\\n for (int i = 0; i < n; i++) {\\n maxJumps[i] = Math.max(maxJumps[i], z[m + i]);\\n }\\n }\\n return jump(maxJumps);\\n }\\n\\n private int[] calcZ(String S) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n int[] z = new int[n];\\n int boxL = 0;\\n int boxR = 0; // z-box 左右边界(闭区间)\\n for (int i = 1; i < n; i++) {\\n if (i <= boxR) {\\n z[i] = Math.min(z[i - boxL], boxR - i + 1);\\n }\\n while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {\\n boxL = i;\\n boxR = i + z[i];\\n z[i]++;\\n }\\n }\\n return z;\\n }\\n\\n // 桥的概念,见我在 45 或 1326 题下的题解\\n private int jump(int[] maxJumps) {\\n int ans = 0;\\n int curR = 0; // 已建造的桥的右端点\\n int nxtR = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < maxJumps.length; i++) {\\n nxtR = Math.max(nxtR, i + maxJumps[i]);\\n if (i == curR) { // 到达已建造的桥的右端点\\n if (i == nxtR) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n curR = nxtR; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n vector<int> calc_z(string s) {\\n int n = s.length();\\n vector<int> z(n);\\n int box_l = 0, box_r = 0; // z-box 左右边界(闭区间)\\n for (int i = 1; i < n; i++) {\\n if (i <= box_r) {\\n z[i] = min(z[i - box_l], box_r - i + 1);\\n }\\n while (i + z[i] < n && s[z[i]] == s[i + z[i]]) {\\n box_l = i;\\n box_r = i + z[i];\\n z[i]++;\\n }\\n }\\n return z;\\n }\\n\\n // 桥的概念,见我在 45 或 1326 题下的题解\\n int jump(vector<int>& max_jumps) {\\n int ans = 0;\\n int cur_r = 0; // 已建造的桥的右端点\\n int nxt_r = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < max_jumps.size(); i++) {\\n nxt_r = max(nxt_r, i + max_jumps[i]);\\n if (i == cur_r) { // 到达已建造的桥的右端点\\n if (i == nxt_r) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n cur_r = nxt_r; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n\\npublic:\\n int minValidStrings(vector<string>& words, string& target) {\\n int n = target.length();\\n vector<int> max_jumps(n);\\n for (auto& word : words) {\\n vector<int> z = calc_z(word + \\"#\\" + target);\\n int m = word.length() + 1;\\n for (int i = 0; i < n; i++) {\\n max_jumps[i] = max(max_jumps[i], z[m + i]);\\n }\\n }\\n return jump(max_jumps);\\n }\\n};\\n
\\n###go
\\nfunc calcZ(s string) []int {\\nn := len(s)\\nz := make([]int, n)\\nboxL, boxR := 0, 0 // z-box 左右边界(闭区间)\\nfor i := 1; i < n; i++ {\\nif i <= boxR {\\nz[i] = min(z[i-boxL], boxR-i+1)\\n}\\nfor i+z[i] < n && s[z[i]] == s[i+z[i]] {\\nboxL, boxR = i, i+z[i]\\nz[i]++\\n}\\n}\\nreturn z\\n}\\n\\n// 桥的概念,见我在 45 或 1326 题下的题解\\nfunc jump(maxJumps []int) (ans int) {\\ncurR := 0 // 已建造的桥的右端点\\nnxtR := 0 // 下一座桥的右端点的最大值\\nfor i, maxJump := range maxJumps {\\nnxtR = max(nxtR, i+maxJump)\\nif i == curR { // 到达已建造的桥的右端点\\nif i == nxtR { // 无论怎么造桥,都无法从 i 到 i+1\\nreturn -1\\n}\\ncurR = nxtR // 造一座桥\\nans++\\n}\\n}\\nreturn\\n}\\n\\nfunc minValidStrings(words []string, target string) int {\\nmaxJumps := make([]int, len(target))\\nfor _, word := range words {\\nz := calcZ(word + \\"#\\" + target)\\nfor i, z := range z[len(word)+1:] {\\nmaxJumps[i] = max(maxJumps[i], z)\\n}\\n}\\nreturn jump(maxJumps)\\n}\\n
\\n预处理每个 $\\\\textit{words}[i]$ 的每个前缀的字符串哈希值,按照前缀长度分组,保存到不同的集合中。每个集合保存的是相同前缀长度的哈希值。
\\n由于 $\\\\textit{words}$ 的长度至多为 $100$,所以每个集合至多保存 $100$ 个哈希值,根据生日攻击理论,单模哈希绰绰有余,碰撞概率很小。
\\n然后对于每个 $i$,二分求出 $\\\\textit{maxJumps}[i]$。
\\n二分的 $\\\\text{check}(\\\\textit{mid})$ 函数怎么写?判断从 $\\\\textit{target}[i]$ 开始的长为 $\\\\textit{mid}$ 的子串,哈希值是否在集合中。
\\n具体请看 本题视频讲解 第四题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n n = len(target)\\n\\n # 多项式字符串哈希(方便计算子串哈希值)\\n # 哈希函数 hash(s) = s[0] * BASE^(n-1) + s[1] * BASE^(n-2) + ... + s[n-2] * BASE + s[n-1]\\n MOD = 1_070_777_777\\n BASE = randint(8 * 10 ** 8, 9 * 10 ** 8) # 随机 BASE,防止 hack\\n pow_base = [1] + [0] * n # pow_base[i] = BASE^i\\n pre_hash = [0] * (n + 1) # 前缀哈希值 pre_hash[i] = hash(s[:i])\\n for i, b in enumerate(target):\\n pow_base[i + 1] = pow_base[i] * BASE % MOD\\n pre_hash[i + 1] = (pre_hash[i] * BASE + ord(b)) % MOD # 秦九韶算法计算多项式哈希\\n\\n # 计算子串 target[l:r] 的哈希值,注意这是左闭右开区间 [l,r)\\n # 计算方法类似前缀和\\n def sub_hash(l: int, r: int) -> int:\\n return (pre_hash[r] - pre_hash[l] * pow_base[r - l]) % MOD\\n\\n # 保存每个 words[i] 的每个前缀的哈希值,按照长度分组\\n max_len = max(map(len, words))\\n sets = [set() for _ in range(max_len)]\\n for w in words:\\n h = 0\\n for j, b in enumerate(w):\\n h = (h * BASE + ord(b)) % MOD\\n sets[j].add(h) # 注意 j 从 0 开始\\n\\n ans = 0\\n cur_r = 0 # 已建造的桥的右端点\\n nxt_r = 0 # 下一座桥的右端点的最大值\\n for i in range(n):\\n check = lambda j: sub_hash(i, i + j + 1) not in sets[j]\\n max_jump = bisect_left(range(min(n - i, max_len)), True, key=check)\\n nxt_r = max(nxt_r, i + max_jump)\\n if i == cur_r: # 到达已建造的桥的右端点\\n if i == nxt_r: # 无论怎么造桥,都无法从 i 到 i+1\\n return -1\\n cur_r = nxt_r # 建造下一座桥\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_070_777_777;\\n\\n public int minValidStrings(String[] words, String target) {\\n char[] t = target.toCharArray();\\n int n = t.length;\\n\\n // 多项式字符串哈希(方便计算子串哈希值)\\n // 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\n final int BASE = (int) 8e8 + new Random().nextInt((int) 1e8); // 随机 base,防止 hack\\n int[] powBase = new int[n + 1]; // powBase[i] = base^i\\n int[] preHash = new int[n + 1]; // 前缀哈希值 preHash[i] = hash(target[0] 到 target[i-1])\\n powBase[0] = 1;\\n for (int i = 0; i < n; i++) {\\n powBase[i + 1] = (int) ((long) powBase[i] * BASE % MOD);\\n preHash[i + 1] = (int) (((long) preHash[i] * BASE + t[i]) % MOD); // 秦九韶算法计算多项式哈希\\n }\\n\\n int maxLen = 0;\\n for (String w : words) {\\n maxLen = Math.max(maxLen, w.length());\\n }\\n Set<Integer>[] sets = new HashSet[maxLen];\\n Arrays.setAll(sets, i -> new HashSet<>());\\n for (String w : words) {\\n long h = 0;\\n for (int j = 0; j < w.length(); j++) {\\n h = (h * BASE + w.charAt(j)) % MOD;\\n sets[j].add((int) h); // 注意 j 从 0 开始\\n }\\n }\\n\\n int ans = 0;\\n int curR = 0; // 已建造的桥的右端点\\n int nxtR = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < n; i++) {\\n int maxJump = calcMaxJump(i, preHash, powBase, sets);\\n nxtR = Math.max(nxtR, i + maxJump);\\n if (i == curR) { // 到达已建造的桥的右端点\\n if (i == nxtR) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n curR = nxtR; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n\\n private int calcMaxJump(int i, int[] preHash, int[] powBase, Set<Integer>[] sets) {\\n // 开区间二分,left 一定满足要求,right 一定不满足要求\\n int left = 0;\\n int right = Math.min(preHash.length - 1 - i, sets.length) + 1;\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n long subHash = (((long) preHash[i + mid] - (long) preHash[i] * powBase[mid]) % MOD + MOD) % MOD;\\n if (sets[mid - 1].contains((int) subHash)) {\\n left = mid;\\n } else {\\n right = mid;\\n }\\n }\\n return left;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minValidStrings(const vector<string>& words, const string& target) {\\n int n = target.length();\\n\\n // 多项式字符串哈希(方便计算子串哈希值)\\n // 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\n const int MOD = 1\'070\'777\'777;\\n mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());\\n const int BASE = uniform_int_distribution<>(8e8, 9e8)(rng); // 随机 base,防止 hack\\n vector<int> pow_base(n + 1); // pow_base[i] = base^i\\n vector<int> pre_hash(n + 1); // 前缀哈希值 pre_hash[i] = hash(s[:i])\\n pow_base[0] = 1;\\n for (int i = 0; i < n; i++) {\\n pow_base[i + 1] = (long long) pow_base[i] * BASE % MOD;\\n pre_hash[i + 1] = ((long long) pre_hash[i] * BASE + target[i]) % MOD; // 秦九韶算法计算多项式哈希\\n }\\n // 计算 target[l] 到 target[r-1] 的哈希值\\n auto sub_hash = [&](int l, int r) {\\n return ((pre_hash[r] - (long long) pre_hash[l] * pow_base[r - l]) % MOD + MOD) % MOD;\\n };\\n\\n int max_len = 0;\\n for (auto& w : words) {\\n max_len = max(max_len, (int) w.length());\\n }\\n vector<unordered_set<int>> sets(max_len);\\n for (auto& w : words) {\\n long long h = 0;\\n for (int j = 0; j < w.size(); j++) {\\n h = (h * BASE + w[j]) % MOD;\\n sets[j].insert(h); // 注意 j 从 0 开始\\n }\\n }\\n\\n auto max_jump = [&](int i) -> int {\\n // 开区间二分,left 一定满足要求,right 一定不满足要求\\n int left = 0, right = min(n - i, max_len) + 1;\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n (sets[mid - 1].contains(sub_hash(i, i + mid)) ? left : right) = mid;\\n }\\n return left;\\n };\\n\\n int ans = 0;\\n int cur_r = 0; // 已建造的桥的右端点\\n int nxt_r = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < n; i++) {\\n nxt_r = max(nxt_r, i + max_jump(i));\\n if (i == cur_r) { // 到达已建造的桥的右端点\\n if (i == nxt_r) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n cur_r = nxt_r; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minValidStrings(words []string, target string) (ans int) {\\nn := len(target)\\n\\n// 多项式字符串哈希(方便计算子串哈希值)\\n// 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\nconst mod = 1_070_777_777\\nbase := 9e8 - rand.Intn(1e8) // 随机 base,防止 hack(注意 Go1.20 之后的版本,每次随机的数都不一样)\\npowBase := make([]int, n+1) // powBase[i] = base^i\\npreHash := make([]int, n+1) // 前缀哈希值 preHash[i] = hash(s[:i])\\npowBase[0] = 1\\nfor i, b := range target {\\npowBase[i+1] = powBase[i] * base % mod\\npreHash[i+1] = (preHash[i]*base + int(b)) % mod // 秦九韶算法计算多项式哈希\\n}\\n// 计算子串 target[l:r] 的哈希值,注意这是左闭右开区间 [l,r)\\n// 计算方法类似前缀和\\nsubHash := func(l, r int) int {\\nreturn ((preHash[r]-preHash[l]*powBase[r-l])%mod + mod) % mod\\n}\\n\\nmaxLen := 0\\nfor _, w := range words {\\nmaxLen = max(maxLen, len(w))\\n}\\nsets := make([]map[int]bool, maxLen)\\nfor i := range sets {\\nsets[i] = map[int]bool{}\\n}\\nfor _, w := range words {\\nh := 0\\nfor j, b := range w {\\nh = (h*base + int(b)) % mod\\nsets[j][h] = true // 注意 j 从 0 开始\\n}\\n}\\n\\ncurR := 0 // 已建造的桥的右端点\\nnxtR := 0 // 下一座桥的右端点的最大值\\nfor i := range target {\\nmaxJump := sort.Search(min(n-i, maxLen), func(j int) bool { return !sets[j][subHash(i, i+j+1)] })\\nnxtR = max(nxtR, i+maxJump)\\nif i == curR { // 到达已建造的桥的右端点\\nif i == nxtR { // 无论怎么造桥,都无法从 i 到 i+1\\nreturn -1\\n}\\ncurR = nxtR // 建造下一座桥\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n用双指针更新代码中的 $\\\\textit{nxtR}$:
\\n###py
\\nclass Solution:\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n n = len(target)\\n\\n # 多项式字符串哈希(方便计算子串哈希值)\\n # 哈希函数 hash(s) = s[0] * BASE^(n-1) + s[1] * BASE^(n-2) + ... + s[n-2] * BASE + s[n-1]\\n MOD = 1_070_777_777\\n BASE = randint(8 * 10 ** 8, 9 * 10 ** 8) # 随机 BASE,防止 hack\\n pow_base = [1] + [0] * n # pow_base[i] = BASE^i\\n pre_hash = [0] * (n + 1) # 前缀哈希值 pre_hash[i] = hash(s[:i])\\n for i, b in enumerate(target):\\n pow_base[i + 1] = pow_base[i] * BASE % MOD\\n pre_hash[i + 1] = (pre_hash[i] * BASE + ord(b)) % MOD # 秦九韶算法计算多项式哈希\\n\\n # 计算子串 target[l:r] 的哈希值,注意这是左闭右开区间 [l,r)\\n # 计算方法类似前缀和\\n def sub_hash(l: int, r: int) -> int:\\n return (pre_hash[r] - pre_hash[l] * pow_base[r - l]) % MOD\\n\\n # 保存每个 words[i] 的每个前缀的哈希值,按照长度分组\\n max_len = max(map(len, words))\\n sets = [set() for _ in range(max_len)]\\n for w in words:\\n h = 0\\n for j, b in enumerate(w):\\n h = (h * BASE + ord(b)) % MOD\\n sets[j].add(h) # 注意 j 从 0 开始\\n\\n ans = 0\\n cur_r = 0 # 已建造的桥的右端点\\n nxt_r = 0 # 下一座桥的右端点的最大值\\n for i in range(n):\\n while nxt_r < n and nxt_r - i < max_len and sub_hash(i, nxt_r + 1) in sets[nxt_r - i]:\\n nxt_r += 1 # 尽量伸长\\n if i == cur_r: # 到达已建造的桥的右端点\\n if i == nxt_r: # 无论怎么造桥,都无法从 i 到 i+1\\n return -1\\n cur_r = nxt_r # 建造下一座桥\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_070_777_777;\\n\\n public int minValidStrings(String[] words, String target) {\\n char[] t = target.toCharArray();\\n int n = t.length;\\n\\n // 多项式字符串哈希(方便计算子串哈希值)\\n // 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\n final int BASE = (int) 8e8 + new Random().nextInt((int) 1e8); // 随机 base,防止 hack\\n int[] powBase = new int[n + 1]; // powBase[i] = base^i\\n int[] preHash = new int[n + 1]; // 前缀哈希值 preHash[i] = hash(target[0] 到 target[i-1])\\n powBase[0] = 1;\\n for (int i = 0; i < n; i++) {\\n powBase[i + 1] = (int) ((long) powBase[i] * BASE % MOD);\\n preHash[i + 1] = (int) (((long) preHash[i] * BASE + t[i]) % MOD); // 秦九韶算法计算多项式哈希\\n }\\n\\n int maxLen = 0;\\n for (String w : words) {\\n maxLen = Math.max(maxLen, w.length());\\n }\\n Set<Integer>[] sets = new HashSet[maxLen];\\n Arrays.setAll(sets, i -> new HashSet<>());\\n for (String w : words) {\\n long h = 0;\\n for (int j = 0; j < w.length(); j++) {\\n h = (h * BASE + w.charAt(j)) % MOD;\\n sets[j].add((int) h); // 注意 j 从 0 开始\\n }\\n }\\n\\n int ans = 0;\\n int curR = 0; // 已建造的桥的右端点\\n int nxtR = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < n; i++) {\\n while (nxtR < n && nxtR - i < maxLen && sets[nxtR - i].contains(subHash(i, nxtR + 1, powBase, preHash))) {\\n nxtR++;\\n }\\n if (i == curR) { // 到达已建造的桥的右端点\\n if (i == nxtR) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n curR = nxtR; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n\\n private int subHash(int l, int r, int[] powBase, int[] preHash) {\\n return (int) ((((long) preHash[r] - (long) preHash[l] * powBase[r - l]) % MOD + MOD) % MOD);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minValidStrings(const vector<string>& words, const string& target) {\\n int n = target.length();\\n\\n // 多项式字符串哈希(方便计算子串哈希值)\\n // 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\n const int MOD = 1\'070\'777\'777;\\n mt19937 rng(chrono::steady_clock::now().time_since_epoch().count());\\n const int BASE = uniform_int_distribution<>(8e8, 9e8)(rng); // 随机 base,防止 hack\\n vector<int> pow_base(n + 1); // pow_base[i] = base^i\\n vector<int> pre_hash(n + 1); // 前缀哈希值 pre_hash[i] = hash(s[:i])\\n pow_base[0] = 1;\\n for (int i = 0; i < n; i++) {\\n pow_base[i + 1] = (long long) pow_base[i] * BASE % MOD;\\n pre_hash[i + 1] = ((long long) pre_hash[i] * BASE + target[i]) % MOD; // 秦九韶算法计算多项式哈希\\n }\\n // 计算 target[l] 到 target[r-1] 的哈希值\\n auto sub_hash = [&](int l, int r) {\\n return ((pre_hash[r] - (long long) pre_hash[l] * pow_base[r - l]) % MOD + MOD) % MOD;\\n };\\n\\n int max_len = 0;\\n for (auto& w : words) {\\n max_len = max(max_len, (int) w.length());\\n }\\n vector<unordered_set<int>> sets(max_len);\\n for (auto& w : words) {\\n long long h = 0;\\n for (int j = 0; j < w.size(); j++) {\\n h = (h * BASE + w[j]) % MOD;\\n sets[j].insert(h); // 注意 j 从 0 开始\\n }\\n }\\n\\n int ans = 0;\\n int cur_r = 0; // 已建造的桥的右端点\\n int nxt_r = 0; // 下一座桥的右端点的最大值\\n for (int i = 0; i < n; i++) {\\n while (nxt_r < n && nxt_r - i < max_len && sets[nxt_r - i].contains(sub_hash(i, nxt_r + 1))) {\\n nxt_r++;\\n }\\n if (i == cur_r) { // 到达已建造的桥的右端点\\n if (i == nxt_r) { // 无论怎么造桥,都无法从 i 到 i+1\\n return -1;\\n }\\n cur_r = nxt_r; // 造一座桥\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minValidStrings(words []string, target string) (ans int) {\\nn := len(target)\\n\\n// 多项式字符串哈希(方便计算子串哈希值)\\n// 哈希函数 hash(s) = s[0] * base^(n-1) + s[1] * base^(n-2) + ... + s[n-2] * base + s[n-1]\\nconst mod = 1_070_777_777\\nbase := 9e8 - rand.Intn(1e8) // 随机 base,防止 hack(注意 Go1.20 之后的版本,每次随机的数都不一样)\\npowBase := make([]int, n+1) // powBase[i] = base^i\\npreHash := make([]int, n+1) // 前缀哈希值 preHash[i] = hash(s[:i])\\npowBase[0] = 1\\nfor i, b := range target {\\npowBase[i+1] = powBase[i] * base % mod\\npreHash[i+1] = (preHash[i]*base + int(b)) % mod // 秦九韶算法计算多项式哈希\\n}\\n// 计算子串 target[l:r] 的哈希值,注意这是左闭右开区间 [l,r)\\n// 计算方法类似前缀和\\nsubHash := func(l, r int) int {\\nreturn ((preHash[r]-preHash[l]*powBase[r-l])%mod + mod) % mod\\n}\\n\\nmaxLen := 0\\nfor _, w := range words {\\nmaxLen = max(maxLen, len(w))\\n}\\nsets := make([]map[int]bool, maxLen)\\nfor i := range sets {\\nsets[i] = map[int]bool{}\\n}\\nfor _, w := range words {\\nh := 0\\nfor j, b := range w {\\nh = (h*base + int(b)) % mod\\nsets[j][h] = true // 注意 j 从 0 开始\\n}\\n}\\n\\ncurR := 0 // 已建造的桥的右端点\\nnxtR := 0 // 下一座桥的右端点的最大值\\nfor i := range target {\\nfor nxtR < n && nxtR-i < maxLen && sets[nxtR-i][subHash(i, nxtR+1)] {\\nnxtR++\\n}\\nif i == curR { // 到达已建造的桥的右端点\\nif i == nxtR { // 无论怎么造桥,都无法从 i 到 i+1\\nreturn -1\\n}\\ncurR = nxtR // 建造下一座桥\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n看示例 1,对比以下两个 $\\\\textit{target}$ 的前缀:
\\n根据上述讨论,如果用 $f[i]$ 表示 $\\\\textit{target}$ 的长为 $i$ 的前缀需要连接的最少字符串数量,那么 $f[i]\\\\le f[i+1]$ 一定成立。
\\n既然 $f$ 是有序数组,那么对于 $f[i]$,我们只需要知道最小的 $j$,满足从 $\\\\textit{target}[j]$ 到 $\\\\textit{target}[i-1]$ 是某个 $\\\\textit{words}[i]$ 的前缀。
\\n也就是说,匹配的 $\\\\textit{words}[i]$ 的前缀要尽量长。这正是 AC 自动机的应用。原理见 OI Wiki。学习之前推荐先看看我的 KMP 原理讲解。
\\n算出了 $j$,那么有
\\n$$
\\nf[i] = f[j] + 1
\\n$$
初始值 $f[0]=0$。
\\n答案为 $f[n]$。
\\n如果 AC 自动机没法匹配任何 $\\\\textit{words}[i]$ 的非空前缀,返回 $-1$。
\\n###py
\\n# 从根到 node 的字符串是某个(某些)words[i] 的前缀\\nclass Node:\\n __slots__ = \'son\', \'fail\', \'len\'\\n\\n def __init__(self, len=0):\\n self.son = [None] * 26\\n self.fail = None # 当 cur.son[i] 不能匹配 target 中的某个字符时,cur.fail.son[i] 即为下一个待匹配节点(等于 root 则表示没有匹配)\\n self.len = len # 从根到 node 的字符串的长度,也是 node 在 trie 中的深度\\n\\nclass AhoCorasick:\\n def __init__(self):\\n self.root = Node()\\n\\n def put(self, s: str) -> None:\\n cur = self.root\\n for b in s:\\n b = ord(b) - ord(\'a\')\\n if cur.son[b] is None:\\n cur.son[b] = Node(cur.len + 1)\\n cur = cur.son[b]\\n\\n def build_fail(self) -> None:\\n self.root.fail = self.root\\n q = deque()\\n for i, son in enumerate(self.root.son):\\n if son is None:\\n self.root.son[i] = self.root\\n else:\\n son.fail = self.root # 第一层的失配指针,都指向根节点 ∅\\n q.append(son)\\n # BFS\\n while q:\\n cur = q.popleft()\\n for i, son in enumerate(cur.son):\\n if son is None:\\n # 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个\\n # 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母)\\n cur.son[i] = cur.fail.son[i]\\n continue\\n son.fail = cur.fail.son[i] # 计算失配位置\\n q.append(son)\\n\\nclass Solution:\\n def minValidStrings(self, words: List[str], target: str) -> int:\\n ac = AhoCorasick()\\n for w in words:\\n ac.put(w)\\n ac.build_fail()\\n\\n n = len(target)\\n f = [0] * (n + 1)\\n cur = root = ac.root\\n for i, c in enumerate(target, 1):\\n # 如果没有匹配相当于移动到 fail 的 son[c]\\n cur = cur.son[ord(c) - ord(\'a\')]\\n # 没有任何字符串的前缀与 target[..i] 的后缀匹配\\n if cur is root:\\n return -1\\n f[i] = f[i - cur.len] + 1\\n return f[n]\\n
\\n###java
\\n// 从根到 node 的字符串是某个(某些)words[i] 的前缀\\nclass Node {\\n Node[] son = new Node[26];\\n Node fail; // 当 cur.son[i] 不能匹配 target 中的某个字符时,cur.fail.son[i] 即为下一个待匹配节点(等于 root 则表示没有匹配)\\n int len;\\n\\n Node(int len) {\\n this.len = len;\\n }\\n}\\n\\nclass AhoCorasick {\\n Node root = new Node(0);\\n\\n void put(String s) {\\n Node cur = root;\\n for (char b : s.toCharArray()) {\\n b -= \'a\';\\n if (cur.son[b] == null) {\\n cur.son[b] = new Node(cur.len + 1);\\n }\\n cur = cur.son[b];\\n }\\n }\\n\\n void buildFail() {\\n root.fail = root;\\n Queue<Node> q = new ArrayDeque<>();\\n for (int i = 0; i < root.son.length; i++) {\\n Node son = root.son[i];\\n if (son == null) {\\n root.son[i] = root;\\n } else {\\n son.fail = root; // 第一层的失配指针,都指向根节点 ∅\\n q.add(son);\\n }\\n }\\n // BFS\\n while (!q.isEmpty()) {\\n Node cur = q.poll();\\n for (int i = 0; i < 26; i++) {\\n Node son = cur.son[i];\\n if (son == null) {\\n // 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个\\n // 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母)\\n cur.son[i] = cur.fail.son[i];\\n continue;\\n }\\n son.fail = cur.fail.son[i]; // 计算失配位置\\n q.add(son);\\n }\\n }\\n }\\n}\\n\\nclass Solution {\\n public int minValidStrings(String[] words, String target) {\\n AhoCorasick ac = new AhoCorasick();\\n for (String w : words) {\\n ac.put(w);\\n }\\n ac.buildFail();\\n\\n char[] t = target.toCharArray();\\n int n = t.length;\\n int[] f = new int[n + 1];\\n Node cur = ac.root;\\n for (int i = 0; i < n; i++) {\\n // 如果没有匹配相当于移动到 fail 的 son[t[i]-\'a\']\\n cur = cur.son[t[i] - \'a\'];\\n // 没有任何字符串的前缀与 target[..i] 的后缀匹配\\n if (cur == ac.root) {\\n return -1;\\n }\\n f[i + 1] = f[i + 1 - cur.len] + 1;\\n }\\n return f[n];\\n }\\n}\\n
\\n###cpp
\\n// 从根到 node 的字符串是某个(某些)words[i] 的前缀\\nstruct Node {\\n Node* son[26]{};\\n Node* fail; // 当 cur.son[i] 不能匹配 target 中的某个字符时,cur.fail.son[i] 即为下一个待匹配节点(等于 root 则表示没有匹配)\\n int len; // 从根到 node 的字符串的长度,也是 node 在 trie 中的深度\\n\\n Node(int len) : len(len) {}\\n};\\n\\nstruct AhoCorasick {\\n Node* root = new Node(0);\\n\\n void put(string& s) {\\n auto cur = root;\\n for (char b : s) {\\n b -= \'a\';\\n if (cur->son[b] == nullptr) {\\n cur->son[b] = new Node(cur->len + 1);\\n }\\n cur = cur->son[b];\\n }\\n }\\n\\n void build_fail() {\\n root->fail = root;\\n queue<Node*> q;\\n for (auto& son : root->son) {\\n if (son == nullptr) {\\n son = root;\\n } else {\\n son->fail = root; // 第一层的失配指针,都指向根节点 ∅\\n q.push(son);\\n }\\n }\\n // BFS\\n while (!q.empty()) {\\n auto cur = q.front();\\n q.pop();\\n for (int i = 0; i < 26; i++) {\\n auto& son = cur->son[i];\\n if (son == nullptr) {\\n // 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个\\n // 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母)\\n son = cur->fail->son[i];\\n continue;\\n }\\n son->fail = cur->fail->son[i]; // 计算失配位置\\n q.push(son);\\n }\\n }\\n }\\n};\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n AhoCorasick ac;\\n for (auto& w : words) {\\n ac.put(w);\\n }\\n ac.build_fail();\\n\\n int n = target.length();\\n vector<int> f(n + 1);\\n auto cur = ac.root;\\n for (int i = 0; i < n; i++) {\\n // 如果没有匹配相当于移动到 fail 的 son[target[i]-\'a\']\\n cur = cur->son[target[i] - \'a\'];\\n // 没有任何字符串的前缀与 target[..i] 的后缀匹配\\n if (cur == ac.root) {\\n return -1;\\n }\\n f[i + 1] = f[i + 1 - cur->len] + 1;\\n }\\n return f[n];\\n }\\n};\\n
\\n###go
\\n// 从根到 node 的字符串是某个(某些)words[i] 的前缀\\ntype node struct {\\nson [26]*node\\nfail *node // 当 cur.son[i] 不能匹配 target 中的某个字符时,cur.fail.son[i] 即为下一个待匹配节点(等于 root 则表示没有匹配)\\nlen int // 从根到 node 的字符串的长度,也是 node 在 trie 中的深度\\n}\\n\\ntype acam struct {\\nroot *node\\n}\\n\\nfunc (ac *acam) put(s string) {\\ncur := ac.root\\nfor _, b := range s {\\nb -= \'a\'\\nif cur.son[b] == nil {\\ncur.son[b] = &node{len: cur.len + 1}\\n}\\ncur = cur.son[b]\\n}\\n}\\n\\nfunc (ac *acam) buildFail() {\\nac.root.fail = ac.root\\nq := []*node{}\\nfor i, son := range ac.root.son[:] {\\nif son == nil {\\nac.root.son[i] = ac.root\\n} else {\\nson.fail = ac.root // 第一层的失配指针,都指向根节点 ∅\\nq = append(q, son)\\n}\\n}\\n// BFS\\nfor len(q) > 0 {\\ncur := q[0]\\nq = q[1:]\\nfor i, son := range cur.son[:] {\\nif son == nil {\\n// 虚拟子节点 cur.son[i],和 cur.fail.son[i] 是同一个\\n// 方便失配时直接跳到下一个可能匹配的位置(但不一定是某个 words[k] 的最后一个字母)\\ncur.son[i] = cur.fail.son[i]\\ncontinue\\n}\\nson.fail = cur.fail.son[i] // 计算失配位置\\nq = append(q, son)\\n}\\n}\\n}\\n\\nfunc minValidStrings(words []string, target string) int {\\nac := &acam{root: &node{}}\\nfor _, w := range words {\\nac.put(w)\\n}\\nac.buildFail()\\n\\nn := len(target)\\nf := make([]int, n+1)\\ncur := ac.root\\nfor i, b := range target {\\n// 如果没有匹配相当于移动到 fail 的 son[b-\'a\']\\ncur = cur.son[b-\'a\']\\n// 没有任何字符串的前缀与 target[:i+1] 的后缀匹配\\nif cur == ac.root {\\nreturn -1\\n}\\nf[i+1] = f[i+1-cur.len] + 1\\n}\\nreturn f[n]\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 把 $\\\\textit{target}$ 划分成若干段,要求每一段都是某个 $\\\\textit{words}[i]$ 的前缀。\\n\\n返回最小划分成多少段。如果无法划分,返回 $-1$。\\n\\n分析\\n\\n示例 1 的 $\\\\textit{words}=[\\\\texttt{abc},\\\\texttt{aaaaa},\\\\texttt{bcdef}]$,$\\\\textit{target}=\\\\texttt{aabcdabc}$。\\n\\n要让划分的段数尽量小,那么每一段的长度要尽量长?\\n\\n虽然还不知道要怎么划分,但这启发我们考虑如下内容:\\n\\n从 $\\\\textit{target}[0]$ 开始的段…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/ac-zi-dong-ji-pythonjavacgo-by-endlessch-hcqk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T05:05:10.985Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【难题讲解】Trie + 动态规划","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/nan-ti-jiang-jie-trie-dong-tai-gui-hua-b-ewfh","content":"【难题讲解】Trie + 动态规划","description":"【难题讲解】Trie + 动态规划","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/nan-ti-jiang-jie-trie-dong-tai-gui-hua-b-ewfh","author":"mpln_w_tomorrow","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T04:24:28.531Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划+字符串哈希+区间最小值线段树","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/dong-tai-gui-hua-zi-fu-chuan-ha-xi-qu-ji-hkdl","content":"题意:字符串 $target$ 能否由 $words$ 中字符串的前缀连接得到。如果能得到,返回最小前缀使用数量;否则返回 $-1$。
\\n这提醒我们可以使用动态规划。
\\n\\n\\n记 $n$ 表示 $target$ 的 长度
\\n
\\n记 $S$ 为 $words$ 中字符串的所有前缀构成的集合
定义 $dp[i]$ 表示得到字符串 $target[i:]$ 的最小前缀使用数量。那么有以下转移:
\\n$$
\\ndp[i] = \\\\min_{\\\\substack{i \\\\leq j < n \\\\ target[i:j] \\\\in S}}dp[j + 1] + 1
\\n$$
此时 $dp[0]$ 就是答案。
\\n但是这个转移的时间复杂度是 $\\\\mathcal{O}(n)$ 的,同时需要快速的查询 $target[i:j]$ 是否属于 $S$。只有降低这个两个部分的复杂度才能通过本题。
\\n如何 $\\\\mathcal{O}(1)$ 查询 $target[i:j]$ 是否属于 $S$ 呢 ?
\\n可以使用字符串哈希。首先将 $words$ 中字符串所有前缀的哈希值收集到集合 $S$ 中,然后对 $target$ 做字符串哈希。那么就可以通过判断 $target[i:j]$ 的哈希值是否在集合 $S$ 中,来判断 $target[i:j]$ 是否属于 $S$。
\\n如何降低转移的时间复杂度呢?
\\n由于 $j$ 越大 $target[i:j]$ 越不可能属于 $S$,那么就可以二分得到一个 $j$ 的右边界 $r$。即:
\\n$$
\\ntarget[i:j] \\\\in S ,,, j \\\\in [i, r]
\\n$$
\\n此时 $target[i:r+1] \\\\notin S$
由于需要获取:
\\n$$
\\n\\\\min_{i \\\\leq j \\\\leq r}dp[j + 1] + 1
\\n$$
\\n那么这部分可以使用区间最小值线段树来维护。
此时转移的整体复杂度就降低为 $\\\\mathcal{O}(\\\\log{n})$ 了。
\\nclass Solution {\\n static int INF = 100000000;\\n static int B = 127;// 哈希的基数\\n static long[] A = new long[50001];// A[i] = B^i\\n static {\\n A[0] = 1;\\n for (int i = 1; i <= 50000; i++) {\\n A[i] = A[i - 1] * B;\\n }\\n }\\n long[] hash;\\n\\n public int minValidStrings(String[] words, String target) {\\n HashSet<Long> set = new HashSet<>();// 用来存储 words 中字符串的前缀哈希值\\n for (String s : words) {\\n long v = 0;\\n for (char c : s.toCharArray()) {\\n // 搜集字符串 s 所有前缀的哈希值\\n set.add(v = v * B + c);\\n }\\n }\\n char[] str = target.toCharArray();\\n int n = str.length;\\n hash = new long[n + 1];\\n for (int i = 1; i <= n; i++) {\\n hash[i] = hash[i - 1] * B + str[i - 1];\\n }\\n int[] dp = new int[n + 1];\\n Arrays.fill(dp, INF);\\n dp[n] = 0;\\n SegTree tree = new SegTree(n);\\n tree.set(n, 0);\\n for (int i = n - 1; i >= 0; i--) {\\n int l = i, r = n - 1, m;\\n while (l <= r) {\\n m = (l + r) >> 1;\\n if (set.contains(query(i, m))) {\\n l = m + 1;\\n } else {\\n r = m - 1;\\n }\\n }\\n if (l > i) {\\n // 此时 target[i:j] ∈ S , j ∈ [i, l)\\n dp[i] = Math.min(dp[i], tree.query(i + 1, l) + 1);\\n }\\n if (i > 0) {\\n // 更新线段树中 i 位置的值\\n tree.set(i, dp[i]);\\n }\\n }\\n return dp[0] == INF ? -1 : dp[0];\\n }\\n\\n long query(int l, int r) {\\n return hash[r + 1] - hash[l] * A[r - l + 1];\\n }\\n\\n static class SegTree {\\n private int[] min;\\n private int N;\\n\\n public SegTree(int len) {\\n N = len;\\n min = new int[N << 2];\\n Arrays.fill(min, INF);\\n }\\n\\n public void set(int o, int v) {\\n set(o, v, 1, N, 1);\\n }\\n\\n private void set(int o, int v, int l, int r, int i) {\\n if (l == r) {\\n min[i] = v;\\n return;\\n }\\n int m = (l + r) >> 1;\\n if (o <= m) {\\n set(o, v, l, m, i << 1);\\n } else {\\n set(o, v, m + 1, r, i << 1 | 1);\\n }\\n min[i] = Math.min(min[i << 1], min[i << 1 | 1]);\\n }\\n\\n public int query(int l, int r) {\\n return query(l, r, 1, N, 1);\\n }\\n\\n private int query(int L, int R, int l, int r, int i) {\\n if (L <= l && r <= R) {\\n return min[i];\\n }\\n int m = (l + r) >> 1;\\n int ans = INF;\\n if (L <= m) {\\n ans = Math.min(ans, query(L, R, l, m, i << 1));\\n }\\n if (R > m) {\\n ans = Math.min(ans, query(L, R, m + 1, r, i << 1 | 1));\\n }\\n return ans;\\n }\\n\\n }\\n}\\n
\\n时间复杂度:$\\\\mathcal{O}(n\\\\log{n} + m)$
\\n空间复杂度:$\\\\mathcal{O}(n + m)$
\\n其中 $m$ 表示 $words$ 中所有字符串的总长度;$n$ 表示 $target$ 的长度
附AC自动机 $\\\\mathcal{O}(n)$ 实现代码:
\\n###Java
\\nclass Solution {\\n public int minValidStrings(String[] words, String target) {\\n AhoCorasick ac = new AhoCorasick();\\n for (String s : words) {\\n ac.insert(s);\\n }\\n ac.setFail();\\n char[] str = target.toCharArray();\\n int n = str.length;\\n int[] dp = new int[n + 1];\\n for (int i = 0, u = 0; i < n; i++) {\\n if ((u = ac.nxt[u][str[i] - \'a\']) == 0) {\\n return -1;\\n }\\n dp[i + 1] = dp[i + 1 - ac.len[u]] + 1;\\n }\\n return dp[n];\\n }\\n\\n static class AhoCorasick {\\n public int[][] nxt;\\n public int[] len;\\n public int[] fail;\\n public int no;\\n\\n public AhoCorasick() {\\n nxt = new int[16][26];\\n len = new int[16];\\n }\\n\\n public void insert(String s) {\\n int u = 0;\\n int l = 0;\\n for (char c : s.toCharArray()) {\\n c -= \'a\';\\n if (nxt[u][c] == 0) {\\n if (++no == nxt.length) {\\n int[][] tmp = new int[no << 1][];\\n for (int i = 0; i < no; i++) {\\n tmp[i] = nxt[i];\\n }\\n for (int i = no; i < no << 1; i++) {\\n tmp[i] = new int[26];\\n }\\n nxt = tmp;\\n len = Arrays.copyOf(len, no << 1);\\n }\\n nxt[u][c] = no;\\n }\\n u = nxt[u][c];\\n len[u] = ++l;\\n }\\n }\\n\\n public void setFail() {\\n fail = new int[no + 1];\\n Deque<Integer> queue = new ArrayDeque<>();\\n for (int i = 0; i < 26; i++) {\\n if (nxt[0][i] != 0) {\\n queue.addLast(nxt[0][i]);\\n }\\n }\\n while (!queue.isEmpty()) {\\n int u = queue.removeFirst();\\n for (int i = 0; i < 26; i++) {\\n int v = nxt[u][i];\\n if (v != 0) {\\n queue.addLast(v);\\n fail[v] = nxt[fail[u]][i];\\n } else {\\n nxt[u][i] = nxt[fail[u]][i];\\n }\\n }\\n }\\n }\\n }\\n\\n}\\n
\\n","description":"思路 题意:字符串 $target$ 能否由 $words$ 中字符串的前缀连接得到。如果能得到,返回最小前缀使用数量;否则返回 $-1$。\\n\\n这提醒我们可以使用动态规划。\\n\\n记 $n$ 表示 $target$ 的 长度\\n 记 $S$ 为 $words$ 中字符串的所有前缀构成的集合\\n\\n定义 $dp[i]$ 表示得到字符串 $target[i:]$ 的最小前缀使用数量。那么有以下转移:\\n $$\\n dp[i] = \\\\min_{\\\\substack{i \\\\leq j < n \\\\ target[i:j] \\\\in S}}dp[j + 1] + 1\\n $$\\n\\n此时 $dp[0]…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/dong-tai-gui-hua-zi-fu-chuan-ha-xi-qu-ji-hkdl","author":"time-v5","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T04:09:49.257Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种做法:贪心 & (AC 自动机 or 跳跃游戏)","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/liang-chong-zuo-fa-tan-xin-ac-zi-dong-ji-09s3","content":"我们从最暴力的 DP 开始。设 $f(i)$ 表示形成目标字符串长度为 $i$ 的前缀的最小代价,有转移方程
\\n$$
\\nf(i) = \\\\min(f(j) + 1)
\\n$$
设 $s[j + 1:i]$ 表示目标字符串第 $(j + 1)$ 个字符到第 $i$ 个字符构成的子串,这里 $s[j + 1:i]$ 必须是 words
里的一个前缀。直接计算这个 DP 方程的复杂度至少是 $\\\\mathcal{O}(n^2)$ 的,因此我们需要找到一些性质进行优化。
关键性质:对于 $i\' \\\\le i$,一定有 $f(i\') \\\\le f(i)$。
\\n为什么呢?因为目标字符串是由若干个前缀拼起来的,而前缀的前缀仍然是前缀,所以越短的前缀答案肯定越小。
\\n有了这个关键性质,我们的转移方程可以直接把 $\\\\min$ 去掉,优化为一个 $\\\\mathcal{O}(n)$ 的递推方程
\\n$$
\\nf(i) = f(j) + 1
\\n$$
其中 $j$ 是满足“$s[j + 1:i]$ 必须是 words
里的一个前缀” 的最小的 $j$。
怎么快速求这个 $j$?了解 AC 自动机的朋友肯定知道,$s[j + 1:i]$ 的最大长度,不就是 AC 自动机上当前节点的深度吗?因此直接用 AC 自动机即可算出答案,复杂度 $\\\\mathcal{O}(n + m)$,其中 $n$ 是目标字符串的长度,$m$ 是 words
里字符串的总长度。不了解 AC 自动机的朋友可以学习一下模板题 leetcode 3213. 最小代价构造字符串,或者看看下文的另一个方法。
###cpp
\\n// AC 自动机模板开始\\n\\nconst int MAXTOT = 1e5;\\n\\nstruct AhoCorasick {\\n int tot, ch[MAXTOT + 10][26], dep[MAXTOT + 10], fail[MAXTOT + 10];\\n\\n int newNode(int d) {\\n int ret = tot++;\\n memset(ch[ret], 0, sizeof(ch[ret]));\\n dep[ret] = d;\\n fail[ret] = 0;\\n return ret;\\n }\\n\\n void reset() {\\n tot = 0;\\n newNode(0);\\n }\\n\\n AhoCorasick() { reset(); }\\n\\n void insert(const char *s) {\\n int now = 0;\\n for (int i = 0; s[i]; s++) {\\n int c = s[i] - \'a\';\\n if (ch[now][c] == 0) ch[now][c] = newNode(dep[now] + 1);\\n now = ch[now][c];\\n }\\n }\\n\\n void buildFail(const char *s) {\\n queue<int> q;\\n for (int c = 0; c < 26; c++) if (ch[0][c] > 0) q.push(ch[0][c]);\\n while (!q.empty()) {\\n int sn = q.front(); q.pop();\\n for (int c = 0; c < 26; c++) {\\n int x = ch[sn][c], y = ch[fail[sn]][c];\\n if (x > 0) {\\n fail[x] = y;\\n q.push(x);\\n } else {\\n ch[sn][c] = y;\\n }\\n }\\n }\\n }\\n} ac;\\n\\n// AC 自动机模板结束\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n // 用 words 里的字符串构建 AC 自动机\\n ac.reset();\\n for (int i = 0; i < words.size(); i++) ac.insert(words[i].c_str());\\n ac.buildFail(target.c_str());\\n\\n int n = target.size();\\n long long f[n + 1];\\n f[0] = 0;\\n // 将 target 输入 AC 自动机,now 是 target 的第 i 个字符匹配的节点\\n for (int i = 1, now = 0; i <= n; i++) {\\n int c = target[i - 1] - \'a\';\\n now = ac.ch[now][c];\\n // AC 自动机上匹配失败,返回无解\\n if (now == 0) return -1;\\n // 最后一次加上的前缀长度,就是当前节点的深度\\n f[i] = f[i - ac.dep[now]] + 1;\\n }\\n return f[n];\\n }\\n};\\n
\\n不想使用 AC 自动机怎么办呢?我们换一个方向思考问题。假设我们已知 $f(i)$ 的值,那么它可以转移给哪些位置呢?肯定是转移给满足 $i < i\' \\\\le r_i$ 的所有 $f(i\')$。为什么转移的范围是连续的?仍然是因为前缀的前缀还是前缀。对于每个 $i$,这个 $r_i$ 可以通过二分 + 字符串哈希的方式求出。
\\n可是直接计算转移方程的复杂度仍然是 $\\\\mathcal{O}(n^2)$ 的。这就需要注意到本题的另一个性质:$f(i + 1) - f(i) \\\\le 1$。因为构造出目标串长度为 $i$ 的前缀后,再加上一个长度为 $1$ 的前缀,就能构造出长度为 $(i + 1)$ 的前缀。
\\n有了这个性质,我们发现 $f(i)$ 的值形如 $1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, \\\\cdots$ 这样递增。熟悉 HOT100 题的朋友看到这个 pattern 肯定知道,我们已经把问题转换成了 leetcode 45. 跳跃游戏 II。我们维护一个递增的指针,即可在 $\\\\mathcal{O}(n)$ 的复杂度内算出答案。因此这个做法整体的复杂度是 $\\\\mathcal{O}(m + n\\\\log m)$ 的。
\\n###c++
\\n// 字符串哈希模板开始\\n\\nmt19937 rng(chrono::steady_clock::now().time_since_epoch().count());\\n\\nint rnd(int x, int y) {\\n return uniform_int_distribution<int>(x, y)(rng);\\n}\\n\\nlong long MOD = 1e18 + rnd(0, 1e9), BASE = 233 + rnd(0, 1e3);\\n\\nstruct HashString {\\n vector<__int128> P, H;\\n\\n HashString(string &s) {\\n int n = s.size();\\n P.resize(n + 1);\\n P[0] = 1;\\n for (int i = 1; i <= n; i++) P[i] = P[i - 1] * BASE % MOD;\\n H.resize(n + 1);\\n H[0] = 0;\\n for (int i = 1; i <= n; i++) H[i] = (H[i - 1] * BASE + s[i - 1]) % MOD;\\n }\\n\\n long long query(int l, int r) {\\n return (H[r] - H[l - 1] * P[r - l + 1] % MOD + MOD) % MOD;\\n }\\n};\\n\\n// 字符串哈希模板结束\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n int n = target.size();\\n\\n // 预处理每个前缀的哈希值\\n unordered_set<long long> st[n + 1];\\n for (auto &w : words) {\\n HashString hs(w);\\n for (int i = 1; i <= n && i <= w.size(); i++) st[i].insert(hs.query(1, i));\\n }\\n\\n HashString hs(target);\\n int f[n + 1]; f[0] = 0;\\n // 单调指针 j,维护 f[j] = f[i] + 1 的右端点\\n int j = 0;\\n for (int i = 0; i < n; i++) {\\n // 判无解\\n if (i > j) break;\\n if (st[1].count(hs.query(i + 1, i + 1)) == 0) continue;\\n // 二分找出能追加的最长前缀\\n int head = i + 1, tail = n;\\n while (head < tail) {\\n int mid = (head + tail + 1) >> 1;\\n if (st[mid - i].count(hs.query(i + 1, mid))) head = mid;\\n else tail = mid - 1;\\n }\\n // 移动指针,更新 f[j]\\n while (j < head) f[++j] = f[i] + 1;\\n }\\n return j == n ? f[n] : -1;\\n }\\n};\\n
\\n","description":"解法:贪心 & (AC 自动机 or 跳跃游戏) 我们从最暴力的 DP 开始。设 $f(i)$ 表示形成目标字符串长度为 $i$ 的前缀的最小代价,有转移方程\\n\\n$$\\n f(i) = \\\\min(f(j) + 1)\\n $$\\n\\n设 $s[j + 1:i]$ 表示目标字符串第 $(j + 1)$ 个字符到第 $i$ 个字符构成的子串,这里 $s[j + 1:i]$ 必须是 words 里的一个前缀。直接计算这个 DP 方程的复杂度至少是 $\\\\mathcal{O}(n^2)$ 的,因此我们需要找到一些性质进行优化。\\n\\n关键性质:对于 $i\' \\\\le i$,一定有 $f(i…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-i//solution/liang-chong-zuo-fa-tan-xin-ac-zi-dong-ji-09s3","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T04:08:29.481Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种解法:贪心 & (AC 自动机 or 跳跃游戏)","url":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/liang-chong-jie-fa-tan-xin-ac-zi-dong-ji-u6wc","content":"我们从最暴力的 DP 开始。设 $f(i)$ 表示形成目标字符串长度为 $i$ 的前缀的最小代价,有转移方程
\\n$$
\\nf(i) = \\\\min(f(j) + 1)
\\n$$
设 $s[j + 1:i]$ 表示目标字符串第 $(j + 1)$ 个字符到第 $i$ 个字符构成的子串,这里 $s[j + 1:i]$ 必须是 words
里的一个前缀。直接计算这个 DP 方程的复杂度至少是 $\\\\mathcal{O}(n^2)$ 的,因此我们需要找到一些性质进行优化。
关键性质:对于 $i\' \\\\le i$,一定有 $f(i\') \\\\le f(i)$。
\\n为什么呢?因为目标字符串是由若干个前缀拼起来的,而前缀的前缀仍然是前缀,所以越短的前缀答案肯定越小。
\\n有了这个关键性质,我们的转移方程可以直接把 $\\\\min$ 去掉,优化为一个 $\\\\mathcal{O}(n)$ 的递推方程
\\n$$
\\nf(i) = f(j) + 1
\\n$$
其中 $j$ 是满足“$s[j + 1:i]$ 必须是 words
里的一个前缀” 的最小的 $j$。
怎么快速求这个 $j$?了解 AC 自动机的朋友肯定知道,$s[j + 1:i]$ 的最大长度,不就是 AC 自动机上当前节点的深度吗?因此直接用 AC 自动机即可算出答案,复杂度 $\\\\mathcal{O}(n + m)$,其中 $n$ 是目标字符串的长度,$m$ 是 words
里字符串的总长度。不了解 AC 自动机的朋友可以学习一下模板题 leetcode 3213. 最小代价构造字符串,或者看看下文的另一个方法。
###cpp
\\n// AC 自动机模板开始\\n\\nconst int MAXTOT = 1e5;\\n\\nstruct AhoCorasick {\\n int tot, ch[MAXTOT + 10][26], dep[MAXTOT + 10], fail[MAXTOT + 10];\\n\\n int newNode(int d) {\\n int ret = tot++;\\n memset(ch[ret], 0, sizeof(ch[ret]));\\n dep[ret] = d;\\n fail[ret] = 0;\\n return ret;\\n }\\n\\n void reset() {\\n tot = 0;\\n newNode(0);\\n }\\n\\n AhoCorasick() { reset(); }\\n\\n void insert(const char *s) {\\n int now = 0;\\n for (int i = 0; s[i]; s++) {\\n int c = s[i] - \'a\';\\n if (ch[now][c] == 0) ch[now][c] = newNode(dep[now] + 1);\\n now = ch[now][c];\\n }\\n }\\n\\n void buildFail(const char *s) {\\n queue<int> q;\\n for (int c = 0; c < 26; c++) if (ch[0][c] > 0) q.push(ch[0][c]);\\n while (!q.empty()) {\\n int sn = q.front(); q.pop();\\n for (int c = 0; c < 26; c++) {\\n int x = ch[sn][c], y = ch[fail[sn]][c];\\n if (x > 0) {\\n fail[x] = y;\\n q.push(x);\\n } else {\\n ch[sn][c] = y;\\n }\\n }\\n }\\n }\\n} ac;\\n\\n// AC 自动机模板结束\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n // 用 words 里的字符串构建 AC 自动机\\n ac.reset();\\n for (int i = 0; i < words.size(); i++) ac.insert(words[i].c_str());\\n ac.buildFail(target.c_str());\\n\\n int n = target.size();\\n long long f[n + 1];\\n f[0] = 0;\\n // 将 target 输入 AC 自动机,now 是 target 的第 i 个字符匹配的节点\\n for (int i = 1, now = 0; i <= n; i++) {\\n int c = target[i - 1] - \'a\';\\n now = ac.ch[now][c];\\n // AC 自动机上匹配失败,返回无解\\n if (now == 0) return -1;\\n // 最后一次加上的前缀长度,就是当前节点的深度\\n f[i] = f[i - ac.dep[now]] + 1;\\n }\\n return f[n];\\n }\\n};\\n
\\n不想使用 AC 自动机怎么办呢?我们换一个方向思考问题。假设我们已知 $f(i)$ 的值,那么它可以转移给哪些位置呢?肯定是转移给满足 $i < i\' \\\\le r_i$ 的所有 $f(i\')$。为什么转移的范围是连续的?仍然是因为前缀的前缀还是前缀。对于每个 $i$,这个 $r_i$ 可以通过二分 + 字符串哈希的方式求出。
\\n可是直接计算转移方程的复杂度仍然是 $\\\\mathcal{O}(n^2)$ 的。这就需要注意到本题的另一个性质:$f(i + 1) - f(i) \\\\le 1$。因为构造出目标串长度为 $i$ 的前缀后,再加上一个长度为 $1$ 的前缀,就能构造出长度为 $(i + 1)$ 的前缀。
\\n有了这个性质,我们发现 $f(i)$ 的值形如 $1, 1, 1, 2, 2, 3, 3, 3, 3, 4, 4, \\\\cdots$ 这样递增。熟悉 HOT100 题的朋友看到这个 pattern 肯定知道,我们已经把问题转换成了 leetcode 45. 跳跃游戏 II。我们维护一个递增的指针,即可在 $\\\\mathcal{O}(n)$ 的复杂度内算出答案。因此这个做法整体的复杂度是 $\\\\mathcal{O}(m + n\\\\log m)$ 的。
\\n###c++
\\n// 字符串哈希模板开始\\n\\nmt19937 rng(chrono::steady_clock::now().time_since_epoch().count());\\n\\nint rnd(int x, int y) {\\n return uniform_int_distribution<int>(x, y)(rng);\\n}\\n\\nlong long MOD = 1e18 + rnd(0, 1e9), BASE = 233 + rnd(0, 1e3);\\n\\nstruct HashString {\\n vector<__int128> P, H;\\n\\n HashString(string &s) {\\n int n = s.size();\\n P.resize(n + 1);\\n P[0] = 1;\\n for (int i = 1; i <= n; i++) P[i] = P[i - 1] * BASE % MOD;\\n H.resize(n + 1);\\n H[0] = 0;\\n for (int i = 1; i <= n; i++) H[i] = (H[i - 1] * BASE + s[i - 1]) % MOD;\\n }\\n\\n long long query(int l, int r) {\\n return (H[r] - H[l - 1] * P[r - l + 1] % MOD + MOD) % MOD;\\n }\\n};\\n\\n// 字符串哈希模板结束\\n\\nclass Solution {\\npublic:\\n int minValidStrings(vector<string>& words, string target) {\\n int n = target.size();\\n\\n // 预处理每个前缀的哈希值\\n unordered_set<long long> st[n + 1];\\n for (auto &w : words) {\\n HashString hs(w);\\n for (int i = 1; i <= n && i <= w.size(); i++) st[i].insert(hs.query(1, i));\\n }\\n\\n HashString hs(target);\\n int f[n + 1]; f[0] = 0;\\n // 单调指针 j,维护 f[j] = f[i] + 1 的右端点\\n int j = 0;\\n for (int i = 0; i < n; i++) {\\n // 判无解\\n if (i > j) break;\\n if (st[1].count(hs.query(i + 1, i + 1)) == 0) continue;\\n // 二分找出能追加的最长前缀\\n int head = i + 1, tail = n;\\n while (head < tail) {\\n int mid = (head + tail + 1) >> 1;\\n if (st[mid - i].count(hs.query(i + 1, mid))) head = mid;\\n else tail = mid - 1;\\n }\\n // 移动指针,更新 f[j]\\n while (j < head) f[++j] = f[i] + 1;\\n }\\n return j == n ? f[n] : -1;\\n }\\n};\\n
\\n","description":"解法:贪心 & (AC 自动机 or 跳跃游戏) 我们从最暴力的 DP 开始。设 $f(i)$ 表示形成目标字符串长度为 $i$ 的前缀的最小代价,有转移方程\\n\\n$$\\n f(i) = \\\\min(f(j) + 1)\\n $$\\n\\n设 $s[j + 1:i]$ 表示目标字符串第 $(j + 1)$ 个字符到第 $i$ 个字符构成的子串,这里 $s[j + 1:i]$ 必须是 words 里的一个前缀。直接计算这个 DP 方程的复杂度至少是 $\\\\mathcal{O}(n^2)$ 的,因此我们需要找到一些性质进行优化。\\n\\n关键性质:对于 $i\' \\\\le i$,一定有 $f(i…","guid":"https://leetcode.cn/problems/minimum-number-of-valid-strings-to-form-target-ii//solution/liang-chong-jie-fa-tan-xin-ac-zi-dong-ji-u6wc","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T04:05:09.755Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题双解:差分数组 & 哈希表+差分+排序(清晰题解)","url":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/python3javacgotypescript-yi-ti-shuang-ji-rfnw","content":"[Python3/Java/C++/Go/TypeScript] 一题双解:差分数组 & 哈希表+差分+排序(清晰题解)","description":"[Python3/Java/C++/Go/TypeScript] 一题双解:差分数组 & 哈希表+差分+排序(清晰题解)","guid":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/python3javacgotypescript-yi-ti-shuang-ji-rfnw","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T01:14:33.994Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-indices-of-stable-mountains//solution/bian-li-pythonjavacgo-by-endlesscheng-7hf1","content":"遍历下标 $0$ 到 $n-2$,如果发现 $\\\\textit{height}[i]>\\\\textit{threshold}$,就说明 $i+1$ 是稳定的,加入答案。
\\n###py
\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i for i, h in enumerate(height[:-1], 1) if h > threshold]\\n
\\n###java
\\nclass Solution {\\n public List<Integer> stableMountains(int[] height, int threshold) {\\n List<Integer> ans = new ArrayList<>();\\n for (int i = 0; i < height.length - 1; i++) {\\n if (height[i] > threshold) {\\n ans.add(i + 1);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> stableMountains(vector<int>& height, int threshold) {\\n vector<int> ans;\\n for (int i = 0; i + 1 < height.size(); i++) {\\n if (height[i] > threshold) {\\n ans.push_back(i + 1);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc stableMountains(height []int, threshold int) (ans []int) {\\nfor i, h := range height[:len(height)-1] {\\nif h > threshold {\\nans = append(ans, i+1)\\n}\\n}\\nreturn\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历下标 $0$ 到 $n-2$,如果发现 $\\\\textit{height}[i]>\\\\textit{threshold}$,就说明 $i+1$ 是稳定的,加入答案。 ###py\\n\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n return [i for i, h in enumerate(height[:-1], 1) if h > threshold]\\n\\n\\n###java\\n\\nclass…","guid":"https://leetcode.cn/problems/find-indices-of-stable-mountains//solution/bian-li-pythonjavacgo-by-endlesscheng-7hf1","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-15T00:50:37.541Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前后缀分解 + 二维 0-1 背包 + 优化所选元素个数 + 试填法(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-maximum-sequence-value-of-array//solution/qian-hou-zhui-fen-jie-er-wei-0-1-bei-bao-8icz","content":"从 $\\\\textit{nums}$ 中选一个长为 $2k$ 的子序列,计算其前一半的 OR,后一半的 OR,这两个 OR 再计算 XOR。
\\n问:计算出的 XOR 最大能是多少?
\\n把 OR 理解成一个类似加法的东西,转换成二维 0-1 背包。如果你不了解 0-1 背包,或者不理解为什么下面代码 $j$ 要倒序枚举,请看【基础算法精讲 18】。
\\n\\n\\n二维:指背包有两个约束,一个是所选元素的个数是 $k$,另一个是所选元素的 OR 是 $x$。
\\n
计算后缀。对于 0-1 背包问题,我们定义 $f[i][j][x]$ 表示从 $\\\\textit{nums}[i]$ 到 $\\\\textit{nums}[n-1]$ 中选 $j$ 个数,这些数的 OR 能否等于 $x$。
\\n设 $v=\\\\textit{nums}[i]$,用刷表法转移:
\\n\\n\\n刷表法:本题计算 $x = v\\\\ |\\\\ ?$ 中的 $?$ 是困难的,但计算 $x\\\\ |\\\\ v$ 是很简单的。也就是说,对于状态 $f[i][j][x]$ 而言,其转移来源是谁不好计算,但从 $f[i][j][x]$ 转移到的目标状态 $f[i][j+1][x\\\\ |\\\\ v]$ 是好计算的。在动态规划中,根据转移来源计算状态叫查表法,用当前状态更新其他状态叫刷表法。
\\n
初始值 $f[n][0][0]=\\\\texttt{true}$。什么也不选,OR 等于 $0$。
\\n对于每个 $i$,由于我们只需要 $f[i][k]$ 中的数据,把 $f[i][k]$ 复制到 $\\\\textit{suf}[i]$ 中。这样做无需创建三维数组,空间复杂度更小。
\\n代码实现时,$f$ 的第一个维度可以优化掉。
\\n对于前缀 $\\\\textit{pre}$ 的计算也同理。
\\n最后,枚举 $i=k-1,k,k+1,\\\\ldots,n-k-1$,两两组合 $\\\\textit{pre}[i]$ 和 $\\\\textit{suf}[i+1]$ 中的数计算 XOR,其中最大值即为答案。
\\n小优化:如果在循环中,发现答案 $\\\\textit{ans}$ 达到了理论最大值 $2^7-1$(或者所有元素的 OR),则立刻返回答案。
\\n\\n\\n也可以用哈希集合代替布尔数组,见下面的 Python 优化代码。
\\n
具体请看 视频讲解 第三题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def maxValue(self, nums: List[int], k: int) -> int:\\n mx = reduce(or_, nums)\\n n = len(nums)\\n suf = [None] * (n - k + 1)\\n f = [[False] * (mx + 1) for _ in range(k + 1)]\\n f[0][0] = True\\n for i in range(n - 1, k - 1, -1):\\n v = nums[i]\\n # 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 False\\n for j in range(min(k - 1, n - 1 - i), -1, -1):\\n for x, has_x in enumerate(f[j]):\\n if has_x:\\n f[j + 1][x | v] = True\\n if i <= n - k:\\n suf[i] = f[k].copy()\\n\\n ans = 0\\n pre = [[False] * (mx + 1) for _ in range(k + 1)]\\n pre[0][0] = True\\n for i, v in enumerate(nums[:-k]):\\n for j in range(min(k - 1, i), -1, -1):\\n for x, has_x in enumerate(pre[j]):\\n if has_x:\\n pre[j + 1][x | v] = True\\n if i < k - 1:\\n continue\\n for x, has_x in enumerate(pre[k]):\\n if has_x:\\n for y, has_y in enumerate(suf[i + 1]):\\n if has_y and x ^ y > ans: # 手写 if\\n ans = x ^ y\\n if ans == mx:\\n return ans\\n return ans\\n
\\n###py
\\n# 使用 set 代替 bool list\\nclass Solution:\\n def maxValue(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n suf = [None] * (n - k + 1)\\n f = [set() for _ in range(k + 1)]\\n f[0].add(0)\\n for i in range(n - 1, k - 1, -1):\\n v = nums[i]\\n for j in range(min(k - 1, n - 1 - i), -1, -1):\\n f[j + 1].update(x | v for x in f[j])\\n if i <= n - k:\\n suf[i] = f[k].copy()\\n\\n mx = reduce(or_, nums)\\n ans = 0\\n pre = [set() for _ in range(k + 1)]\\n pre[0].add(0)\\n for i, v in enumerate(nums[:-k]):\\n for j in range(min(k - 1, i), -1, -1):\\n pre[j + 1].update(x | v for x in pre[j])\\n if i < k - 1:\\n continue\\n ans = max(ans, max(x ^ y for x in pre[k] for y in suf[i + 1]))\\n if ans == mx:\\n return ans\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maxValue(int[] nums, int k) {\\n final int MX = 1 << 7;\\n int n = nums.length;\\n boolean[][] suf = new boolean[n - k + 1][];\\n boolean[][] f = new boolean[k + 1][MX];\\n f[0][0] = true;\\n for (int i = n - 1; i >= k; i--) {\\n int v = nums[i];\\n // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false\\n for (int j = Math.min(k - 1, n - 1 - i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (f[j][x]) {\\n f[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i <= n - k) {\\n suf[i] = f[k].clone();\\n }\\n }\\n\\n int ans = 0;\\n boolean[][] pre = new boolean[k + 1][MX];\\n pre[0][0] = true;\\n for (int i = 0; i < n - k; i++) {\\n int v = nums[i];\\n for (int j = Math.min(k - 1, i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (pre[j][x]) {\\n pre[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i < k - 1) {\\n continue;\\n }\\n for (int x = 0; x < MX; x++) {\\n if (pre[k][x]) {\\n for (int y = 0; y < MX; y++) {\\n if (suf[i + 1][y]) {\\n ans = Math.max(ans, x ^ y);\\n }\\n }\\n }\\n }\\n if (ans == MX - 1) {\\n return ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxValue(vector<int>& nums, int k) {\\n const int MX = 1 << 7;\\n int n = nums.size();\\n vector<array<int, MX>> suf(n - k + 1);\\n vector<array<int, MX>> f(k + 1);\\n f[0][0] = true;\\n for (int i = n - 1; i >= k; i--) {\\n int v = nums[i];\\n // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false\\n for (int j = min(k - 1, n - 1 - i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (f[j][x]) {\\n f[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i <= n - k) {\\n suf[i] = f[k];\\n }\\n }\\n\\n int ans = 0;\\n vector<array<int, MX>> pre(k + 1);\\n pre[0][0] = true;\\n for (int i = 0; i < n - k; i++) {\\n int v = nums[i];\\n for (int j = min(k - 1, i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (pre[j][x]) {\\n pre[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i < k - 1) {\\n continue;\\n }\\n for (int x = 0; x < MX; x++) {\\n if (pre[k][x]) {\\n for (int y = 0; y < MX; y++) {\\n if (suf[i + 1][y]) {\\n ans = max(ans, x ^ y);\\n }\\n }\\n }\\n }\\n if (ans == MX - 1) {\\n return ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxValue(nums []int, k int) (ans int) {\\nconst mx = 1 << 7\\nn := len(nums)\\nsuf := make([][mx]bool, n-k+1)\\nf := make([][mx]bool, k+1)\\nf[0][0] = true\\nfor i := n - 1; i >= k; i-- {\\nv := nums[i]\\n// 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false\\nfor j := min(k-1, n-1-i); j >= 0; j-- {\\nfor x, hasX := range f[j] {\\nif hasX {\\nf[j+1][x|v] = true\\n}\\n}\\n}\\nif i <= n-k {\\nsuf[i] = f[k]\\n}\\n}\\n\\npre := make([][mx]bool, k+1)\\npre[0][0] = true\\nfor i, v := range nums[:n-k] {\\nfor j := min(k-1, i); j >= 0; j-- {\\nfor x, hasX := range pre[j] {\\nif hasX {\\npre[j+1][x|v] = true\\n}\\n}\\n}\\nif i < k-1 {\\ncontinue\\n}\\nfor x, hasX := range pre[k] {\\nif hasX {\\nfor y, hasY := range suf[i+1] {\\nif hasY {\\nans = max(ans, x^y)\\n}\\n}\\n}\\n}\\nif ans == mx-1 {\\nreturn\\n}\\n}\\nreturn\\n}\\n
\\n例如 $x=1101_{(2)}$,我们至多要选几个 $\\\\textit{nums}[i]$,就能 OR 得到 $x$?(前提是可以得到 $x$)
\\n答案是 $3$ 个。考虑 $x$ 中的每个比特 $1$,它来自某个 $\\\\textit{nums}[i]$。
\\n设 $\\\\textit{nums}$ 所有元素 OR 的二进制中的 $1$ 的个数为 $\\\\textit{ones}$(本题数据范围保证 $textit{ones}\\\\le 7$)。一般地,我们至多选 $\\\\textit{ones}$ 个 $\\\\textit{nums}[i]$,就能 OR 得到 $x$。
\\n但是,本题要求(前缀/后缀)恰好选 $k$ 个元素。选的元素越多 OR 越大,那么某些比较小的 $x$ 可能无法 OR 出来。
\\n为了判断(前缀/后缀)恰好选 $k$ 个元素能否 OR 出整数 $x$,定义:
\\n根据 从集合论到位运算,如果能 OR 得到 $x$,那么参与 OR 运算的元素都是 $x$ 的子集。换句话说,$x$ 是参与 OR 运算的元素的超集(superset)。
\\n对于 $\\\\textit{minI}[x]$ 的计算,我们可以在遍历 $\\\\textit{nums}$ 的同时,用一个数组 $\\\\textit{cnt}$ 维护 $\\\\textit{nums}$ 元素的超集的出现次数。如果发现 $\\\\textit{cnt}[x]=k$,说明至少要遍历到 $i$ 才有可能找到 $k$ 个数 OR 等于 $x$,记录 $\\\\textit{minI}[x]=i$。对于 $\\\\textit{maxI}[x]$ 的计算也同理。
\\n对于两数异或最大值的计算,可以用试填法解决,原理请看【图解】421. 数组中两个数的最大异或值。
\\n###py
\\nclass Solution:\\n def maxValue(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n mx = reduce(or_, nums)\\n k2 = min(k, mx.bit_count()) # 至多选 k2 个数\\n\\n suf = [None] * (n - k + 1)\\n f = [set() for _ in range(k2 + 1)]\\n f[0].add(0)\\n max_i = [0] * (mx + 1)\\n cnt = [0] * (mx + 1)\\n for i in range(n - 1, k - 1, -1):\\n v = nums[i]\\n for j in range(min(k2 - 1, n - 1 - i), -1, -1):\\n f[j + 1].update(x | v for x in f[j])\\n if i <= n - k:\\n suf[i] = f[k2].copy()\\n # 枚举 v 的超集\\n s = v\\n while s <= mx:\\n cnt[s] += 1\\n if cnt[s] == k:\\n # 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n max_i[s] = i\\n s = (s + 1) | v\\n\\n ans = 0\\n pre = [set() for _ in range(k2 + 1)]\\n pre[0].add(0)\\n min_i = [inf] * (mx + 1)\\n cnt = [0] * (mx + 1)\\n w = mx.bit_length() # 用于 findMaximumXOR\\n for i, v in enumerate(nums[:-k]):\\n for j in range(min(k2 - 1, i), -1, -1):\\n pre[j + 1].update(x | v for x in pre[j])\\n # 枚举 v 的超集\\n s = v\\n while s <= mx:\\n cnt[s] += 1\\n if cnt[s] == k:\\n # 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n min_i[s] = i\\n s = (s + 1) | v\\n if i < k - 1:\\n continue\\n a = [x for x in pre[k2] if min_i[x] <= i]\\n b = [x for x in suf[i + 1] if max_i[x] > i]\\n ans = max(ans, self.findMaximumXOR(a, b, w))\\n if ans == mx:\\n return ans\\n return ans\\n\\n # 421. 数组中两个数的最大异或值\\n # 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决\\n def findMaximumXOR(self, a: List[int], b: List[int], w: int) -> int:\\n ans = mask = 0\\n for i in range(w - 1, -1, -1): # 从最高位开始枚举\\n mask |= 1 << i\\n new_ans = ans | (1 << i) # 这个比特位可以是 1 吗?\\n set_a = set(x & mask for x in a) # 低于 i 的比特位置为 0\\n for x in b:\\n x &= mask # 低于 i 的比特位置为 0\\n if new_ans ^ x in set_a:\\n ans = new_ans # 这个比特位可以是 1\\n break\\n return ans\\n
\\n###java
\\nclass Solution {\\n private static final int BIT_WIDTH = 7;\\n\\n public int maxValue(int[] nums, int k) {\\n final int MX = 1 << BIT_WIDTH;\\n int n = nums.length;\\n int k2 = Math.min(k, BIT_WIDTH); // 至多选 k2 个数\\n\\n boolean[][] suf = new boolean[n - k + 1][];\\n boolean[][] f = new boolean[k2 + 1][MX];\\n f[0][0] = true;\\n int[] maxI = new int[MX];\\n int[] cnt = new int[MX];\\n for (int i = n - 1; i >= k; i--) {\\n int v = nums[i];\\n for (int j = Math.min(k2 - 1, n - 1 - i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (f[j][x]) {\\n f[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i <= n - k) {\\n suf[i] = f[k2].clone();\\n }\\n // 枚举 v 的超集\\n for (int s = v; s < MX; s = (s + 1) | v) {\\n if (++cnt[s] == k) {\\n // 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n maxI[s] = i;\\n }\\n }\\n }\\n\\n int ans = 0;\\n boolean[][] pre = new boolean[k2 + 1][MX];\\n pre[0][0] = true;\\n int[] minI = new int[MX];\\n Arrays.fill(minI, Integer.MAX_VALUE);\\n Arrays.fill(cnt, 0);\\n int[] a = new int[MX];\\n int[] b = new int[MX];\\n for (int i = 0; i < n - k; i++) {\\n int v = nums[i];\\n for (int j = Math.min(k2 - 1, i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (pre[j][x]) {\\n pre[j + 1][x | v] = true;\\n }\\n }\\n }\\n // 枚举 v 的超集\\n for (int s = v; s < MX; s = (s + 1) | v) {\\n if (++cnt[s] == k) {\\n // 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n minI[s] = i;\\n }\\n }\\n if (i < k - 1) {\\n continue;\\n }\\n int na = 0;\\n int nb = 0;\\n for (int x = 0; x < MX; x++) {\\n if (pre[k2][x] && minI[x] <= i) {\\n a[na++] = x;\\n }\\n if (suf[i + 1][x] && maxI[x] > i) {\\n b[nb++] = x;\\n }\\n }\\n ans = Math.max(ans, findMaximumXOR(a, na, b, nb));\\n if (ans == MX - 1) {\\n return ans;\\n }\\n }\\n return ans;\\n }\\n\\n // 421. 数组中两个数的最大异或值\\n // 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决\\n private int findMaximumXOR(int[] a, int n, int[] b, int m) {\\n int ans = 0;\\n int mask = 0;\\n boolean[] seen = new boolean[1 << BIT_WIDTH];\\n for (int i = BIT_WIDTH - 1; i >= 0; i--) { // 从最高位开始枚举\\n mask |= 1 << i;\\n int newAns = ans | (1 << i); // 这个比特位可以是 1 吗?\\n Arrays.fill(seen, false);\\n for (int j = 0; j < n; j++) {\\n seen[a[j] & mask] = true; // 低于 i 的比特位置为 0\\n }\\n for (int j = 0; j < m; j++) {\\n int x = b[j] & mask; // 低于 i 的比特位置为 0\\n if (seen[newAns ^ x]) {\\n ans = newAns; // 这个比特位可以是 1\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n static constexpr int BIT_WIDTH = 7;\\n\\n // 421. 数组中两个数的最大异或值\\n // 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决\\n int findMaximumXOR(vector<int>& a, vector<int>& b) {\\n int ans = 0, mask = 0;\\n vector<int> seen(1 << BIT_WIDTH);\\n for (int i = BIT_WIDTH - 1; i >= 0; i--) { // 从最高位开始枚举\\n mask |= 1 << i;\\n int new_ans = ans | (1 << i); // 这个比特位可以是 1 吗?\\n ranges::fill(seen, false);\\n for (int x : a) {\\n seen[x & mask] = true; // 低于 i 的比特位置为 0\\n }\\n for (int x : b) {\\n x &= mask; // 低于 i 的比特位置为 0\\n if (seen[new_ans ^ x]) {\\n ans = new_ans; // 这个比特位可以是 1\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n\\npublic:\\n int maxValue(vector<int>& nums, int k) {\\n const int MX = 1 << BIT_WIDTH;\\n int n = nums.size();\\n int k2 = min(k, BIT_WIDTH); // 至多选 k2 个数\\n\\n vector<array<int, MX>> suf(n - k + 1);\\n vector<array<int, MX>> f(k2 + 1);\\n f[0][0] = true;\\n int max_i[MX]{}, cnt[MX]{};\\n for (int i = n - 1; i >= k; i--) {\\n int v = nums[i];\\n // 注意当 i 比较大的时候,循环次数应和 i 有关,因为更大的 j,对应的 f[j] 全为 false\\n for (int j = min(k2 - 1, n - 1 - i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (f[j][x]) {\\n f[j + 1][x | v] = true;\\n }\\n }\\n }\\n if (i <= n - k) {\\n suf[i] = f[k2];\\n }\\n // 枚举 v 的超集\\n for (int s = v; s < MX; s = (s + 1) | v) {\\n if (++cnt[s] == k) {\\n // 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n max_i[s] = i;\\n }\\n }\\n }\\n\\n int ans = 0;\\n vector<array<int, MX>> pre(k2 + 1);\\n pre[0][0] = true;\\n int min_i[MX];\\n ranges::fill(min_i, INT_MAX);\\n ranges::fill(cnt, 0);\\n for (int i = 0; i < n - k; i++) {\\n int v = nums[i];\\n for (int j = min(k2 - 1, i); j >= 0; j--) {\\n for (int x = 0; x < MX; x++) {\\n if (pre[j][x]) {\\n pre[j + 1][x | v] = true;\\n }\\n }\\n }\\n // 枚举 v 的超集\\n for (int s = v; s < MX; s = (s + 1) | v) {\\n if (++cnt[s] == k) {\\n // 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\n min_i[s] = i;\\n }\\n }\\n if (i < k - 1) {\\n continue;\\n }\\n vector<int> a, b;\\n for (int x = 0; x < MX; x++) {\\n if (pre[k2][x] && min_i[x] <= i) {\\n a.push_back(x);\\n }\\n if (suf[i + 1][x] && max_i[x] > i) {\\n b.push_back(x);\\n }\\n }\\n ans = max(ans, findMaximumXOR(a, b));\\n if (ans == MX - 1) {\\n return ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nconst bitWidth = 7\\nconst mx = 1 << bitWidth\\n\\nfunc maxValue(nums []int, k int) (ans int) {\\nn := len(nums)\\nk2 := min(k, bitWidth) // 至多选 k2 个数\\nsuf := make([][mx]bool, n-k+1)\\nf := make([][mx]bool, k2+1)\\nf[0][0] = true\\nmaxI := [mx]int{}\\ncnt := [mx]int{}\\nfor i := n - 1; i >= k; i-- {\\nv := nums[i]\\nfor j := min(k2-1, n-1-i); j >= 0; j-- {\\nfor x, hasX := range f[j] {\\nif hasX {\\nf[j+1][x|v] = true\\n}\\n}\\n}\\nif i <= n-k {\\nsuf[i] = f[k2]\\n}\\n// 枚举 v 的超集\\nfor s := v; s < mx; s = (s + 1) | v {\\ncnt[s]++\\nif cnt[s] == k {\\n// 从 n-1 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\nmaxI[s] = i\\n}\\n}\\n}\\n\\npre := make([][mx]bool, k2+1)\\npre[0][0] = true\\nminI := [mx]int{}\\nfor i := range minI {\\nminI[i] = math.MaxInt\\n}\\ncnt = [mx]int{}\\nfor i, v := range nums[:n-k] {\\nfor j := min(k2-1, i); j >= 0; j-- {\\nfor x, hasX := range pre[j] {\\nif hasX {\\npre[j+1][x|v] = true\\n}\\n}\\n}\\n// 枚举 v 的超集\\nfor s := v; s < mx; s = (s + 1) | v {\\ncnt[s]++\\nif cnt[s] == k {\\n// 从 0 开始遍历,至少要遍历到 i 才有可能找到 k 个数 OR 等于 s\\nminI[s] = i\\n}\\n}\\nif i < k-1 {\\ncontinue\\n}\\na := []int{}\\nb := []int{}\\nfor x, has := range pre[k2] {\\nif has && minI[x] <= i {\\na = append(a, x)\\n}\\nif suf[i+1][x] && maxI[x] > i {\\nb = append(b, x)\\n}\\n}\\nans = max(ans, findMaximumXOR(a, b))\\nif ans == mx-1 {\\nreturn\\n}\\n}\\nreturn\\n}\\n\\n// 421. 数组中两个数的最大异或值\\n// 改成两个数组的最大异或值,做法是类似的,仍然可以用【试填法】解决\\nfunc findMaximumXOR(a, b []int) (ans int) {\\nmask := 0\\nfor i := bitWidth - 1; i >= 0; i-- { // 从最高位开始枚举\\nmask |= 1 << i\\nnewAns := ans | 1<<i // 这个比特位可以是 1 吗?\\nseen := [mx]bool{}\\nfor _, x := range a {\\nseen[x&mask] = true // 低于 i 的比特位置为 0\\n}\\nfor _, x := range b {\\nx &= mask // 低于 i 的比特位置为 0\\nif seen[newAns^x] {\\nans = newAns\\nbreak\\n}\\n}\\n}\\nreturn\\n}\\n
\\n更多相似题目,见下面动态规划题单中的「§3.1 0-1 背包」和「专题:前后缀分解」。
\\n数组 $\\\\textit{height}$ 的长度是 $n$。由于下标 $0$ 的山一定不稳定,因此为了得到所有稳定山的下标,只需要考虑 $1 \\\\le i < n$ 的每个下标 $i$ 的山是否稳定。
\\n遍历 $0 \\\\le i < n$ 的每个下标 $i$,如果 $\\\\textit{height}[i - 1] > \\\\textit{threshold}$,则下标 $i$ 的山是稳定的,将 $i$ 添加到结果数组。遍历结束之后即可得到所有稳定山的下标。
\\n###Java
\\nclass Solution {\\n public List<Integer> stableMountains(int[] height, int threshold) {\\n List<Integer> stable = new ArrayList<Integer>();\\n int n = height.length;\\n for (int i = 1; i < n; i++) {\\n if (height[i - 1] > threshold) {\\n stable.add(i);\\n }\\n }\\n return stable;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public IList<int> StableMountains(int[] height, int threshold) {\\n IList<int> stable = new List<int>();\\n int n = height.Length;\\n for (int i = 1; i < n; i++) {\\n if (height[i - 1] > threshold) {\\n stable.Add(i);\\n }\\n }\\n return stable;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> stableMountains(vector<int>& height, int threshold) {\\n vector<int> stable;\\n int n = height.size();\\n for (int i = 1; i < n; i++) {\\n if (height[i - 1] > threshold) {\\n stable.push_back(i);\\n }\\n }\\n return stable;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def stableMountains(self, height: List[int], threshold: int) -> List[int]:\\n stable = []\\n n = len(height)\\n for i in range(1, n):\\n if height[i - 1] > threshold:\\n stable.append(i)\\n return stable\\n
\\n###C
\\nint* stableMountains(int* height, int heightSize, int threshold, int* returnSize) {\\n int* stable = (int*) malloc(sizeof(int) * heightSize);\\n int count = 0;\\n for (int i = 1; i < heightSize; i++) {\\n if (height[i - 1] > threshold) {\\n stable[count] = i;\\n count++;\\n }\\n }\\n *returnSize = count;\\n return stable;\\n}\\n
\\n###Go
\\nfunc stableMountains(height []int, threshold int) []int {\\n stable := []int{}\\n n := len(height)\\n for i := 1; i < n; i++ {\\n if height[i - 1] > threshold {\\n stable = append(stable, i)\\n }\\n }\\n return stable\\n}\\n
\\n###JavaScript
\\nvar stableMountains = function(height, threshold) {\\n let stable = [];\\n let n = height.length;\\n for (let i = 1; i < n; i++) {\\n if (height[i - 1] > threshold) {\\n stable.push(i);\\n }\\n }\\n return stable;\\n};\\n
\\n###TypeScript
\\nfunction stableMountains(height: number[], threshold: number): number[] {\\n let stable = [];\\n let n = height.length;\\n for (let i = 1; i < n; i++) {\\n if (height[i - 1] > threshold) {\\n stable.push(i);\\n }\\n }\\n return stable;\\n};\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{height}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(1)$。注意返回值不计入空间复杂度。
\\n一般这种一个序列分两半的题目,我们会尝试枚举分界点。
\\n设 $f(i, j, x)$ 表示从前 $i$ 个元素中选出 $j$ 个,OR 起来能否得到 $x$;$g(i, j, x)$ 表示从第 $i$ 个元素到最后一个元素中选出 $j$ 个,OR 起来能否得到 $x$。我们枚举子序列左半边的结束点 $i$,左半边 OR 起来的值 $x$,以及右半边 OR 起来的值 $y$,那么答案就是
\\n$$
\\n\\\\max\\\\limits_{f(i, k, x) = \\\\text{true and } g(i + 1, k, y) = \\\\text{true}} x \\\\oplus y
\\n$$
枚举的复杂度是 $\\\\mathcal{O}(n \\\\times m^2)$,其中 $m = 2^7$ 是题目中提到的取值范围。
\\n剩下的问题就是 $f$ 和 $g$ 怎么求。考虑是否选择第 $i$ 个元素,可以得到转移方程
\\nf(i, j, x) -> f(i + 1, j, x) // 不选 nums[i]\\nf(i, j, x) -> f(i + 1, j + 1, x | nums[i]) // 选 nums[i]\\n
\\n初值 f(0, 0, 0) = true
,其它位置初值为 false
。$g$ 的求法类似。这一部分的复杂度为 $\\\\mathcal{O}(nkm)$。
###cpp
\\nbool f[410][210][1 << 7], g[410][210][1 << 7];\\n\\nclass Solution {\\npublic:\\n int maxValue(vector<int>& nums, int K) {\\n int n = nums.size();\\n int m = 1 << 7;\\n for (int i = 0; i <= n + 1; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++)\\n f[i][j][x] = g[i][j][x] = false;\\n\\n auto update = [&](bool &x, bool y) { x = x || y; };\\n\\n // DP 求子序列前半部分的情况\\n f[0][0][0] = true;\\n for (int i = 0; i < n; i++) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (f[i][j][x]) {\\n update(f[i + 1][j][x], f[i][j][x]);\\n if (j < K) update(f[i + 1][j + 1][x | nums[i]], f[i][j][x]);\\n }\\n\\n // DP 求子序列后半部分的情况\\n g[n + 1][0][0] = true;\\n for (int i = n + 1; i > 1; i--) for (int j = 0; j <= K; j++) for (int x = 0; x < m; x++) if (g[i][j][x]) {\\n update(g[i - 1][j][x], g[i][j][x]);\\n if (j < K) update(g[i - 1][j + 1][x | nums[i - 2]], g[i][j][x]);\\n }\\n\\n int ans = 0;\\n // 枚举子序列的分界点,以及前后半的值\\n for (int i = K; i + K <= n; i++) for (int x = 0; x < m; x++) for (int y = 0; y < m; y++)\\n if (f[i][K][x] && g[i + 1][K][y]) ans = max(ans, x ^ y);\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 & DP 一般这种一个序列分两半的题目,我们会尝试枚举分界点。\\n\\n设 $f(i, j, x)$ 表示从前 $i$ 个元素中选出 $j$ 个,OR 起来能否得到 $x$;$g(i, j, x)$ 表示从第 $i$ 个元素到最后一个元素中选出 $j$ 个,OR 起来能否得到 $x$。我们枚举子序列左半边的结束点 $i$,左半边 OR 起来的值 $x$,以及右半边 OR 起来的值 $y$,那么答案就是\\n\\n$$\\n \\\\max\\\\limits_{f(i, k, x) = \\\\text{true and } g(i + 1, k, y) = \\\\text{true}}…","guid":"https://leetcode.cn/problems/find-the-maximum-sequence-value-of-array//solution/mei-ju-dp-by-tsreaper-ozq5","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-14T16:16:32.221Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-与车相交的点🟢","url":"https://leetcode.cn/problems/points-that-intersect-with-cars/","content":"给你一个下标从 0 开始的二维整数数组 nums
表示汽车停放在数轴上的坐标。对于任意下标 i
,nums[i] = [starti, endi]
,其中 starti
是第 i
辆车的起点,endi
是第 i
辆车的终点。
返回数轴上被车 任意部分 覆盖的整数点的数目。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:nums = [[3,6],[1,5],[4,7]]\\n输出:7\\n解释:从 1 到 7 的所有点都至少与一辆车相交,因此答案为 7 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [[1,3],[5,8]]\\n输出:7\\n解释:1、2、3、5、6、7、8 共计 7 个点满足至少与一辆车相交,因此答案为 7 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 100
nums[i].length == 2
1 <= starti <= endi <= 100
代码实现时,为方便起见,车站编号是一个一个出队的。
\\n有哪些公交车会经过车站 $x$?
\\n创建一个哈希表 $\\\\textit{stopToBuses}$,key 为车站编号,value 为经过该车站的公交车编号列表。
\\n遍历第 $i$ 辆公交车的路线 $\\\\textit{routes}[i]$,对于车站 $x=\\\\textit{routes}[i][j]$,把公交车编号 $i$ 加到 $\\\\textit{stopToBuses}[x]$ 列表中。
\\n在 BFS 中,如何保证每辆公交车的路线只遍历一次?
\\n可以创建一个 $\\\\textit{vis}$ 数组。更简单的办法是,当公交车路线 $\\\\textit{routes}[i]$ 遍历结束后,把 $\\\\textit{routes}[i]$ 置为空。
\\n在 BFS 中,如何保证每个车站只入队一次?
\\n为了记录起点到每个站的最短路(最少乘坐的公交车数量),创建一个哈希表 $\\\\textit{dis}$,key 为车站编号,value 为起点到该车站的最短路。
\\n我们可以利用 $\\\\textit{dis}$ 来知道车站 $x$ 是否入队过:看 $x$ 是否在 $\\\\textit{dis}$ 中即可。
\\n小优化
\\n如果没有公交车经过起点或终点,直接返回:
\\nclass Solution:\\n def numBusesToDestination(self, routes: List[List[int]], source: int, target: int) -> int:\\n # 记录经过车站 x 的公交车编号\\n stop_to_buses = defaultdict(list)\\n for i, route in enumerate(routes):\\n for x in route:\\n stop_to_buses[x].append(i)\\n\\n # 小优化:如果没有公交车经过起点或终点,直接返回\\n if source not in stop_to_buses or target not in stop_to_buses:\\n # 注意原地 TP 的情况\\n return -1 if source != target else 0\\n\\n # BFS\\n dis = {source: 0}\\n q = deque([source])\\n while q:\\n x = q.popleft() # 当前在车站 x\\n dis_x = dis[x]\\n for i in stop_to_buses[x]: # 遍历所有经过车站 x 的公交车 i\\n if routes[i]:\\n for y in routes[i]: # 遍历公交车 i 的路线\\n if y not in dis: # 没有访问过车站 y\\n dis[y] = dis_x + 1 # 从 x 站上车然后在 y 站下车\\n q.append(y)\\n routes[i] = None # 标记 routes[i] 遍历过\\n\\n return dis.get(target, -1)\\n
\\nclass Solution {\\n public int numBusesToDestination(int[][] routes, int source, int target) {\\n // 记录经过车站 x 的公交车编号\\n Map<Integer, List<Integer>> stopToBuses = new HashMap<>();\\n for (int i = 0; i < routes.length; i++) {\\n for (int x : routes[i]) {\\n stopToBuses.computeIfAbsent(x, k -> new ArrayList<>()).add(i);\\n }\\n }\\n\\n // 小优化:如果没有公交车经过起点或终点,直接返回\\n if (!stopToBuses.containsKey(source) || !stopToBuses.containsKey(target)) {\\n // 注意原地 TP 的情况\\n return source != target ? -1 : 0;\\n }\\n\\n // BFS\\n Map<Integer, Integer> dis = new HashMap<>();\\n dis.put(source, 0);\\n Queue<Integer> q = new ArrayDeque<>();\\n q.add(source);\\n while (!q.isEmpty()) {\\n int x = q.poll(); // 当前在车站 x\\n int disX = dis.get(x);\\n for (int i : stopToBuses.get(x)) { // 遍历所有经过车站 x 的公交车 i\\n if (routes[i] != null) {\\n for (int y : routes[i]) { // 遍历公交车 i 的路线\\n if (!dis.containsKey(y)) { // 没有访问过车站 y\\n dis.put(y, disX + 1); // 从 x 站上车然后在 y 站下车\\n q.add(y);\\n }\\n }\\n routes[i] = null; // 标记 routes[i] 遍历过\\n }\\n }\\n }\\n\\n return dis.getOrDefault(target, -1);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {\\n // 记录经过车站 x 的公交车编号\\n unordered_map<int, vector<int>> stop_to_buses;\\n for (int i = 0; i < routes.size(); i++) {\\n for (int x : routes[i]) {\\n stop_to_buses[x].push_back(i);\\n }\\n }\\n\\n // 小优化:如果没有公交车经过起点或终点,直接返回\\n if (!stop_to_buses.contains(source) || !stop_to_buses.contains(target)) {\\n // 注意原地 TP 的情况\\n return source != target ? -1 : 0;\\n }\\n\\n // BFS\\n unordered_map<int, int> dis;\\n dis[source] = 0;\\n queue<int> q;\\n q.push(source);\\n while (!q.empty()) {\\n int x = q.front(); // 当前在车站 x\\n q.pop();\\n int dis_x = dis[x];\\n for (int i : stop_to_buses[x]) { // 遍历所有经过车站 x 的公交车 i\\n for (int y : routes[i]) { // 遍历公交车 i 的路线\\n if (!dis.contains(y)) { // 没有访问过车站 y\\n dis[y] = dis_x + 1; // 从 x 站上车然后在 y 站下车\\n q.push(y);\\n }\\n }\\n routes[i].clear(); // 标记 routes[i] 遍历过\\n }\\n }\\n\\n return dis.contains(target) ? dis[target] : -1;\\n }\\n};\\n
\\nfunc numBusesToDestination(routes [][]int, source, target int) int {\\n // 记录经过车站 x 的公交车编号\\n stopToBuses := map[int][]int{}\\n for i, route := range routes {\\n for _, x := range route {\\n stopToBuses[x] = append(stopToBuses[x], i)\\n }\\n }\\n\\n // 小优化:如果没有公交车经过起点或终点,直接返回\\n if stopToBuses[source] == nil || stopToBuses[target] == nil {\\n if source != target {\\n return -1\\n }\\n // 注意原地 TP 的情况\\n return 0\\n }\\n\\n // BFS\\n dis := map[int]int{source: 0}\\n q := []int{source}\\n for len(q) > 0 {\\n x := q[0] // 当前在车站 x\\n q = q[1:]\\n disX := dis[x]\\n for _, i := range stopToBuses[x] { // 遍历所有经过车站 x 的公交车 i\\n for _, y := range routes[i] { // 遍历公交车 i 的路线\\n if _, ok := dis[y]; !ok { // 没有访问过车站 y\\n dis[y] = disX + 1 // 从 x 站上车然后在 y 站下车\\n q = append(q, y)\\n }\\n }\\n routes[i] = nil // 标记 routes[i] 遍历过\\n }\\n }\\n\\n if d, ok := dis[target]; ok {\\n return d\\n }\\n return -1\\n}\\n
\\nvar numBusesToDestination = function(routes, source, target) {\\n // 记录经过车站 x 的公交车编号\\n const stopToBuses = new Map();\\n for (let i = 0; i < routes.length; i++) {\\n for (const x of routes[i]) {\\n if (!stopToBuses.has(x)) {\\n stopToBuses.set(x, [i]);\\n } else {\\n stopToBuses.get(x).push(i);\\n }\\n }\\n }\\n\\n // 小优化:如果没有公交车经过起点或终点,直接返回\\n if (!stopToBuses.has(source) || !stopToBuses.has(target)) {\\n // 注意原地 TP 的情况\\n return source !== target ? -1 : 0;\\n }\\n\\n // BFS\\n const dis = new Map();\\n dis.set(source, 0);\\n const q = Array(stopToBuses.size); // 用数组模拟队列\\n q[0] = source;\\n let ql = 0, qr = 1; // 队首队尾,左闭右开区间\\n while (ql < qr) {\\n const x = q[ql++]; // 当前在车站 x\\n const disX = dis.get(x);\\n for (const i of stopToBuses.get(x)) { // 遍历所有经过车站 x 的公交车 i\\n if (routes[i]) {\\n for (const y of routes[i]) { // 遍历公交车 i 的路线\\n if (!dis.has(y)) { // 没有访问过车站 y\\n dis.set(y, disX + 1); // 从 x 站上车然后在 y 站下车\\n q[qr++] = y;\\n }\\n }\\n routes[i] = null; // 标记 routes[i] 遍历过\\n }\\n }\\n }\\n\\n return dis.get(target) ?? -1;\\n};\\n
\\nuse std::collections::{HashMap, VecDeque};\\n\\nimpl Solution {\\n pub fn num_buses_to_destination(mut routes: Vec<Vec<i32>>, source: i32, target: i32) -> i32 {\\n // 记录经过车站 x 的公交车编号\\n let mut stop_to_buses = HashMap::new();\\n for (i, route) in routes.iter().enumerate() {\\n for &x in route {\\n stop_to_buses.entry(x).or_insert(vec![]).push(i);\\n }\\n }\\n\\n // 小优化:如果没有公交车经过起点或终点,直接返回\\n if !stop_to_buses.contains_key(&source) || !stop_to_buses.contains_key(&target) {\\n // 注意原地 TP 的情况\\n return if source != target { -1 } else { 0 };\\n }\\n\\n // BFS\\n let mut dis = HashMap::new();\\n dis.insert(source, 0);\\n let mut q = VecDeque::new();\\n q.push_back(source);\\n while let Some(x) = q.pop_front() {\\n let dis_x = dis[&x]; // 当前在车站 x\\n for &i in &stop_to_buses[&x] { // 遍历所有经过车站 x 的公交车 i\\n for &y in &routes[i] { // 遍历公交车 i 的路线\\n if !dis.contains_key(&y) { // 没有访问过车站 y\\n dis.insert(y, dis_x + 1); // 从 x 站上车然后在 y 站下车\\n q.push_back(y);\\n }\\n }\\n routes[i].clear(); // 标记 routes[i] 遍历过\\n }\\n }\\n\\n dis.get(&target).copied().unwrap_or(-1)\\n }\\n}\\n
\\n把乘坐的公交车的编号,即 $\\\\textit{routes}$ 的下标,按乘坐顺序记录下来,得到一个列表 $\\\\textit{order}$。
\\n输出 $\\\\textit{order}$。
\\n如果从起点到终点有多条最短路,输出字典序最小的 $\\\\textit{order}$。
\\n欢迎在评论区分享你的思路/代码。
\\n更多相似题目,见下面图论题单中的「BFS」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 代码实现时,为方便起见,车站编号是一个一个出队的。\\n\\n细节\\n\\n有哪些公交车会经过车站 $x$?\\n\\n创建一个哈希表 $\\\\textit{stopToBuses}$,key 为车站编号,value 为经过该车站的公交车编号列表。\\n\\n遍历第 $i$ 辆公交车的路线 $\\\\textit{routes}[i]$,对于车站 $x=\\\\textit{routes}[i][j]$,把公交车编号 $i$ 加到 $\\\\textit{stopToBuses}[x]$ 列表中。\\n\\n在 BFS 中,如何保证每辆公交车的路线只遍历一次?\\n\\n可以创建一个 $\\\\textit{vis}$ 数组…","guid":"https://leetcode.cn/problems/bus-routes//solution/tu-jie-bfspythonjavacgojsrust-by-endless-t7oc","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-14T06:13:05.679Z","media":[{"url":"https://pic.leetcode.cn/1726294233-KCZYIg-lc815-c.png","type":"photo","width":2069,"height":7480}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-从字符串中移除星号🟡","url":"https://leetcode.cn/problems/removing-stars-from-a-string/","content":"给你一个包含若干星号 *
的字符串 s
。
在一步操作中,你可以:
\\n\\ns
中的一个星号。返回移除 所有 星号之后的字符串。
\\n\\n注意:
\\n\\n\\n\\n
示例 1:
\\n\\n输入:s = \\"leet**cod*e\\"\\n输出:\\"lecoe\\"\\n解释:从左到右执行移除操作:\\n- 距离第 1 个星号最近的字符是 \\"leet**cod*e\\" 中的 \'t\' ,s 变为 \\"lee*cod*e\\" 。\\n- 距离第 2 个星号最近的字符是 \\"lee*cod*e\\" 中的 \'e\' ,s 变为 \\"lecod*e\\" 。\\n- 距离第 3 个星号最近的字符是 \\"lecod*e\\" 中的 \'d\' ,s 变为 \\"lecoe\\" 。\\n不存在其他星号,返回 \\"lecoe\\" 。\\n\\n
示例 2:
\\n\\n输入:s = \\"erase*****\\"\\n输出:\\"\\"\\n解释:整个字符串都会被移除,所以返回空字符串。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= s.length <= 105
s
由小写英文字母和星号 *
组成s
可以执行上述操作为简化描述,把 $\\\\textit{start}$ 记作 $s$,把 $\\\\textit{destination}$ 记作 $t$。不妨设 $s\\\\le t$(如果 $s>t$ 则交换 $s$ 和 $t$)。
\\n从 $s$ 到 $t$,可以按照 $s,s+1,s+2,\\\\cdots,t$ 的顺序行驶。
\\n设距离为 $d_1$,那么有
\\n$$
\\nd_1 = \\\\textit{distance}[s] + \\\\textit{distance}[s+1] + \\\\cdots + \\\\textit{distance}[t-1]
\\n$$
也可以按照 $s,s-1,s-2,\\\\cdots,1,0,n-1,n-2,\\\\cdots,t$ 的顺序行驶,其距离等价于从 $0$ 到 $s$ 的距离,加上从 $t$ 到 $n-1$ 的距离,设其为 $d_2$。
\\n二者取最小值,即为答案。
\\n\\n\\n注:$d_2$ 也等于 $\\\\textit{distance}$ 的元素和减去 $d_1$。
\\n
class Solution:\\n def distanceBetweenBusStops(self, distance: List[int], s: int, t: int) -> int:\\n if s > t:\\n s, t = t, s\\n d1 = sum(distance[s: t])\\n return min(d1, sum(distance) - d1)\\n
\\nclass Solution:\\n def distanceBetweenBusStops(self, distance: List[int], s: int, t: int) -> int:\\n if s > t:\\n s, t = t, s\\n return min(sum(distance[s: t]), sum(distance[:s]) + sum(distance[t:]))\\n
\\nclass Solution {\\n public int distanceBetweenBusStops(int[] distance, int s, int t) {\\n if (s > t) { // swap(s, t)\\n int tmp = s;\\n s = t;\\n t = tmp;\\n }\\n int d1 = 0;\\n int d2 = 0;\\n for (int i = 0; i < distance.length; i++) {\\n if (s <= i && i < t) {\\n d1 += distance[i];\\n } else {\\n d2 += distance[i];\\n }\\n }\\n return Math.min(d1, d2);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int distanceBetweenBusStops(vector<int>& distance, int s, int t) {\\n if (s > t) {\\n swap(s, t);\\n }\\n int d1 = reduce(distance.begin() + s, distance.begin() + t);\\n int d2 = reduce(distance.begin(), distance.begin() + s) +\\n reduce(distance.begin() + t, distance.end());\\n return min(d1, d2);\\n }\\n};\\n
\\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\\n\\nint distanceBetweenBusStops(int* distance, int distanceSize, int s, int t) {\\n if (s > t) { // swap(s, t)\\n int tmp = s;\\n s = t;\\n t = tmp;\\n }\\n int d1 = 0, d2 = 0;\\n for (int i = 0; i < distanceSize; i++) {\\n if (s <= i && i < t) {\\n d1 += distance[i];\\n } else {\\n d2 += distance[i];\\n }\\n }\\n return MIN(d1, d2);\\n}\\n
\\nfunc distanceBetweenBusStops(distance []int, s, t int) int {\\n if s > t {\\n s, t = t, s\\n }\\n d1, d2 := 0, 0\\n for i, d := range distance {\\n if s <= i && i < t {\\n d1 += d\\n } else {\\n d2 += d\\n }\\n }\\n return min(d1, d2)\\n}\\n
\\nvar distanceBetweenBusStops = function(distance, s, t) {\\n if (s > t) {\\n [s, t] = [t, s];\\n }\\n const d1 = _.sum(distance.slice(s, t));\\n return Math.min(d1, _.sum(distance) - d1);\\n};\\n
\\nimpl Solution {\\n pub fn distance_between_bus_stops(distance: Vec<i32>, s: i32, t: i32) -> i32 {\\n let mut s = s as usize;\\n let mut t = t as usize;\\n if s > t {\\n std::mem::swap(&mut s, &mut t);\\n }\\n let d1 = distance[s..t].iter().sum::<i32>();\\n let d2 = distance[..s].iter().sum::<i32>() + distance[t..].iter().sum::<i32>();\\n d1.min(d2)\\n }\\n}\\n
\\n改成输入一个长为 $10^5$ 的 $\\\\textit{queries}$ 数组,每个 $\\\\textit{queries}[i]$ 包含两个数 $\\\\textit{start}_i$ 和 $\\\\textit{destination}_i$,如何快速计算从 $\\\\textit{start}_i$ 到 $\\\\textit{destination}_i$ 的最短距离?
\\n欢迎在评论区分享你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"为简化描述,把 $\\\\textit{start}$ 记作 $s$,把 $\\\\textit{destination}$ 记作 $t$。不妨设 $s\\\\le t$(如果 $s>t$ 则交换 $s$ 和 $t$)。 从 $s$ 到 $t$,可以按照 $s,s+1,s+2,\\\\cdots,t$ 的顺序行驶。\\n\\n设距离为 $d_1$,那么有\\n\\n$$\\n d_1 = \\\\textit{distance}[s] + \\\\textit{distance}[s+1] + \\\\cdots + \\\\textit{distance}[t-1]\\n $$\\n\\n也可以按照 $s,s-1,s-2,\\\\cdots,1…","guid":"https://leetcode.cn/problems/distance-between-bus-stops//solution/bian-li-pythonjavaccgojsrust-by-endlessc-te4r","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-13T05:34:37.982Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:双指针 + 单调队列(清晰题解)","url":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/python3javacgotypescript-yi-ti-yi-jie-sh-f185","content":"[Python3/Java/C++/Go/TypeScript] 一题一解:双指针 + 单调队列(清晰题解)","description":"[Python3/Java/C++/Go/TypeScript] 一题一解:双指针 + 单调队列(清晰题解)","guid":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/python3javacgotypescript-yi-ti-yi-jie-sh-f185","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-13T00:33:42.484Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-预算内的最多机器人数目🔴","url":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget/","content":"你有 n
个机器人,给你两个下标从 0 开始的整数数组 chargeTimes
和 runningCosts
,两者长度都为 n
。第 i
个机器人充电时间为 chargeTimes[i]
单位时间,花费 runningCosts[i]
单位时间运行。再给你一个整数 budget
。
运行 k
个机器人 总开销 是 max(chargeTimes) + k * sum(runningCosts)
,其中 max(chargeTimes)
是这 k
个机器人中最大充电时间,sum(runningCosts)
是这 k
个机器人的运行时间之和。
请你返回在 不超过 budget
的前提下,你 最多 可以 连续 运行的机器人数目为多少。
\\n\\n
示例 1:
\\n\\n输入:chargeTimes = [3,6,1,3,4], runningCosts = [2,1,3,4,5], budget = 25\\n输出:3\\n解释:\\n可以在 budget 以内运行所有单个机器人或者连续运行 2 个机器人。\\n选择前 3 个机器人,可以得到答案最大值 3 。总开销是 max(3,6,1) + 3 * sum(2,1,3) = 6 + 3 * 6 = 24 ,小于 25 。\\n可以看出无法在 budget 以内连续运行超过 3 个机器人,所以我们返回 3 。\\n\\n\\n
示例 2:
\\n\\n输入:chargeTimes = [11,12,19], runningCosts = [10,8,7], budget = 19\\n输出:0\\n解释:即使运行任何一个单个机器人,还是会超出 budget,所以我们返回 0 。\\n\\n\\n
\\n\\n
提示:
\\n\\nchargeTimes.length == runningCosts.length == n
1 <= n <= 5 * 104
1 <= chargeTimes[i], runningCosts[i] <= 105
1 <= budget <= 1015
给你一个下标从 0 开始的整数数组 nums
。
一开始,所有下标都没有被标记。你可以执行以下操作任意次:
\\n\\ni
和 j
,满足 2 * nums[i] <= nums[j]
,标记下标 i
和 j
。请你执行上述操作任意次,返回 nums
中最多可以标记的下标数目。
\\n\\n
示例 1:
\\n\\n输入:nums = [3,5,2,4]\\n输出:2\\n解释:第一次操作中,选择 i = 2 和 j = 1 ,操作可以执行的原因是 2 * nums[2] <= nums[1] ,标记下标 2 和 1 。\\n没有其他更多可执行的操作,所以答案为 2 。\\n\\n\\n
示例 2:
\\n\\n输入:nums = [9,2,5,4]\\n输出:4\\n解释:第一次操作中,选择 i = 3 和 j = 0 ,操作可以执行的原因是 2 * nums[3] <= nums[0] ,标记下标 3 和 0 。\\n第二次操作中,选择 i = 1 和 j = 2 ,操作可以执行的原因是 2 * nums[1] <= nums[2] ,标记下标 1 和 2 。\\n没有其他更多可执行的操作,所以答案为 4 。\\n\\n\\n
示例 3:
\\n\\n输入:nums = [7,6,8]\\n输出:0\\n解释:没有任何可以执行的操作,所以答案为 0 。\\n\\n\\n
\\n\\n
提示:
\\n\\n1 <= nums.length <= 105
1 <= nums[i] <= 109
思路与算法
\\n由于最早到达的乘客优先上车,为了方便模拟,我们将公交车到达的时间和乘客到达的时间按照先后顺序进行排序。设第 $i$ 班公交车到达的时间为 $\\\\textit{buses}[i]$,此时未上车且在 $\\\\textit{buses}[i]$ 时刻之前到达的乘客按照时间先后顺序依次上车,直到车辆载客人数达到上限 $\\\\textit{capacity}$ 为止,则继续模拟第 $i+1$ 班公交车乘客上车,直到所有的车辆均模拟完毕。
\\n此时记录最后一班公交车发车时的空位数为 $\\\\textit{space}$,此时有以下两种情形:
\\n如果此时 $\\\\textit{space} > 0$,则表示最后一班公交车发车时车上还有空位,这意味着我们最晚可以在最后一班公交发车时刻到站即可,由于不能跟别的乘客同时刻到达,此时从最后一班发车时刻 $\\\\textit{buses}[n - 1]$ 开始向前找到一个没有乘客到达的时刻即可;
\\n如果此时满足 $\\\\textit{space} = 0$,则表示最后一班公交车发车时车上没有空位,这意味着我们最后一个上车的乘客上车以后载客已满,此时我们从最后一个上车乘客的到达时间往前找到一个没有乘客到达的时刻即可,如果到达时间晚于最后一个上车的乘客的到达时间,则一定无法乘车。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int latestTimeCatchTheBus(vector<int>& buses, vector<int>& passengers, int capacity) {\\n sort(buses.begin(), buses.end());\\n sort(passengers.begin(), passengers.end());\\n int pos = 0;\\n int space = 0;\\n for (int arrive : buses) {\\n space = capacity;\\n while (space > 0 && pos < passengers.size() && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n \\n pos--;\\n int lastCatchTime = space > 0 ? buses.back() : passengers[pos];\\n while (pos >= 0 && passengers[pos] == lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int latestTimeCatchTheBus(int[] buses, int[] passengers, int capacity) {\\n Arrays.sort(buses);\\n Arrays.sort(passengers);\\n int pos = 0;\\n int space = 0;\\n\\n for (int arrive : buses) {\\n space = capacity;\\n while (space > 0 && pos < passengers.length && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n\\n pos--;\\n int lastCatchTime = space > 0 ? buses[buses.length - 1] : passengers[pos];\\n while (pos >= 0 && passengers[pos] == lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int LatestTimeCatchTheBus(int[] buses, int[] passengers, int capacity) {\\n Array.Sort(buses);\\n Array.Sort(passengers);\\n int pos = 0;\\n int space = 0;\\n\\n foreach (int arrive in buses) {\\n space = capacity;\\n while (space > 0 && pos < passengers.Length && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n\\n pos--;\\n int lastCatchTime = space > 0 ? buses[buses.Length - 1] : passengers[pos];\\n while (pos >= 0 && passengers[pos] == lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n }\\n}\\n
\\n###C
\\nint compare(const void *a, const void *b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint latestTimeCatchTheBus(int* buses, int busesSize, int* passengers, int passengersSize, int capacity) {\\n qsort(buses, busesSize, sizeof(int), compare);\\n qsort(passengers, passengersSize, sizeof(int), compare);\\n int pos = 0;\\n int space = 0;\\n\\n for (int i = 0; i < busesSize; i++) {\\n int arrive = buses[i];\\n space = capacity;\\n while (space > 0 && pos < passengersSize && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n\\n pos--;\\n int lastCatchTime = space > 0 ? buses[busesSize - 1] : passengers[pos];\\n while (pos >= 0 && passengers[pos] == lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n}\\n
\\n###Python
\\nclass Solution:\\n def latestTimeCatchTheBus(self, buses: List[int], passengers: List[int], capacity: int) -> int:\\n buses.sort()\\n passengers.sort()\\n pos = 0\\n\\n for arrive in buses:\\n space = capacity\\n while space > 0 and pos < len(passengers) and passengers[pos] <= arrive:\\n space -= 1\\n pos += 1\\n\\n pos -= 1\\n last_catch_time = buses[-1] if space > 0 else passengers[pos]\\n while pos >= 0 and passengers[pos] == last_catch_time:\\n pos -= 1\\n last_catch_time -= 1\\n\\n return last_catch_time\\n
\\n###Go
\\nfunc latestTimeCatchTheBus(buses []int, passengers []int, capacity int) int {\\n sort.Ints(buses)\\n sort.Ints(passengers)\\n pos := 0\\n space := 0\\n\\n for _, arrive := range buses {\\n space = capacity\\n for space > 0 && pos < len(passengers) && passengers[pos] <= arrive {\\n space--\\n pos++\\n }\\n }\\n\\n pos--\\n lastCatchTime := buses[len(buses)-1]\\n if space <= 0 {\\n lastCatchTime = passengers[pos]\\n }\\n for pos >= 0 && passengers[pos] == lastCatchTime {\\n pos--\\n lastCatchTime--\\n }\\n\\n return lastCatchTime\\n}\\n
\\n###JavaScript
\\nvar latestTimeCatchTheBus = function(buses, passengers, capacity) {\\n buses.sort((a, b) => a - b);\\n passengers.sort((a, b) => a - b);\\n let pos = 0;\\n let space = 0;\\n\\n for (const arrive of buses) {\\n space = capacity;\\n while (space > 0 && pos < passengers.length && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n\\n pos--;\\n let lastCatchTime = space > 0 ? buses[buses.length - 1] : passengers[pos];\\n while (pos >= 0 && passengers[pos] === lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n};\\n
\\n###TypeScript
\\nfunction latestTimeCatchTheBus(buses: number[], passengers: number[], capacity: number): number {\\n buses.sort((a, b) => a - b);\\n passengers.sort((a, b) => a - b);\\n let pos = 0;\\n let space = 0;\\n\\n for (const arrive of buses) {\\n space = capacity;\\n while (space > 0 && pos < passengers.length && passengers[pos] <= arrive) {\\n space--;\\n pos++;\\n }\\n }\\n\\n pos--;\\n let lastCatchTime = space > 0 ? buses[buses.length - 1] : passengers[pos];\\n while (pos >= 0 && passengers[pos] === lastCatchTime) {\\n pos--;\\n lastCatchTime--;\\n }\\n\\n return lastCatchTime;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn latest_time_catch_the_bus(buses: Vec<i32>, passengers: Vec<i32>, capacity: i32) -> i32 {\\n let mut buses = buses;\\n let mut passengers = passengers;\\n buses.sort();\\n passengers.sort();\\n let mut pos = 0;\\n let mut space = 0;\\n\\n for &arrive in &buses {\\n space = capacity;\\n while space > 0 && pos < passengers.len() && passengers[pos] <= arrive {\\n space -= 1;\\n pos += 1;\\n }\\n }\\n\\n pos -= 1;\\n let mut last_catch_time = if space > 0 { *buses.last().unwrap() } else { passengers[pos]} ;\\n while pos >= 0 && passengers[pos as usize] == last_catch_time {\\n pos -= 1;\\n last_catch_time -= 1;\\n }\\n\\n last_catch_time\\n }\\n}\\n
\\n###Cangjie
\\nimport std.sort.SortExtension\\n\\nclass Solution {\\n func latestTimeCatchTheBus(buses: Array<Int64>, passengers: Array<Int64>, capacity: Int64): Int64 {\\n buses.sort()\\n passengers.sort()\\n var pos = 0\\n var space = 0\\n for (arrive in buses) {\\n space = capacity\\n while (space > 0 && pos < passengers.size && passengers[pos] <= arrive) {\\n space--\\n pos++\\n }\\n }\\n \\n pos--\\n var lastCatchTime = buses[buses.size - 1]\\n if (space == 0) {\\n lastCatchTime = passengers[pos]\\n }\\n while (pos >= 0 && passengers[pos] == lastCatchTime) {\\n pos--\\n lastCatchTime--\\n }\\n\\n return lastCatchTime\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\log n + m \\\\log m)$,其中 $n$ 表示数组 $\\\\textit{buses}$ 的长度, 其中 $m$ 表示数组 $\\\\textit{buses}$ 的长度。排序需要的时间为 $O(n \\\\log n + m \\\\log m)$,双指针遍历两个数组需要的时间为 $O(m + n)$,因此总共需要的时间为 $O(n \\\\log n + m \\\\log m)$。
\\n空间复杂度:$O(\\\\log n + \\\\log m)$,其中 $n$ 表示数组 $\\\\textit{buses}$ 的长度, 其中 $m$ 表示数组 $\\\\textit{buses}$ 的长度。排序需要 $O(\\\\log n + \\\\log m)$ 的栈空间。
\\n\\n\\nProblem: 3270. 求出数字答案
\\n
[TOC]
\\n\\n\\n模拟+设定位基准。
\\n
\\n\\n1、模拟,除以当前位的基准,取商即为当前位数值。
\\n
###Java
\\nclass Solution {\\n public int generateKey(int num1, int num2, int num3) {\\n int rs = 0;\\n // 设定位基准\\n int basic = 1000;\\n // num放入数组\\n int[] nums = {num1, num2, num3};\\n // 枚举低四位\\n for (int i = 0; i < 4; i++) {\\n int minVal = 9;\\n // 枚举这三个num\\n for (int j = 0; j < 3; j++) {\\n // 除以当前位的基准,取商\\n int val = nums[j] / basic;\\n minVal = Math.min(val, minVal);\\n // 擦除当前位的值\\n nums[j] -= val * basic;\\n }\\n rs += minVal * basic;\\n basic /= 10;\\n }\\n return rs;\\n }\\n}\\n
\\n","description":"Problem: 3270. 求出数字答案 [TOC]\\n\\n模拟+设定位基准。\\n\\n1、模拟,除以当前位的基准,取商即为当前位数值。\\n\\n时间复杂度: $O(1)$\\n空间复杂度: $O(1)$\\n\\n###Java\\n\\nclass Solution {\\n public int generateKey(int num1, int num2, int num3) {\\n int rs = 0;\\n // 设定位基准\\n int basic = 1000;\\n // num放入数组…","guid":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/3270-qiu-chu-shu-zi-da-an-ti-jie-mo-ni-s-9zxe","author":"fa-xing-bu-luan","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-11T07:42:28.263Z","media":[{"url":"https://pic.leetcode.cn/1726040540-NVcPOi-%E6%8D%95%E8%8E%B7.JPG","type":"photo","width":687,"height":481,"blurhash":"L25Oc{reyCxb?[VGx[niyWMfpHaM"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go/TypeScript] 一题一解:二分查找(清晰题解)","url":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/python3javacgotypescript-yi-ti-yi-jie-er-zqes","content":"我们定义 $f[i]$ 表示在前 $i$ 个奖品中,选择一个长度为 $k$ 的线段,可以获得的最多奖品数目。初始时 $f[0] = 0$。定义答案变量 $ans = 0$。
\\n接下来,我们枚举每个奖品的位置 $x$,通过二分查找,找到最左边的奖品下标 $j$,使得 $\\\\textit{prizePositions}[j] \\\\geq x - k$。此时,我们更新答案 $ans = \\\\max(ans, f[j] + i - j)$,并更新 $f[i] = \\\\max(f[i - 1], i - j)$。
\\n最后,返回 $ans$ 即可。
\\n###python
\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n f = [0] * (n + 1)\\n ans = 0\\n for i, x in enumerate(prizePositions, 1):\\n j = bisect_left(prizePositions, x - k)\\n ans = max(ans, f[j] + i - j)\\n f[i] = max(f[i - 1], i - j)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maximizeWin(int[] prizePositions, int k) {\\n int n = prizePositions.length;\\n int[] f = new int[n + 1];\\n int ans = 0;\\n for (int i = 1; i <= n; ++i) {\\n int x = prizePositions[i - 1];\\n int j = search(prizePositions, x - k);\\n ans = Math.max(ans, f[j] + i - j);\\n f[i] = Math.max(f[i - 1], i - j);\\n }\\n return ans;\\n }\\n\\n private int search(int[] nums, int x) {\\n int left = 0, right = nums.length;\\n while (left < right) {\\n int mid = (left + right) >> 1;\\n if (nums[mid] >= x) {\\n right = mid;\\n } else {\\n left = mid + 1;\\n }\\n }\\n return left;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maximizeWin(vector<int>& prizePositions, int k) {\\n int n = prizePositions.size();\\n vector<int> f(n + 1);\\n int ans = 0;\\n for (int i = 1; i <= n; ++i) {\\n int x = prizePositions[i - 1];\\n int j = lower_bound(prizePositions.begin(), prizePositions.end(), x - k) - prizePositions.begin();\\n ans = max(ans, f[j] + i - j);\\n f[i] = max(f[i - 1], i - j);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maximizeWin(prizePositions []int, k int) (ans int) {\\nn := len(prizePositions)\\nf := make([]int, n+1)\\nfor i, x := range prizePositions {\\nj := sort.Search(n, func(h int) bool { return prizePositions[h] >= x-k })\\nans = max(ans, f[j]+i-j+1)\\nf[i+1] = max(f[i], i-j+1)\\n}\\nreturn\\n}\\n
\\n###ts
\\nfunction maximizeWin(prizePositions: number[], k: number): number {\\n const n = prizePositions.length;\\n const f: number[] = Array(n + 1).fill(0);\\n let ans = 0;\\n const search = (x: number): number => {\\n let left = 0;\\n let right = n;\\n while (left < right) {\\n const mid = (left + right) >> 1;\\n if (prizePositions[mid] >= x) {\\n right = mid;\\n } else {\\n left = mid + 1;\\n }\\n }\\n return left;\\n };\\n for (let i = 1; i <= n; ++i) {\\n const x = prizePositions[i - 1];\\n const j = search(x - k);\\n ans = Math.max(ans, f[j] + i - j);\\n f[i] = Math.max(f[i - 1], i - j);\\n }\\n return ans;\\n}\\n
\\n时间复杂度 $O(n \\\\times \\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 为奖品数目。
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:二分查找 我们定义 $f[i]$ 表示在前 $i$ 个奖品中,选择一个长度为 $k$ 的线段,可以获得的最多奖品数目。初始时 $f[0] = 0$。定义答案变量 $ans = 0$。\\n\\n接下来,我们枚举每个奖品的位置 $x$,通过二分查找,找到最左边的奖品下标 $j$,使得 $\\\\textit{prizePositions}[j] \\\\geq x - k$。此时,我们更新答案 $ans = \\\\max(ans, f[j] + i - j)$,并更新 $f[i] = \\\\max(f[i - 1], i - j)$。\\n\\n最后,返回 $ans$ 即可。\\n\\n###py…","guid":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/python3javacgotypescript-yi-ti-yi-jie-er-zqes","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-11T00:21:39.665Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每日一题-两个线段获得的最多奖品🟡","url":"https://leetcode.cn/problems/maximize-win-from-two-segments/","content":"在 X轴 上有一些奖品。给你一个整数数组 prizePositions
,它按照 非递减 顺序排列,其中 prizePositions[i]
是第 i
件奖品的位置。数轴上一个位置可能会有多件奖品。再给你一个整数 k
。
你可以同时选择两个端点为整数的线段。每个线段的长度都必须是 k
。你可以获得位置在任一线段上的所有奖品(包括线段的两个端点)。注意,两个线段可能会有相交。
k = 2
,你可以选择线段 [1, 3]
和 [2, 4]
,你可以获得满足 1 <= prizePositions[i] <= 3
或者 2 <= prizePositions[i] <= 4
的所有奖品 i 。请你返回在选择两个最优线段的前提下,可以获得的 最多 奖品数目。
\\n\\n\\n\\n
示例 1:
\\n\\n输入:prizePositions = [1,1,2,2,3,3,5], k = 2\\n输出:7\\n解释:这个例子中,你可以选择线段 [1, 3] 和 [3, 5] ,获得 7 个奖品。\\n\\n\\n
示例 2:
\\n\\n输入:prizePositions = [1,2,3,4], k = 0\\n输出:2\\n解释:这个例子中,一个选择是选择线段\\n\\n[3, 3]
和[4, 4]
,获得 2 个奖品。\\n
\\n\\n
提示:
\\n\\n1 <= prizePositions.length <= 105
1 <= prizePositions[i] <= 109
0 <= k <= 109
prizePositions
有序非递减。思路与算法
\\n我们可以根据题目要求直接进行模拟。
\\n首先遍历数组 $\\\\textit{nums}$ 得到坐标的最大值 $C$,然后使用一个数组 $\\\\textit{count}$ 表示每个坐标被覆盖的次数,它的下标范围是 $[1, C]$(大部分语言的数组下标都需要从 $0$ 开始,因此在代码中下标范围是 $[0, C]$)。
\\n对于数组 $\\\\textit{nums}$ 中的每个元素 $(x, y)$,我们将数组 $\\\\textit{count}$ 中下标从 $x$ 到 $y$ 的元素均增加 $1$。最后数组 $\\\\textit{count}$ 中非零元素的数量即为答案。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfPoints(vector<vector<int>>& nums) {\\n int C = 0;\\n for (const auto& interval: nums) {\\n C = max(C, interval[1]);\\n }\\n\\n vector<int> count(C + 1);\\n for (const auto& interval: nums) {\\n for (int i = interval[0]; i <= interval[1]; ++i) {\\n ++count[i];\\n }\\n }\\n\\n int ans = 0;\\n for (int i = 1; i <= C; ++i) {\\n if (count[i]) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int numberOfPoints(List<List<Integer>> nums) {\\n int C = 0;\\n for (List<Integer> interval : nums) {\\n C = Math.max(C, interval.get(1));\\n }\\n\\n int[] count = new int[C + 1];\\n for (List<Integer> interval : nums) {\\n for (int i = interval.get(0); i <= interval.get(1); ++i) {\\n ++count[i];\\n }\\n }\\n\\n int ans = 0;\\n for (int i = 1; i <= C; ++i) {\\n if (count[i] > 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfPoints(IList<IList<int>> nums) {\\n int C = 0;\\n foreach (IList<int> interval in nums) {\\n C = Math.Max(C, interval[1]);\\n }\\n\\n int[] count = new int[C + 1];\\n foreach (IList<int> interval in nums) {\\n for (int i = interval[0]; i <= interval[1]; ++i) {\\n ++count[i];\\n }\\n }\\n\\n int ans = 0;\\n for (int i = 1; i <= C; ++i) {\\n if (count[i] > 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def numberOfPoints(self, nums: List[List[int]]) -> int:\\n C = max(y for _, y in nums)\\n count = [0] * (C + 1)\\n for x, y in nums:\\n for i in range(x, y + 1):\\n count[i] += 1\\n \\n ans = sum(1 for i in range(1, C + 1) if count[i] > 0)\\n return ans\\n
\\n###Go
\\nfunc numberOfPoints(nums [][]int) int {\\n C := 0\\nfor _, interval := range nums {\\nif interval[1] > C {\\nC = interval[1]\\n}\\n}\\n\\ncount := make([]int, C + 1)\\nfor _, interval := range nums {\\nfor i := interval[0]; i <= interval[1]; i++ {\\ncount[i]++\\n}\\n}\\n\\nans := 0\\nfor i := 1; i <= C; i++ {\\nif count[i] > 0 {\\nans++\\n}\\n}\\nreturn ans\\n}\\n
\\n###C
\\nint numberOfPoints(int** nums, int numsSize, int* numsColSize){\\n int C = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i][1] > C) {\\n C = nums[i][1];\\n }\\n }\\n\\n int count[C + 1];\\n memset(count, 0, sizeof(count));\\n for (int i = 0; i < numsSize; i++) {\\n for (int j = nums[i][0]; j <= nums[i][1]; j++) {\\n count[j]++;\\n }\\n }\\n\\n int ans = 0;\\n for (int i = 1; i <= C; i++) {\\n if (count[i] > 0) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar numberOfPoints = function(nums) {\\n let C = 0;\\n for (const interval of nums) {\\n C = Math.max(C, interval[1]);\\n }\\n\\n const count = new Array(C + 1).fill(0);\\n for (const interval of nums) {\\n for (let i = interval[0]; i <= interval[1]; i++) {\\n count[i]++;\\n }\\n }\\n\\n let ans = 0;\\n for (let i = 1; i <= C; i++) {\\n if (count[i] > 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction numberOfPoints(nums: number[][]): number {\\n let C = 0;\\n for (const interval of nums) {\\n C = Math.max(C, interval[1]);\\n }\\n\\n const count = new Array(C + 1).fill(0);\\n for (const interval of nums) {\\n for (let i = interval[0]; i <= interval[1]; i++) {\\n count[i]++;\\n }\\n }\\n\\n let ans = 0;\\n for (let i = 1; i <= C; i++) {\\n if (count[i] > 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn number_of_points(nums: Vec<Vec<i32>>) -> i32 {\\n let mut C = 0;\\n for interval in &nums {\\n C = C.max(interval[1]);\\n }\\n\\n let mut count = vec![0; (C + 1) as usize];\\n for interval in &nums {\\n for i in interval[0]..=interval[1] {\\n count[i as usize] += 1;\\n }\\n }\\n\\n let mut ans = 0;\\n for i in 1..=C {\\n if count[i as usize] > 0 {\\n ans += 1;\\n }\\n }\\n\\n ans\\n }\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func numberOfPoints(nums: ArrayList<ArrayList<Int64>>): Int64 {\\n var C = 0\\n for (interval in nums) {\\n C = max(C, interval[1])\\n }\\n var count = Array<Int64>(C + 1, item: 0)\\n for (interval in nums) {\\n for (i in interval[0]..=interval[1]) {\\n count[i]++\\n }\\n }\\n var ans = 0\\n for (i in 1..=C) {\\n if (count[i] != 0) {\\n ans++\\n }\\n }\\n \\n return ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(nC)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$C$ 是数组 $\\\\textit{nums}$ 的元素范围。
\\n空间复杂度:$O(C)$,即为数组 $\\\\textit{count}$ 需要的空间。
\\n思路与算法
\\n在方法一中,对于每一辆汽车我们都需要 $O(C)$ 的时间更新数组 $\\\\textit{count}$。注意到我们一定是对数组 $\\\\textit{count}$ 的连续一段增加一个相同的值 $1$,因此可以使用差分的思想优化时间复杂度。
\\n具体地,我们令数组 $\\\\textit{diff}$ 中的每个元素是数组 $\\\\textit{count}$ 中相邻两个元素的差值,即:
\\n$$
\\n\\\\textit{diff}[i] = \\\\begin{cases}
\\n\\\\textit{count}[i], & \\\\text{if} \\\\quad i = 0 \\\\
\\n\\\\textit{count}[i] - \\\\textit{count}[i - 1], \\\\quad & \\\\text{if} \\\\quad i > 0
\\n\\\\end{cases}
\\n$$
如果我们维护数组 $\\\\textit{diff}$,那么 $\\\\textit{count}[i]$ 可以通过从 $\\\\textit{diff}[0]$ 累加到 $\\\\textit{diff}[i]$ 方便地求出。
\\n当我们需要将数组 $\\\\textit{count}$ 中下标从 $x$ 到 $y$ 的元素均增加 $1$ 时,对应到数组 $\\\\textit{diff}$,只需要将 $\\\\textit{diff}[x]$ 增加 $1$,并将 $\\\\textit{diff}[y + 1]$ 减少 $1$,时间复杂度从 $O(C)$ 降低至 $O(1)$。
\\n最后只需要对数组 $\\\\textit{diff}$ 求一遍前缀和,就还原出了数组 $\\\\textit{count}$,其中非零元素的数量即为答案。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int numberOfPoints(vector<vector<int>>& nums) {\\n int C = 0;\\n for (const auto& interval: nums) {\\n C = max(C, interval[1]);\\n }\\n\\n vector<int> diff(C + 2);\\n for (const auto& interval: nums) {\\n ++diff[interval[0]];\\n --diff[interval[1] + 1];\\n }\\n\\n int ans = 0, count = 0;\\n for (int i = 1; i <= C; ++i) {\\n count += diff[i];\\n if (count) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int numberOfPoints(List<List<Integer>> nums) {\\n int C = 0;\\n for (List<Integer> interval : nums) {\\n C = Math.max(C, interval.get(1));\\n }\\n\\n int[] diff = new int[C + 2];\\n for (List<Integer> interval : nums) {\\n ++diff[interval.get(0)];\\n --diff[interval.get(1) + 1];\\n }\\n\\n int ans = 0, count = 0;\\n for (int i = 1; i <= C; ++i) {\\n count += diff[i];\\n if (count > 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfPoints(IList<IList<int>> nums) {\\n int C = 0;\\n foreach (IList<int> interval in nums) {\\n C = Math.Max(C, interval[1]);\\n }\\n\\n int[] diff = new int[C + 2];\\n foreach (IList<int> interval in nums) {\\n ++diff[interval[0]];\\n --diff[interval[1] + 1];\\n }\\n\\n int ans = 0, count = 0;\\n for (int i = 1; i <= C; ++i) {\\n count += diff[i];\\n if (count > 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def numberOfPoints(self, nums: List[List[int]]) -> int:\\n C = max(y for _, y in nums)\\n diff = [0] * (C + 2)\\n for x, y in nums:\\n diff[x] += 1\\n diff[y + 1] -= 1\\n \\n ans = count = 0\\n for i in range(1, C + 1):\\n count += diff[i]\\n if count > 0:\\n ans += 1\\n return ans\\n
\\n###Go
\\nfunc numberOfPoints(nums [][]int) int {\\n C := 0\\nfor _, interval := range nums {\\nif interval[1] > C {\\nC = interval[1]\\n}\\n}\\n\\ndiff := make([]int, C + 2)\\nfor _, interval := range nums {\\ndiff[interval[0]]++\\ndiff[interval[1] + 1]--\\n}\\n\\nans, count := 0, 0\\nfor i := 1; i <= C; i++ {\\ncount += diff[i]\\nif count > 0 {\\nans++\\n}\\n}\\n\\nreturn ans\\n}\\n
\\n###C
\\nint numberOfPoints(int** nums, int numsSize, int* numsColSize){\\n int C = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i][1] > C) {\\n C = nums[i][1];\\n }\\n }\\n\\n int diff[C + 2];\\n memset(diff, 0, sizeof(diff));\\n for (int i = 0; i < numsSize; i++) {\\n diff[nums[i][0]]++;\\n diff[nums[i][1] + 1]--;\\n }\\n\\n int ans = 0, count = 0;\\n for (int i = 1; i <= C; i++) {\\n count += diff[i];\\n if (count > 0) {\\n ans++;\\n }\\n }\\n\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar numberOfPoints = function(nums) {\\n let C = 0;\\n for (const interval of nums) {\\n C = Math.max(C, interval[1]);\\n }\\n\\n const diff = new Array(C + 2).fill(0);\\n for (const interval of nums) {\\n diff[interval[0]]++;\\n diff[interval[1] + 1]--;\\n }\\n\\n let ans = 0, count = 0;\\n for (let i = 1; i <= C; i++) {\\n count += diff[i];\\n if (count > 0) {\\n ans++;\\n }\\n }\\n\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction numberOfPoints(nums: number[][]): number {\\n let C = 0;\\n for (const interval of nums) {\\n C = Math.max(C, interval[1]);\\n }\\n\\n const diff = new Array(C + 2).fill(0);\\n for (const interval of nums) {\\n diff[interval[0]]++;\\n diff[interval[1] + 1]--;\\n }\\n\\n let ans = 0, count = 0;\\n for (let i = 1; i <= C; i++) {\\n count += diff[i];\\n if (count > 0) {\\n ans++;\\n }\\n }\\n\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn number_of_points(nums: Vec<Vec<i32>>) -> i32 {\\n let mut C = 0;\\n for interval in &nums {\\n C = C.max(interval[1]);\\n }\\n\\n let mut diff = vec![0; (C + 2) as usize];\\n for interval in &nums {\\n diff[interval[0] as usize] += 1;\\n diff[(interval[1] + 1) as usize] -= 1;\\n }\\n\\n let mut ans = 0;\\n let mut count = 0;\\n for i in 1..=C {\\n count += diff[i as usize];\\n if count > 0 {\\n ans += 1;\\n }\\n }\\n\\n ans\\n }\\n}\\n
\\n###Cangjie
\\nclass Solution {\\n func numberOfPoints(nums: ArrayList<ArrayList<Int64>>): Int64 {\\n var C = 0\\n for (interval in nums) {\\n C = max(C, interval[1])\\n }\\n var diff = Array<Int64>(C + 2, item: 0)\\n for (interval in nums) {\\n diff[interval[0]]++\\n diff[interval[1] + 1]--\\n }\\n\\n var ans = 0\\n var count = 0\\n for (i in 1..= C) {\\n count += diff[i]\\n if (count != 0) {\\n ans++\\n }\\n }\\n return ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n + C)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$C$ 是数组 $\\\\textit{nums}$ 的元素范围。
\\n空间复杂度:$O(C)$,即为数组 $\\\\textit{diff}$ 需要的空间。
\\n思路与算法
\\n由于题目给定的数组按照非递减排序,且数组中第 $i$ 个元素表示第 $i$ 个奖品的坐标位置(位置可以重复),可以选择长度为 $k$ 两个线段,求两个线段可以覆盖奖品的最大数量。当然根据贪心法则,肯定是这两个线段尽量不要有重叠部分,这样才能使得覆盖的奖品数量尽可能的多。此时我们假设第二条线段的右侧刚好覆盖第 $i$ 个奖品所在的位置,第二条线段的左侧刚好覆盖第 $j$ 个奖品所在的位置,此时第一条线段右端点一定刚好处于第 $j-1, j-2, \\\\cdots, 0$ 个礼物的位置,即两条线段不存在重叠部分这样才能保证覆盖的奖品尽可能的多。
\\n假设我们知道第二条线段覆盖的右端点刚好为 $\\\\textit{prizePositions}[i]$,此时由于线段长度为 $k$,则此时该线段左端点为 $\\\\textit{prizePositions}[i] - k$,由于每个奖品的位置坐标已排序,此时可以通过二分找到最左侧可以覆盖的奖品为 $\\\\textit{prizePositions}[j]$,此时只需知道位于 $\\\\textit{prizePositions}[j]$ 左侧的第一条线段可以覆盖奖品奖品的最大数量,即可知道以 $\\\\textit{prizePositions}[i]$ 为第二条线段右端点时可以覆盖奖品的最大数量,依次枚举第二条线段的右端点即可知道全局最优解。
\\n我们用 $\\\\textit{dp}[i]$ 表示右端点不超过 $\\\\textit{prizePositions}[i]$,一条长度为 $k$ 的线段最多可以覆盖的奖品的最大数量,可以推导如下:
\\n我们依次枚举第二条线段的最右侧端点 $\\\\textit{prizePositions}[i]$,此时第二条线段覆盖最左侧的奖品为 $\\\\textit{prizePositions}[j]$,则此时最多可以覆盖的奖品数量为:
\\n$$ i - j + 1 + dp[j-1]$$
\\n此时 $\\\\textit{dp}[j-1]$ 表示第一条线段右端点不超过 $\\\\textit{prizePositions}[j-1]$ 时最多可以覆盖的奖品数量。枚举过程中取最大值即为最终结果,返回即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int maximizeWin(vector<int>& prizePositions, int k) {\\n int n = prizePositions.size();\\n vector<int> dp(n + 1);\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n int x = lower_bound(prizePositions.begin(), prizePositions.end(), prizePositions[i] - k) - prizePositions.begin();\\n ans = max(ans, i - x + 1 + dp[x]);\\n dp[i + 1] = max(dp[i], i - x + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long minEnd(int n, int x) {\\n int bitCount = 128 - Long.numberOfLeadingZeros(n) - Long.numberOfLeadingZeros(x);\\n long res = x;\\n long m = n - 1;\\n int j = 0;\\n for (int i = 0; i < bitCount; ++i) {\\n if (((res >> i) & 1) == 0) {\\n if (((m >> j) & 1) == 1) {\\n res |= (1L << i);\\n }\\n j++;\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###Go
\\nfunc maximizeWin(prizePositions []int, k int) int {\\n n := len(prizePositions)\\n dp := make([]int, n+1)\\n ans := 0\\n for i := 0; i < n; i++ {\\n x := sort.SearchInts(prizePositions, prizePositions[i]-k)\\n ans = max(ans, i - x + 1 + dp[x])\\n dp[i + 1] = max(dp[i], i - x + 1)\\n }\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n dp = [0] * (n + 1)\\n ans = 0\\n for i in range(n):\\n x = bisect.bisect_left(prizePositions, prizePositions[i] - k)\\n ans = max(ans, i - x + 1 + dp[x])\\n dp[i + 1] = max(dp[i], i - x + 1)\\n return ans\\n
\\n###C
\\nint lower_bound(int* arr, int size, int val) {\\n int low = 0, high = size;\\n while (low < high) {\\n int mid = (low + high) / 2;\\n if (arr[mid] < val) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return low;\\n}\\n\\nint maximizeWin(int* prizePositions, int prizePositionsSize, int k) {\\n int* dp = (int*)calloc(prizePositionsSize + 1, sizeof(int));\\n int ans = 0;\\n for (int i = 0; i < prizePositionsSize; i++) {\\n int x = lower_bound(prizePositions, prizePositionsSize, prizePositions[i] - k);\\n ans = fmax(ans, i - x + 1 + dp[x]);\\n dp[i + 1] = fmax(dp[i], i - x + 1);\\n }\\n free(dp);\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar maximizeWin = function(prizePositions, k) {\\n const n = prizePositions.length;\\n const dp = new Array(n + 1).fill(0);\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n let x = binarySearch(prizePositions, prizePositions[i] - k);\\n ans = Math.max(ans, i - x + 1 + dp[x]);\\n dp[i + 1] = Math.max(dp[i], i - x + 1);\\n }\\n return ans;\\n};\\n\\nconst binarySearch = (arr, target) => {\\n let left = 0;\\n let right = arr.length - 1;\\n while (left <= right) {\\n let mid = Math.floor((left + right) / 2);\\n if (arr[mid] < target) {\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return left;\\n}\\n
\\n###TypeScript
\\nfunction maximizeWin(prizePositions: number[], k: number): number {\\n const n = prizePositions.length;\\n const dp: number[] = new Array(n + 1).fill(0);\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n let x = binarySearch(prizePositions, prizePositions[i] - k);\\n ans = Math.max(ans, i - x + 1 + dp[x]);\\n dp[i + 1] = Math.max(dp[i], i - x + 1);\\n }\\n return ans;\\n};\\n\\nconst binarySearch = (arr: number[], target: number):number => {\\n let left = 0;\\n let right = arr.length - 1;\\n while (left <= right) {\\n let mid = Math.floor((left + right) / 2);\\n if (arr[mid] < target) {\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return left;\\n}\\n
\\n###Rust
\\nimpl Solution {\\n pub fn maximize_win(prize_positions: Vec<i32>, k: i32) -> i32 {\\n let n = prize_positions.len();\\n let mut dp = vec![0; n + 1];\\n let mut ans = 0;\\n for i in 0..n {\\n let x = Self::binarySearch(&prize_positions, prize_positions[i] - k);\\n ans = ans.max(i as i32 - x as i32 + 1 + dp[x]);\\n dp[i + 1] = dp[i].max(i as i32 - x as i32 + 1);\\n }\\n ans\\n }\\n\\n fn binarySearch(arr: &Vec<i32>, target: i32) -> usize {\\n let mut left = 0;\\n let mut right = arr.len();\\n while left < right {\\n let mid = left + (right - left) / 2;\\n if arr[mid] < target {\\n left = mid + 1;\\n } else {\\n right = mid;\\n }\\n }\\n left\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 表示给定数组的长度。每次遍历时都需要进行二分查找,二分查找的时间复杂度为 $O(\\\\log n)$,一共需要进行 $n$ 次二分查找,需要的时间为 $O(n \\\\log n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 表示给定数组的长度。需要存储每个取 $k$ 长度的最大值,需要的空间为 $O(n)$。
\\n思路与算法
\\n根据解法一可以知道,两条线段不存在重叠部分一定可以保证最优解。同样的思路与方法我们可以使用双指针枚举第二条线段的右端点。
\\n为了计算方便我们可以强制每条线段的左右端点刚好覆盖在某个奖品上,因为对于没有覆盖在奖品上的部分可以忽略。假设第二条线段覆盖的右端点刚好为 $\\\\textit{prizePositions}[\\\\textit{right}]$,设当前线段可以覆盖的最左侧奖品位置为 $\\\\textit{prizePositions}[\\\\textit{left}]$,由于线段长度为 $k$ 且奖品位置有序,此时一定满足 $\\\\textit{prizePositions}[\\\\textit{right}] - \\\\textit{prizePositions}[\\\\textit{left}] \\\\le k$,此时第二条线段覆盖的奖品数量则为 $\\\\textit{right} - \\\\textit{left} + 1$,若已知处于 $\\\\textit{prizePositions}[\\\\textit{left}]$ 左侧的线段可以覆盖奖品的最大数量,即可求出以 $\\\\textit{prizePositions}[\\\\textit{right}]$ 为第二条线段的右终点时可以覆盖的最大奖品数量。
\\n设 $\\\\textit{dp}[\\\\textit{right}]$ 表示右端点不超过 $\\\\textit{prizePositions}[\\\\textit{right}]$ 的线段可以覆盖最大奖品数量,可以得到推论如下:
\\n如果不选择位于 $\\\\textit{prizePositions}[\\\\textit{right}]$ 处的奖品,线段的右端点一定不超过 $\\\\textit{prizePositions}[\\\\textit{right} - 1]$,当前可以覆盖奖品的最大数量即为:$\\\\textit{dp}[\\\\textit{right}-1]$,此时 $\\\\textit{dp}[\\\\textit{right}] = \\\\textit{dp}[\\\\textit{right}-1]$;
\\n如果选择位于 $\\\\textit{prizePositions}[\\\\textit{right}]$ 处的奖品,由于线段长度为 $k$,需要移动左侧指针 $\\\\textit{left}$,使得满足 $\\\\textit{prizePositions}[\\\\textit{right}] - \\\\textit{prizePositions}[\\\\textit{left}] \\\\le k$ 为止,当前可以覆盖的最大奖品数量即为:$\\\\textit{right} - \\\\textit{left} + 1$,此时 $\\\\textit{dp}[\\\\textit{right}] = \\\\textit{right} - \\\\textit{left} + 1$;
\\n取二者的最大值即为右端点不超过 $\\\\textit{prizePositions}[\\\\textit{right}]$ 时,可以覆盖的最大奖品数量,递推公式如下:
\\n$$\\\\textit{dp}[\\\\textit{right}] = \\\\max(\\\\textit{dp}[\\\\textit{right}-1], \\\\textit{right} - \\\\textit{left} + 1)$$
\\n依次枚举第二条线段的最右侧端点 $\\\\textit{prizePositions}[\\\\textit{right}]$,此时第二条线段覆盖最左侧的奖品为 $\\\\textit{prizePositions}[\\\\textit{left}]$,则此时最多可以覆盖的奖品数量为:
\\n$$ \\\\textit{right} - \\\\textit{left} + 1 + dp[\\\\textit{left}-1]$$
\\n此时 $\\\\textit{dp}[\\\\textit{left}-1]$ 表示第一条线段右端点不超过 $\\\\textit{prizePositions}[\\\\textit{left}-1]$ 时最多可以覆盖的奖品数量。枚举过程中取最大值即为最终结果,返回即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int maximizeWin(vector<int>& prizePositions, int k) {\\n int n = prizePositions.size();\\n vector<int> dp(n + 1);\\n int ans = 0;\\n for (int left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = max(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = max(dp[right], right - left + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int maximizeWin(int[] prizePositions, int k) {\\n int n = prizePositions.length;\\n int[] dp = new int[n + 1];\\n int ans = 0;\\n for (int left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.max(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = Math.max(dp[right], right - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaximizeWin(int[] prizePositions, int k) {\\n int n = prizePositions.Length;\\n int[] dp = new int[n + 1];\\n int ans = 0;\\n for (int left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.Max(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = Math.Max(dp[right], right - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc maximizeWin(prizePositions []int, k int) int {\\n n := len(prizePositions)\\n dp := make([]int, n+1)\\n ans := 0\\n for left, right := 0, 0; right < n; right++ {\\n for prizePositions[right] - prizePositions[left] > k {\\n left++\\n }\\n ans = max(ans, right - left + 1 + dp[left])\\n dp[right+1] = max(dp[right], right - left + 1)\\n }\\n\\n return ans\\n}\\n
\\n###Python
\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n dp = [0] * (n + 1)\\n ans, left = 0, 0\\n for right, x in enumerate(prizePositions):\\n while x - prizePositions[left] > k:\\n left += 1\\n ans = max(ans, right - left + 1 + dp[left])\\n dp[right + 1] = max(dp[right], right - left + 1)\\n\\n return ans\\n
\\n###C
\\nint maximizeWin(int* prizePositions, int prizePositionsSize, int k) {\\n int n = prizePositionsSize;\\n int *dp = calloc(n + 1, sizeof(int));\\n int ans = 0;\\n for (int left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = fmax(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = fmax(dp[right], right - left + 1);\\n }\\n return ans;\\n}\\n
\\n###JavaScript
\\nvar maximizeWin = function(prizePositions, k) {\\n const n = prizePositions.length;\\n const dp = new Array(n + 1).fill(0);\\n let ans = 0;\\n\\n for (let left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.max(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = Math.max(dp[right], right - left + 1);\\n }\\n\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction maximizeWin(prizePositions: number[], k: number): number {\\n const n = prizePositions.length;\\n const dp = new Array(n + 1).fill(0);\\n let ans = 0;\\n\\n for (let left = 0, right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.max(ans, right - left + 1 + dp[left]);\\n dp[right + 1] = Math.max(dp[right], right - left + 1);\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn maximize_win(prize_positions: Vec<i32>, k: i32) -> i32 {\\n let n = prize_positions.len();\\n let mut dp = vec![0; n + 1];\\n let mut ans = 0;\\n let mut left = 0;\\n for right in 0..n {\\n while prize_positions[right] - prize_positions[left] > k {\\n left += 1;\\n }\\n ans = ans.max(right as i32 - left as i32 + 1 + dp[left]);\\n dp[right + 1] = dp[right].max(right as i32 - left as i32 + 1);\\n }\\n ans\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 表示给定数组的长度。双指针遍历需要的时间为 $O(n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 表示给定数组的长度。需要存储每个取 $k$ 长度的最大值,需要的空间为 $O(n)$。
\\n首先从字符串 $\\\\textit{date}$ 中得到年、月、日,然后分别将年、月、日转换为二进制表示。
\\n将非负的十进制数 $\\\\textit{num}$ 转换为二进制表示的方法如下:当 $\\\\textit{num} \\\\ne 0$ 时,每次将 $\\\\textit{num}$ 除以 $2$,反向取余数,直到 $\\\\textit{num}$ 变成零,即可得到原始 $\\\\textit{num}$ 的二进制表示。
\\n由于转换为二进制表示需要反向取余数,因此需要在计算每一位余数之后将所有余数翻转。可以依次计算日、月、年的二进制表示,将计算得到的余数按照计算顺序拼接到结果字符串,并使用分隔符分割日、月、年,此时每一部分的二进制表示都是与实际二进制表示翻转后的结果,将整个字符串翻转,即可得到年、月、日的顺序且每一部分都是正确的二进制表示。
\\n###Java
\\nclass Solution {\\n public String convertDateToBinary(String date) {\\n StringBuffer sb = new StringBuffer();\\n int year = Integer.parseInt(date.substring(0, 4));\\n int month = Integer.parseInt(date.substring(5, 7));\\n int day = Integer.parseInt(date.substring(8, 10));\\n int[] nums = {day, month, year};\\n for (int num : nums) {\\n if (sb.length() > 0) {\\n sb.append(\\"-\\");\\n }\\n appendBinary(sb, num);\\n }\\n return sb.reverse().toString();\\n }\\n\\n public void appendBinary(StringBuffer sb, int num) {\\n while (num != 0) {\\n sb.append(num % 2);\\n num /= 2;\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public string ConvertDateToBinary(string date) {\\n StringBuilder sb = new StringBuilder();\\n int year = int.Parse(date.Substring(0, 4));\\n int month = int.Parse(date.Substring(5, 2));\\n int day = int.Parse(date.Substring(8, 2));\\n int[] nums = {day, month, year};\\n foreach (int num in nums) {\\n if (sb.Length > 0) {\\n sb.Append(\\"-\\");\\n }\\n AppendBinary(sb, num);\\n }\\n StringBuilder sb2 = new StringBuilder();\\n for (int i = sb.Length - 1; i >= 0; i--) {\\n sb2.Append(sb[i]);\\n }\\n return sb2.ToString();\\n }\\n\\n public void AppendBinary(StringBuilder sb, int num) {\\n while (num != 0) {\\n sb.Append(num % 2);\\n num /= 2;\\n }\\n }\\n}\\n
\\n时间复杂度:$O(1)$。这里将字符串的长度视为常数。
\\n空间复杂度:$O(1)$。
\\n算法:
\\n-
分割。-
拼接每个二进制字符串。###py
\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n a = date.split(\'-\')\\n for i in range(len(a)):\\n a[i] = bin(int(a[i]))[2:]\\n return \'-\'.join(a)\\n
\\n###py
\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n return \'-\'.join(bin(int(s))[2:] for s in date.split(\'-\'))\\n
\\n###java
\\nclass Solution {\\n public String convertDateToBinary(String date) {\\n String[] a = date.split(\\"-\\");\\n for (int i = 0; i < a.length; i++) {\\n a[i] = Integer.toBinaryString(Integer.parseInt(a[i]));\\n }\\n return String.join(\\"-\\", a);\\n }\\n}\\n
\\n###java
\\nclass Solution {\\n public String convertDateToBinary(String date) {\\n return Arrays.stream(date.split(\\"-\\"))\\n .map(s -> Integer.toBinaryString(Integer.parseInt(s)))\\n .collect(Collectors.joining(\\"-\\"));\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string convertDateToBinary(string date) {\\n auto bin = [](int x) -> string {\\n string s = bitset<32>(x).to_string();\\n return s.substr(s.find(\'1\'));\\n };\\n return bin(stoi(date.substr(0, 4))) + \\"-\\" +\\n bin(stoi(date.substr(5, 2))) + \\"-\\" +\\n bin(stoi(date.substr(8, 2)));\\n }\\n};\\n
\\n###go
\\nfunc convertDateToBinary(date string) string {\\n a := strings.Split(date, \\"-\\")\\n for i := range a {\\n x, _ := strconv.Atoi(a[i])\\n a[i] = strconv.FormatUint(uint64(x), 2)\\n }\\n return strings.Join(a, \\"-\\")\\n}\\n
\\n###js
\\nvar convertDateToBinary = function(date) {\\n return date.split(\'-\')\\n .map(s => parseInt(s, 10).toString(2))\\n .join(\'-\');\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn convert_date_to_binary(date: String) -> String {\\n date.split(\'-\')\\n .map(|s| format!(\\"{:b}\\", s.parse::<u16>().unwrap()))\\n .collect::<Vec<_>>()\\n .join(\\"-\\")\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"算法: 把字符串 $\\\\textit{date}$ 按照 - 分割。\\n对于每个字符串,首先转成数字。\\n然后转成二进制字符串。\\n最后,用 - 拼接每个二进制字符串。\\n\\n###py\\n\\nclass Solution:\\n def convertDateToBinary(self, date: str) -> str:\\n a = date.split(\'-\')\\n for i in range(len(a)):\\n a[i] = bin(int(a[i]))[2:]\\n return \'-\'.join…","guid":"https://leetcode.cn/problems/convert-date-to-binary//solution/ku-han-shu-jian-ji-xie-fa-pythonjavacgo-ohojk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-08T04:47:02.266Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"买卖股票I和本题的思考","url":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii//solution/mai-mai-gu-piao-ihe-ben-ti-de-si-kao-by-tx5rh","content":"买卖股票I和本题的思考","description":"买卖股票I和本题的思考","guid":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii//solution/mai-mai-gu-piao-ihe-ben-ti-de-si-kao-by-tx5rh","author":"jian-xi-zhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-07T02:03:41.113Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最长的字母序连续子字符串的长度","url":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/zui-chang-de-zi-mu-xu-lian-xu-zi-zi-fu-c-n1ic","content":"思路与算法
\\n我们从左到右遍历字符串,过程中维护以当前字符结尾的最长「字母序连续子字符串」的长度 $\\\\textit{cur}$:
\\n取遍历过程中所有 $\\\\textit{cur}$ 的最大值即为答案。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n int longestContinuousSubstring(string s) {\\n int res = 1;\\n int cur = 1;\\n for (int i = 1; i < s.size(); i++) {\\n if (s[i] == s[i - 1] + 1) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = max(res, cur);\\n }\\n return res;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public int longestContinuousSubstring(String s) {\\n int res = 1;\\n int cur = 1;\\n for (int i = 1; i < s.length(); i++) {\\n if (s.charAt(i) == s.charAt(i - 1) + 1) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = Math.max(res, cur);\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int LongestContinuousSubstring(string s) {\\n int res = 1;\\n int cur = 1;\\n for (int i = 1; i < s.Length; i++) {\\n if (s[i] == s[i - 1] + 1) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = Math.Max(res, cur);\\n }\\n return res;\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n res = 1\\n cur = 1\\n for i in range(1, len(s)):\\n if ord(s[i]) == ord(s[i - 1]) + 1:\\n cur += 1\\n else:\\n cur = 1\\n res = max(res, cur)\\n return res\\n
\\n###Go
\\nfunc longestContinuousSubstring(s string) int {\\n res, cur := 1, 1\\n for i := 1; i < len(s); i++ {\\n if s[i] == s[i - 1] + byte(1) {\\n cur++\\n } else {\\n cur = 1\\n }\\n res = max(res, cur)\\n }\\n return res\\n}\\n
\\n###C
\\nint longestContinuousSubstring(char* s) {\\n int res = 1;\\n int cur = 1;\\n int len = strlen(s);\\n for (int i = 1; i < len; i++) {\\n if (s[i] == s[i - 1] + 1) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = fmax(res, cur);\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar longestContinuousSubstring = function(s) {\\n let res = 1;\\n let cur = 1;\\n for (let i = 1; i < s.length; i++) {\\n if (s[i] == String.fromCharCode(s.charCodeAt(i - 1) + 1)) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = Math.max(res, cur);\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction longestContinuousSubstring(s: string): number {\\n let res: number = 1;\\n let cur: number = 1;\\n for (let i: number = 1; i < s.length; i++) {\\n if (s[i] === String.fromCharCode(s.charCodeAt(i - 1) + 1)) {\\n cur++;\\n } else {\\n cur = 1;\\n }\\n res = Math.max(res, cur);\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn longest_continuous_substring(s: String) -> i32 {\\n let mut res = 1;\\n let mut cur = 1;\\n for i in 1..s.len() {\\n if s.as_bytes()[i] == s.as_bytes()[i - 1] + 1 {\\n cur += 1;\\n } else {\\n cur = 1;\\n }\\n res = i32::max(res, cur);\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 为字符串 $s$ 的长度。过程中对 $s$ 遍历一次,因此总体复杂度为 $O(n)$。
\\n空间复杂度:$O(1)$。过程中只使用了若干个变量。
\\n\\n\\nProblem: 3274. 检查棋盘方格颜色是否相同
\\n
[TOC]
\\nascii码值相加,判奇偶性即可。
\\n执行用时分布0ms击败100.00%;消耗内存分布7.44MB击败100.00%
\\n###C
\\nbool checkTwoChessboards(char* coordinate1, char* coordinate2) {\\n return ! ((coordinate1[0] + coordinate1[1] + coordinate2[0] + coordinate2[1]) & 1);\\n}\\n
\\n###Python3
\\nclass Solution:\\n def checkTwoChessboards(self, coordinate1: str, coordinate2: str) -> bool:\\n return sum(map(ord, coordinate1 + coordinate2)) & 1 == 0\\n
\\n","description":"Problem: 3274. 检查棋盘方格颜色是否相同 [TOC]\\n\\nascii码值相加,判奇偶性即可。\\n\\n执行用时分布0ms击败100.00%;消耗内存分布7.44MB击败100.00%\\n\\n###C\\n\\nbool checkTwoChessboards(char* coordinate1, char* coordinate2) {\\n return ! ((coordinate1[0] + coordinate1[1] + coordinate2[0] + coordinate2[1]) & 1);\\n}\\n\\n\\n###Python3\\n\\nclass…","guid":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color//solution/asciima-zhi-xiang-jia-pan-qi-ou-xing-yi-snyg0","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-02T00:37:17.942Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3274. 检查棋盘方格颜色是否相同","url":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color//solution/3274-jian-cha-qi-pan-fang-ge-yan-se-shi-dc58v","content":"国际象棋的棋盘有 $8$ 行 $8$ 列,棋盘上的每条直线和横线都有编号,每条直线的编号依次是 $\\\\text{a}$ 到 $\\\\text{h}$,每条横线的编号依次是 $1$ 到 $8$。
\\n将每条直线的编号转换成 $1$ 到 $8$ 的整数之后,即可根据格子所在的直线编号和横线编号判断格子的颜色。如果直线编号和横线编号的奇偶性相同,则是黑色格子;如果直线编号和横线编号的奇偶性不同,则是白色格子。
\\n分别计算给定的两个坐标表示的格子颜色,然后判断是否相同。
\\n###Java
\\nclass Solution {\\n static final int BLACK = 0, WHITE = 1;\\n\\n public boolean checkTwoChessboards(String coordinate1, String coordinate2) {\\n return squareColor(coordinate1) == squareColor(coordinate2);\\n }\\n\\n public int squareColor(String coordinates) {\\n int column = coordinates.charAt(0) - \'a\' + 1;\\n int row = coordinates.charAt(1) - \'0\';\\n return column % 2 == row % 2 ? BLACK : WHITE;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const int BLACK = 0, WHITE = 1;\\n\\n public bool CheckTwoChessboards(string coordinate1, string coordinate2) {\\n return SquareColor(coordinate1) == SquareColor(coordinate2);\\n }\\n\\n public int SquareColor(string coordinates) {\\n int column = coordinates[0] - \'a\' + 1;\\n int row = coordinates[1] - \'0\';\\n return column % 2 == row % 2 ? BLACK : WHITE;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\n const int BLACK = 0, WHITE = 1;\\n\\npublic:\\n bool checkTwoChessboards(string coordinate1, string coordinate2) {\\n return squareColor(coordinate1) == squareColor(coordinate2);\\n }\\n\\n int squareColor(string coordinates) {\\n int column = coordinates[0] - \'a\' + 1;\\n int row = coordinates[1] - \'0\';\\n return column % 2 == row % 2 ? BLACK : WHITE;\\n }\\n};\\n
\\n###Python
\\nBLACK, WHITE = 0, 1\\n\\nclass Solution:\\n def checkTwoChessboards(self, coordinate1: str, coordinate2: str) -> bool:\\n return self.squareColor(coordinate1) == self.squareColor(coordinate2)\\n\\n def squareColor(self, coordinates: str) -> int:\\n column = ord(coordinates[0]) - ord(\'a\') + 1\\n row = int(coordinates[1])\\n return BLACK if column % 2 == row % 2 else WHITE\\n
\\n###C
\\nconst int BLACK = 0, WHITE = 1;\\n\\nint squareColor(char* coordinates) {\\n int column = coordinates[0] - \'a\' + 1;\\n int row = coordinates[1] - \'0\';\\n return column % 2 == row % 2 ? BLACK : WHITE;\\n}\\n\\nbool checkTwoChessboards(char* coordinate1, char* coordinate2) {\\n return squareColor(coordinate1) == squareColor(coordinate2);\\n}\\n
\\n###Go
\\nconst BLACK, WHITE = 0, 1\\n\\nfunc checkTwoChessboards(coordinate1 string, coordinate2 string) bool {\\n return squareColor(coordinate1) == squareColor(coordinate2)\\n}\\n\\nfunc squareColor(coordinates string) int {\\n column := coordinates[0] - \'a\' + 1\\n row := coordinates[1] - \'0\'\\n if column % 2 == row % 2 {\\n return BLACK\\n } else {\\n return WHITE\\n }\\n}\\n
\\n###JavaScript
\\nconst BLACK = 0, WHITE = 1;\\n\\nvar checkTwoChessboards = function(coordinate1, coordinate2) {\\n return squareColor(coordinate1) === squareColor(coordinate2);\\n};\\n\\nvar squareColor = function(coordinates) {\\n let column = coordinates[0].charCodeAt(0) - \'a\'.charCodeAt(0) + 1;\\n let row = coordinates[1].charCodeAt(0) - \'0\'.charCodeAt(0);\\n return column % 2 === row % 2 ? BLACK : WHITE;\\n};\\n
\\n###TypeScript
\\nconst BLACK = 0, WHITE = 1;\\n\\nfunction checkTwoChessboards(coordinate1: string, coordinate2: string): boolean {\\n return squareColor(coordinate1) === squareColor(coordinate2);\\n};\\n\\nfunction squareColor(coordinates: string): number {\\n let column = coordinates[0].charCodeAt(0) - \'a\'.charCodeAt(0) + 1;\\n let row = coordinates[1].charCodeAt(0) - \'0\'.charCodeAt(0);\\n return column % 2 === row % 2 ? BLACK : WHITE;\\n};\\n
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n把 $\\\\textit{coordinate}_1$ 和 $\\\\textit{coordinate}_2$ 简记为 $s$ 和 $t$。
\\n根据题目中的图片,如果 $s[0]$ 和 $s[1]$ 的 ASCII 值的奇偶性相同,那么格子是黑格,否则是白格。
\\n进一步地,由于奇数+奇数=偶数,偶数+偶数=偶数,所以如果 $(s[0] + s[1])\\\\bmod 2$ 是偶数,那么格子是黑格;否则奇数+偶数=奇数,格子是白格。
\\n如果
\\n$$
\\n(s[0] + s[1])\\\\bmod 2 = (t[0] + t[1])\\\\bmod 2
\\n$$
那么两个格子颜色相同,否则不同。
\\n也可以取 $(s[0] \\\\oplus s[1])$ 的最低位,其中 $\\\\oplus$ 是异或运算。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def checkTwoChessboards(self, s: str, t: str) -> bool:\\n return (ord(s[0]) + ord(s[1])) % 2 == (ord(t[0]) + ord(t[1])) % 2\\n
\\n###java
\\nclass Solution {\\n public boolean checkTwoChessboards(String s, String t) {\\n int a = (s.charAt(0) + s.charAt(1)) % 2;\\n int b = (t.charAt(0) + t.charAt(1)) % 2;\\n return a == b;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool checkTwoChessboards(string s, string t) {\\n return ((s[0] ^ s[1]) & 1) == ((t[0] ^ t[1]) & 1);\\n }\\n};\\n
\\n###go
\\nfunc checkTwoChessboards(s, t string) bool {\\n return (s[0]^s[1])&1 == (t[0]^t[1])&1\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"把 $\\\\textit{coordinate}_1$ 和 $\\\\textit{coordinate}_2$ 简记为 $s$ 和 $t$。 根据题目中的图片,如果 $s[0]$ 和 $s[1]$ 的 ASCII 值的奇偶性相同,那么格子是黑格,否则是白格。\\n\\n进一步地,由于奇数+奇数=偶数,偶数+偶数=偶数,所以如果 $(s[0] + s[1])\\\\bmod 2$ 是偶数,那么格子是黑格;否则奇数+偶数=奇数,格子是白格。\\n\\n如果\\n\\n$$\\n (s[0] + s[1])\\\\bmod 2 = (t[0] + t[1])\\\\bmod 2\\n $$\\n\\n那么两个格子颜色相同,否则不同。\\n\\n也…","guid":"https://leetcode.cn/problems/check-if-two-chessboard-squares-have-the-same-color//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-n9bf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-01T05:19:08.574Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"回溯+全排列模板题","url":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/hui-su-quan-pai-lie-mo-ban-ti-by-sierpin-mqaz","content":"\\n\\nProblem: 3272. 统计好整数的数目
\\n
[TOC]
\\n\\n\\n先回溯找到所有可能的回文串,然后统计每个回文串的排列个数,注意排列不能0开头
\\n
###Python3
\\n@cache\\ndef fac(x):\\n if x == 0:\\n return 1\\n return x * fac(x-1)\\n\\nclass Solution:\\n def countGoodIntegers(self, n: int, k: int) -> int:\\n pow10 = [1] * n\\n for i in range(1, n):\\n pow10[i] = pow10[i - 1] * 10 % k\\n\\n st = set()\\n m = (n + 1) // 2\\n res = []\\n def dfs(i: int, j: int) -> None:\\n if i == m:\\n if j == 0:\\n cur = res.copy()\\n cur += cur[:n // 2]\\n cur.sort()\\n st.add(\\"\\".join(cur))\\n\\n return\\n \\n mn = 1 if i == 0 else 0\\n for d in range(mn, 10): \\n if n % 2 and i == m - 1: # 正中间\\n j2 = (j + d * pow10[i]) % k\\n else:\\n j2 = (j + d * (pow10[i] + pow10[-1 - i])) % k\\n res.append(str(d))\\n dfs(i + 1, j2)\\n res.pop()\\n \\n dfs(0, 0)\\n ans = 0\\n for x in st:\\n cnt = Counter(x)\\n f = fac(n)\\n for v in cnt.values():\\n f //= fac(v)\\n \\n # 减去0开头的\\n if cnt[\'0\'] > 0:\\n f1 = fac(n-1) \\n for k, v in cnt.items():\\n if k == \'0\':\\n f1 //= fac(v - 1)\\n else:\\n f1 //= fac(v)\\n f -= f1 \\n ans += f \\n \\n return ans\\n
\\n","description":"Problem: 3272. 统计好整数的数目 [TOC]\\n\\n先回溯找到所有可能的回文串,然后统计每个回文串的排列个数,注意排列不能0开头\\n\\n###Python3\\n\\n@cache\\ndef fac(x):\\n if x == 0:\\n return 1\\n return x * fac(x-1)\\n\\nclass Solution:\\n def countGoodIntegers(self, n: int, k: int) -> int:\\n pow10 = [1] * n\\n for i in range…","guid":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/hui-su-quan-pai-lie-mo-ban-ti-by-sierpin-mqaz","author":"sierpinski-f","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-01T01:42:24.558Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历数位,不用字符串(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/bian-li-shu-wei-bu-yong-zi-fu-chuan-pyth-zbln","content":"从最低位开始枚举,取三个数在该数位的最小值加入答案。然后把三个数都除以 $10$,继续枚举数位。
\\n循环直到其中一个数等于 $0$ 为止,因为后面的数位,最小值都是 $0$。
\\n为方便写代码,下面把变量名改成 $x,y,z$。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def generateKey(self, x: int, y: int, z: int) -> int:\\n ans = 0\\n pow10 = 1\\n while x and y and z:\\n ans += min(x % 10, y % 10, z % 10) * pow10\\n x //= 10\\n y //= 10\\n z //= 10\\n pow10 *= 10\\n return ans\\n
\\n###java
\\nclass Solution {\\n int generateKey(int x, int y, int z) {\\n int ans = 0;\\n for (int pow10 = 1; x > 0 && y > 0 && z > 0; pow10 *= 10) {\\n ans += Math.min(Math.min(x % 10, y % 10), z % 10) * pow10;\\n x /= 10;\\n y /= 10;\\n z /= 10;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int generateKey(int x, int y, int z) {\\n int ans = 0;\\n for (int pow10 = 1; x && y && z; pow10 *= 10) {\\n ans += min({x % 10, y % 10, z % 10}) * pow10;\\n x /= 10;\\n y /= 10;\\n z /= 10;\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint generateKey(int x, int y, int z) {\\n int ans = 0;\\n for (int pow10 = 1; x && y && z; pow10 *= 10) {\\n ans += MIN(MIN(x % 10, y % 10), z % 10) * pow10;\\n x /= 10;\\n y /= 10;\\n z /= 10;\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc generateKey(x, y, z int) (ans int) {\\nfor pow10 := 1; x > 0 && y > 0 && z > 0; pow10 *= 10 {\\nans += min(x%10, y%10, z%10) * pow10\\nx /= 10\\ny /= 10\\nz /= 10\\n}\\nreturn\\n}\\n
\\n###js
\\nvar generateKey = function(x, y, z) {\\n let ans = 0;\\n for (let pow10 = 1; x && y && z; pow10 *= 10) {\\n ans += Math.min(x % 10, y % 10, z % 10) * pow10;\\n x = Math.floor(x / 10);\\n y = Math.floor(y / 10);\\n z = Math.floor(z / 10);\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn generate_key(mut x: i32, mut y: i32, mut z: i32) -> i32 {\\n let mut ans = 0;\\n let mut pow10 = 1;\\n while x > 0 && y > 0 && z > 0 {\\n ans += (x % 10).min(y % 10).min(z % 10) * pow10;\\n x /= 10;\\n y /= 10;\\n z /= 10;\\n pow10 *= 10;\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"从最低位开始枚举,取三个数在该数位的最小值加入答案。然后把三个数都除以 $10$,继续枚举数位。 循环直到其中一个数等于 $0$ 为止,因为后面的数位,最小值都是 $0$。\\n\\n为方便写代码,下面把变量名改成 $x,y,z$。\\n\\n具体请看 视频讲解,欢迎点赞关注~\\n\\n###py\\n\\nclass Solution:\\n def generateKey(self, x: int, y: int, z: int) -> int:\\n ans = 0\\n pow10 = 1\\n while x and y and z:…","guid":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/bian-li-shu-wei-bu-yong-zi-fu-chuan-pyth-zbln","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-01T00:54:40.010Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举所有回文数+组合数学(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/mei-ju-suo-you-hui-wen-shu-zu-he-shu-xue-3d35","content":"考虑枚举所有长为 $n$ 的回文数。
\\n首先,知道了回文数的左半边,就知道了回文数的右半边,所以可以枚举回文数的左半边。
\\n设 $m = \\\\left\\\\lfloor\\\\dfrac{n-1}{2}\\\\right\\\\rfloor$,设 $\\\\textit{base} = 10^m$。
\\n在 $[\\\\textit{base}, 10\\\\cdot\\\\textit{base})$ 范围内枚举所有长为 $n$ 的回文数的左半边。
\\n如果回文数 $x$ 能被 $k$ 整除,那么接下来需要解决的问题有两个:
\\n为了保证不重复统计,可以像 49. 字母异位词分组 那样,把 $x$ 的十进制字符串 $s$ 排序,如果之前遇到过同样的字符串 $t$,那么 $s$ 生成的所有排列,$t$ 也能生成。用哈希表记录排序后的字符串,如果 $s$ 排序后在哈希表中,那么就跳过。
\\n下面是组合数学时间。
\\n本质上计算的是「有重复元素的排列个数」。
\\n统计 $s$ 中的每个数字的出现次数 $\\\\textit{cnt}$。
\\n先填最高位。由于不能有前导零,最高位可以填的数有 $n-\\\\textit{cnt}_0$ 个。其余 $n-1$ 个数随便排,有 $(n-1)!$ 种方案。
\\n当然,这里面有重复的,例如 $x=34543$,其中两个 $3$ 和两个 $4$ 的排列就是重复的,由于这两个 $3$ 无法区分,两个 $4$ 无法区分,方案数要除以 $2!2!$。
\\n综上,排列个数为
\\n$$
\\n\\\\dfrac{(n-\\\\textit{cnt}0)\\\\cdot (n-1)!}{\\\\prod\\\\limits{i=0}^{9}\\\\textit{cnt}_i!}
\\n$$
加入答案。
\\n具体请看 视频讲解 第三题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def countGoodIntegers(self, n: int, k: int) -> int:\\n fac = [factorial(i) for i in range(n + 1)]\\n ans = 0\\n vis = set()\\n base = 10 ** ((n - 1) // 2)\\n for i in range(base, base * 10): # 枚举回文数左半边\\n s = str(i)\\n s += s[::-1][n % 2:]\\n if int(s) % k: # 回文数不能被 k 整除\\n continue\\n\\n sorted_s = \'\'.join(sorted(s))\\n if sorted_s in vis: # 不能重复统计\\n continue\\n vis.add(sorted_s)\\n\\n cnt = Counter(sorted_s)\\n res = (n - cnt[\'0\']) * fac[n - 1]\\n for c in cnt.values():\\n res //= fac[c]\\n ans += res\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long countGoodIntegers(int n, int k) {\\n int[] factorial = new int[n + 1];\\n factorial[0] = 1;\\n for (int i = 1; i <= n; i++) {\\n factorial[i] = factorial[i - 1] * i;\\n }\\n\\n long ans = 0;\\n Set<String> vis = new HashSet<>();\\n int base = (int) Math.pow(10, (n - 1) / 2);\\n for (int i = base; i < base * 10; i++) { // 枚举回文数左半边\\n String s = Integer.toString(i);\\n s += new StringBuilder(s).reverse().substring(n % 2);\\n if (Long.parseLong(s) % k > 0) { // 回文数不能被 k 整除\\n continue;\\n }\\n\\n char[] sortedS = s.toCharArray();\\n Arrays.sort(sortedS);\\n if (!vis.add(new String(sortedS))) { // 不能重复统计\\n continue;\\n }\\n\\n int[] cnt = new int[10];\\n for (char c : sortedS) {\\n cnt[c - \'0\']++;\\n }\\n int res = (n - cnt[0]) * factorial[n - 1];\\n for (int c : cnt) {\\n res /= factorial[c];\\n }\\n ans += res;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countGoodIntegers(int n, int k) {\\n vector<int> factorial(n + 1);\\n factorial[0] = 1;\\n for (int i = 1; i <= n; i++) {\\n factorial[i] = factorial[i - 1] * i;\\n }\\n\\n long long ans = 0;\\n unordered_set<string> vis;\\n int base = pow(10, (n - 1) / 2);\\n for (int i = base; i < base * 10; i++) { // 枚举回文数左半边\\n string s = to_string(i);\\n s += string(s.rbegin() + (n % 2), s.rend());\\n if (stoll(s) % k) { // 回文数不能被 k 整除\\n continue;\\n }\\n\\n ranges::sort(s);\\n if (!vis.insert(s).second) { // 不能重复统计\\n continue;\\n }\\n\\n int cnt[10]{};\\n for (char c : s) {\\n cnt[c - \'0\']++;\\n }\\n int res = (n - cnt[0]) * factorial[n - 1];\\n for (int c : cnt) {\\n res /= factorial[c];\\n }\\n ans += res;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countGoodIntegers(n, k int) (ans int64) {\\nfactorial := make([]int, n+1)\\nfactorial[0] = 1\\nfor i := 1; i <= n; i++ {\\nfactorial[i] = factorial[i-1] * i\\n}\\n\\nvis := map[string]bool{}\\nbase := int(math.Pow10((n - 1) / 2))\\nfor i := base; i < base*10; i++ { // 枚举回文数左半边\\nx := i\\nt := i\\nif n%2 > 0 {\\nt /= 10\\n}\\nfor ; t > 0; t /= 10 {\\nx = x*10 + t%10\\n}\\nif x%k > 0 { // 回文数不能被 k 整除\\ncontinue\\n}\\n\\nbs := []byte(strconv.Itoa(x))\\nslices.Sort(bs)\\ns := string(bs)\\nif vis[s] { // 不能重复统计\\ncontinue\\n}\\nvis[s] = true\\n\\ncnt := [10]int{}\\nfor _, c := range bs {\\ncnt[c-\'0\']++\\n}\\nres := (n - cnt[0]) * factorial[n-1]\\nfor _, c := range cnt {\\nres /= factorial[c]\\n}\\nans += int64(res)\\n}\\nreturn\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"考虑枚举所有长为 $n$ 的回文数。 首先,知道了回文数的左半边,就知道了回文数的右半边,所以可以枚举回文数的左半边。\\n\\n设 $m = \\\\left\\\\lfloor\\\\dfrac{n-1}{2}\\\\right\\\\rfloor$,设 $\\\\textit{base} = 10^m$。\\n\\n在 $[\\\\textit{base}, 10\\\\cdot\\\\textit{base})$ 范围内枚举所有长为 $n$ 的回文数的左半边。\\n\\n如果回文数 $x$ 能被 $k$ 整除,那么接下来需要解决的问题有两个:\\n\\n计算 $x$ 有多少个不同的排列。\\n不能重复统计。\\n\\n为了保证不重复统计,可以像 4…","guid":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/mei-ju-suo-you-hui-wen-shu-zu-he-shu-xue-3d35","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-09-01T00:48:31.204Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Problem.3270 求出数字答案","url":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/problem3270-qiu-chu-shu-zi-da-an-by-zhch-hr5s","content":"Problem.3270 求出数字答案","description":"Problem.3270 求出数字答案","guid":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/problem3270-qiu-chu-shu-zi-da-an-by-zhch-hr5s","author":"zhchen2000","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-31T16:25:40.567Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 + 组合数学","url":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/mei-ju-zu-he-shu-xue-by-mipha-2022-a9mc","content":"\\n\\nProblem: 3272. 统计好整数的数目
\\n
[TOC]
\\n回文
,很容易想到折半枚举,n
最大10
位,折半枚举最大就5
位,所以:
k
整除 # 枚举\\n n_half = (n+1) // 2\\n start = 10**(n_half-1)\\n end = 10**n_half\\n dt = set()\\n for num in range(start,end,1):\\n l = str(num)\\n if n&1:\\n r = l[:-1][::-1]\\n else:\\n r = l[::-1]\\n \\n s = l+r\\n if int(s) % k == 0:\\n # 去重\\n dt.add(\'\'.join(sorted(s)))\\n
\\n将第一步得到的数字打乱,看可以得到多少个没有前缀0
的整数。
0
头位 res = 0\\n # 组合数学\\n for s in dt:\\n # 计数\\n cnt = Counter(s)\\n\\n # 开头非0\\n for w in \'123456789\':\\n if cnt[w]:\\n cnt[w] -= 1\\n # 组合数学\\n t = 1\\n rest = n - 1\\n for lw in \'0123456789\':\\n # 剩余位中插入数字\\n t *= comb(rest,cnt[lw])\\n rest -= cnt[lw]\\n # 回溯\\n cnt[w] += 1\\n res += t\\n\\n return res\\n
\\n更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def countGoodIntegers(self, n: int, k: int) -> int:\\n \'\'\'\\n 打表\\n 折半 + 组合数字\\n \'\'\'\\n \\n # 枚举\\n n_half = (n+1) // 2\\n start = 10**(n_half-1)\\n end = 10**n_half\\n dt = set()\\n for num in range(start,end,1):\\n l = str(num)\\n if n&1:\\n r = l[:-1][::-1]\\n else:\\n r = l[::-1]\\n \\n s = l+r\\n if int(s) % k == 0:\\n # 去重\\n dt.add(\'\'.join(sorted(s)))\\n \\n res = 0\\n # 组合数学\\n for s in dt:\\n # 计数\\n cnt = Counter(s)\\n\\n # 开头非0\\n for w in \'123456789\':\\n if cnt[w]:\\n cnt[w] -= 1\\n # 组合数学\\n t = 1\\n rest = n - 1\\n for lw in \'0123456789\':\\n if cnt[lw]:\\n t *= comb(rest,cnt[lw])\\n rest -= cnt[lw]\\n # 回溯\\n cnt[w] += 1\\n res += t\\n\\n return res\\n
\\n","description":"Problem: 3272. 统计好整数的数目 [TOC]\\n\\n枚举\\n\\n回文,很容易想到折半枚举,n最大10位,折半枚举最大就5位,所以:\\n\\n枚举回文左边\\n镜像得到右边\\n拼接成完整数字,再判断该数字能否被k整除\\n # 枚举\\n n_half = (n+1) // 2\\n start = 10**(n_half-1)\\n end = 10**n_half\\n dt = set()\\n for num in range(start,end,1):\\n l…","guid":"https://leetcode.cn/problems/find-the-count-of-good-integers//solution/mei-ju-zu-he-shu-xue-by-mipha-2022-a9mc","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-31T16:15:19.574Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Leetcode 3270.求出数字答案(C++小白写法)","url":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/leetcode-3270qiu-chu-shu-zi-da-an-cxiao-kzsu4","content":"class Solution {
\\npublic:
\\nint generateKey(int num1, int num2, int num3) {
\\nint result = 0;
\\nfor(int i = 0;i <= 3;i++){
\\nresult += min(min(num1%10,num2%10),num3%10)*pow(10,i);
\\nif(num1 != 0){
\\nnum1 = num1/10;
\\n}
\\nif(num2 != 0){
\\nnum2 = num2/10;
\\n}
\\nif(num3 != 0){
\\nnum3 = num3/10;
\\n}
\\n}
\\nreturn result;
\\n}
\\n};
\\n","description":"class Solution { public:\\n\\nint generateKey(int num1, int num2, int num3) {\\n\\nint result = 0;\\n\\nfor(int i = 0;i <= 3;i++){\\n\\nresult += min(min(num1%10,num2%10),num3%10)*pow(10,i);\\n\\nif(num1 != 0){\\n\\nnum1 = num1/10;\\n\\n}…","guid":"https://leetcode.cn/problems/find-the-key-of-the-numbers//solution/leetcode-3270qiu-chu-shu-zi-da-an-cxiao-kzsu4","author":"yu-gada-hao-kidesu","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-31T16:10:35.709Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"优先队列模拟+快速幂处理特别大的k","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/you-xian-dui-lie-mo-ni-kuai-su-mi-chu-li-77ir","content":"优先队列模拟+快速幂处理特别大的k","description":"优先队列模拟+快速幂处理特别大的k","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/you-xian-dui-lie-mo-ni-kuai-su-mi-chu-li-77ir","author":"7NTxx-13","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-25T14:53:50.973Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3264. K 次乘运算后的最终数组 I","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-i//solution/3264-k-ci-cheng-yun-suan-hou-de-zui-zhon-6ora","content":"当 $\\\\textit{multiplier} = 1$ 时,数组 $\\\\textit{nums}$ 中的任何元素乘以 $1$ 之后保持不变,因此等价于不执行操作,直接返回数组 $\\\\textit{nums}$。以下只考虑 $\\\\textit{multiplier} > 1$ 的情况。
\\n可以模拟对数组 $\\\\textit{nums}$ 执行的 $k$ 次操作。为了能快速定位到数组 $\\\\textit{nums}$ 中的元素值最小且下标最小的元素,需要使用优先队列存储数组 $\\\\textit{nums}$ 中的每个元素与下标组成的二元组,优先队列的队首元素是元素值最小的二元组,如果有多个元素值并列最小则优先队列的队首元素是元素值最小且下标最小的二元组。
\\n首先遍历数组 $\\\\textit{nums}$ 将每个元素与下标组成的二元组加入优先队列,然后执行 $k$ 次操作。每次执行操作的做法如下。
\\n取出优先队列的队首二元组,将该二元组中的下标记为 $\\\\textit{index}$。
\\n将 $\\\\textit{nums}[\\\\textit{index}]$ 的值更新为 $\\\\textit{nums}[\\\\textit{index}] \\\\times \\\\textit{multiplier}$。
\\n将二元组 $[\\\\textit{nums}[\\\\textit{index}], \\\\textit{index}]$ 加入优先队列。
\\n执行 $k$ 次操作之后,数组 $\\\\textit{nums}$ 即为结果数组。
\\n用 $n$ 表示数组 $\\\\textit{nums}$ 的长度。上述做法中,将数组 $\\\\textit{nums}$ 中的每个元素与下标组成的二元组加入优先队列的时间是 $O(n \\\\log n)$,执行每次操作的时间是 $O(\\\\log n)$,因此时间复杂度是 $O((n + k) \\\\log n)$。
\\n###Java
\\nclass Solution {\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) {\\n return nums;\\n }\\n PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] != b[0] ? Integer.compare(a[0], b[0]) : Integer.compare(a[1], b[1]));\\n int n = nums.length;\\n for (int i = 0; i < n; i++) {\\n pq.offer(new int[]{nums[i], i});\\n }\\n while (k > 0) {\\n int[] pair = pq.poll();\\n int index = pair[1];\\n nums[index] *= multiplier;\\n pq.offer(new int[]{nums[index], index});\\n k--;\\n }\\n return nums;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] GetFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) {\\n return nums;\\n }\\n PriorityQueue<int, long> pq = new PriorityQueue<int, long>();\\n int n = nums.Length;\\n for (int i = 0; i < n; i++) {\\n pq.Enqueue(i, (long) nums[i] * n + i);\\n }\\n while (k > 0) {\\n int index = pq.Dequeue();;\\n nums[index] *= multiplier;\\n pq.Enqueue(index, (long) nums[index] * n + index);\\n k--;\\n }\\n return nums;\\n }\\n}\\n
\\n时间复杂度:$O((n + k) \\\\log n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$k$ 是给定的整数。将数组 $\\\\textit{nums}$ 中的每个元素与下标组成的二元组加入优先队列的时间是 $O(n \\\\log n)$,执行每次操作的时间是 $O(\\\\log n)$,因此时间复杂度是 $O((n + k) \\\\log n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。优先队列的空间是 $O(n)$。
\\n本题和周赛第三题是一样的,请看 我的题解。
\\n","description":"本题和周赛第三题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-i//solution/fei-bao-li-zuo-fa-jie-jue-k-hen-da-de-qi-vbpf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-25T05:48:06.478Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最小堆模拟+数学公式+优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/zui-xiao-dui-mo-ni-shu-xue-gong-shi-pyth-z4zw","content":"核心观察:对于两个数 $x$ 和 $y$,如果 $x$ 在 $y$ 左边,且 $x\\\\le y$ 以及 $x\\\\cdot \\\\textit{multiplier} > y$,那么操作 $y$ 之后,根据 $x\\\\le y$,我们有 $x\\\\cdot \\\\textit{multiplier} \\\\le y\\\\cdot \\\\textit{multiplier}$,这意味着下一次一定会操作 $x$。继续推导下去,后面的操作顺序是 $y,x,y,x,\\\\cdots$
\\n这意味着当两个数接近时,我们会交替操作这两个数,而不会连续操作同一个数。
\\n对于更多的数的情况也同理,当这些数接近时,我们会按照从小到大的顺序依次操作这些数。
\\n那么,首先用最小堆手动模拟操作,直到原数组的最大值 $\\\\textit{mx}$ 成为这 $n$ 个数的最小值。根据上面的结论,后面的操作就不需要手动模拟了。
\\n设此时还剩下 $k$ 次操作,那么:
\\n用快速幂计算操作这么多次后的结果,原理见【图解】一张图秒懂快速幂。
\\n具体请看 视频讲解,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n if multiplier == 1: # 数组不变\\n return nums\\n\\n MOD = 1_000_000_007\\n n = len(nums)\\n mx = max(nums)\\n h = [(x, i) for i, x in enumerate(nums)]\\n heapify(h)\\n\\n # 模拟,直到堆顶是 mx\\n while k and h[0][0] < mx:\\n x, i = h[0]\\n heapreplace(h, (x * multiplier, i))\\n k -= 1\\n\\n # 剩余的操作可以直接用公式计算\\n h.sort()\\n for i, (x, j) in enumerate(h):\\n nums[j] = x * pow(multiplier, k // n + (i < k % n), MOD) % MOD\\n return nums\\n
\\n###py
\\n# 也可以模拟到 k 刚好是 n 的倍数时才停止,这样最后无需排序\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n if multiplier == 1: # 数组不变\\n return nums\\n\\n MOD = 1_000_000_007\\n n = len(nums)\\n mx = max(nums)\\n h = [(x, i) for i, x in enumerate(nums)]\\n heapify(h)\\n\\n # 模拟,直到堆顶是 mx\\n while k and (h[0][0] < mx or k % n):\\n x, i = h[0]\\n heapreplace(h, (x * multiplier, i))\\n k -= 1\\n\\n # 剩余的操作可以直接用公式计算\\n for x, j in h:\\n nums[j] = x * pow(multiplier, k // n, MOD) % MOD\\n return nums\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return nums;\\n }\\n\\n int n = nums.length;\\n int mx = 0;\\n PriorityQueue<long[]> pq = new PriorityQueue<>((a, b) -> a[0] != b[0] ? Long.compare(a[0], b[0]) : Long.compare(a[1], b[1]));\\n for (int i = 0; i < n; i++) {\\n mx = Math.max(mx, nums[i]);\\n pq.offer(new long[]{nums[i], i});\\n }\\n\\n // 模拟,直到堆顶是 mx\\n for (; k > 0 && pq.peek()[0] < mx; k--) {\\n long[] p = pq.poll();\\n p[0] *= multiplier;\\n pq.offer(p);\\n }\\n\\n // 剩余的操作可以直接用公式计算\\n for (int i = 0; i < n; i++) {\\n long[] p = pq.poll();\\n nums[(int) p[1]] = (int) (p[0] % MOD * pow(multiplier, k / n + (i < k % n ? 1 : 0)) % MOD);\\n }\\n return nums;\\n }\\n\\n private long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\n\\n long long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return move(nums);\\n }\\n\\n int n = nums.size();\\n int mx = ranges::max(nums);\\n vector<pair<long long, int>> h(n);\\n for (int i = 0; i < n; i++) {\\n h[i] = {nums[i], i};\\n }\\n ranges::make_heap(h, greater<>()); // 最小堆,O(n) 堆化\\n\\n // 模拟,直到堆顶是 mx\\n for (; k && h[0].first < mx; k--) {\\n ranges::pop_heap(h, greater<>());\\n h.back().first *= multiplier;\\n ranges::push_heap(h, greater<>());\\n }\\n\\n // 剩余的操作可以直接用公式计算\\n ranges::sort(h);\\n for (int i = 0; i < n; i++) {\\n auto& [x, j] = h[i];\\n nums[j] = x % MOD * pow(multiplier, k / n + (i < k % n)) % MOD;\\n }\\n return move(nums);\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\nif multiplier == 1 { // 数组不变\\nreturn nums\\n}\\n\\nn := len(nums)\\nmx := 0\\nh := make(hp, n)\\nfor i, x := range nums {\\nmx = max(mx, x)\\nh[i] = pair{x, i}\\n}\\nheap.Init(&h)\\n\\n// 模拟,直到堆顶是 mx\\nfor ; k > 0 && h[0].x < mx; k-- {\\nh[0].x *= multiplier\\nheap.Fix(&h, 0)\\n}\\n\\n// 剩余的操作可以直接用公式计算\\nsort.Slice(h, func(i, j int) bool { return less(h[i], h[j]) })\\nfor i, p := range h {\\ne := k / n\\nif i < k%n {\\ne++\\n}\\nnums[p.i] = p.x % mod * pow(multiplier, e) % mod\\n}\\nreturn nums\\n}\\n\\ntype pair struct{ x, i int }\\nfunc less(a, b pair) bool { return a.x < b.x || a.x == b.x && a.i < b.i }\\n\\ntype hp []pair\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return less(h[i], h[j]) }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (hp) Push(any) {}\\nfunc (hp) Pop() (_ any) { return }\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n设把每个 $\\\\textit{nums}[i]$ 都操作到至少为 $\\\\textit{nums}$ 的最大值,总共需要操作 $\\\\textit{total}$ 次。
\\n分类讨论:
\\n此外,可以先把 pow
算出来,而不是在循环中计算。
###py
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n if multiplier == 1: # 数组不变\\n return nums\\n\\n MOD = 1_000_000_007\\n n = len(nums)\\n mn = min(nums)\\n mx = max(nums)\\n t = 0\\n while mn < mx:\\n mn *= multiplier\\n t += 1\\n\\n if k < t * n:\\n # 暴力模拟\\n h = [(x, i) for i, x in enumerate(nums)]\\n heapify(h)\\n for _ in range(k):\\n x, i = h[0]\\n heapreplace(h, (x * multiplier, i))\\n for x, j in h:\\n nums[j] = x % MOD\\n return nums\\n\\n # 每个数直接暴力操作到 >= mx\\n for i, x in enumerate(nums):\\n while x < mx:\\n x *= multiplier\\n k -= 1\\n nums[i] = x\\n\\n # 剩余的操作可以直接用公式计算\\n pow1 = pow(multiplier, k // n, MOD)\\n pow2 = pow1 * multiplier % MOD\\n for i, (x, j) in enumerate(sorted((x, i) for i, x in enumerate(nums))):\\n nums[j] = x * (pow2 if i < k % n else pow1) % MOD\\n return nums\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return nums;\\n }\\n\\n int n = nums.length;\\n int mx = 0;\\n for (int x : nums) {\\n mx = Math.max(mx, x);\\n }\\n\\n // 每个数直接暴力操作到 >= mx\\n long[] a = new long[n];\\n int left = k;\\n outer:\\n for (int i = 0; i < n; i++) {\\n long x = nums[i];\\n while (x < mx) {\\n x *= multiplier;\\n if (--left < 0) {\\n break outer;\\n }\\n }\\n a[i] = x;\\n }\\n\\n if (left < 0) {\\n // 暴力模拟\\n PriorityQueue<long[]> pq = new PriorityQueue<>((p, q) -> p[0] != q[0] ? Long.compare(p[0], q[0]) : Long.compare(p[1], q[1]));\\n for (int i = 0; i < n; i++) {\\n pq.offer(new long[]{nums[i], i});\\n }\\n while (k-- > 0) {\\n long[] p = pq.poll();\\n p[0] *= multiplier;\\n pq.offer(p);\\n }\\n while (!pq.isEmpty()) {\\n long[] p = pq.poll();\\n nums[(int) p[1]] = (int) (p[0] % MOD);\\n }\\n return nums;\\n }\\n\\n Integer[] ids = new Integer[n];\\n Arrays.setAll(ids, i -> i);\\n Arrays.sort(ids, (i, j) -> Long.compare(a[i], a[j]));\\n\\n // 剩余的操作可以直接用公式计算\\n k = left;\\n long pow1 = pow(multiplier, k / n);\\n long pow2 = pow1 * multiplier % MOD;\\n for (int i = 0; i < n; i++) {\\n int j = ids[i];\\n nums[j] = (int) (a[j] % MOD * (i < k % n ? pow2 : pow1) % MOD);\\n }\\n return nums;\\n }\\n\\n private long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\n\\n long long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return move(nums);\\n }\\n\\n int n = nums.size();\\n long long mx = ranges::max(nums);\\n vector<pair<long long, int>> h(n);\\n for (int i = 0; i < n; i++) {\\n h[i] = {nums[i], i};\\n }\\n auto clone = h;\\n\\n // 每个数直接暴力操作到 >= mx\\n int left = k;\\n for (auto& [x, _] : h) {\\n while (x < mx) {\\n x *= multiplier;\\n if (--left < 0) {\\n goto outer;\\n }\\n }\\n }\\n outer:;\\n\\n if (left < 0) {\\n // 暴力模拟\\n h = move(clone);\\n ranges::make_heap(h, greater<>()); // 最小堆,O(n) 堆化\\n while (k--) {\\n ranges::pop_heap(h, greater<>());\\n h.back().first *= multiplier;\\n ranges::push_heap(h, greater<>());\\n }\\n for (auto& [x, j] : h) {\\n nums[j] = x % MOD;\\n }\\n return move(nums);\\n }\\n\\n // 剩余的操作可以直接用公式计算\\n k = left;\\n long long pow1 = pow(multiplier, k / n);\\n long long pow2 = pow1 * multiplier % MOD;\\n // ranges::sort(h) 换成快速选择\\n ranges::nth_element(h, h.begin() + k % n);\\n for (int i = 0; i < n; i++) {\\n auto& [x, j] = h[i];\\n nums[j] = x % MOD * (i < k % n ? pow2 : pow1) % MOD;\\n }\\n return move(nums);\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\nif multiplier == 1 { // 数组不变\\nreturn nums\\n}\\n\\nn := len(nums)\\nmx := 0\\nh := make(hp, n)\\nfor i, x := range nums {\\nmx = max(mx, x)\\nh[i] = pair{x, i}\\n}\\nclone := slices.Clone(h)\\n\\n// 每个数直接暴力操作到 >= mx\\nleft := k\\nouter:\\nfor i := range h {\\nfor h[i].x < mx {\\nh[i].x *= multiplier\\nleft--\\nif left < 0 {\\nbreak outer\\n}\\n}\\n}\\n\\nif left < 0 {\\n// 暴力模拟\\nh = clone\\nheap.Init(&h)\\nfor ; k > 0; k-- {\\nh[0].x *= multiplier\\nheap.Fix(&h, 0)\\n}\\nfor _, p := range h {\\nnums[p.i] = p.x % mod\\n}\\nreturn nums\\n}\\n\\n// 剩余的操作可以直接用公式计算\\nk = left\\npow1 := pow(multiplier, k/n)\\npow2 := pow1 * multiplier % mod\\nsort.Slice(h, func(i, j int) bool { return less(h[i], h[j]) })\\nfor i, p := range h {\\npw := pow1\\nif i < k%n {\\npw = pow2\\n}\\nnums[p.i] = p.x % mod * pw % mod\\n}\\nreturn nums\\n}\\n\\ntype pair struct{ x, i int }\\nfunc less(a, b pair) bool { return a.x < b.x || a.x == b.x && a.i < b.i }\\n\\ntype hp []pair\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return less(h[i], h[j]) }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (hp) Push(any) {}\\nfunc (hp) Pop() (_ any) { return }\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n设 $n$ 是 $\\\\textit{nums}$ 的长度,$U=\\\\max(\\\\textit{nums})$,$m=\\\\textit{multiplier}$。
\\n方法二瓶颈在暴力把每个 $\\\\textit{nums}[i]$ 乘到至少为 $\\\\textit{mx}$ 上。
\\n通过预处理,把 $\\\\textit{nums}[i]$ 乘到至少为 $\\\\textit{mx}$,这一步可以做到 $\\\\mathcal{O}(1)$。
\\n\\n\\n\\n注:由于常数比较大,该优化并不明显。仅用来说明当 $k$ 比较大时,存在 $\\\\mathcal{O}(n)$ 的做法。
\\n
###py
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n if multiplier == 1: # 数组不变\\n return nums\\n\\n MOD = 1_000_000_007\\n n = len(nums)\\n mx = max(nums)\\n\\n # 打表,计算出最小的 e 满足 multiplier^e >= 2^i\\n e_pow_m = []\\n pow_m = pow2 = 1\\n e = 0\\n while pow2 <= mx:\\n if pow_m < pow2: # 由于 multiplier >= 2,这里只需写 if 而不是 while\\n pow_m *= multiplier\\n e += 1\\n e_pow_m.append((e, pow_m))\\n pow2 <<= 1\\n\\n # 把每个数都操作到 >= mx\\n left = k\\n clone = nums.copy()\\n mx_len = mx.bit_length()\\n for i, x in enumerate(nums):\\n e, pow_m = e_pow_m[mx_len - x.bit_length()]\\n if pow_m // multiplier * x >= mx: # 多操作了一次\\n pow_m //= multiplier\\n e -= 1\\n elif x * pow_m < mx: # 少操作了一次\\n pow_m *= multiplier\\n e += 1\\n left -= e\\n if left < 0:\\n break\\n nums[i] *= pow_m\\n\\n if left < 0:\\n # 暴力模拟\\n h = [(x, i) for i, x in enumerate(clone)]\\n heapify(h)\\n for _ in range(k):\\n x, i = h[0]\\n heapreplace(h, (x * multiplier, i))\\n for x, j in h:\\n clone[j] = x % MOD\\n return clone\\n\\n # 剩余的操作可以直接用公式计算\\n k = left\\n pow1 = pow(multiplier, k // n, MOD)\\n pow2 = pow1 * multiplier % MOD\\n for i, (x, j) in enumerate(sorted((x, i) for i, x in enumerate(nums))):\\n nums[j] = x * (pow2 if i < k % n else pow1) % MOD\\n return nums\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int[] getFinalState(int[] nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return nums;\\n }\\n\\n int n = nums.length;\\n int mx = 0;\\n for (int x : nums) {\\n mx = Math.max(mx, x);\\n }\\n\\n // 打表,计算出最小的 exp 满足 multiplier^exp >= 2^i\\n int mxLZ = Integer.numberOfLeadingZeros(mx);\\n int mxLen = Integer.SIZE - mxLZ;\\n long[] powMs = new long[mxLen];\\n int[] exps = new int[mxLen];\\n int exp = 0, m = 0;\\n for (long pow2 = 1, powM = 1; pow2 <= mx; pow2 <<= 1) {\\n if (powM < pow2) { // 由于 multiplier >= 2,这里只需写 if 而不是 while\\n powM *= multiplier;\\n exp++;\\n }\\n powMs[m] = powM;\\n exps[m++] = exp;\\n }\\n\\n // 把每个数都操作到 >= mx\\n long[] a = new long[n];\\n int left = k;\\n for (int i = 0; i < n && left >= 0; i++) {\\n int j = Integer.numberOfLeadingZeros(nums[i]) - mxLZ;\\n long x = nums[i];\\n long powM = powMs[j];\\n int e = exps[j];\\n if (powM / multiplier * x >= mx) { // 多操作了一次\\n powM /= multiplier;\\n e--;\\n } else if (x * powM < mx) { // 少操作了一次\\n powM *= multiplier;\\n e++;\\n }\\n left -= e;\\n a[i] = x * powM;\\n }\\n\\n if (left < 0) {\\n // 暴力模拟\\n PriorityQueue<long[]> pq = new PriorityQueue<>((p, q) -> p[0] != q[0] ? Long.compare(p[0], q[0]) : Long.compare(p[1], q[1]));\\n for (int i = 0; i < n; i++) {\\n pq.offer(new long[]{nums[i], i});\\n }\\n while (k-- > 0) {\\n long[] p = pq.poll();\\n p[0] *= multiplier;\\n pq.offer(p);\\n }\\n while (!pq.isEmpty()) {\\n long[] p = pq.poll();\\n nums[(int) p[1]] = (int) (p[0] % MOD);\\n }\\n return nums;\\n }\\n\\n Integer[] ids = new Integer[n];\\n Arrays.setAll(ids, i -> i);\\n Arrays.sort(ids, (i, j) -> Long.compare(a[i], a[j]));\\n\\n // 剩余的操作可以直接用公式计算\\n k = left;\\n long pow1 = pow(multiplier, k / n);\\n long pow2 = pow1 * multiplier % MOD;\\n for (int i = 0; i < n; i++) {\\n int j = ids[i];\\n nums[j] = (int) (a[j] % MOD * (i < k % n ? pow2 : pow1) % MOD);\\n }\\n return nums;\\n }\\n\\n private long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\n\\n long long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int k, int multiplier) {\\n if (multiplier == 1) { // 数组不变\\n return move(nums);\\n }\\n\\n int n = nums.size();\\n int mx = ranges::max(nums);\\n // 打表,计算出最小的 e 满足 multiplier^e >= 2^i\\n vector<pair<int, long long>> e_pow_m;\\n long long pow_m = 1;\\n int e = 0;\\n for (int pow2 = 1; pow2 <= mx; pow2 <<= 1) {\\n if (pow_m < pow2) { // 由于 multiplier >= 2,这里只需写 if 而不是 while\\n pow_m *= multiplier;\\n e++;\\n }\\n e_pow_m.emplace_back(e, pow_m);\\n }\\n\\n vector<pair<long long, int>> h(n);\\n for (int i = 0; i < n; i++) {\\n h[i] = {nums[i], i};\\n }\\n auto clone = h;\\n\\n // 把每个数都操作到 >= mx\\n int left = k;\\n int mx_clz = __builtin_clz(mx);\\n for (int i = 0; i < n && left >= 0; i++) {\\n auto [e, pow_m] = e_pow_m[__builtin_clz(nums[i]) - mx_clz];\\n long long x = nums[i];\\n if (pow_m / multiplier * x >= mx) { // 多操作了一次\\n pow_m /= multiplier;\\n e--;\\n } else if (x * pow_m < mx) { // 少操作了一次\\n pow_m *= multiplier;\\n e++;\\n }\\n left -= e;\\n h[i].first *= pow_m;\\n }\\n\\n if (left < 0) {\\n // 暴力模拟\\n h = move(clone);\\n ranges::make_heap(h, greater<>()); // 最小堆,O(n) 堆化\\n while (k--) {\\n ranges::pop_heap(h, greater<>());\\n h.back().first *= multiplier;\\n ranges::push_heap(h, greater<>());\\n }\\n for (auto& [x, j] : h) {\\n nums[j] = x % MOD;\\n }\\n return move(nums);\\n }\\n\\n // 剩余的操作可以直接用公式计算\\n k = left;\\n long long pow1 = pow(multiplier, k / n);\\n long long pow2 = pow1 * multiplier % MOD;\\n // ranges::sort(h) 换成快速选择\\n ranges::nth_element(h, h.begin() + k % n);\\n for (int i = 0; i < n; i++) {\\n auto& [x, j] = h[i];\\n nums[j] = x % MOD * (i < k % n ? pow2 : pow1) % MOD;\\n }\\n return move(nums);\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\n\\nfunc getFinalState(nums []int, k int, multiplier int) []int {\\nif multiplier == 1 { // 数组不变\\nreturn nums\\n}\\n\\nn := len(nums)\\nmx := 0\\nh := make(hp, n)\\nfor i, x := range nums {\\nmx = max(mx, x)\\nh[i] = pair{x, i}\\n}\\nclone := slices.Clone(h)\\n\\n// 打表,计算出最小的 e 满足 multiplier^e >= 2^i\\nmxLen := bits.Len(uint(mx))\\ntype ep struct{ e, powM int }\\nePowM := make([]ep, 0, mxLen)\\nfor pow2, powM, e := 1, 1, 0; pow2 <= mx; pow2 <<= 1 {\\nif powM < pow2 { // 由于 multiplier >= 2,这里只需写 if 而不是 for\\npowM *= multiplier\\ne++\\n}\\nePowM = append(ePowM, ep{e, powM})\\n}\\n\\n// 把每个数都操作到 >= mx\\nleft := k\\nfor i := range h {\\nx := h[i].x\\np := ePowM[mxLen-bits.Len(uint(x))]\\ne, powM := p.e, p.powM\\nif powM/multiplier*x >= mx { // 多操作了一次\\npowM /= multiplier\\ne--\\n} else if x*powM < mx { // 少操作了一次\\npowM *= multiplier\\ne++\\n}\\nleft -= e\\nif left < 0 {\\nbreak\\n}\\nh[i].x *= powM\\n}\\n\\nif left < 0 {\\n// 暴力模拟\\nh = clone\\nheap.Init(&h)\\nfor ; k > 0; k-- {\\nh[0].x *= multiplier\\nheap.Fix(&h, 0)\\n}\\nsort.Slice(h, func(i, j int) bool { return less(h[i], h[j]) })\\nfor _, p := range h {\\nnums[p.i] = p.x % mod\\n}\\nreturn nums\\n}\\n\\n// 剩余的操作可以直接用公式计算\\nk = left\\npow1 := pow(multiplier, k/n)\\npow2 := pow1 * multiplier % mod\\nsort.Slice(h, func(i, j int) bool { return less(h[i], h[j]) })\\nfor i, p := range h {\\npw := pow1\\nif i < k%n {\\npw = pow2\\n}\\nnums[p.i] = p.x % mod * pw % mod\\n}\\nreturn nums\\n}\\n\\ntype pair struct{ x, i int }\\nfunc less(a, b pair) bool { return a.x < b.x || a.x == b.x && a.i < b.i }\\n\\ntype hp []pair\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return less(h[i], h[j]) }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (hp) Push(any) {}\\nfunc (hp) Pop() (_ any) { return }\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n设 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n打表和计算快速幂的时间由于只有 $\\\\mathcal{O}(\\\\log)$,忽略不计。
\\n更多相似题目,见下面数据结构题单中的「堆」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"写法一 核心观察:对于两个数 $x$ 和 $y$,如果 $x$ 在 $y$ 左边,且 $x\\\\le y$ 以及 $x\\\\cdot \\\\textit{multiplier} > y$,那么操作 $y$ 之后,根据 $x\\\\le y$,我们有 $x\\\\cdot \\\\textit{multiplier} \\\\le y\\\\cdot \\\\textit{multiplier}$,这意味着下一次一定会操作 $x$。继续推导下去,后面的操作顺序是 $y,x,y,x,\\\\cdots$\\n\\n这意味着当两个数接近时,我们会交替操作这两个数,而不会连续操作同一个数。\\n\\n对于更多的数的情况也同理…","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/zui-xiao-dui-mo-ni-shu-xue-gong-shi-pyth-z4zw","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-25T05:46:30.990Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最小堆模拟+快速幂模板题","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/zui-xiao-dui-mo-ni-kuai-su-mi-mo-ban-ti-bsta6","content":"\\n\\nProblem: 3266. K 次乘运算后的最终数组 II
\\n
[TOC]
\\n\\n\\n先模拟直到操作完最大索引位置,后面就是按顺序依次操作,用快速幂直接计算
\\n
###Python3
\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n MOD = 10 ** 9 + 7\\n if multiplier == 1:\\n return nums\\n \\n n = len(nums)\\n\\n # 快速幂\\n def myPow(x, y):\\n res = 1\\n while y > 0:\\n if y % 2:\\n res = res * x % MOD\\n x = x * x % MOD\\n y //= 2\\n return res\\n \\n # 找到最大值的索引, 先模拟直到找到最大值,后面就开始循环模式\\n mx = 0\\n ans = []\\n h = []\\n for i, x in enumerate(nums):\\n heappush(h, (x, i))\\n ans.append(x)\\n if x >= nums[mx]:\\n mx = i \\n \\n while k > 0:\\n k -= 1\\n x, i = heappop(h)\\n heappush(h, (x * multiplier, i))\\n ans[i] = ans[i] * multiplier % MOD\\n if i == mx:\\n break\\n if k == 0:\\n return ans\\n # 后面就是每个循环,直接用公式\\n for i in range(n):\\n _, idx = heappop(h)\\n cnt = k // n + 1 if i < k % n else k // n\\n ans[idx] = ans[idx] * myPow(multiplier, cnt) % MOD\\n\\n return ans\\n
\\n","description":"Problem: 3266. K 次乘运算后的最终数组 II [TOC]\\n\\n先模拟直到操作完最大索引位置,后面就是按顺序依次操作,用快速幂直接计算\\n\\n###Python3\\n\\nclass Solution:\\n def getFinalState(self, nums: List[int], k: int, multiplier: int) -> List[int]:\\n MOD = 10 ** 9 + 7\\n if multiplier == 1:\\n return nums…","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/zui-xiao-dui-mo-ni-kuai-su-mi-mo-ban-ti-bsta6","author":"sierpinski-f","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-25T05:02:43.002Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"堆 & 模拟","url":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/dui-mo-ni-by-tsreaper-5wc7","content":"相比 K 次乘运算后的最终数组 I,最大的变化是 $k$ 变得很大。这提示我们寻找 $k$ 很大时,被操作的数有什么规律。
\\n方便起见,我们先假设所有数是从小到大排序的。那么第一次被操作的数肯定是 $a_1$,后续也会一直操作它,直到它至少和 $a_2$ 一样大;接下来就是 $a_1$、$a_2$ 轮流操作,直到它们都至少和 $a_3$ 一样大;接下来就是 $a_1$、$a_2$、$a_3$ 轮流操作,...,直到前 $(n - 1)$ 个数都和 $a_n$ 一样大之后,接下来就是所有数从第一个到最后一个按顺序轮流操作。
\\n也就是说,当 $k$ 很大的时候,其实所有数是轮流操作的。如果进入了轮流操作阶段,假设还剩 $k\'$ 次操作,那么第 $i$ 个数还会被操作 $\\\\lfloor \\\\frac{k\'}{n} \\\\rfloor + [i \\\\le k\' \\\\bmod n]$ 次,其中 $[X]$ 当条件 $X$ 为真时等于 $1$,为假时等于 $0$。有了这个公式,进入轮流操作阶段后,我们就能 $\\\\mathcal{O}(n)$ 算出答案。
\\n那么需要操作几次才能进入轮流操作阶段呢?如果 multiplier
至少是 $2$(multiplier
如果是 $1$,直接返回原数组即可),那么一个数只要被操作 $\\\\mathcal{O}(\\\\log A)$ 次(其中 $A = 10^9$ 是值域),就能至少和最大的数一样大。因此,一共操作 $\\\\mathcal{O}(n\\\\log A)$ 次之后,我们就能进入轮流操作阶段,直接算出答案。所以前面的这些操作,我们用一个堆来模拟即可。
复杂度 $\\\\mathcal{O}(n\\\\log A\\\\log n)$。
\\n本题还有一个加法版本,感兴趣的读者可以尝试一下:代码力量 1181D
。
###cpp
\\nclass Solution {\\npublic:\\n vector<int> getFinalState(vector<int>& nums, int K, int multiplier) {\\n const int MOD = 1e9 + 7;\\n // 特殊情况\\n if (multiplier == 1) return nums;\\n\\n int n = nums.size();\\n // 因为 nums 不是有序的,我们需要先找出最大的数在哪\\n int mx = 0;\\n for (int i = 1; i < n; i++) if (nums[i] >= nums[mx]) mx = i;\\n\\n // 前面的操作先用堆模拟,每次取出最小的要操作的数\\n typedef pair<long long, int> pli;\\n priority_queue<pli, vector<pli>, greater<pli>> pq;\\n vector<long long> vec;\\n for (int i = 0; i < n; i++) pq.push({nums[i], i}), vec.push_back(nums[i]);\\n while (K > 0) {\\n K--;\\n pli p = pq.top(); pq.pop();\\n p.first *= multiplier; pq.push(p);\\n vec[p.second] = vec[p.second] * multiplier % MOD;\\n if (p.second == mx) break;\\n }\\n\\n // 快速幂\\n auto power = [&](long long a, long long b) {\\n long long y = 1;\\n for (; b; b >>= 1) {\\n if (b & 1) y = y * a % MOD;\\n a = a * a % MOD;\\n }\\n return y;\\n };\\n\\n // 进入循环操作阶段,套公式\\n for (int i = 0; i < n; i++) {\\n int idx = pq.top().second; pq.pop();\\n vec[idx] = vec[idx] * power(multiplier, K / n + (i < K % n ? 1 : 0)) % MOD;\\n }\\n\\n vector<int> ans;\\n for (auto x : vec) ans.push_back(x);\\n return ans;\\n }\\n};\\n
\\n","description":"解法:堆 & 模拟 相比 K 次乘运算后的最终数组 I,最大的变化是 $k$ 变得很大。这提示我们寻找 $k$ 很大时,被操作的数有什么规律。\\n\\n方便起见,我们先假设所有数是从小到大排序的。那么第一次被操作的数肯定是 $a_1$,后续也会一直操作它,直到它至少和 $a_2$ 一样大;接下来就是 $a_1$、$a_2$ 轮流操作,直到它们都至少和 $a_3$ 一样大;接下来就是 $a_1$、$a_2$、$a_3$ 轮流操作,...,直到前 $(n - 1)$ 个数都和 $a_n$ 一样大之后,接下来就是所有数从第一个到最后一个按顺序轮流操作。\\n\\n也就是说,当…","guid":"https://leetcode.cn/problems/final-array-state-after-k-multiplication-operations-ii//solution/dui-mo-ni-by-tsreaper-5wc7","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-25T04:11:30.367Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3254. 长度为 K 的子数组的能量值 I","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/3254-chang-du-wei-k-de-zi-shu-zu-de-neng-paow","content":"数组 $\\\\textit{nums}$ 的长度是 $n$,对于 $1 \\\\le k \\\\le n$,数组 $\\\\textit{nums}$ 中的长度为 $k$ 的子数组的数量是 $n - k + 1$。
\\n根据数组的能量值的定义,当数组中的所有相邻元素满足后一个元素与前一个元素之差等于 $1$ 时,数组的能量值等于数组的最后一个元素,其余情况下数组的能量值等于 $-1$。
\\n最直观的思路是遍历数组 $\\\\textit{nums}$ 中的每个长度为 $k$ 的子数组,判断子数组中的所有相邻元素是否满足后一个元素与前一个元素之差等于 $1$,如果满足则子数组的能量值等于子数组的最后一个元素,否则子数组的能量值等于 $-1$。
\\n###Java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] results = new int[n - k + 1];\\n for (int i = 0; i < n - k + 1; i++) {\\n boolean flag = true;\\n for (int j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] != 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results[i] = nums[i + k - 1];\\n } else {\\n results[i] = -1;\\n }\\n }\\n return results;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ResultsArray(int[] nums, int k) {\\n int n = nums.Length;\\n int[] results = new int[n - k + 1];\\n for (int i = 0; i < n - k + 1; i++) {\\n bool flag = true;\\n for (int j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] != 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results[i] = nums[i + k - 1];\\n } else {\\n results[i] = -1;\\n }\\n }\\n return results;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n vector<int> results;\\n for (int i = 0; i < n - k + 1; i++) {\\n bool flag = true;\\n for (int j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] != 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results.push_back(nums[i + k - 1]);\\n } else {\\n results.push_back(-1);\\n }\\n }\\n return results;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n results = []\\n for i in range(0, n - k + 1):\\n flag = True\\n for j in range(1, k):\\n if nums[i + j] - nums[i + j - 1] != 1:\\n flag = False\\n break\\n if flag:\\n results.append(nums[i + k - 1])\\n else:\\n results.append(-1)\\n return results\\n
\\n###C
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n int* results = (int*) malloc(sizeof(int) * (numsSize - k + 1));\\n *returnSize = numsSize - k + 1;\\n for (int i = 0; i < numsSize - k + 1; i++) {\\n bool flag = true;\\n for (int j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] != 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results[i] = nums[i + k - 1];\\n } else {\\n results[i] = -1;\\n }\\n }\\n return results;\\n}\\n
\\n###Go
\\nfunc resultsArray(nums []int, k int) []int {\\n n := len(nums)\\n results := []int{}\\n for i := 0; i < n - k + 1; i++ {\\n flag := true\\n for j := 1; j < k && flag; j++ {\\n if nums[i + j] - nums[i + j - 1] != 1 {\\n flag = false\\n }\\n }\\n if flag {\\n results = append(results, nums[i + k - 1])\\n } else {\\n results = append(results, -1)\\n }\\n }\\n return results\\n}\\n
\\n###JavaScript
\\nvar resultsArray = function(nums, k) {\\n let n = nums.length;\\n let results = [];\\n for (let i = 0; i < n - k + 1; i++) {\\n let flag = true;\\n for (let j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] !== 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results.push(nums[i + k - 1]);\\n } else {\\n results.push(-1);\\n }\\n }\\n return results;\\n};\\n
\\n###TypeScript
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n let n = nums.length;\\n let results = [];\\n for (let i = 0; i < n - k + 1; i++) {\\n let flag = true;\\n for (let j = 1; j < k && flag; j++) {\\n if (nums[i + j] - nums[i + j - 1] !== 1) {\\n flag = false;\\n }\\n }\\n if (flag) {\\n results.push(nums[i + k - 1]);\\n } else {\\n results.push(-1);\\n }\\n }\\n return results;\\n};\\n
\\n时间复杂度:$O(nk)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$k$ 是给定的正整数。子数组的数量是 $n - k + 1$,对于每个子数组计算能量值的时间是 $O(k)$,因此时间复杂度是 $O(nk)$。
\\n空间复杂度:$O(1)$。注意返回值不计入空间复杂度。
\\n为了计算数组 $\\\\textit{nums}$ 中的每个长度为 $k$ 的子数组的能量值,需要分别计算以数组 $\\\\textit{nums}$ 中的每个元素结尾的最长连续加 $1$ 子数组的长度,即子数组满足所有相邻元素满足后一个元素与前一个元素之差等于 $1$。
\\n用 $\\\\textit{prev}$ 表示上一个元素,用 $\\\\textit{consecutive}$ 表示当前遍历到的连续加 $1$ 的元素个数。由于数组 $\\\\textit{nums}$ 中的元素都是正整数,数组 $\\\\textit{nums}$ 的首个元素的前面没有其他元素,因此初始时 $\\\\textit{prev} = -1$,$\\\\textit{consecutive} = 1$。
\\n从左到右遍历数组 $\\\\textit{nums}$,对于遍历到的每个下标 $i$,记 $\\\\textit{curr} = \\\\textit{nums}[i]$,执行如下操作。
\\n更新 $\\\\textit{consecutive}$。如果 $\\\\textit{curr} - \\\\textit{prev} = 1$,则将 $\\\\textit{consecutive}$ 的值增加 $1$,否则将 $\\\\textit{consecutive}$ 的值更新为 $1$。
\\n将 $\\\\textit{prev}$ 的值更新为 $\\\\textit{curr}$。
\\n如果 $i \\\\ge k - 1$,则当前下标 $i$ 是一个长度为 $k$ 的子数组的结束下标,该长度为 $k$ 的子数组的起始下标是 $i - k + 1$,判断 $\\\\textit{consecutive} \\\\ge k$ 是否成立,计算 $\\\\textit{results}[i - k + 1]$ 的值。
\\n如果 $\\\\textit{consecutive} \\\\ge k$,则数组 $\\\\textit{nums}$ 的下标范围 $[i - k + 1, i]$ 的子数组中的所有相邻元素满足后一个元素与前一个元素之差等于 $1$,因此 $\\\\textit{results}[i - k + 1] = \\\\textit{nums}[i]$。
\\n如果 $\\\\textit{consecutive} < k$,则数组 $\\\\textit{nums}$ 的下标范围 $[i - k + 1, i]$ 的子数组中存在相邻元素不满足后一个元素与前一个元素之差等于 $1$,因此 $\\\\textit{results}[i - k + 1] = -1$。
\\n遍历结束之后,即可得到结果数组 $\\\\textit{results}$。
\\n###Java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int n = nums.length;\\n int[] results = new int[n - k + 1];\\n int prev = -1, consecutive = 1;\\n for (int i = 0; i < n; i++) {\\n int curr = nums[i];\\n if (curr - prev == 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results[i - k + 1] = nums[i];\\n } else {\\n results[i - k + 1] = -1;\\n }\\n }\\n }\\n return results;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] ResultsArray(int[] nums, int k) {\\n int n = nums.Length;\\n int[] results = new int[n - k + 1];\\n int prev = -1, consecutive = 1;\\n for (int i = 0; i < n; i++) {\\n int curr = nums[i];\\n if (curr - prev == 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results[i - k + 1] = nums[i];\\n } else {\\n results[i - k + 1] = -1;\\n }\\n }\\n }\\n return results;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n int n = nums.size();\\n vector<int> results;\\n int prev = -1, consecutive = 1;\\n for (int i = 0; i < n; i++) {\\n int curr = nums[i];\\n if (curr - prev == 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results.push_back(nums[i]);\\n } else {\\n results.push_back(-1);\\n }\\n }\\n }\\n return results;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n results = []\\n prev, consecutive = -1, 1\\n for i in range(n):\\n curr = nums[i]\\n if curr - prev == 1:\\n consecutive += 1\\n else:\\n consecutive = 1\\n prev = curr\\n if i >= k - 1:\\n if consecutive >= k:\\n results.append(nums[i])\\n else:\\n results.append(-1)\\n return results\\n
\\n###C
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n int* results = (int*) malloc(sizeof(int) * (numsSize - k + 1));\\n *returnSize = numsSize - k + 1;\\n int prev = -1, consecutive = 1;\\n for (int i = 0; i < numsSize; i++) {\\n int curr = nums[i];\\n if (curr - prev == 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results[i - k + 1] = nums[i];\\n } else {\\n results[i - k + 1] = -1;\\n }\\n }\\n }\\n return results;\\n}\\n
\\n###Go
\\nfunc resultsArray(nums []int, k int) []int {\\n n := len(nums)\\n results := []int{}\\n prev, consecutive := -1, 1\\n for i := 0; i < n; i++ {\\n curr := nums[i]\\n if curr - prev == 1 {\\n consecutive++\\n } else {\\n consecutive = 1\\n }\\n prev = curr;\\n if i >= k - 1 {\\n if consecutive >= k {\\n results = append(results, nums[i])\\n } else {\\n results = append(results, -1)\\n }\\n }\\n }\\n return results\\n}\\n
\\n###JavaScript
\\nvar resultsArray = function(nums, k) {\\n let n = nums.length;\\n let results = [];\\n let prev = -1, consecutive = 1;\\n for (let i = 0; i < n; i++) {\\n let curr = nums[i];\\n if (curr - prev === 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results[i - k + 1] = nums[i];\\n } else {\\n results[i - k + 1] = -1;\\n }\\n }\\n }\\n return results;\\n};\\n
\\n###TypeScript
\\nfunction resultsArray(nums: number[], k: number): number[] {\\n let n = nums.length;\\n let results = [];\\n let prev = -1, consecutive = 1;\\n for (let i = 0; i < n; i++) {\\n let curr = nums[i];\\n if (curr - prev === 1) {\\n consecutive++;\\n } else {\\n consecutive = 1;\\n }\\n prev = curr;\\n if (i >= k - 1) {\\n if (consecutive >= k) {\\n results[i - k + 1] = nums[i];\\n } else {\\n results[i - k + 1] = -1;\\n }\\n }\\n }\\n return results;\\n};\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次,每个元素的操作时间都是 $O(1)$。
\\n空间复杂度:$O(1)$。注意返回值不计入空间复杂度。
\\n\\n\\nProblem: 100386. 超级饮料的最大强化能量
\\n
[TOC]
\\n\\n\\n你选用何种方法解题?
\\n
\\n\\n这些方法具体怎么运用?
\\n
###Python3
\\nimport math\\nfrom typing import List\\nfrom functools import lru_cache\\nfrom collections import deque, defaultdict, Counter\\nfrom sortedcontainers import SortedList\\nfrom bisect import bisect_left, bisect_right\\nfrom itertools import combinations\\n\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n \\"\\"\\"\\n\\n :param energyDrinkA:\\n :param energyDrinkB:\\n :return:\\n \\"\\"\\"\\n n = len(energyDrinkA)\\n @lru_cache(None)\\n def get_max(idx, is_A):\\n if idx == n:\\n return 0\\n return (max(energyDrinkA[idx] + get_max(idx + 1, True), get_max(idx + 1, False))) if is_A else (max(get_max(idx+1, True), energyDrinkB[idx] + get_max(idx+1, False)))\\n return max(get_max(0, True), get_max(0, False))\\n
\\n","description":"Problem: 100386. 超级饮料的最大强化能量 [TOC]\\n\\n你选用何种方法解题?\\n\\n这些方法具体怎么运用?\\n\\n时间复杂度: $O(*)$\\n空间复杂度: $O(*)$\\n\\n###Python3\\n\\nimport math\\nfrom typing import List\\nfrom functools import lru_cache\\nfrom collections import deque, defaultdict, Counter\\nfrom sortedcontainers import SortedList\\nfrom bisect…","guid":"https://leetcode.cn/problems/maximum-energy-boost-from-two-drinks//solution/ji-yi-hua-sou-suo-by-lambda2void-o23q","author":"lambda2void","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T14:54:25.665Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Elixir 暴力枚举","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/elixir-bao-li-mei-ju-by-lambda2void-bkjf","content":"\\n\\nProblem: 100402. 统计满足 K 约束的子字符串数量 I
\\n
[TOC]
\\n\\n\\n你选用何种方法解题?
\\n
\\n\\n这些方法具体怎么运用?
\\n
###Elixir
\\ndefmodule Solution do\\n @spec count_k_constraint_substrings(s :: String.t, k :: integer) :: integer\\n def count_k_constraint_substrings(s, k) do\\n for x<-0..String.length(s) - 1, y<-x..String.length(s) - 1 do\\n String.slice(s, x..y)\\n |> String.graphemes()\\n |> Enum.frequencies()\\n |> is_ok(k)\\n end\\n |> Enum.filter(&(&1))\\n |> Enum.count\\n end\\n \\n defp is_ok(map1, k) do\\n Map.get(map1, \\"1\\", 0) <= k or Map.get(map1, \\"0\\", 0) <= k\\n end\\nend\\n
\\n","description":"Problem: 100402. 统计满足 K 约束的子字符串数量 I [TOC]\\n\\n你选用何种方法解题?\\n\\n这些方法具体怎么运用?\\n\\n时间复杂度: $O(*)$\\n空间复杂度: $O(*)$\\n\\n###Elixir\\n\\ndefmodule Solution do\\n @spec count_k_constraint_substrings(s :: String.t, k :: integer) :: integer\\n def count_k_constraint_substrings(s, k) do\\n for x<-0..String…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/elixir-bao-li-mei-ju-by-lambda2void-bkjf","author":"lambda2void","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T12:12:13.313Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3261. 统计满足 K 约束的子字符串数量 II","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/3261-tong-ji-man-zu-k-yue-shu-de-zi-zi-f-55dv","content":"对于每个查询,直接计算满足 $k$ 约束的子字符串的数量比较困难,可以计算查询中的所有子字符串的数量与不满足 $k$ 约束的子字符串的数量,然后得到满足 $k$ 约束的子字符串的数量。
\\n考虑字符串 $s$ 的以下标 $\\\\textit{end}_1$ 和 $\\\\textit{end}_2$ 结尾的满足 $k$ 约束的最长子字符串,其中 $\\\\textit{end}_1 < \\\\textit{end}_2$,将两个最长子字符串的起始下标分别记为 $\\\\textit{start}_1$ 和 $\\\\textit{start}_2$,则必有 $\\\\textit{start}_1 \\\\le \\\\textit{start}_2$。理由如下:将字符串 $s$ 的下标范围 $[\\\\textit{start}_2, \\\\textit{end}_2]$ 的子字符串中的 $0$ 的数量和 $1$ 的数量分别记为 $\\\\textit{zeros}$ 和 $\\\\textit{ones}$,将字符串 $s$ 的下标范围 $[\\\\textit{start}_2, \\\\textit{end}_1]$ 的子字符串中的 $0$ 的数量和 $1$ 的数量分别记为 $\\\\textit{zeros}\'$ 和 $\\\\textit{ones}\'$,则 $\\\\textit{zeros} \\\\le k$ 或 $\\\\textit{ones} \\\\le k$,由于 $\\\\textit{end}_1 < \\\\textit{end}_2$,因此必有 $\\\\textit{zeros}\' \\\\le \\\\textit{zeros}$ 和 $\\\\textit{ones}\' \\\\le \\\\textit{ones}$,因此 $\\\\textit{zeros}\' \\\\le k$ 或 $\\\\textit{ones}\' \\\\le k$,以下标 $\\\\textit{end}_1$ 结尾的满足 $k$ 约束的最长子字符串的起始下标一定小于等于 $\\\\textit{start}_2$,$\\\\textit{start}_1 \\\\le \\\\textit{start}_2$。
\\n同理可以得到如下结论:考虑字符串 $s$ 的以下标 $\\\\textit{start}_1$ 和 $\\\\textit{start}_2$ 起始的满足 $k$ 约束的最长子字符串,其中 $\\\\textit{start}_1 < \\\\textit{start}_2$,将两个最长子字符串的结尾下标分别记为 $\\\\textit{end}_1$ 和 $\\\\textit{end}_2$,则必有 $\\\\textit{end}_1 \\\\le \\\\textit{end}_2$。
\\n因此,可以使用滑动窗口分别计算以字符串 $s$ 的每个下标结尾的满足 $k$ 约束的最长子字符串的起始下标,以及以字符串 $s$ 的每个下标起始的满足 $k$ 约束的最长子字符串的结尾下标,将最长子字符串的长度增加 $1$ 之后,该长度的子字符串不满足 $k$ 约束。
\\n创建长度为 $n$ 的数组 $\\\\textit{maxStartIndices}$ 和 $\\\\textit{minEndIndices}$,其中 $\\\\textit{maxStartIndices}[i]$ 表示以字符串 $s$ 的下标 $i$ 结尾的不满足 $k$ 约束的子字符串的最大起始下标,$\\\\textit{minEndIndices}[i]$ 表示以字符串 $s$ 的下标 $i$ 起始的不满足 $k$ 约束的子字符串的最小结束下标。如果不存在以字符串 $s$ 的下标 $i$ 结尾的不满足 $k$ 约束的子字符串,则 $\\\\textit{maxStartIndices}[i] = -1$;如果不存在以字符串 $s$ 的下标 $i$ 起始的不满足 $k$ 约束的子字符串,则 $\\\\textit{minEndIndices}[i] = n$。使用滑动窗口正向遍历字符串 $s$ 计算数组 $\\\\textit{maxStartIndices}$ 的值,使用滑动窗口反向遍历字符串 $s$ 计算数组 $\\\\textit{minEndIndices}$ 的值。
\\n得到数组 $\\\\textit{maxStartIndices}$ 和 $\\\\textit{minEndIndices}$ 之后,即可计算以字符串 $s$ 的每个下标作为起始下标的不满足 $k$ 约束的子字符串数量。创建长度为 $n$ 的数组 $\\\\textit{nonConstraints}$,其中 $\\\\textit{nonConstraints}$ 为以字符串 $s$ 的下标 $i$ 起始的不满足 $k$ 约束的子字符串数量。对于 $0 \\\\le i < n$ 的每个下标 $i$,由于以字符串 $s$ 的下标 $i$ 起始的不满足 $k$ 约束的子字符串的最小结束下标是 $\\\\textit{minEndIndices}[i]$,最大结束下标是 $n - 1$,因此 $\\\\textit{nonConstraints}[i] = n - \\\\textit{minEndIndices}[i]$,对于 $\\\\textit{minEndIndices}[i] = n$ 的情况也适用。
\\n得到数组 $\\\\textit{nonConstraints}$ 之后,为了方便处理,需要计算数组 $\\\\textit{nonConstraints}$ 的前缀和。创建长度为 $n + 1$ 的前缀和数组 $\\\\textit{prefixSums}$,其中 $\\\\textit{prefixSums}[i]$ 表示数组 $\\\\textit{nonConstraints}$ 的长度为 $i$ 的前缀的前缀和,满足 $\\\\textit{prefixSums}[0] = 0$,对于 $0 \\\\le i < n$ 有 $\\\\textit{prefixSums}[i + 1] = \\\\textit{prefixSums}[i] + \\\\textit{nonConstraints}[i]$,则数组 $\\\\textit{nonConstraints}$ 的下标范围 $[\\\\textit{left}, \\\\textit{right}]$ 的子数组的元素和等于 $\\\\textit{prefixSums}[\\\\textit{right} + 1] - \\\\textit{prefixSums}[\\\\textit{left}]$。
\\n根据上述信息,可以计算每个查询的结果。对于查询数组 $\\\\textit{queries}$ 中的每个下标 $i$,查询结果为 $\\\\textit{answer}[i]$,计算方法如下。
\\n得到 $\\\\textit{start} = \\\\textit{queries}[i][0]$,$\\\\textit{end} = \\\\textit{queries}[i][1]$,则该查询的子字符串长度是 $\\\\textit{rangeLength} = \\\\textit{end} - \\\\textit{start} + 1$,查询范围中的所有子字符串的数量是 $\\\\dfrac{\\\\textit{rangeLength} \\\\times (\\\\textit{rangeLength} + 1)}{2}$,将 $\\\\textit{answer}[i]$ 的值更新为 $\\\\dfrac{\\\\textit{rangeLength} \\\\times (\\\\textit{rangeLength} + 1)}{2}$。
\\n如果 $\\\\textit{minEndIndices}[\\\\textit{start}] > \\\\textit{end}$,则以字符串 $s$ 的下标 $\\\\textit{start}$ 起始的子字符串都满足 $k$ 约束,因此 $\\\\textit{answer}[i]$ 的值确定;如果 $\\\\textit{minEndIndices}[\\\\textit{start}] \\\\le \\\\textit{end}$,则需要计算以字符串 $s$ 的下标 $\\\\textit{start}$ 起始的不满足 $k$ 约束的子字符串,更新 $\\\\textit{answer}[i]$ 的值,做法如下。
\\n得到 $\\\\textit{maxStart} = \\\\textit{maxStartIndices}[\\\\textit{end}]$,则当前查询的子字符串的起始下标的范围是 $[\\\\textit{start}, \\\\textit{maxStart}]$,起始下标的数量是 $\\\\textit{maxStart} - \\\\textit{start} + 1$。
\\n起始下标在范围 $[\\\\textit{start}, \\\\textit{maxStart}]$ 中的不满足 $k$ 约束的的子字符串数量是 $\\\\textit{prefixSums}[\\\\textit{maxStart} + 1] - \\\\textit{prefixSums}[\\\\textit{start}]$,对于每个起始下标,需要将结尾下标大于 $\\\\textit{end}$ 的子字符串排除在查询结果以外,因此每个起始下标需要排除的子字符串数量是 $n - \\\\textit{end} - 1$。
\\n计算 $\\\\textit{currCount} = \\\\textit{prefixSums}[\\\\textit{maxStart} + 1] - \\\\textit{prefixSums}[\\\\textit{start}] - (n - \\\\textit{end} - 1) \\\\times (\\\\textit{maxStart} - \\\\textit{start} + 1)$,将 $\\\\textit{answer}[i]$ 的值减少 $\\\\textit{currCount}$。
\\n遍历每个查询并计算查询结果之后,返回答案数组 $\\\\textit{answer}$。
\\n###Java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String s, int k, int[][] queries) {\\n int[] maxStartIndices = getMaxStartIndices(s, k);\\n int[] minEndIndices = getMinEndIndices(s, k);\\n int n = s.length();\\n int[] nonConstraints = new int[n];\\n for (int i = 0; i < n; i++) {\\n nonConstraints[i] = n - minEndIndices[i];\\n }\\n long[] prefixSums = new long[n + 1];\\n for (int i = 0; i < n; i++) {\\n prefixSums[i + 1] = prefixSums[i] + nonConstraints[i];\\n }\\n int queriesCount = queries.length;\\n long[] answer = new long[queriesCount];\\n for (int i = 0; i < queriesCount; i++) {\\n int start = queries[i][0], end = queries[i][1];\\n int rangeLength = end - start + 1;\\n answer[i] = (long) rangeLength * (rangeLength + 1) / 2;\\n if (minEndIndices[start] <= end) {\\n int maxStart = maxStartIndices[end];\\n long currCount = prefixSums[maxStart + 1] - prefixSums[start] - (long) (n - end - 1) * (maxStart - start + 1);\\n answer[i] -= currCount;\\n }\\n }\\n return answer;\\n }\\n\\n public int[] getMaxStartIndices(String s, int k) {\\n int n = s.length();\\n int[] maxStartIndices = new int[n];\\n Arrays.fill(maxStartIndices, -1);\\n int[] counts = new int[2];\\n int start = 0, end = 0;\\n while (end < n) {\\n int curr = s.charAt(end) - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s.charAt(start) - \'0\';\\n counts[prev]--;\\n start++;\\n }\\n maxStartIndices[end] = start - 1;\\n end++;\\n }\\n return maxStartIndices;\\n }\\n\\n public int[] getMinEndIndices(String s, int k) {\\n int n = s.length();\\n int[] minEndIndices = new int[n];\\n Arrays.fill(minEndIndices, n);\\n int[] counts = new int[2];\\n int start = n - 1, end = n - 1;\\n while (start >= 0) {\\n int curr = s.charAt(start) - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s.charAt(end) - \'0\';\\n counts[prev]--;\\n end--;\\n }\\n minEndIndices[start] = end + 1;\\n start--;\\n }\\n return minEndIndices;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long[] CountKConstraintSubstrings(string s, int k, int[][] queries) {\\n int[] maxStartIndices = GetMaxStartIndices(s, k);\\n int[] minEndIndices = GetMinEndIndices(s, k);\\n int n = s.Length;\\n int[] nonConstraints = new int[n];\\n for (int i = 0; i < n; i++) {\\n nonConstraints[i] = n - minEndIndices[i];\\n }\\n long[] prefixSums = new long[n + 1];\\n for (int i = 0; i < n; i++) {\\n prefixSums[i + 1] = prefixSums[i] + nonConstraints[i];\\n }\\n int queriesCount = queries.Length;\\n long[] answer = new long[queriesCount];\\n for (int i = 0; i < queriesCount; i++) {\\n int start = queries[i][0], end = queries[i][1];\\n int rangeLength = end - start + 1;\\n answer[i] = (long) rangeLength * (rangeLength + 1) / 2;\\n if (minEndIndices[start] <= end) {\\n int maxStart = maxStartIndices[end];\\n long currCount = prefixSums[maxStart + 1] - prefixSums[start] - (long) (n - end - 1) * (maxStart - start + 1);\\n answer[i] -= currCount;\\n }\\n }\\n return answer;\\n }\\n\\n public int[] GetMaxStartIndices(string s, int k) {\\n int n = s.Length;\\n int[] maxStartIndices = new int[n];\\n Array.Fill(maxStartIndices, -1);\\n int[] counts = new int[2];\\n int start = 0, end = 0;\\n while (end < n) {\\n int curr = s[end] - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s[start] - \'0\';\\n counts[prev]--;\\n start++;\\n }\\n maxStartIndices[end] = start - 1;\\n end++;\\n }\\n return maxStartIndices;\\n }\\n\\n public int[] GetMinEndIndices(string s, int k) {\\n int n = s.Length;\\n int[] minEndIndices = new int[n];\\n Array.Fill(minEndIndices, n);\\n int[] counts = new int[2];\\n int start = n - 1, end = n - 1;\\n while (start >= 0) {\\n int curr = s[start] - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s[end] - \'0\';\\n counts[prev]--;\\n end--;\\n }\\n minEndIndices[start] = end + 1;\\n start--;\\n }\\n return minEndIndices;\\n }\\n}\\n
\\n时间复杂度:$O(n + q)$,其中 $n$ 是字符串 $s$ 的长度,$q$ 是数组 $\\\\textit{queries}$ 的长度。计算数组 $\\\\textit{maxStartIndices}$ 与 $\\\\textit{minEndIndices}$ 时使用滑动窗口遍历字符串 $s$ 两次,每次遍历滑动窗口的左右端点最多各遍历字符串 $s$ 一次,计算数组 $\\\\textit{nonConstraints}$ 与前缀和数组 $\\\\textit{prefixSums}$ 的时间是 $O(1)$,每个查询的计算时间是 $O(1)$,因此时间复杂度是 $O(n + q)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。数组 $\\\\textit{maxStartIndices}$、$\\\\textit{minEndIndices}$、$\\\\textit{nonConstraints}$ 与前缀和数组 $\\\\textit{prefixSums}$ 的空间是 $O(n)$。注意返回值不计入空间复杂度。
\\n最直观的思路是遍历字符串 $s$ 的每个子字符串判断是否满足 $k$ 约束。
\\n可以从小到大遍历子字符串的起始下标 $i$,然后从小到大遍历大于等于 $i$ 的下标 $j$,根据字符 $s[j]$ 更新 $0$ 或 $1$ 的数量,则可以实现每个子字符串的计算时间是 $O(1)$。
\\n###Java
\\nclass Solution {\\n public int countKConstraintSubstrings(String s, int k) {\\n int substrings = 0;\\n int n = s.length();\\n for (int i = 0; i < n; i++) {\\n int zeros = 0, ones = 0;\\n for (int j = i; j < n; j++) {\\n if (s.charAt(j) == \'0\') {\\n zeros++;\\n } else {\\n ones++;\\n }\\n if (zeros <= k || ones <= k) {\\n substrings++;\\n }\\n }\\n }\\n return substrings;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountKConstraintSubstrings(string s, int k) {\\n int substrings = 0;\\n int n = s.Length;\\n for (int i = 0; i < n; i++) {\\n int zeros = 0, ones = 0;\\n for (int j = i; j < n; j++) {\\n if (s[j] == \'0\') {\\n zeros++;\\n } else {\\n ones++;\\n }\\n if (zeros <= k || ones <= k) {\\n substrings++;\\n }\\n }\\n }\\n return substrings;\\n }\\n}\\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是字符串 $s$ 的长度。子字符串的数量是 $O(n^2)$,每个子字符串的计算时间是 $O(1)$,因此时间复杂度是 $O(n^2)$。
\\n空间复杂度:$O(1)$。
\\n为了计算字符串 $s$ 的满足 $k$ 约束的子字符串数量,需要分别计算以字符串 $s$ 的每个下标结尾的满足 $k$ 约束的子字符串数量。对于一个满足 $k$ 约束的子字符串,其任意子字符串中的 $0$ 的数量和 $1$ 的数量都不可能更多,因此满足 $k$ 约束的子字符串的任意子字符串也一定满足 $k$ 约束,因此需要分别计算以字符串 $s$ 的每个下标结尾的满足 $k$ 约束的子字符串的最大长度。
\\n考虑字符串 $s$ 的以下标 $\\\\textit{end}_1$ 和 $\\\\textit{end}_2$ 结尾的满足 $k$ 约束的最长子字符串,其中 $\\\\textit{end}_1 < \\\\textit{end}_2$,将两个最长子字符串的起始下标分别记为 $\\\\textit{start}_1$ 和 $\\\\textit{start}_2$,则必有 $\\\\textit{start}_1 \\\\le \\\\textit{start}_2$。理由如下:将字符串 $s$ 的下标范围 $[\\\\textit{start}_2, \\\\textit{end}_2]$ 的子字符串中的 $0$ 的数量和 $1$ 的数量分别记为 $\\\\textit{zeros}$ 和 $\\\\textit{ones}$,将字符串 $s$ 的下标范围 $[\\\\textit{start}_2, \\\\textit{end}_1]$ 的子字符串中的 $0$ 的数量和 $1$ 的数量分别记为 $\\\\textit{zeros}\'$ 和 $\\\\textit{ones}\'$,则 $\\\\textit{zeros} \\\\le k$ 或 $\\\\textit{ones} \\\\le k$,由于 $\\\\textit{end}_1 < \\\\textit{end}_2$,因此必有 $\\\\textit{zeros}\' \\\\le \\\\textit{zeros}$ 和 $\\\\textit{ones}\' \\\\le \\\\textit{ones}$,因此 $\\\\textit{zeros}\' \\\\le k$ 或 $\\\\textit{ones}\' \\\\le k$,以下标 $\\\\textit{end}_1$ 结尾的满足 $k$ 约束的最长子字符串的起始下标一定小于等于 $\\\\textit{start}_2$,$\\\\textit{start}_1 \\\\le \\\\textit{start}_2$。
\\n因此可以使用滑动窗口遍历字符串 $s$,分别计算以字符串 $s$ 的每个下标结尾的满足 $k$ 约束的子字符串的最大长度。
\\n用 $[\\\\textit{start}, \\\\textit{end}]$ 表示滑动窗口,初始时 $\\\\textit{start} = \\\\textit{end} = 0$。从小到大遍历 $0 \\\\le \\\\textit{end} < n$ 的每个下标 $\\\\textit{end}$,执行如下操作。
\\n将字符 $s[\\\\textit{end}]$ 对应的数字($0$ 或 $1$)的数量增加 $1$。
\\n如果数字 $0$ 和数字 $1$ 的数量都大于 $k$,则将字符 $s[\\\\textit{start}]$ 对应的数字($0$ 或 $1$)的数量减少 $1$,然后将 $\\\\textit{start}$ 向右移动一位。重复该操作直到如果数字 $0$ 和数字 $1$ 当中至少有一个数字的数量小于等于 $k$。
\\n字符串 $s$ 的以下标 $\\\\textit{end}$ 结尾的满足 $k$ 约束的最长子字符串的起始下标是 $\\\\textit{start}$,因此字符串 $s$ 的以下标 $\\\\textit{end}$ 结尾的满足 $k$ 约束的子字符串的数量是 $\\\\textit{end} - \\\\textit{start} + 1$,将答案增加 $\\\\textit{end} - \\\\textit{start} + 1$。
\\n遍历结束之后,即可得到字符串 $s$ 的满足 $k$ 约束的子字符串数量。
\\n###Java
\\nclass Solution {\\n public int countKConstraintSubstrings(String s, int k) {\\n int substrings = 0;\\n int[] counts = new int[2];\\n int n = s.length();\\n int start = 0, end = 0;\\n while (end < n) {\\n int curr = s.charAt(end) - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s.charAt(start) - \'0\';\\n counts[prev]--;\\n start++;\\n }\\n substrings += end - start + 1;\\n end++;\\n }\\n return substrings;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountKConstraintSubstrings(string s, int k) {\\n int substrings = 0;\\n int[] counts = new int[2];\\n int n = s.Length;\\n int start = 0, end = 0;\\n while (end < n) {\\n int curr = s[end] - \'0\';\\n counts[curr]++;\\n while (counts[0] > k && counts[1] > k) {\\n int prev = s[start] - \'0\';\\n counts[prev]--;\\n start++;\\n }\\n substrings += end - start + 1;\\n end++;\\n }\\n return substrings;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。滑动窗口的左右端点最多各遍历字符串 $s$ 一次。
\\n空间复杂度:$O(1)$。
\\n定义 dfs(i, p) 表示前 i 个小时能获得的最大总强化能量,并且第 i 个小时选择的饮料为 p:
\\np == True,表示选择 energyDrinkA;p == False,表示选择 energyDrinkB。
对于第 i 个小时来说,可以选择饮料 p,也可以选择切换饮料,两者取最大,即:
\\ndfs(i, p) = max(dfs(i - 1, not p), dfs(i - 1, p) + value)
###python3
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n n = len(energyDrinkA)\\n @cache\\n def dfs(i: int, p: bool) -> int:\\n if i < 0:\\n return 0\\n value = energyDrinkA[i] if p else energyDrinkB[i]\\n return max(dfs(i - 1, not p), dfs(i - 1, p) + value)\\n return max(dfs(n - 1, True), dfs(n - 1, False))\\n
\\n###Python3
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n n = len(energyDrinkA)\\n f = [[0] * 2 for _ in range(n + 1)]\\n for i in range(n):\\n f[i + 1][0] = max(f[i][1], f[i][0] + energyDrinkA[i])\\n f[i + 1][1] = max(f[i][0], f[i][1] + energyDrinkB[i])\\n return max(f[-1][0], f[-1][1])\\n
\\n因为 f[i] 仅跟 f[i - 1] 有关,因此可以用滚动数组优化dp。
\\n###python3
\\nclass Solution:\\n def maxEnergyBoost(self, energyDrinkA: List[int], energyDrinkB: List[int]) -> int:\\n f0 = f1 = 0\\n for i, (x, y) in enumerate(zip(energyDrinkA, energyDrinkB)):\\n f0, f1 = max(f1, f0 + x), max(f0, f1 + y)\\n return max(f0, f1)\\n
\\n计算以 $0$ 为右端点的合法子串个数,以 $1$ 为右端点的合法子串个数,……,以 $n-1$ 为右端点的合法子串个数。
\\n我们需要知道以 $i$ 为右端点的合法子串,其左端点最小是多少。
\\n由于随着 $i$ 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 $i$ 的增大而增大,有单调性,因此可以用 滑动窗口 计算。
\\n设以 $i$ 为右端点的合法子串,其左端点最小是 $\\\\textit{left}_i$。
\\n那么以 $i$ 为右端点的合法子串,其左端点可以是 $\\\\textit{left}_i,\\\\textit{left}_i+1,\\\\ldots,i$,一共
\\n$$
\\ni-\\\\textit{left}_i+1
\\n$$
个,累加到答案中。
\\n细节:字符 $0$ 的 ASCII 值是偶数,字符 $1$ 的 ASCII 值是奇数,所以可以用 ASCII 值 $c\\\\bmod 2$ 得到对应的数字。这也等价于和 $1$ 计算 AND。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int) -> int:\\n ans = left = 0\\n cnt = [0, 0]\\n for i, c in enumerate(s):\\n cnt[ord(c) & 1] += 1\\n while cnt[0] > k and cnt[1] > k:\\n cnt[ord(s[left]) & 1] -= 1\\n left += 1\\n ans += i - left + 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int countKConstraintSubstrings(String S, int k) {\\n char[] s = S.toCharArray();\\n int ans = 0;\\n int left = 0;\\n int[] cnt = new int[2];\\n for (int i = 0; i < s.length; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[left] & 1]--;\\n left++;\\n }\\n ans += i - left + 1;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countKConstraintSubstrings(string s, int k) {\\n int ans = 0, left = 0, cnt[2]{};\\n for (int i = 0; i < s.length(); i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[left] & 1]--;\\n left++;\\n }\\n ans += i - left + 1;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int) (ans int) {\\ncnt := [2]int{}\\nleft := 0\\nfor i, c := range s {\\ncnt[c&1]++\\nfor cnt[0] > k && cnt[1] > k {\\ncnt[s[left]&1]--\\nleft++\\n}\\nans += i - left + 1\\n}\\nreturn\\n}\\n
\\n更多相似题目,见下面滑动窗口题单中的「§2.3.2 越短越合法」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"计算以 $0$ 为右端点的合法子串个数,以 $1$ 为右端点的合法子串个数,……,以 $n-1$ 为右端点的合法子串个数。 我们需要知道以 $i$ 为右端点的合法子串,其左端点最小是多少。\\n\\n由于随着 $i$ 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 $i$ 的增大而增大,有单调性,因此可以用 滑动窗口 计算。\\n\\n设以 $i$ 为右端点的合法子串,其左端点最小是 $\\\\textit{left}_i$。\\n\\n那么以 $i$ 为右端点的合法子串,其左端点可以是 $\\\\textit{left}_i,\\\\textit{left}_i+1…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/on-hua-dong-chuang-kou-pythonjavacgo-by-keubv","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T04:54:24.660Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/mo-ni-by-tsreaper-ur2a","content":"按题意模拟即可。复杂度 $\\\\mathcal{O}(n^2)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int countKConstraintSubstrings(string s, int K) {\\n int n = s.size();\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n int cnt[2] = {0};\\n for (int j = i; j < n; j++) {\\n cnt[s[j] - \'0\']++;\\n if (cnt[0] <= K || cnt[1] <= K) ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:模拟 按题意模拟即可。复杂度 $\\\\mathcal{O}(n^2)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int countKConstraintSubstrings(string s, int K) {\\n int n = s.size();\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n int cnt[2] = {0};\\n for (int j = i; j <…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-i//solution/mo-ni-by-tsreaper-ur2a","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T04:47:42.027Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"教你一步步思考 DP:从记忆化搜索到递推(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-energy-boost-from-two-drinks//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-iebb","content":"给你两个数组 $a$ 和 $b$。从 $i=0$ 开始,要么选 $a[i]$,要么选 $b[i]$。如果你当前选了 $a$ 中的元素,后面想选 $b$ 中的元素,那么下一个元素必须不能选。例如现在选 $a[i]$,那么后面可以选 $b[i+2]$,但不能选 $b[i+1]$。
\\n返回所选元素之和的最大值。
\\n本题有点类似 198. 打家劫舍,不过我们还是从子问题开始讨论。
\\n例如 $a=[1,3,1,2,3],\\\\ b=[3,1,1,2,3]$。
\\n如果最后一个数我们选了 $a[4]=3$,那么:
\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n\\n\\n注 1:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。
\\n注 2:动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。在做题时,可根据题目要求,选择适合题目的一种来思考。本题用到的是「选或不选」:如果当前选了 $a$ 中的元素,接下来要么继续选 $a$ 中的元素,要么不继续选 $a$ 中的元素,也就是选 $b$ 中的元素。
\\n
因为要解决的问题都形如「从下标 $[0,i]$ 中选数字,且最后选的是 $a[i]$ 或 $b[i]$ 的情况下,所选元素之和的最大值」,所以用它作为本题的状态定义 $\\\\textit{dfs}(i,j)$。其中 $j=0,1$,分别表示最后选的是 $a[i]$ 还是 $b[i]$。
\\n为方便实现,把 $a$ 和 $b$ 加到一个长为 $2$ 的二维数组 $c$ 中。
\\n分类讨论:
\\n这两种情况取最大值,就得到了 $\\\\textit{dfs}(i,j)$,即
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\max(\\\\textit{dfs}(i-1,j),\\\\textit{dfs}(i-2,j\\\\oplus 1)) + c[j][i]
\\n$$
递归边界:$\\\\textit{dfs}(-2, j)=\\\\textit{dfs}(-1, j)=0$。没有元素可以选了。
\\n递归入口:枚举最后一个数选的是 $a[n-1]$ 还是 $b[n-1]$,取最大值,即 $\\\\max(\\\\textit{dfs}(n-1,0), \\\\textit{dfs}(n-1,1))$,也就是答案。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。
\\n本题元素值均为正数,初始化成 $0$ 也可以。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n本题 视频讲解 第二题,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def maxEnergyBoost(self, a: List[int], b: List[int]) -> int:\\n c = (a, b)\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i < 0:\\n return 0\\n return max(dfs(i - 1, j), dfs(i - 2, j ^ 1)) + c[j][i]\\n return max(dfs(len(a) - 1, 0), dfs(len(a) - 1, 1))\\n
\\n###java
\\nclass Solution {\\n public long maxEnergyBoost(int[] a, int[] b) {\\n int n = a.length;\\n int[][] c = {a, b};\\n long[][] memo = new long[n][2];\\n return Math.max(dfs(n - 1, 0, c, memo), dfs(n - 1, 1, c, memo));\\n }\\n\\n private long dfs(int i, int j, int[][] c, long[][] memo) {\\n if (i < 0) {\\n return 0;\\n }\\n if (memo[i][j] > 0) { // 之前计算过\\n return memo[i][j];\\n }\\n return memo[i][j] = Math.max(dfs(i - 1, j, c, memo), dfs(i - 2, j ^ 1, c, memo)) + c[j][i];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxEnergyBoost(vector<int>& a, vector<int>& b) {\\n int n = a.size();\\n vector<int> c[2] = {move(a), move(b)};\\n vector<array<long long, 2>> memo(n);\\n auto dfs = [&](auto&& dfs, int i, int j) -> long long {\\n if (i < 0) {\\n return 0;\\n }\\n auto& res = memo[i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n return res = max(dfs(dfs, i - 1, j), dfs(dfs, i - 2, j ^ 1)) + c[j][i];\\n };\\n return max(dfs(dfs, n - 1, 0), dfs(dfs, n - 1, 1));\\n }\\n};\\n
\\n###go
\\nfunc maxEnergyBoost(a, b []int) int64 {\\nn := len(a)\\nc := [2][]int{a, b}\\nmemo := make([][2]int64, n)\\nvar dfs func(int, int) int64\\ndfs = func(i, j int) int64 {\\nif i < 0 {\\nreturn 0\\n}\\np := &memo[i][j]\\nif *p == 0 { // 首次计算\\n*p = max(dfs(i-1, j), dfs(i-2, j^1)) + int64(c[j][i])\\n}\\nreturn *p\\n}\\nreturn max(dfs(n-1, 0), dfs(n-1, 1))\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i+2][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示从下标 $[0,i]$ 中选数字,且最后选的是 $a[i]$ 或 $b[i]$ 的情况下,所选元素之和的最大值。这里 $+2$ 是为了把 $\\\\textit{dfs}(-2,j)$ 和 $\\\\textit{dfs}(-1,j)$ 这两个状态也翻译过来,这样我们可以把 $f[0]$ 和 $f[1]$ 作为初始值。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样(注意 $+2$):
\\n$$
\\nf[i+2][j] = \\\\max(f[i+1][j],f[i][j\\\\oplus 1]) + c[j][i]
\\n$$
初始值 $f[0][j]=f[1][j]=0$,翻译自递归边界 $\\\\textit{dfs}(-2,j)=\\\\textit{dfs}(-1,j)=0$。
\\n答案为 $\\\\max(f[n+1][0],f[n+1][1])$,翻译自递归入口 $\\\\max(\\\\textit{dfs}(n-1,0), \\\\textit{dfs}(n-1,1))$。
\\n###py
\\nclass Solution:\\n def maxEnergyBoost(self, a: List[int], b: List[int]) -> int:\\n n = len(a)\\n f = [[0, 0] for _ in range(n + 2)]\\n for i, (x, y) in enumerate(zip(a, b)):\\n f[i + 2][0] = max(f[i + 1][0], f[i][1]) + x\\n f[i + 2][1] = max(f[i + 1][1], f[i][0]) + y\\n return max(f[-1])\\n
\\n###java
\\nclass Solution {\\n public long maxEnergyBoost(int[] a, int[] b) {\\n int n = a.length;\\n long[][] f = new long[n + 2][2];\\n for (int i = 0; i < n; i++) {\\n f[i + 2][0] = Math.max(f[i + 1][0], f[i][1]) + a[i];\\n f[i + 2][1] = Math.max(f[i + 1][1], f[i][0]) + b[i];\\n }\\n return Math.max(f[n + 1][0], f[n + 1][1]);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxEnergyBoost(vector<int>& a, vector<int>& b) {\\n int n = a.size();\\n vector<array<long long, 2>> f(n + 2);\\n for (int i = 0; i < n; i++) {\\n f[i + 2][0] = max(f[i + 1][0], f[i][1]) + a[i];\\n f[i + 2][1] = max(f[i + 1][1], f[i][0]) + b[i];\\n }\\n return max(f[n + 1][0], f[n + 1][1]);\\n }\\n};\\n
\\n###go
\\nfunc maxEnergyBoost(a, b []int) int64 {\\nn := len(a)\\nf := make([][2]int64, n+2)\\nfor i, x := range a {\\nf[i+2][0] = max(f[i+1][0], f[i][1]) + int64(x)\\nf[i+2][1] = max(f[i+1][1], f[i][0]) + int64(b[i])\\n}\\nreturn max(f[n+1][0], f[n+1][1])\\n}\\n
\\n注:利用滚动变量,空间复杂度可以优化至 $\\\\mathcal{O}(1)$。
\\n注:本题还有另外一种状态定义:考虑选当前元素,还是跳过不选当前元素(切换到另一个数组)。与之对比,本题解的思路是当前元素一定要选,用递归到 $i-2$ 表示跳过 $i-1$ 不选。如果数组中有负数,哪种定义方式更符合题目描述?注意有负数也得喝饮料。
\\n更多相似题目,见 动态规划题单 中的「五、状态机 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 给你两个数组 $a$ 和 $b$。从 $i=0$ 开始,要么选 $a[i]$,要么选 $b[i]$。如果你当前选了 $a$ 中的元素,后面想选 $b$ 中的元素,那么下一个元素必须不能选。例如现在选 $a[i]$,那么后面可以选 $b[i+2]$,但不能选 $b[i+1]$。\\n\\n返回所选元素之和的最大值。\\n\\n一、寻找子问题\\n\\n本题有点类似 198. 打家劫舍,不过我们还是从子问题开始讨论。\\n\\n例如 $a=[1,3,1,2,3],\\\\ b=[3,1,1,2,3]$。\\n\\n如果最后一个数我们选了 $a[4]=3$,那么:\\n\\n继续选 $a$ 中的元素,那么下一个数选…","guid":"https://leetcode.cn/problems/maximum-energy-boost-from-two-drinks//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-iebb","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T04:47:06.569Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针 & 排序 & 树状数组","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/shuang-zhi-zhen-pai-xu-shu-zhuang-shu-zu-6ugp","content":"先来想想,求整个字符串里满足 $k$ 约束的子串有几个,怎么做。
\\n注意到如果一个子串满足 $k$ 约束,那么它的子串也满足 $k$ 约束。所以我们可以用双指针求出 $f_i$,表示满足约束的子串的结束下标为 $i$ 时,它的起始下标最小可以是多少。那么整个字符串里满足 $k$ 约束的子串数量,就有
\\n$$
\\n\\\\sum\\\\limits_{i = 1}^n (i - f_i + 1)
\\n$$
接下来考虑如何处理区间询问 $[l, r]$。因为区间左端点限制了 $k$ 约束子串的左端点,所以上述公式里的 $f_i$ 要改成 $\\\\max(f_i, l)$。因此区间里满足 $k$ 约束的子串数量为
\\n$$
\\n\\\\sum\\\\limits_{i = l}^r (i - \\\\max(f_i, l) + 1)
\\n$$
式子拆一下,变成
\\n$$
\\n\\\\sum\\\\limits_{i = l}^r (i + 1) - \\\\sum\\\\limits_{i = l}^r \\\\max(f_i, l)
\\n$$
第一项用等差数列求和公式就可以 $\\\\mathcal{O}(1)$ 求了。第二项我们可以拆成两个动态序列 $A$ 和 $B$,其中若 $f_i \\\\ge l$ 则 $a_i = f_i$,$b_i = 0$;否则若 $f_i < l$ 则 $a_i = 0$,$b_i = 1$。这样第二项其实就是 $A$ 的区间和,加上 $B$ 的区间和乘以 $l$。
\\n这两个动态序列怎么维护呢?我们可以离线处理询问。一开始将所有 $a_i = f_i$,$b_i = 0$,接下来将所有询问按 $l$ 从小到大排序并依次处理。这样当处理左端点为 $l$ 的询问时,若上一次询问的左端点为 $l\'$,则对于所有满足 $l\' \\\\le f_i < l$ 的下标 $i$,将 $a_i$ 改成 $0$,$b_i$ 改成 $1$。因为 $l$ 是递增的,所以每个下标只会被修改一次。
\\n最后我们需要一个数据结构,支持单点修改以及区间求和。树状数组就能满足我们的需要。
\\n因此整体复杂度 $\\\\mathcal{O}((n + q)\\\\log n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int K, vector<vector<int>>& queries) {\\n int n = s.size();\\n\\n // 双指针求出 f[i],表示满足约束的子串的结束下标为 i 时,它的起始下标最小可以是多少\\n int f[n + 1];\\n int cnt[2] = {0};\\n for (int i = 1, j = 1; i <= n; i++) {\\n cnt[s[i - 1] - \'0\']++;\\n while (cnt[0] > K && cnt[1] > K) {\\n cnt[s[j - 1] - \'0\']--;\\n j++;\\n }\\n f[i] = j;\\n }\\n\\n // 将所有下标按 f[i] 排序,方便后续处理询问时,对动态序列进行修改\\n typedef pair<int, int> pii;\\n vector<pii> vec;\\n for (int i = 1; i <= n; i++) vec.push_back({f[i], i});\\n sort(vec.begin(), vec.end(), greater<pii>());\\n\\n // 树状数组模板开始\\n\\n long long tree1[n + 1], tree2[n + 1];\\n memset(tree1, 0, sizeof(tree1)); memset(tree2, 0, sizeof(tree2));\\n\\n auto lb = [&](int x) { return x & (-x); };\\n\\n auto add = [&](long long *tree, int pos, int val) {\\n for (; pos <= n; pos += lb(pos)) tree[pos] += val;\\n };\\n\\n auto query = [&](long long *tree, int pos) {\\n long long ret = 0;\\n for (; pos; pos -= lb(pos)) ret += tree[pos];\\n return ret;\\n };\\n\\n // 树状数组模板结束\\n\\n // 询问按左端点从小到大排序\\n int q = queries.size();\\n vector<array<int, 3>> qry;\\n for (int i = 0; i < q; i++) qry.push_back({queries[i][0] + 1, queries[i][1] + 1, i});\\n sort(qry.begin(), qry.end());\\n\\n // 先把所有 A[i] = f[i]\\n for (int i = 1; i <= n; i++) add(tree1, i, f[i]);\\n vector<long long> ans(q);\\n for (auto &arr : qry) {\\n int L = arr[0], R = arr[1], len = R - L + 1;\\n // 修改动态序列\\n while (!vec.empty() && vec.back().first < L) {\\n int idx = vec.back().second;\\n add(tree1, idx, -f[idx]);\\n add(tree2, idx, 1);\\n vec.pop_back();\\n }\\n // 套公式求答案\\n ans[arr[2]] = 1LL * (L + R) * len / 2 + len;\\n ans[arr[2]] -= query(tree1, R) - query(tree1, L - 1);\\n ans[arr[2]] -= (query(tree2, R) - query(tree2, L - 1)) * L;\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:双指针 & 排序 & 树状数组 先来想想,求整个字符串里满足 $k$ 约束的子串有几个,怎么做。\\n\\n注意到如果一个子串满足 $k$ 约束,那么它的子串也满足 $k$ 约束。所以我们可以用双指针求出 $f_i$,表示满足约束的子串的结束下标为 $i$ 时,它的起始下标最小可以是多少。那么整个字符串里满足 $k$ 约束的子串数量,就有\\n\\n$$\\n \\\\sum\\\\limits_{i = 1}^n (i - f_i + 1)\\n $$\\n\\n接下来考虑如何处理区间询问 $[l, r]$。因为区间左端点限制了 $k$ 约束子串的左端点,所以上述公式里的 $f_i$ 要改成…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/shuang-zhi-zhen-pai-xu-shu-zhuang-shu-zu-6ugp","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T04:44:16.741Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"滑动窗口+前缀和+二分查找/双指针(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/hua-dong-chuang-kou-qian-zhui-he-er-fen-jzo25","content":"核心思路:对于每个询问,计算以 $l$ 为右端点的合法子串个数,以 $l+1$ 为右端点的合法子串个数,……,以 $r$ 为右端点的合法子串个数。
\\n我们需要知道以 $i$ 为右端点的合法子串,其左端点最小是多少。
\\n由于随着 $i$ 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 $i$ 的增大而增大,有单调性,因此可以用 滑动窗口 计算。
\\n设以 $i$ 为右端点的合法子串,其左端点最小是 $\\\\textit{left}[i]$。
\\n那么以 $i$ 为右端点的合法子串,其左端点可以是 $\\\\textit{left}[i],\\\\textit{left}[i]+1, \\\\ldots, i$,一共
\\n$$
\\ni-\\\\textit{left}[i]+1
\\n$$
个。
\\n回答询问时,分类讨论:
\\n代码实现时,两种情况可以合并为一种。
\\n具体请看 视频讲解 第四题,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int, queries: List[List[int]]) -> List[int]:\\n n = len(s)\\n left = [0] * n\\n pre = [0] * (n + 1)\\n cnt = [0, 0]\\n l = 0\\n for i, c in enumerate(s):\\n cnt[ord(c) & 1] += 1\\n while cnt[0] > k and cnt[1] > k:\\n cnt[ord(s[l]) & 1] -= 1\\n l += 1\\n left[i] = l # 记录合法子串右端点 i 对应的最小左端点 l\\n # 计算 i-left[i]+1 的前缀和\\n pre[i + 1] = pre[i] + i - l + 1\\n\\n ans = []\\n for l, r in queries:\\n j = bisect_left(left, l, l, r + 1) # 如果区间内所有数都小于 l,结果是 j=r+1\\n ans.append(pre[r + 1] - pre[j] + (j - l + 1) * (j - l) // 2)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String S, int k, int[][] queries) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n int[] left = new int[n];\\n long[] sum = new long[n + 1];\\n int[] cnt = new int[2];\\n int l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l++] & 1]--;\\n }\\n left[i] = l; // 记录合法子串右端点 i 对应的最小左端点 l\\n // 计算 i-left[i]+1 的前缀和\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n\\n long[] ans = new long[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n int ql = queries[i][0];\\n int qr = queries[i][1];\\n // 如果区间内所有数都小于 ql,结果是 j=qr+1\\n int j = lowerBound(left, ql - 1, qr + 1, ql);\\n ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2;\\n }\\n return ans;\\n }\\n\\n // 返回在开区间 (left, right) 中的最小的 j,满足 nums[j] >= target\\n // 如果没有这样的数,返回 right\\n // 原理见 https://www.bilibili.com/video/BV1AP41137w7/\\n private int lowerBound(int[] nums, int left, int right, int target) {\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // nums[left] < target\\n // nums[right] >= target\\n int mid = left + (right - left) / 2;\\n if (nums[mid] < target) {\\n left = mid; // 范围缩小到 (mid, right)\\n } else {\\n right = mid; // 范围缩小到 (left, mid)\\n }\\n }\\n return right;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {\\n int n = s.length();\\n vector<int> left(n);\\n vector<long long> sum(n + 1);\\n int cnt[2]{}, l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l++] & 1]--;\\n }\\n left[i] = l; // 记录合法子串右端点 i 对应的最小左端点 l\\n // 计算 i-left[i]+1 的前缀和\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n\\n vector<long long> ans(queries.size());\\n for (int i = 0; i < queries.size(); i++) {\\n int l = queries[i][0], r = queries[i][1];\\n // 如果区间内所有数都小于 l,结果是 j=r+1\\n int j = lower_bound(left.begin() + l, left.begin() + r + 1, l) - left.begin();\\n ans[i] = sum[r + 1] - sum[j] + (long long) (j - l + 1) * (j - l) / 2;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int, queries [][]int) []int64 {\\nn := len(s)\\nleft := make([]int, n)\\nsum := make([]int, n+1)\\ncnt := [2]int{}\\nl := 0\\nfor i, c := range s {\\ncnt[c&1]++\\nfor cnt[0] > k && cnt[1] > k {\\ncnt[s[l]&1]--\\nl++\\n}\\nleft[i] = l // 记录合法子串右端点 i 对应的最小左端点 l\\n// 计算 i-left[i]+1 的前缀和\\nsum[i+1] = sum[i] + i - l + 1\\n}\\n\\nans := make([]int64, len(queries))\\nfor i, q := range queries {\\nl, r := q[0], q[1]\\nj := l + sort.SearchInts(left[l:r+1], l) // 如果区间内所有数都小于 l,结果是 j=r+1\\nans[i] = int64(sum[r+1] - sum[j] + (j-l+1)*(j-l)/2)\\n}\\nreturn ans\\n}\\n
\\n上面的做法,每次都要二分找最小 $j$,满足 $\\\\textit{left}[j]\\\\ge l$。能否不用二分呢?
\\n也可以直接预处理,对每个左端点 $l=0,1,2,\\\\ldots,n-1$,计算出最小的 $j$,满足 $\\\\textit{left}[j]\\\\ge l$。
\\n由于 $\\\\textit{left}$ 数组是有序的,这个过程可以用双指针实现。
\\n将计算出的 $j$ 保存到 $\\\\textit{right}[l]$ 中。如果不存在满足 $\\\\textit{left}[j]\\\\ge l$ 的 $j$,则 $\\\\textit{right}[l] = n$。
\\n现在 $\\\\textit{left}[\\\\textit{right}[l]]\\\\ge l$ 且 $\\\\textit{left}[\\\\textit{right}[l]-1] < l$。
\\n预处理后,回答询问时,$j$ 可以直接通过 $\\\\textit{right}[l]$ 获取到。注意这个数不能超过 $r+1$,所以有
\\n$$
\\nj = \\\\min(\\\\textit{right}[l], r+1)
\\n$$
更快的写法见下面的写法二。
\\n###py
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int, queries: List[List[int]]) -> List[int]:\\n n = len(s)\\n left = [0] * n\\n pre = [0] * (n + 1)\\n cnt = [0, 0]\\n l = 0\\n for i, c in enumerate(s):\\n cnt[ord(c) & 1] += 1\\n while cnt[0] > k and cnt[1] > k:\\n cnt[ord(s[l]) & 1] -= 1\\n l += 1\\n left[i] = l\\n pre[i + 1] = pre[i] + i - l + 1\\n\\n right = [0] * n\\n l = 0\\n for i in range(n):\\n while l < n and left[l] < i:\\n l += 1\\n right[i] = l\\n\\n ans = []\\n for l, r in queries:\\n j = min(right[l], r + 1)\\n ans.append(pre[r + 1] - pre[j] + (j - l + 1) * (j - l) // 2)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String S, int k, int[][] queries) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n int[] left = new int[n];\\n long[] sum = new long[n + 1];\\n int[] cnt = new int[2];\\n int l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l++] & 1]--;\\n }\\n left[i] = l;\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n\\n int[] right = new int[n];\\n l = 0;\\n for (int i = 0; i < n; i++) {\\n while (l < n && left[l] < i) {\\n l++;\\n }\\n right[i] = l;\\n }\\n\\n long[] ans = new long[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n int ql = queries[i][0];\\n int qr = queries[i][1];\\n int j = Math.min(right[ql], qr + 1);\\n ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {\\n int n = s.length();\\n vector<int> left(n);\\n vector<long long> sum(n + 1);\\n int cnt[2]{}, l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l++] & 1]--;\\n }\\n left[i] = l;\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n\\n vector<int> right(n);\\n l = 0;\\n for (int i = 0; i < n; i++) {\\n while (l < n && left[l] < i) {\\n l++;\\n }\\n right[i] = l;\\n }\\n\\n vector<long long> ans(queries.size());\\n for (int i = 0; i < queries.size(); i++) {\\n int l = queries[i][0], r = queries[i][1];\\n int j = min(right[l], r + 1);\\n ans[i] = sum[r + 1] - sum[j] + (long long) (j - l + 1) * (j - l) / 2;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int, queries [][]int) []int64 {\\nn := len(s)\\nleft := make([]int, n)\\nsum := make([]int, n+1)\\ncnt := [2]int{}\\nl := 0\\nfor i, c := range s {\\ncnt[c&1]++\\nfor cnt[0] > k && cnt[1] > k {\\ncnt[s[l]&1]--\\nl++\\n}\\nleft[i] = l\\nsum[i+1] = sum[i] + i - l + 1\\n}\\n\\nright := make([]int, n)\\nl = 0\\nfor i := range right {\\nfor l < n && left[l] < i {\\nl++\\n}\\nright[i] = l\\n}\\n\\nans := make([]int64, len(queries))\\nfor i, q := range queries {\\nl, r := q[0], q[1]\\nj := min(right[l], r+1)\\nans[i] = int64(sum[r+1] - sum[j] + (j-l+1)*(j-l)/2)\\n}\\nreturn ans\\n}\\n
\\n在上面的方法中,我们找的是最小的 $j$,满足 $\\\\textit{left}[j]\\\\ge l$。
\\n也可以找最小的 $j$,满足 $\\\\textit{left}[j] > l$,这不会影响计算出的合法子串个数。
\\n现在 $\\\\textit{left}[\\\\textit{right}[l]] > l$ 且 $\\\\textit{left}[\\\\textit{right}[l]-1] \\\\le l$。
\\n在滑窗的过程中,如果发现窗口不满足要求,那么在移动左端点 $l$ 之前,可以记录 $\\\\textit{right}[l]=i$。
\\n###py
\\nclass Solution:\\n def countKConstraintSubstrings(self, s: str, k: int, queries: List[List[int]]) -> List[int]:\\n n = len(s)\\n right = [n] * n\\n pre = [0] * (n + 1)\\n cnt = [0, 0]\\n l = 0\\n for i, c in enumerate(s):\\n cnt[ord(c) & 1] += 1\\n while cnt[0] > k and cnt[1] > k:\\n cnt[ord(s[l]) & 1] -= 1\\n right[l] = i\\n l += 1\\n pre[i + 1] = pre[i] + i - l + 1\\n\\n ans = []\\n for l, r in queries:\\n j = min(right[l], r + 1)\\n ans.append(pre[r + 1] - pre[j] + (j - l + 1) * (j - l) // 2)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long[] countKConstraintSubstrings(String S, int k, int[][] queries) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n int[] right = new int[n];\\n long[] sum = new long[n + 1];\\n int[] cnt = new int[2];\\n int l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l] & 1]--;\\n right[l++] = i;\\n }\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n // 剩余没填的 right[l] 均为 n\\n Arrays.fill(right, l, n, n);\\n\\n long[] ans = new long[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n int ql = queries[i][0];\\n int qr = queries[i][1];\\n int j = Math.min(right[ql], qr + 1);\\n ans[i] = sum[qr + 1] - sum[j] + (long) (j - ql + 1) * (j - ql) / 2;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<long long> countKConstraintSubstrings(string s, int k, vector<vector<int>>& queries) {\\n int n = s.length();\\n vector<int> right(n, n);\\n vector<long long> sum(n + 1);\\n int cnt[2]{}, l = 0;\\n for (int i = 0; i < n; i++) {\\n cnt[s[i] & 1]++;\\n while (cnt[0] > k && cnt[1] > k) {\\n cnt[s[l] & 1]--;\\n right[l++] = i;\\n }\\n sum[i + 1] = sum[i] + i - l + 1;\\n }\\n\\n vector<long long> ans(queries.size());\\n for (int i = 0; i < queries.size(); i++) {\\n int l = queries[i][0], r = queries[i][1];\\n int j = min(right[l], r + 1);\\n ans[i] = sum[r + 1] - sum[j] + (long long) (j - l + 1) * (j - l) / 2;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKConstraintSubstrings(s string, k int, queries [][]int) []int64 {\\nn := len(s)\\nright := make([]int, n)\\nsum := make([]int, n+1)\\ncnt := [2]int{}\\nl := 0\\nfor i, c := range s {\\ncnt[c&1]++\\nfor cnt[0] > k && cnt[1] > k {\\ncnt[s[l]&1]--\\nright[l] = i\\nl++\\n}\\nsum[i+1] = sum[i] + i - l + 1\\n}\\n// 剩余没填的 right[l] 均为 n\\nfor ; l < n; l++ {\\nright[l] = n\\n}\\n\\nans := make([]int64, len(queries))\\nfor i, q := range queries {\\nl, r := q[0], q[1]\\nj := min(right[l], r+1)\\nans[i] = int64(sum[r+1] - sum[j] + (j-l+1)*(j-l)/2)\\n}\\nreturn ans\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:滑动窗口+前缀和+二分查找 核心思路:对于每个询问,计算以 $l$ 为右端点的合法子串个数,以 $l+1$ 为右端点的合法子串个数,……,以 $r$ 为右端点的合法子串个数。\\n\\n我们需要知道以 $i$ 为右端点的合法子串,其左端点最小是多少。\\n\\n由于随着 $i$ 的变大,窗口内的字符数量变多,越不能满足题目要求,所以最小左端点会随着 $i$ 的增大而增大,有单调性,因此可以用 滑动窗口 计算。\\n\\n设以 $i$ 为右端点的合法子串,其左端点最小是 $\\\\textit{left}[i]$。\\n\\n那么以 $i$ 为右端点的合法子串,其左端点可以是 $\\\\textit…","guid":"https://leetcode.cn/problems/count-substrings-that-satisfy-k-constraint-ii//solution/hua-dong-chuang-kou-qian-zhui-he-er-fen-jzo25","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T04:42:49.263Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/on-jian-ji-xie-fa-pythonjavacgo-by-endle-ymma","content":"本题和双周赛第二题是一样的,请看 我的题解。
\\n","description":"本题和双周赛第二题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/on-jian-ji-xie-fa-pythonjavacgo-by-endle-ymma","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-18T00:34:14.457Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii//solution/on-jian-ji-xie-fa-pythonjavacgo-by-endle-gtek","content":"核心思路:找连续上升的段。如果段长至少是 $k$,那么这段中的所有长为 $k$ 的子数组都是符合要求的,子数组的最后一个元素是最大的。
\\n具体来说,遍历数组的同时,用一个计数器 $\\\\textit{cnt}$ 统计连续递增的元素个数:
\\n具体例子请看 视频讲解,欢迎关注~
\\n###py
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n ans = [-1] * (len(nums) - k + 1)\\n cnt = 0\\n for i, x in enumerate(nums):\\n cnt = cnt + 1 if i == 0 or x == nums[i - 1] + 1 else 1\\n if cnt >= k:\\n ans[i - k + 1] = x\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] resultsArray(int[] nums, int k) {\\n int[] ans = new int[nums.length - k + 1];\\n Arrays.fill(ans, -1);\\n int cnt = 0;\\n for (int i = 0; i < nums.length; i++) {\\n cnt = i == 0 || nums[i] == nums[i - 1] + 1 ? cnt + 1 : 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int k) {\\n vector<int> ans(nums.size() - k + 1, -1);\\n int cnt = 0;\\n for (int i = 0; i < nums.size(); i++) {\\n cnt = i == 0 || nums[i] == nums[i - 1] + 1 ? cnt + 1 : 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint* resultsArray(int* nums, int numsSize, int k, int* returnSize) {\\n *returnSize = numsSize - k + 1;\\n int* ans = malloc(*returnSize * sizeof(int));\\n memset(ans, -1, *returnSize * sizeof(int));\\n\\n int cnt = 0;\\n for (int i = 0; i < numsSize; i++) {\\n cnt = i == 0 || nums[i] == nums[i - 1] + 1 ? cnt + 1 : 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc resultsArray(nums []int, k int) []int {\\nans := make([]int, len(nums)-k+1)\\nfor i := range ans {\\nans[i] = -1\\n}\\ncnt := 0\\nfor i, x := range nums {\\nif i == 0 || x == nums[i-1]+1 {\\ncnt++\\n} else {\\ncnt = 1\\n}\\nif cnt >= k {\\nans[i-k+1] = x\\n}\\n}\\nreturn ans\\n}\\n
\\n###js
\\nvar resultsArray = function(nums, k) {\\n const ans = Array(nums.length - k + 1).fill(-1);\\n let cnt = 0;\\n for (let i = 0; i < nums.length; i++) {\\n cnt = i === 0 || nums[i] === nums[i - 1] + 1 ? cnt + 1 : 1;\\n if (cnt >= k) {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn results_array(nums: Vec<i32>, k: i32) -> Vec<i32> {\\n let k = k as usize;\\n let mut ans = vec![-1; nums.len() - k + 1];\\n let mut cnt = 0;\\n for i in 0..nums.len() {\\n if i == 0 || nums[i] == nums[i - 1] + 1 {\\n cnt += 1;\\n } else {\\n cnt = 1;\\n }\\n if cnt >= k {\\n ans[i - k + 1] = nums[i];\\n }\\n }\\n ans\\n }\\n}\\n
\\n\\n\\nProblem: 100384. 长度为 K 的子数组的能量值 II
\\n
[TOC]
\\nRT
\\n更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n res = []\\n last = nums[0] - 1\\n t = 0\\n for i,num in enumerate(nums):\\n if num == last + 1:\\n t += 1\\n else:\\n t = 1\\n \\n if t >= k:\\n res.append(num)\\n elif i - k + 1 >= 0:\\n res.append(-1)\\n\\n last = num\\n \\n return res\\n
\\n","description":"Problem: 100384. 长度为 K 的子数组的能量值 II [TOC]\\n\\nRT\\n\\n更多题目模板总结,请参考2023年度总结与题目分享\\n\\n###Python3\\n\\nclass Solution:\\n def resultsArray(self, nums: List[int], k: int) -> List[int]:\\n n = len(nums)\\n res = []\\n last = nums[0] - 1\\n t = 0\\n for i,num in enumerate…","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii//solution/yi-ci-bian-li-by-mipha-2022-0fp2","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-17T17:01:00.437Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 前缀和","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/mei-ju-qian-zhui-he-by-tsreaper-h0v1","content":"维护布尔值 $f_i$ 表示 $a_i > a_{i - 1}$。
\\ntrue
。另外可以由此推出,$a_i$ 是最小值,$a_{i + k - 1}$ 是最大值。枚举每个长度为 $k$ 的子数组并检查以上两个条件,其中第一个条件用前缀和检查连续的 $f_i$ 是否都为 true
即可。复杂度 $\\\\mathcal{O}(n)$。
###cpp
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int K) {\\n int n = nums.size();\\n int f[n];\\n f[0] = 0;\\n for (int i = 1; i < n; i++) {\\n f[i] = f[i - 1];\\n if (nums[i] > nums[i - 1]) f[i]++;\\n }\\n\\n vector<int> ans;\\n for (int i = 0; i + K <= n; i++) {\\n if (f[i + K - 1] - f[i] == K - 1 && nums[i + K - 1] - nums[i] == K - 1)\\n ans.push_back(nums[i + K - 1]);\\n else\\n ans.push_back(-1);\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 & 前缀和 维护布尔值 $f_i$ 表示 $a_i > a_{i - 1}$。\\n\\n如果子数组 $a_i, a_{i + 1}, \\\\cdots, a_{i + k - 1}$ 是上升,那么 $f_{i + 1}$ 到 $f_{i + k - 1}$ 就都是 true。另外可以由此推出,$a_i$ 是最小值,$a_{i + k - 1}$ 是最大值。\\n如果子数组是连续的,则 $a_{i + k - 1} - a_i = k - 1$。\\n\\n枚举每个长度为 $k$ 的子数组并检查以上两个条件,其中第一个条件用前缀和检查连续的 $f_i$ 是否都为 t…","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-i//solution/mei-ju-qian-zhui-he-by-tsreaper-h0v1","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-17T16:28:29.002Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 前缀和","url":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii//solution/mei-ju-qian-zhui-he-by-tsreaper-33ku","content":"维护布尔值 $f_i$ 表示 $a_i > a_{i - 1}$。
\\ntrue
。另外可以由此推出,$a_i$ 是最小值,$a_{i + k - 1}$ 是最大值。枚举每个长度为 $k$ 的子数组并检查以上两个条件,其中第一个条件用前缀和检查连续的 $f_i$ 是否都为 true
即可。复杂度 $\\\\mathcal{O}(n)$。
###cpp
\\nclass Solution {\\npublic:\\n vector<int> resultsArray(vector<int>& nums, int K) {\\n int n = nums.size();\\n int f[n];\\n f[0] = 0;\\n for (int i = 1; i < n; i++) {\\n f[i] = f[i - 1];\\n if (nums[i] > nums[i - 1]) f[i]++;\\n }\\n\\n vector<int> ans;\\n for (int i = 0; i + K <= n; i++) {\\n if (f[i + K - 1] - f[i] == K - 1 && nums[i + K - 1] - nums[i] == K - 1)\\n ans.push_back(nums[i + K - 1]);\\n else\\n ans.push_back(-1);\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 & 前缀和 维护布尔值 $f_i$ 表示 $a_i > a_{i - 1}$。\\n\\n如果子数组 $a_i, a_{i + 1}, \\\\cdots, a_{i + k - 1}$ 是上升,那么 $f_{i + 1}$ 到 $f_{i + k - 1}$ 就都是 true。另外可以由此推出,$a_i$ 是最小值,$a_{i + k - 1}$ 是最大值。\\n如果子数组是连续的,则 $a_{i + k - 1} - a_i = k - 1$。\\n\\n枚举每个长度为 $k$ 的子数组并检查以上两个条件,其中第一个条件用前缀和检查连续的 $f_i$ 是否都为 t…","guid":"https://leetcode.cn/problems/find-the-power-of-k-size-subarrays-ii//solution/mei-ju-qian-zhui-he-by-tsreaper-33ku","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-17T16:28:06.508Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3248. 矩阵中的蛇","url":"https://leetcode.cn/problems/snake-in-matrix//solution/3248-ju-zhen-zhong-de-she-by-stormsunshi-gn3a","content":"为了计算执行所有命令之后的蛇所在单元格位置,需要分别计算蛇所在单元格的行下标 $\\\\textit{row}$ 和列下标 $\\\\textit{column}$,然后计算蛇所在单元格位置 $\\\\textit{row} \\\\times n + \\\\textit{column}$。
\\n初始时,$\\\\textit{row} = \\\\textit{column} = 0$。遍历字符串数组 $\\\\textit{commands}$,根据每个命令模拟蛇的移动,具体为:遇到 $\\\\text{UP\\"}$ 将 $\\\\textit{row}$ 减少 $1$,遇到 $\\\\text{
RIGHT\\"}$ 将 $\\\\textit{column}$ 增加 $1$,遇到 $\\\\text{DOWN\\"}$ 将 $\\\\textit{row}$ 增加 $1$,遇到 $\\\\text{
LEFT\\"}$ 将 $\\\\textit{column}$ 减少 $1$。
实现方面,由于四种命令的首字母各不相同,因此可以根据命令的首字母得到蛇的移动。
\\n###Java
\\nclass Solution {\\n public int finalPositionOfSnake(int n, List<String> commands) {\\n int row = 0, column = 0;\\n for (String command : commands) {\\n char c = command.charAt(0);\\n if (c == \'U\') {\\n row--;\\n } else if (c == \'R\') {\\n column++;\\n } else if (c == \'D\') {\\n row++;\\n } else if (c == \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int FinalPositionOfSnake(int n, IList<string> commands) {\\n int row = 0, column = 0;\\n foreach (string command in commands) {\\n char c = command[0];\\n if (c == \'U\') {\\n row--;\\n } else if (c == \'R\') {\\n column++;\\n } else if (c == \'D\') {\\n row++;\\n } else if (c == \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int finalPositionOfSnake(int n, vector<string>& commands) {\\n int row = 0, column = 0;\\n for (string command : commands) {\\n char c = command[0];\\n if (c == \'U\') {\\n row--;\\n } else if (c == \'R\') {\\n column++;\\n } else if (c == \'D\') {\\n row++;\\n } else if (c == \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n row, column = 0, 0\\n for command in commands:\\n c = command[0]\\n if c == \'U\':\\n row -= 1\\n elif c == \'R\':\\n column += 1\\n elif c == \'D\':\\n row += 1\\n elif c == \'L\':\\n column -= 1\\n return row * n + column\\n
\\n###C
\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int row = 0, column = 0;\\n for (int i = 0; i < commandsSize; i++) {\\n char c = commands[i][0];\\n if (c == \'U\') {\\n row--;\\n } else if (c == \'R\') {\\n column++;\\n } else if (c == \'D\') {\\n row++;\\n } else if (c == \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n}\\n
\\n###Go
\\nfunc finalPositionOfSnake(n int, commands []string) int {\\n row, column := 0, 0\\n for _, command := range commands {\\n c := command[0]\\n if c == \'U\' {\\n row--\\n } else if c == \'R\' {\\n column++\\n } else if c == \'D\' {\\n row++\\n } else if c == \'L\' {\\n column--\\n }\\n }\\n return row * n + column\\n}\\n
\\n###JavaScript
\\nvar finalPositionOfSnake = function(n, commands) {\\n let row = 0, column = 0;\\n for (let command of commands) {\\n let c = command[0];\\n if (c === \'U\') {\\n row--;\\n } else if (c === \'R\') {\\n column++;\\n } else if (c === \'D\') {\\n row++;\\n } else if (c === \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n};\\n
\\n###TypeScript
\\nfunction finalPositionOfSnake(n: number, commands: string[]): number {\\n let row = 0, column = 0;\\n for (let command of commands) {\\n let c = command[0];\\n if (c === \'U\') {\\n row--;\\n } else if (c === \'R\') {\\n column++;\\n } else if (c === \'D\') {\\n row++;\\n } else if (c === \'L\') {\\n column--;\\n }\\n }\\n return row * n + column;\\n};\\n
\\n时间复杂度:$O(m)$,其中 $m$ 是数组 $\\\\textit{commands}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(1)$。
\\n本题解没讲太多细节,需要读者有 DP 基础。
\\n记 $i$ 为数组下标(从 0 开始),$j = arr1[i]$, $k = arr1[i-1]$
\\n根据题意
\\narr1
非递减,所以限制 $k \\\\leq j$arr2
非递增,所以限制 $nums[i-1] - k \\\\geq nums[i] - j$所以 $0 \\\\leq k \\\\leq min(j, nums[i-1] - nums[i] + j)$
\\n记 $ceil = min(j,\\\\ nums[i-1] - nums[i] + j)$
\\n可得状态转移方程 $f(i, j) = \\\\sum\\\\limits_{k=0}^{ceil} f(i-1, k)$
\\n初始状态为
\\n$$
\\nf(0, j) =
\\n\\\\begin{cases}
\\n1 & if\\\\ j \\\\in [0, nums[0]]\\\\
\\n0 & 其他
\\n\\\\end{cases}
\\n$$
在多维的实现中,计算每行的每个元素时都要取上一行的某段前缀和。
\\n###C++
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int MOD = 1e9 + 7;\\n int m = nums.size();\\n int n = *max_element(nums.begin(), nums.end());\\n vector<int> f(n + 1);\\n\\n // 初始状态\\n for (int v = 0; v <= nums[0]; ++v)\\n f[v] = 1;\\n\\n vector<int> preSums;\\n\\n // 多执行一轮\\n for (int i = 1; i <= m; ++i) {\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear();\\n int preSum = 0;\\n for(int v : f){\\n preSum += v;\\n preSum %= MOD; \\n preSums.push_back(preSum);\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if (i == m) return preSums.back();\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for (int j = 0; j <= nums[i]; ++j) {\\n int ceil = min(j, nums[i-1] - nums[i] + j);\\n if (ceil >= 0)\\n f[j] = preSums[ceil];\\n else // ceil < 0 说明不存在\\n f[j] = 0;\\n }\\n for (int j = nums[i] + 1; j < f.size(); ++j) {\\n f[j] = 0;\\n }\\n }\\n \\n // 不会执行\\n return 0;\\n }\\n};\\n
\\n###Python3
\\nclass Solution:\\n def countOfPairs(self, nums):\\n MOD = 1_000_000_007\\n m = len(nums)\\n n = max(nums)\\n f = [0] * (n + 1)\\n\\n # 初始状态\\n for v in range(nums[0] + 1):\\n f[v] = 1\\n\\n preSums = []\\n\\n # 多执行一轮\\n for i in range(1, m + 1):\\n # 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear()\\n preSum = 0\\n for v in f:\\n preSum += v\\n preSum %= MOD\\n preSums.append(preSum)\\n # 最后一轮提前返回结尾处的方案数总和\\n if i == m:\\n return preSums[-1]\\n\\n # 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for j in range(nums[i] + 1):\\n ceil = min(j, nums[i-1] - nums[i] + j)\\n if ceil >= 0:\\n f[j] = preSums[ceil]\\n else: # ceil < 0 说明不存在\\n f[j] = 0\\n for j in range(nums[i] + 1, len(f)):\\n f[j] = 0\\n \\n # 不会执行\\n return 0\\n
\\n###Java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int MOD = (int)(1e9 + 7);\\n int m = nums.length;\\n int n = Arrays.stream(nums).max().getAsInt();\\n int[] f = new int[n + 1];\\n\\n // 初始状态\\n for (int v = 0; v <= nums[0]; ++v) f[v] = 1;\\n\\n List<Integer> preSums = new ArrayList<>();\\n\\n // 多执行一轮\\n for (int i = 1; i <= m; ++i) {\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear();\\n int preSum = 0;\\n for(int v : f){\\n preSum += v;\\n preSum %= MOD; \\n preSums.add(preSum);\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if (i == m) return preSums.get(preSums.size() - 1);\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for (int j = 0; j <= nums[i]; ++j) {\\n int ceil = Math.min(j, nums[i-1] - nums[i] + j);\\n if (ceil >= 0)\\n f[j] = preSums.get(ceil);\\n else // ceil < 0 说明不存在\\n f[j] = 0;\\n }\\n for (int j = nums[i] + 1; j < f.length; ++j) {\\n f[j] = 0;\\n }\\n }\\n \\n // 不会执行\\n return 0;\\n }\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun countOfPairs(nums: IntArray): Int {\\n val MOD = 1_000_000_007\\n val m = nums.size \\n val n = nums.max()\\n val f = IntArray(n+1)\\n // 初始状态\\n for(v in 0..nums[0]) f[v] = 1\\n\\n val preSums = mutableListOf<Int>()\\n\\n // 多执行一轮\\n for(i in 1..m){\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear()\\n for(v in f){\\n val lastPreSum = preSums.lastOrNull() ?: 0\\n preSums += (lastPreSum + v) % MOD\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if(i == m) return preSums.last()\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for(j in 0..nums[i]){\\n val ceil = min(j, nums[i-1] - nums[i] + j)\\n if(ceil >= 0)\\n f[j] = preSums[ceil]\\n else // ceil < 0 说明不存在\\n f[j] = 0 \\n }\\n for(j in (nums[i] + 1)..f.lastIndex){\\n f[j] = 0\\n }\\n }\\n \\n // 不会执行\\n return 0\\n }\\n}\\n
\\n记 $m$ 为 nums
长度,$n$ 为 nums
元素最大值。
f
的长度限制为 nums
最后一个元素的大小 + 1。我就偷懒不写这么详细了。
\\n以下皆为个人所著,兼顾了职场面试和本硕阶段的学术考试。
\\n点赞关注不迷路。祝君早日上岸,飞黄腾达!
\\n","description":"本题解没讲太多细节,需要读者有 DP 基础。 记 $i$ 为数组下标(从 0 开始),$j = arr1[i]$, $k = arr1[i-1]$\\n\\n根据题意\\n\\n$j \\\\in [0, nums[i]]$。(当然不是每个 $j$ 都符合要求,还需进一步限制)\\narr1 非递减,所以限制 $k \\\\leq j$\\narr2 非递增,所以限制 $nums[i-1] - k \\\\geq nums[i] - j$\\n\\n所以 $0 \\\\leq k \\\\leq min(j, nums[i-1] - nums[i] + j)$\\n\\n记 $ceil = min(j,\\\\ nums[i-1]…","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i//solution/qian-zhui-he-ya-suo-kong-jian-jian-ji-da-phub","author":"shawxing-kwok","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T11:40:25.327Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"无向树模版题,清晰写法,(C++ Java / Python3 / Kotlin)","url":"https://leetcode.cn/problems/count-the-number-of-good-nodes//solution/wu-xiang-shu-mo-ban-ti-qing-xi-xie-fa-c-924la","content":"我们可以通过在一遍 DFS 中枚举节点,令 DFS 返回每个子树的节点总数。如果子树的节点总数均相同,即令待返回的 ans
加一。
如果读者还未掌握基础的递归和图论知识,可参考我写的 《图论入门》。
\\n###C++
\\nclass Solution {\\npublic:\\n int countGoodNodes(vector<vector<int>>& edges) {\\n int n = edges.size() + 1; // 节点数\\n\\n // 构建邻接表\\n vector<vector<int>> adj(n);\\n for (const auto& edge : edges) {\\n int u = edge[0], v = edge[1];\\n adj[u].push_back(v);\\n adj[v].push_back(u);\\n }\\n \\n int ans = 0;\\n \\n // parent 为父节点\\n function<int(int, int)> dfs = [&](int u, int parent) -> int {\\n int cnt_sum = 1; // 节点总数,先计入自身\\n int single_cnt = 0;\\n bool valid = true;\\n\\n for (int v : adj[u]) {\\n // 规避邻接点中已访问过的父节点,不需要 visited 数组。\\n if (v == parent) continue;\\n\\n int cnt = dfs(v, u);\\n cnt_sum += cnt;\\n // 检测子树的节点数量是否相同\\n if (single_cnt == 0)\\n single_cnt = cnt;\\n else if (single_cnt != cnt)\\n valid = false;\\n }\\n ans += valid;\\n return cnt_sum;\\n };\\n\\n dfs(0, -1); // -1 即不存在父节点\\n return ans;\\n }\\n};\\n
\\n###Python3
\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n n = len(edges) + 1 # 节点数\\n\\n # 构建邻接表\\n adj = [[] for _ in range(n)]\\n for u, v in edges:\\n adj[u].append(v)\\n adj[v].append(u)\\n \\n ans = 0\\n \\n # parent 为父节点\\n def dfs(u: int, parent: int) -> int:\\n cnt_sum = 1 # 节点总数,先计入自身\\n single_cnt = 0\\n valid = True\\n\\n for v in adj[u]:\\n # 规避邻接点中已访问过的父节点,不需要 visited 数组。\\n if v == parent:\\n continue\\n\\n cnt = dfs(v, u)\\n cnt_sum += cnt\\n # 检测子树的节点数量是否相同\\n if single_cnt == 0:\\n single_cnt = cnt\\n elif single_cnt != cnt:\\n valid = False\\n \\n nonlocal ans\\n ans += valid\\n return cnt_sum\\n\\n dfs(0, -1) # -1 即不存在父节点\\n return ans\\n
\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n n = len(edges) + 1\\n adj = [[] for _ in range(n)]\\n\\n for u, v in edges:\\n adj[u].append(v)\\n adj[v].append(u)\\n \\n ans = 0\\n \\n def dfs(u: int, parent: int) -> int:\\n counts = [dfs(v, u) for v in adj[u] if v != parent]\\n nonlocal ans\\n if len(set(counts)) <= 1: # 考虑 adj[u] 为空对应的 len(set(counts)) == 0 \\n ans += 1\\n return sum(counts) + 1\\n \\n dfs(0, -1)\\n return ans\\n
\\n###Java
\\nclass Solution {\\n public int countGoodNodes(int[][] edges) {\\n int n = edges.length + 1; // 节点数\\n // 构建邻接表\\n List<List<Integer>> adj = new ArrayList<>(n);\\n for (int i = 0; i < n; ++i) {\\n adj.add(new ArrayList<>());\\n }\\n\\n for (int[] edge : edges) {\\n int u = edge[0], v = edge[1];\\n adj.get(u).add(v);\\n adj.get(v).add(u);\\n }\\n \\n int[] ans = {0};\\n \\n dfs(0, -1, adj, ans);\\n return ans[0];\\n }\\n\\n // parent 为父节点\\n private int dfs(int u, int parent, List<List<Integer>> adj, int[] ans) {\\n int cntSum = 1; // 节点总数,先计入自身\\n int singleCnt = 0;\\n boolean valid = true;\\n\\n for (int v : adj.get(u)) {\\n // 规避邻接点中已访问过的父节点,不需要 visited 数组。\\n if (v == parent) continue; \\n\\n int cnt = dfs(v, u, adj, ans);\\n cntSum += cnt;\\n // 检测子树的节点数量是否相同\\n if (singleCnt == 0)\\n singleCnt = cnt;\\n else if (singleCnt != cnt)\\n valid = false;\\n }\\n if (valid) ans[0]++; // -1 即不存在父节点\\n return cntSum;\\n }\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun countGoodNodes(edges: Array<IntArray>): Int {\\n val n = edges.size + 1 // 节点数\\n\\n // 构建邻接表\\n val adj = Array(n){ mutableListOf<Int>() }\\n for((u, v) in edges){\\n adj[u] += v \\n adj[v] += u \\n }\\n \\n var ans = 0 \\n \\n // parent 为父节点\\n fun dfs(u: Int, parent: Int): Int{\\n var cntSum = 1 // 节点总数,先计入自身\\n var singleCnt = 0 \\n var valid = true \\n\\n for(v in adj[u]){\\n // 规避邻接点中已访问过的父节点,不需要 visited 数组。\\n if(v == parent) continue \\n\\n val cnt = dfs(v, u)\\n cntSum += cnt \\n // 检测子树的节点数量是否相同\\n when{\\n singleCnt == 0 -> singleCnt = cnt \\n singleCnt != cnt -> valid = false \\n }\\n }\\n if(valid) ans++\\n return cntSum\\n }\\n\\n dfs(0, -1) // -1 即不存在父节点\\n return ans\\n }\\n}\\n
\\nclass Solution {\\n fun countGoodNodes(edges: Array<IntArray>): Int {\\n val n = edges.size + 1 \\n val adj = Array(n){ mutableListOf<Int>() }\\n\\n for((u, v) in edges){\\n adj[u] += v \\n adj[v] += u \\n }\\n \\n var ans = 0 \\n \\n fun dfs(u: Int, parent: Int): Int{\\n val counts = adj[u].filter{ it != parent }\\n .map{ dfs(it, u) }\\n \\n // 考虑 adj[u] 为空对应的 counts.toSet().size == 0\\n if(counts.toSet().size <= 1) ans++\\n return counts.sum() + 1\\n }\\n\\n dfs(0, -1)\\n return ans\\n }\\n}\\n
\\n$m$ 为边数,无向树也是生成树,在本题中边数比点数少 1。
\\n以下皆为个人所著,兼顾了职场面试和本硕阶段的学术考试。
\\n点赞关注不迷路。祝君早日上岸,飞黄腾达!
\\n","description":"我们可以通过在一遍 DFS 中枚举节点,令 DFS 返回每个子树的节点总数。如果子树的节点总数均相同,即令待返回的 ans 加一。 如果读者还未掌握基础的递归和图论知识,可参考我写的 《图论入门》。\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int countGoodNodes(vector\\n\\nProblem: 100395. 单调数组对的数目 I
\\n
[TOC]
\\n\\n\\n你选用何种方法解题?
\\n
\\n\\n这些方法具体怎么运用?
\\n
###Rust
\\nuse std::collections::{HashMap, HashSet};\\nimpl Solution {\\n pub fn count_of_pairs(nums: Vec<i32>) -> i32 {\\n let mut memo = HashMap::new();\\n fn get_total_cnt(idx:usize, prev:i32, memo:& mut HashMap<(usize, i32), i32>, nums:& Vec<i32>) -> i32 {\\n let mod1 = 1_000_000_007;\\n if let Some(&v) = memo.get(&(idx, prev)) {\\n return v;\\n }\\n if idx == nums.len() {\\n return 1\\n }\\n let mut cnt = 0;\\n for i in (prev as usize..=nums[idx] as usize).rev() {\\n if idx > 0 {\\n let prev = nums[idx-1] - prev;\\n if nums[idx] - i as i32 > prev {\\n break;\\n }\\n }\\n cnt += get_total_cnt(idx+1, i as i32, memo, nums);\\n cnt %= mod1;\\n }\\n memo.insert((idx, prev), cnt);\\n cnt\\n }\\n get_total_cnt(0, 0, &mut memo, &nums)\\n }\\n}\\n
\\n","description":"Problem: 100395. 单调数组对的数目 I [TOC]\\n\\n你选用何种方法解题?\\n\\n这些方法具体怎么运用?\\n\\n时间复杂度: $O(*)$\\n空间复杂度: $O(*)$\\n\\n###Rust\\n\\nuse std::collections::{HashMap, HashSet};\\nimpl Solution {\\n pub fn count_of_pairs(nums: Vec本题解没讲太多细节,需要读者有 DP 基础。
\\n记 $i$ 为数组下标(从 0 开始),$j = arr1[i]$, $k = arr1[i-1]$
\\n根据题意
\\narr1
非递减,所以限制 $k \\\\leq j$arr2
非递增,所以限制 $nums[i-1] - k \\\\geq nums[i] - j$所以 $0 \\\\leq k \\\\leq min(j, nums[i-1] - nums[i] + j)$
\\n记 $ceil = min(j,\\\\ nums[i-1] - nums[i] + j)$
\\n可得状态转移方程 $f(i, j) = \\\\sum\\\\limits_{k=0}^{ceil} f(i-1, k)$
\\n初始状态为
\\n$$
\\nf(0, j) =
\\n\\\\begin{cases}
\\n1 & if\\\\ j \\\\in [0, nums[0]]\\\\
\\n0 & 其他
\\n\\\\end{cases}
\\n$$
在多维的实现中,计算每行的每个元素时都要取上一行的某段前缀和。
\\n###C++
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int MOD = 1e9 + 7;\\n int m = nums.size();\\n int n = *max_element(nums.begin(), nums.end());\\n vector<int> f(n + 1);\\n\\n // 初始状态\\n for (int v = 0; v <= nums[0]; ++v)\\n f[v] = 1;\\n\\n vector<int> preSums;\\n\\n // 多执行一轮\\n for (int i = 1; i <= m; ++i) {\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear();\\n int preSum = 0;\\n for(int v : f){\\n preSum += v;\\n preSum %= MOD; \\n preSums.push_back(preSum);\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if (i == m) return preSums.back();\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for (int j = 0; j <= nums[i]; ++j) {\\n int ceil = min(j, nums[i-1] - nums[i] + j);\\n if (ceil >= 0)\\n f[j] = preSums[ceil];\\n else // ceil < 0 说明不存在\\n f[j] = 0;\\n }\\n for (int j = nums[i] + 1; j < f.size(); ++j) {\\n f[j] = 0;\\n }\\n }\\n \\n // 不会执行\\n return 0;\\n }\\n};\\n
\\n###Python3
\\nclass Solution:\\n def countOfPairs(self, nums):\\n MOD = 1_000_000_007\\n m = len(nums)\\n n = max(nums)\\n f = [0] * (n + 1)\\n\\n # 初始状态\\n for v in range(nums[0] + 1):\\n f[v] = 1\\n\\n preSums = []\\n\\n # 多执行一轮\\n for i in range(1, m + 1):\\n # 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear()\\n preSum = 0\\n for v in f:\\n preSum += v\\n preSum %= MOD\\n preSums.append(preSum)\\n # 最后一轮提前返回结尾处的方案数总和\\n if i == m:\\n return preSums[-1]\\n\\n # 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for j in range(nums[i] + 1):\\n ceil = min(j, nums[i-1] - nums[i] + j)\\n if ceil >= 0:\\n f[j] = preSums[ceil]\\n else: # ceil < 0 说明不存在\\n f[j] = 0\\n for j in range(nums[i] + 1, len(f)):\\n f[j] = 0\\n \\n # 不会执行\\n return 0\\n
\\n###Java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int MOD = (int)(1e9 + 7);\\n int m = nums.length;\\n int n = Arrays.stream(nums).max().getAsInt();\\n int[] f = new int[n + 1];\\n\\n // 初始状态\\n for (int v = 0; v <= nums[0]; ++v) f[v] = 1;\\n\\n List<Integer> preSums = new ArrayList<>();\\n\\n // 多执行一轮\\n for (int i = 1; i <= m; ++i) {\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear();\\n int preSum = 0;\\n for(int v : f){\\n preSum += v;\\n preSum %= MOD; \\n preSums.add(preSum);\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if (i == m) return preSums.get(preSums.size() - 1);\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for (int j = 0; j <= nums[i]; ++j) {\\n int ceil = Math.min(j, nums[i-1] - nums[i] + j);\\n if (ceil >= 0)\\n f[j] = preSums.get(ceil);\\n else // ceil < 0 说明不存在\\n f[j] = 0;\\n }\\n for (int j = nums[i] + 1; j < f.length; ++j) {\\n f[j] = 0;\\n }\\n }\\n \\n // 不会执行\\n return 0;\\n }\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun countOfPairs(nums: IntArray): Int {\\n val MOD = 1_000_000_007\\n val m = nums.size \\n val n = nums.max()\\n val f = IntArray(n+1)\\n // 初始状态\\n for(v in 0..nums[0]) f[v] = 1\\n\\n val preSums = mutableListOf<Int>()\\n\\n // 多执行一轮\\n for(i in 1..m){\\n // 前缀和,此处不重复开辟空间以提升性能。\\n preSums.clear()\\n for(v in f){\\n val lastPreSum = preSums.lastOrNull() ?: 0\\n preSums += (lastPreSum + v) % MOD\\n }\\n // 最后一轮提前返回结尾处的方案数总和\\n if(i == m) return preSums.last()\\n\\n // 优化空间,二维变一维,没用到的格子都要使元素归 0 \\n for(j in 0..nums[i]){\\n val ceil = min(j, nums[i-1] - nums[i] + j)\\n if(ceil >= 0)\\n f[j] = preSums[ceil]\\n else // ceil < 0 说明不存在\\n f[j] = 0 \\n }\\n for(j in (nums[i] + 1)..f.lastIndex){\\n f[j] = 0\\n }\\n }\\n \\n // 不会执行\\n return 0\\n }\\n}\\n
\\n记 $m$ 为 nums
长度,$n$ 为 nums
元素最大值。
f
的长度限制为 nums
最后一个元素的大小 + 1。我就偷懒不写这么详细了。
\\n以下皆为个人所著,兼顾了职场面试和本硕阶段的学术考试。
\\n点赞关注不迷路。祝君早日上岸,飞黄腾达!
\\n","description":"本题解没讲太多细节,需要读者有 DP 基础。 记 $i$ 为数组下标(从 0 开始),$j = arr1[i]$, $k = arr1[i-1]$\\n\\n根据题意\\n\\n$j \\\\in [0, nums[i]]$。(当然不是每个 $j$ 都符合要求,还需进一步限制)\\narr1 非递减,所以限制 $k \\\\leq j$\\narr2 非递增,所以限制 $nums[i-1] - k \\\\geq nums[i] - j$\\n\\n所以 $0 \\\\leq k \\\\leq min(j, nums[i-1] - nums[i] + j)$\\n\\n记 $ceil = min(j,\\\\ nums[i-1]…","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/qian-zhui-he-ya-suo-kong-jian-jian-ji-da-pn8j","author":"shawxing-kwok","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T08:45:38.729Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"直接模拟","url":"https://leetcode.cn/problems/snake-in-matrix//solution/zhi-jie-mo-ni-by-admiring-meninskyuli-seyn","content":"\\n\\nProblem: 100393. 矩阵中的蛇
\\n
[TOC]
\\n直接遍历模拟计算。
\\n执行用时分布4ms击败100.00%;消耗内存分布10.23MB击败100.00%
\\n###C
\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int i = 0, j = 0, k = 0;\\n while (k < commandsSize)\\n switch (commands[k ++][0]) {\\n case \'U\': -- i; break;\\n case \'D\': ++ i; break;\\n case \'L\': -- j; break;\\n default : ++ j; \\n }\\n return i * n + j;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n cmd = {\\"UP\\" : (-1, 0), \\"RIGHT\\" : (0, 1), \\"DOWN\\" : (1, 0), \\"LEFT\\" : (0, -1)}\\n i, j = 0, 0\\n for c in commands:\\n i += cmd[c][0]\\n j += cmd[c][1]\\n return i * n + j\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100393. 矩阵中的蛇 [TOC]\\n\\n直接遍历模拟计算。\\n\\n执行用时分布4ms击败100.00%;消耗内存分布10.23MB击败100.00%\\n\\n###C\\n\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int i = 0, j = 0, k = 0;\\n while (k < commandsSize)\\n switch (commands[k ++][0]) {\\n case \'U\': -- i…","guid":"https://leetcode.cn/problems/snake-in-matrix//solution/zhi-jie-mo-ni-by-admiring-meninskyuli-seyn","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:55:42.832Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学,O(n)时间,O(1)空间","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/shu-xue-yi-ci-bian-li-by-hungry-shavvzcy-1tz6","content":"数学,O(n)时间,O(1)空间","description":"数学,O(n)时间,O(1)空间","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/shu-xue-yi-ci-bian-li-by-hungry-shavvzcy-1tz6","author":"hungry-shavvzcy","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:46:56.418Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简单题,简单做(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/snake-in-matrix//solution/jian-dan-ti-jian-dan-zuo-pythonjavacgo-b-l929","content":"初始化 $i=j=0$,按题意要求上下左右移动即可,注意题目保证不会出界。
\\n最后返回 $i\\\\cdot n+j$。
\\n具体请看 视频讲解,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n i = j = 0\\n for s in commands:\\n if s[0] == \'U\': i -= 1\\n elif s[0] == \'D\': i += 1\\n elif s[0] == \'L\': j -= 1\\n else: j += 1\\n return i * n + j\\n
\\n###py
\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n i = j = 0\\n for s in commands:\\n match s[0]:\\n case \'U\': i -= 1\\n case \'D\': i += 1\\n case \'L\': j -= 1\\n case _: j += 1 # 匹配其他任意值,相当于 switch 中的 default\\n return i * n + j\\n
\\n###java
\\nclass Solution {\\n public int finalPositionOfSnake(int n, List<String> commands) {\\n int i = 0;\\n int j = 0;\\n for (String s : commands) {\\n switch (s.charAt(0)) {\\n case \'U\' -> i--;\\n case \'D\' -> i++;\\n case \'L\' -> j--;\\n default -> j++;\\n }\\n }\\n return i * n + j;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int finalPositionOfSnake(int n, vector<string>& commands) {\\n int i = 0, j = 0;\\n for (auto& s : commands) {\\n switch (s[0]) {\\n case \'U\': i--; break;\\n case \'D\': i++; break;\\n case \'L\': j--; break;\\n default: j++;\\n }\\n }\\n return i * n + j;\\n }\\n};\\n
\\n###c
\\nint finalPositionOfSnake(int n, char** commands, int commandsSize) {\\n int i = 0, j = 0;\\n for (int k = 0; k < commandsSize; k++) {\\n switch (commands[k][0]) {\\n case \'U\': i--; break;\\n case \'D\': i++; break;\\n case \'L\': j--; break;\\n default: j++;\\n }\\n }\\n return i * n + j;\\n}\\n
\\n###go
\\nfunc finalPositionOfSnake(n int, commands []string) int {\\ni, j := 0, 0\\nfor _, s := range commands {\\nswitch s[0] {\\ncase \'U\': i--\\ncase \'D\': i++\\ncase \'L\': j--\\ndefault: j++\\n}\\n}\\nreturn i*n + j\\n}\\n
\\n###js
\\nvar finalPositionOfSnake = function(n, commands) {\\n let i = 0, j = 0;\\n for (const s of commands) {\\n switch (s[0]) {\\n case \'U\': i--; break;\\n case \'D\': i++; break;\\n case \'L\': j--; break;\\n default: j++;\\n }\\n }\\n return i * n + j;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn final_position_of_snake(n: i32, commands: Vec<String>) -> i32 {\\n let mut i = 0;\\n let mut j = 0;\\n for s in commands {\\n match s.as_bytes()[0] {\\n b\'U\' => i -= 1,\\n b\'D\' => i += 1,\\n b\'L\' => j -= 1,\\n _ => j += 1,\\n }\\n }\\n i * n + j\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"初始化 $i=j=0$,按题意要求上下左右移动即可,注意题目保证不会出界。 最后返回 $i\\\\cdot n+j$。\\n\\n具体请看 视频讲解,欢迎点赞关注!\\n\\n###py\\n\\nclass Solution:\\n def finalPositionOfSnake(self, n: int, commands: List[str]) -> int:\\n i = j = 0\\n for s in commands:\\n if s[0] == \'U\': i -= 1\\n elif s[0] == \'D\': i…","guid":"https://leetcode.cn/problems/snake-in-matrix//solution/jian-dan-ti-jian-dan-zuo-pythonjavacgo-b-l929","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:09:06.975Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DFS 计算子树大小,附相似题目(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-the-number-of-good-nodes//solution/dfs-ji-suan-zi-shu-da-xiao-pythonjavacgo-9atl","content":"建树,然后从根节点 $0$ 开始 DFS 这棵树。
\\nDFS 返回子树大小。
\\n对于节点 $x$,如果其是叶子节点,或者其所有儿子子树大小都一样,那么答案加一。
\\n具体请看 视频讲解 第二题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n n = len(edges) + 1\\n g = [[] for _ in range(n)]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n ans = 0\\n def dfs(x: int, fa: int) -> int:\\n size, sz0, ok = 1, 0, True\\n for y in g[x]:\\n if y == fa:\\n continue # 不能递归到父节点\\n sz = dfs(y, x)\\n if sz0 == 0:\\n sz0 = sz # 记录第一个儿子子树的大小\\n elif sz != sz0: # 存在大小不一样的儿子子树\\n ok = False # 注意不能 break,其他子树 y 仍然要递归\\n size += sz\\n nonlocal ans\\n ans += ok\\n return size\\n dfs(0, -1)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int countGoodNodes(int[][] edges) {\\n int n = edges.length + 1;\\n List<Integer>[] g = new ArrayList[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n dfs(0, -1, g);\\n return ans;\\n }\\n\\n private int ans;\\n\\n private int dfs(int x, int fa, List<Integer>[] g) {\\n int size = 1;\\n int sz0 = 0;\\n boolean ok = true;\\n for (int y : g[x]) {\\n if (y == fa) {\\n continue; // 不能递归到父节点\\n }\\n int sz = dfs(y, x, g);\\n if (sz0 == 0) {\\n sz0 = sz; // 记录第一个儿子子树的大小\\n } else if (sz != sz0) { // 存在大小不一样的儿子子树\\n ok = false; // 注意不能 break,其他子树 y 仍然要递归\\n }\\n size += sz;\\n }\\n ans += ok ? 1 : 0;\\n return size;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countGoodNodes(vector<vector<int>>& edges) {\\n int n = edges.size() + 1;\\n vector<vector<int>> g(n);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n\\n int ans = 0;\\n auto dfs = [&](auto&& dfs, int x, int fa) -> int {\\n int size = 1, sz0 = 0;\\n bool ok = true;\\n for (int y : g[x]) {\\n if (y == fa) {\\n continue; // 不能递归到父节点\\n }\\n int sz = dfs(dfs, y, x);\\n if (sz0 == 0) {\\n sz0 = sz; // 记录第一个儿子子树的大小\\n } else if (sz != sz0) { // 存在大小不一样的儿子子树\\n ok = false; // 注意不能 break,其他子树 y 仍然要递归\\n }\\n size += sz;\\n }\\n ans += ok;\\n return size;\\n };\\n dfs(dfs, 0, -1);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countGoodNodes(edges [][]int) (ans int) {\\nn := len(edges) + 1\\ng := make([][]int, n)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\n\\nvar dfs func(int, int) int\\ndfs = func(x, fa int) int {\\nsize, sz0, ok := 1, 0, true\\nfor _, y := range g[x] {\\nif y == fa {\\ncontinue\\n}\\nsz := dfs(y, x)\\nif sz0 == 0 {\\nsz0 = sz // 记录第一个儿子子树的大小\\n} else if sz != sz0 { // 存在大小不一样的儿子子树\\nok = false // 注意不能 break,其他子树 y 仍然要递归\\n}\\nsize += sz\\n}\\nif ok {\\nans++\\n}\\nreturn size\\n}\\ndfs(0, -1)\\nreturn\\n}\\n
\\n更多相似题目,见 一般树题单 中的「§3.3 自底向上 DFS」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"建树,然后从根节点 $0$ 开始 DFS 这棵树。 DFS 返回子树大小。\\n\\n对于节点 $x$,如果其是叶子节点,或者其所有儿子子树大小都一样,那么答案加一。\\n\\n具体请看 视频讲解 第二题,欢迎点赞关注~\\n\\n###py\\n\\nclass Solution:\\n def countGoodNodes(self, edges: List[List[int]]) -> int:\\n n = len(edges) + 1\\n g = [[] for _ in range(n)]\\n for x, y in edges:…","guid":"https://leetcode.cn/problems/count-the-number-of-good-nodes//solution/dfs-ji-suan-zi-shu-da-xiao-pythonjavacgo-9atl","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:08:13.172Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:前缀和优化 DP / 组合数学(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i//solution/onm-qian-zhui-he-you-hua-dppythonjavacgo-psla","content":"本题和 3251. 单调数组对的数目 II 是一样的,请看 我的题解。
\\n优化之前的 $\\\\mathcal{O}(nm^2)$ 记忆化搜索和递推的做法,请看 视频讲解 第三题,欢迎点赞关注~
\\n","description":"本题和 3251. 单调数组对的数目 II 是一样的,请看 我的题解。 优化之前的 $\\\\mathcal{O}(nm^2)$ 记忆化搜索和递推的做法,请看 视频讲解 第三题,欢迎点赞关注~","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-i//solution/onm-qian-zhui-he-you-hua-dppythonjavacgo-psla","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:07:33.355Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:前缀和优化 DP / 组合数学(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/qian-zhui-he-you-hua-dppythonjavacgo-by-3biek","content":"看示例 1,$\\\\textit{nums}=[2,3,2]$。
\\n单调数组对有 $4$ 个:
\\n假设 $\\\\textit{arr}_1[2]=2$,那么 $\\\\textit{arr}_2[2]=\\\\textit{nums}[2] - \\\\textit{arr}_1[2]=2-2= 0$。考虑枚举 $\\\\textit{arr}_1[1]$ 是多少:
\\n累加这些个数,我们就得到了在 $\\\\textit{arr}_1[2]=2$ 的情况下,下标 $0$ 到 $2$ 中的单调数组对的个数。(有 $3$ 个)
\\n此外,在 $\\\\textit{arr}_1[2]=1$ 的情况下,下标 $0$ 到 $2$ 中的单调数组对的个数是 $1$;在 $\\\\textit{arr}_1[2]=0$ 的情况下,下标 $0$ 到 $2$ 中的单调数组对的个数是 $0$。所以 $\\\\textit{nums}=[2,3,2]$ 一共有 $4$ 个单调数组对。
\\n上面的讨论说明:
\\n考虑枚举 $\\\\textit{arr}_1[i-1] = k$,那么必须满足 $\\\\textit{arr}_1[i-1]\\\\le \\\\textit{arr}_1[i]$ 且 $\\\\textit{arr}_2[i-1]\\\\ge \\\\textit{arr}_2[i]$,即 $k\\\\le j$ 且 $\\\\textit{nums}[i-1]-k\\\\ge \\\\textit{nums}[i] - j$。
\\n整理得
\\n$$
\\nk \\\\le \\\\min(j,\\\\textit{nums}[i-1] - \\\\textit{nums}[i] + j) = j + \\\\min(\\\\textit{nums}[i-1] - \\\\textit{nums}[i], 0)
\\n$$
⚠注意:无需判断 $k\\\\le \\\\textit{nums}[i-1]$,因为 $\\\\textit{nums}[i-1] - \\\\textit{nums}[i] + j = \\\\textit{nums}[i-1] - (\\\\textit{nums}[i] - j)\\\\le \\\\textit{nums}[i-1]$,由数学归纳法可以证明 $k\\\\le \\\\textit{nums}[i-1]$。
\\n记 $\\\\textit{maxK} = j + \\\\min(\\\\textit{nums}[i-1] - \\\\textit{nums}[i], 0)$,那么有
\\n$$
\\nf[i][j] =
\\n\\\\begin{cases}
\\n0, & \\\\textit{maxK} < 0 \\\\
\\n\\\\sum\\\\limits_{k=0}^{\\\\textit{maxK}} f[i-1][k], & \\\\textit{maxK} \\\\ge 0 \\\\
\\n\\\\end{cases}
\\n$$
其中和式可以用 前缀和 优化。
\\n设 $f[i-1]$ 的前缀和 $s[j] = \\\\sum\\\\limits_{k=0}^{j} f[i-1][k]$,状态转移方程化简为
\\n$$
\\nf[i][j] =
\\n\\\\begin{cases}
\\n0, & \\\\textit{maxK} < 0 \\\\
\\ns[\\\\textit{maxK}], & \\\\textit{maxK} \\\\ge 0 \\\\
\\n\\\\end{cases}
\\n$$
初始值:$f[0][j] = 1$,其中 $j=0,1,2,\\\\ldots,\\\\textit{nums}[0]$。
\\n答案:枚举 $\\\\textit{arr}1[n-1] = 0,1,2,\\\\ldots,\\\\textit{nums}[n-1]$,累加得 $\\\\sum\\\\limits{j=0}^{\\\\textit{nums}[n-1]} f[n-1][j]$。
\\n具体请看 视频讲解,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n MOD = 1_000_000_007\\n n = len(nums)\\n m = max(nums)\\n f = [[0] * (m + 1) for _ in range(n)]\\n for j in range(nums[0] + 1):\\n f[0][j] = 1\\n for i in range(1, n):\\n s = list(accumulate(f[i - 1])) # f[i-1] 的前缀和\\n for j in range(nums[i] + 1):\\n max_k = j + min(nums[i - 1] - nums[i], 0)\\n f[i][j] = s[max_k] % MOD if max_k >= 0 else 0\\n return sum(f[-1][:nums[-1] + 1]) % MOD\\n
\\n###java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int MOD = 1_000_000_007;\\n int n = nums.length;\\n int m = Arrays.stream(nums).max().getAsInt();\\n long[][] f = new long[n][m + 1];\\n long[] s = new long[m + 1];\\n\\n Arrays.fill(f[0], 0, nums[0] + 1, 1);\\n for (int i = 1; i < n; i++) {\\n s[0] = f[i - 1][0];\\n for (int k = 1; k <= m; k++) {\\n s[k] = (s[k - 1] + f[i - 1][k]) % MOD; // f[i-1] 的前缀和\\n }\\n for (int j = 0; j <= nums[i]; j++) {\\n int maxK = j + Math.min(nums[i - 1] - nums[i], 0);\\n f[i][j] = maxK >= 0 ? s[maxK] % MOD : 0;\\n }\\n }\\n\\n return (int) (Arrays.stream(f[n - 1], 0, nums[n - 1] + 1).sum() % MOD);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int MOD = 1\'000\'000\'007;\\n int n = nums.size();\\n int m = ranges::max(nums);\\n vector f(n, vector<long long>(m + 1));\\n vector<long long> s(m + 1);\\n\\n fill(f[0].begin(), f[0].begin() + nums[0] + 1, 1);\\n for (int i = 1; i < n; i++) {\\n partial_sum(f[i - 1].begin(), f[i - 1].end(), s.begin()); // f[i-1] 的前缀和\\n for (int j = 0; j <= nums[i]; j++) {\\n int max_k = j + min(nums[i - 1] - nums[i], 0);\\n f[i][j] = max_k >= 0 ? s[max_k] % MOD : 0;\\n }\\n }\\n\\n return reduce(f[n - 1].begin(), f[n - 1].begin() + nums[n - 1] + 1, 0LL) % MOD;\\n }\\n};\\n
\\n###go
\\nfunc countOfPairs(nums []int) (ans int) {\\nconst mod = 1_000_000_007\\nn := len(nums)\\nm := slices.Max(nums)\\nf := make([][]int, n)\\nfor i := range f {\\nf[i] = make([]int, m+1)\\n}\\ns := make([]int, m+1)\\n\\nfor j := 0; j <= nums[0]; j++ {\\nf[0][j] = 1\\n}\\nfor i := 1; i < n; i++ {\\ns[0] = f[i-1][0]\\nfor k := 1; k <= m; k++ {\\ns[k] = s[k-1] + f[i-1][k] // f[i-1] 的前缀和\\n}\\nfor j := 0; j <= nums[i]; j++ {\\nmaxK := j + min(nums[i-1]-nums[i], 0)\\nif maxK >= 0 {\\nf[i][j] = s[maxK] % mod\\n}\\n}\\n}\\n\\nfor _, v := range f[n-1][:nums[n-1]+1] {\\nans += v\\n}\\nreturn ans % mod\\n}\\n
\\n进一步分析,$\\\\textit{maxK} \\\\ge 0$ 即
\\n$$
\\nj + \\\\min(\\\\textit{nums}[i-1] - \\\\textit{nums}[i], 0) \\\\ge 0
\\n$$
变形得
\\n$$
\\nj\\\\ge \\\\max(\\\\textit{nums}[i] - \\\\textit{nums}[i-1], 0)
\\n$$
记 $j_0 = \\\\max(\\\\textit{nums}[i] - \\\\textit{nums}[i-1], 0)$,那么我们只需要计算在 $j$ 区间 $[j_0, \\\\textit{nums}[i]]$ 中的 $f[i][j]$,其余 $f[i][j]$ 均为 $0$。
\\n设 $s[j] = \\\\sum\\\\limits_{k=0}^{j} f[i-1][k]$,状态转移方程化简为
\\n$$
\\nf[i][j] =
\\n\\\\begin{cases}
\\n0, & j < j_0 \\\\
\\ns[j-j_0], & j\\\\ge j_0 \\\\
\\n\\\\end{cases}
\\n$$
代码实现时,$f[i][j]$ 可以用一维数组滚动计算:先把前缀和直接保存在 $f$ 数组中,然后倒序更新 $f[j] = f[j-j_0]$(倒序更新的理由同 0-1 背包)。
\\n此外,由于在计算答案时只考虑 $j\\\\le \\\\textit{nums}[n-1]$ 的状态,所以 $f$ 数组只需要开 $\\\\textit{nums}[n-1]+1$ 的大小。
\\n###py
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n MOD = 1_000_000_007\\n m = nums[-1]\\n f = [0] * (m + 1)\\n for j in range(min(nums[0], m) + 1):\\n f[j] = 1\\n for pre, cur in pairwise(nums):\\n for j in range(1, m + 1):\\n f[j] += f[j - 1] # 计算前缀和\\n j0 = max(cur - pre, 0)\\n for j in range(min(cur, m), j0 - 1, -1):\\n f[j] = f[j - j0] % MOD\\n for j in range(min(j0, m + 1)):\\n f[j] = 0\\n return sum(f) % MOD\\n
\\n###java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int MOD = 1_000_000_007;\\n int n = nums.length;\\n int m = nums[n - 1];\\n int[] f = new int[m + 1];\\n Arrays.fill(f, 0, Math.min(nums[0], m) + 1, 1);\\n\\n for (int i = 1; i < n; i++) {\\n for (int j = 1; j <= m; j++) {\\n f[j] = (f[j] + f[j - 1]) % MOD; // 计算前缀和\\n }\\n int j0 = Math.max(nums[i] - nums[i - 1], 0);\\n for (int j = Math.min(nums[i], m); j >= j0; j--) {\\n f[j] = f[j - j0];\\n }\\n Arrays.fill(f, 0, Math.min(j0, m + 1), 0);\\n }\\n\\n long ans = 0;\\n for (int v : f) {\\n ans += v;\\n }\\n return (int) (ans % MOD);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int MOD = 1\'000\'000\'007;\\n int n = nums.size(), m = nums.back();\\n vector<int> f(m + 1);\\n fill(f.begin(), f.begin() + min(nums[0], m) + 1, 1);\\n for (int i = 1; i < n; i++) {\\n for (int j = 1; j <= m; j++) {\\n f[j] = (f[j] + f[j - 1]) % MOD; // 计算前缀和\\n }\\n int j0 = max(nums[i] - nums[i - 1], 0);\\n for (int j = min(nums[i], m); j >= j0; j--) {\\n f[j] = f[j - j0];\\n }\\n fill(f.begin(), f.begin() + min(j0, m + 1), 0);\\n }\\n return reduce(f.begin(), f.end(), 0LL) % MOD;\\n }\\n};\\n
\\n###go
\\nfunc countOfPairs(nums []int) (ans int) {\\nconst mod = 1_000_000_007\\nn := len(nums)\\nm := nums[n-1]\\nf := make([]int, m+1)\\nfor j := range f[:min(nums[0], m)+1] {\\nf[j] = 1\\n}\\nfor i := 1; i < n; i++ {\\nfor j := 1; j <= m; j++ {\\nf[j] += f[j-1] // 计算前缀和\\n}\\nj0 := max(nums[i]-nums[i-1], 0)\\nfor j := min(nums[i], m); j >= j0; j-- {\\nf[j] = f[j-j0] % mod\\n}\\nclear(f[:min(j0, m+1)])\\n}\\nfor _, v := range f {\\nans += v\\n}\\nreturn ans % mod\\n}\\n
\\n如果 $j_0 > \\\\min(\\\\textit{nums}[i],m)$,那么后面计算出的 $f[j]$ 均为 $0$,我们可以直接返回 $0$。
\\n此外,前缀和只需要计算到 $j=\\\\min(\\\\textit{nums}[i],m) - j_0$ 为止。
\\n###py
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n MOD = 1_000_000_007\\n m = nums[-1]\\n f = [0] * (m + 1)\\n for j in range(min(nums[0], m) + 1):\\n f[j] = 1\\n for pre, cur in pairwise(nums):\\n j0 = max(cur - pre, 0)\\n m2 = min(cur, m)\\n if j0 > m2:\\n return 0\\n for j in range(1, m2 - j0 + 1):\\n f[j] = (f[j] + f[j - 1]) % MOD # 计算前缀和\\n f[j0: m2 + 1] = f[:m2 - j0 + 1]\\n f[:j0] = [0] * j0\\n return sum(f) % MOD\\n
\\n###java
\\nclass Solution {\\n public int countOfPairs(int[] nums) {\\n final int MOD = 1_000_000_007;\\n int n = nums.length;\\n int m = nums[n - 1];\\n int[] f = new int[m + 1];\\n Arrays.fill(f, 0, Math.min(nums[0], m) + 1, 1);\\n\\n for (int i = 1; i < n; i++) {\\n int j0 = Math.max(nums[i] - nums[i - 1], 0);\\n int m2 = Math.min(nums[i], m);\\n if (j0 > m2) {\\n return 0;\\n }\\n for (int j = 1; j <= m2 - j0; j++) {\\n f[j] = (f[j] + f[j - 1]) % MOD; // 计算前缀和\\n }\\n System.arraycopy(f, 0, f, j0, m2 - j0 + 1);\\n Arrays.fill(f, 0, j0, 0);\\n }\\n\\n long ans = 0;\\n for (int v : f) {\\n ans += v;\\n }\\n return (int) (ans % MOD);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n const int MOD = 1\'000\'000\'007;\\n int n = nums.size(), m = nums[n - 1];\\n vector<int> f(m + 1);\\n fill(f.begin(), f.begin() + min(nums[0], m) + 1, 1);\\n for (int i = 1; i < n; i++) {\\n int j0 = max(nums[i] - nums[i - 1], 0);\\n int m2 = min(nums[i], m);\\n if (j0 > m2) {\\n return 0;\\n }\\n for (int j = 1; j <= m2 - j0; j++) {\\n f[j] = (f[j] + f[j - 1]) % MOD; // 计算前缀和\\n }\\n copy(f.begin(), f.begin() + m2 - j0 + 1, f.begin() + j0);\\n fill(f.begin(), f.begin() + j0, 0);\\n }\\n return reduce(f.begin(), f.end(), 0LL) % MOD;\\n }\\n};\\n
\\n###go
\\nfunc countOfPairs(nums []int) (ans int) {\\nconst mod = 1_000_000_007\\nn := len(nums)\\nm := nums[n-1]\\nf := make([]int, m+1)\\nfor j := range f[:min(nums[0], m)+1] {\\nf[j] = 1\\n}\\nfor i := 1; i < n; i++ {\\nj0 := max(nums[i]-nums[i-1], 0)\\nm2 := min(nums[i], m)\\nif j0 > m2 {\\nreturn 0\\n}\\nfor j := 1; j <= m2-j0; j++ {\\nf[j] = (f[j] + f[j-1]) % mod // 计算前缀和\\n}\\ncopy(f[j0:m2+1], f)\\nclear(f[:j0])\\n}\\nfor _, v := range f {\\nans += v\\n}\\nreturn ans % mod\\n}\\n
\\n首先来看一个特殊的情况:所有 $\\\\textit{nums}[i]$ 都相同。
\\n例如示例 2,$\\\\textit{nums}=[5,5,5,5]$,在所有元素都相同的情况下,只要 $\\\\textit{arr}_1$ 是单调非递减的,那么 $\\\\textit{arr}_2$ 就一定是单调非递增的。
\\n问题变成:
\\n考虑一条从左下角走到右上角的路径,每次只能向右或向上走。向右走时,把之前向上走的次数(或者说当前高度)作为数组元素值。如上图,对应的数组为 $[2,3,3,4]$。
\\n由于路径与单调非递减数组是一一对应的,所以问题变成路径个数是多少。
\\n要向上走 $5$ 步,向右走 $4$ 步,一共走 $5+4=9$ 步。选择其中 $4$ 步向右走,于是问题变成从 $9$ 步中选 $4$ 步的方案数,即
\\n$$
\\nC(9,4) = 126
\\n$$
以 $m=\\\\textit{nums}[n-1]$ 为基准,如果所有元素都等于 $m$,那么问题等价于从 $m+n$ 步中选 $n$ 步的方案数,即
\\n$$
\\nC(m+n,n)
\\n$$
回到原问题,来看看 $\\\\textit{nums}[i]$ 会如何影响路径个数。
\\n为方便描述,下文把 $\\\\textit{arr}_1$ 记作 $a$。
\\n如果 $a[i-1] = x,\\\\ a[i] = y$,那么必须满足 $x\\\\le y$ 且 $\\\\textit{nums}[i-1]-x\\\\ge \\\\textit{nums}[i]-y$,即
\\n$$
\\ny \\\\ge \\\\max(x, x+ \\\\textit{nums}[i]-\\\\textit{nums}[i-1])
\\n$$
分类讨论:
\\n剩下的 $m+n-d$ 步可以随意安排向右走还是向上走。于是问题变成从 $m+n-d$ 步中选 $n$ 步向右走的方案数,即
\\n$$
\\nC(m+n-d,n)
\\n$$
一般地,设 $d_i=\\\\max(\\\\textit{nums}[i]-\\\\textit{nums}[i-1],0)$,计算
\\n$$
\\nm = \\\\textit{nums}[n-1] - d_1 - d_2 - \\\\cdots - d_{n-1}
\\n$$
那么答案为
\\n$$
\\n\\\\begin{cases}
\\n0, & m < 0 \\\\
\\nC(m+n,n), & m \\\\ge 0 \\\\
\\n\\\\end{cases}
\\n$$
由于 $C(m+n,n) = C(m+n,m)$,答案也可以是 $C(m+n,m)$。
\\n问:为什么要以 $\\\\textit{nums}[n-1]$ 为基准?
\\n答:从路径的角度上看,$\\\\textit{nums}[n-1]$ 与 $n$ 一起,决定了我们总共要走多少步。至于 $\\\\textit{nums}$ 中的其他元素,影响的是 $a[i]$ 与 $a[i-1]$ 的差值,相当于在某些位置强行向上走若干步,这不会改变「总共要走 $\\\\textit{nums}[n-1]+n$ 步」的事实。此外,如果以 $\\\\textit{arr}_2$,也就是「单调非递增的数组个数」计算答案,也可以以 $\\\\textit{nums}[0]$ 为基准,计算方法是类似的,感兴趣的读者可以自行推导,从而加深对方法二的理解。
\\n###py
\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n MOD = 1_000_000_007\\n m = nums[-1]\\n for x, y in pairwise(nums):\\n m -= max(y - x, 0)\\n return comb(m + len(nums), m) % MOD if m >= 0 else 0\\n
\\n###py
\\nMOD = 1_000_000_007\\nMX = 3001 # MAX_N + MAX_M = 2000 + 1000 = 3000\\n\\nfac = [0] * MX # f[i] = i!\\nfac[0] = 1\\nfor i in range(1, MX):\\n fac[i] = fac[i - 1] * i % MOD\\n\\ninv_f = [0] * MX # inv_f[i] = i!^-1\\ninv_f[-1] = pow(fac[-1], -1, MOD)\\nfor i in range(MX - 1, 0, -1):\\n inv_f[i - 1] = inv_f[i] * i % MOD\\n\\ndef comb(n: int, m: int) -> int:\\n return fac[n] * inv_f[m] * inv_f[n - m] % MOD\\n\\nclass Solution:\\n def countOfPairs(self, nums: List[int]) -> int:\\n m = nums[-1]\\n for x, y in pairwise(nums):\\n m -= max(y - x, 0)\\n return comb(m + len(nums), m) if m >= 0 else 0\\n
\\n###java
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MX = 3001; // MAX_N + MAX_M = 2000 + 1000 = 3000\\n\\n private static final long[] F = new long[MX]; // f[i] = i!\\n private static final long[] INV_F = new long[MX]; // inv_f[i] = i!^-1\\n\\n static {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i > 0; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n }\\n\\n public int countOfPairs(int[] nums) {\\n int n = nums.length;\\n int m = nums[n - 1];\\n for (int i = 1; i < n; i++) {\\n m -= Math.max(nums[i] - nums[i - 1], 0);\\n if (m < 0) {\\n return 0;\\n }\\n }\\n return (int) comb(m + n, n);\\n }\\n\\n private long comb(int n, int m) {\\n return F[n] * INV_F[m] % MOD * INV_F[n - m] % MOD;\\n }\\n\\n private static long pow(long x, int n) {\\n long res = 1;\\n for (; n > 0; n /= 2) {\\n if (n % 2 > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 3001; // MAX_N + MAX_M = 2000 + 1000 = 3000\\n\\nlong long F[MX]; // F[i] = i!\\nlong long INV_F[MX]; // INV_F[i] = i!^-1\\n\\nlong long pow(long long x, int n) {\\n long long res = 1;\\n for (; n; n /= 2) {\\n if (n % 2) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n }\\n return res;\\n}\\n\\nauto init = [] {\\n F[0] = 1;\\n for (int i = 1; i < MX; i++) {\\n F[i] = F[i - 1] * i % MOD;\\n }\\n\\n INV_F[MX - 1] = pow(F[MX - 1], MOD - 2);\\n for (int i = MX - 1; i; i--) {\\n INV_F[i - 1] = INV_F[i] * i % MOD;\\n }\\n return 0;\\n}();\\n\\nlong long comb(int n, int m) {\\n return F[n] * INV_F[m] % MOD * INV_F[n - m] % MOD;\\n}\\n\\nclass Solution {\\npublic:\\n int countOfPairs(vector<int>& nums) {\\n int n = nums.size(), m = nums.back();\\n for (int i = 1; i < n; i++) {\\n m -= max(nums[i] - nums[i - 1], 0);\\n if (m < 0) {\\n return 0;\\n }\\n }\\n return comb(m + n, n);\\n }\\n};\\n
\\n###go
\\nconst mod = 1_000_000_007\\nconst mx = 3001 // MAX_N + MAX_M = 2000 + 1000 = 3000\\n\\nvar f [mx]int // f[i] = i!\\nvar invF [mx]int // invF[i] = i!^-1\\n\\nfunc init() {\\nf[0] = 1\\nfor i := 1; i < mx; i++ {\\nf[i] = f[i-1] * i % mod\\n}\\n\\ninvF[mx-1] = pow(f[mx-1], mod-2)\\nfor i := mx - 1; i > 0; i-- {\\ninvF[i-1] = invF[i] * i % mod\\n}\\n}\\n\\nfunc comb(n, m int) int {\\nreturn f[n] * invF[m] % mod * invF[n-m] % mod\\n}\\n\\nfunc countOfPairs(nums []int) int {\\nn := len(nums)\\nm := nums[n-1]\\nfor i := 1; i < n; i++ {\\nm -= max(nums[i]-nums[i-1], 0)\\nif m < 0 {\\nreturn 0\\n}\\n}\\nreturn comb(m+n, n)\\n}\\n\\nfunc pow(x, n int) int {\\nres := 1\\nfor ; n > 0; n /= 2 {\\nif n%2 > 0 {\\nres = res * x % mod\\n}\\nx = x * x % mod\\n}\\nreturn res\\n}\\n
\\n更多相似题目,见下面动态规划题单中的「§11.1 前缀和优化 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:前缀和优化 DP 看示例 1,$\\\\textit{nums}=[2,3,2]$。\\n\\n单调数组对有 $4$ 个:\\n\\n$\\\\textit{arr}_1 = [0, 1, 1],\\\\ \\\\textit{arr}_2 = [2, 2, 1]$\\n$\\\\textit{arr}_1 = [0, 1, 2],\\\\ \\\\textit{arr}_2 = [2, 2, 0]$\\n$\\\\textit{arr}_1 = [0, 2, 2],\\\\ \\\\textit{arr}_2 = [2, 1, 0]$\\n$\\\\textit{arr}_1 = [1, 2, 2],\\\\ \\\\textit{arr}_2 =…","guid":"https://leetcode.cn/problems/find-the-count-of-monotonic-pairs-ii//solution/qian-zhui-he-you-hua-dppythonjavacgo-by-3biek","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-11T04:06:45.980Z","media":[{"url":"https://pic.leetcode.cn/1723373547-JXaMKp-w410d-C.png","type":"photo","width":621,"height":775,"blurhash":"LAS6GN_3fQ_3~qOraef6pIoLr=j["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Elixir 计数","url":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/elixir-ji-shu-by-lambda2void-umok","content":"\\n\\nProblem: 100381. 求出胜利玩家的数目
\\n
[TOC]
\\n\\n\\n你选用何种方法解题?
\\n
\\n\\n这些方法具体怎么运用?
\\n
###Elixir
\\ndefmodule Solution do\\n @spec winning_player_count(n :: integer, pick :: [[integer]]) :: integer\\n def winning_player_count(n, pick) do\\n pick\\n |> Enum.reduce(%{}, fn [a, b], acc ->\\n Map.update(acc, a, [b], fn x -> [b | x] end)\\n end)\\n |> Enum.map(fn {k, v} -> {k, get_v(Enum.frequencies(v))} end)\\n |> Enum.filter(fn {k,v} -> k < v end)\\n |> Enum.count\\n end\\n\\n defp get_v(m) when m == nil do\\n 0\\n end\\n\\n defp get_v(m) do\\n Enum.max_by(m, &(elem(&1, 1)))\\n |> elem(1)\\n end\\n\\nend\\n
\\n","description":"Problem: 100381. 求出胜利玩家的数目 [TOC]\\n\\n你选用何种方法解题?\\n\\n这些方法具体怎么运用?\\n\\n时间复杂度: $O(*)$\\n空间复杂度: $O(*)$\\n\\n###Elixir\\n\\ndefmodule Solution do\\n @spec winning_player_count(n :: integer, pick :: [[integer]]) :: integer\\n def winning_player_count(n, pick) do\\n pick\\n |> Enum.reduce(%{}, fn [a…","guid":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/elixir-ji-shu-by-lambda2void-umok","author":"lambda2void","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T15:18:37.876Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:BFS / DP(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-i//solution/liang-chong-fang-fa-bfs-dppythonjavacgo-mgunf","content":"暴力。每次加边后,重新跑一遍 BFS,求出从 $0$ 到 $n-1$ 的最短路。
\\n为避免反复创建 $\\\\textit{vis}$ 数组,可以在 $\\\\textit{vis}$ 中保存当前节点是第几次询问访问的。
\\n###py
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n g = [[i + 1] for i in range(n - 1)]\\n vis = [-1] * (n - 1)\\n\\n def bfs(i: int) -> int:\\n q = deque([0])\\n for step in count(1):\\n tmp = q\\n q = []\\n for x in tmp:\\n for y in g[x]:\\n if y == n - 1:\\n return step\\n if vis[y] != i:\\n vis[y] = i\\n q.append(y)\\n return -1\\n\\n ans = [0] * len(queries)\\n for i, (l, r) in enumerate(queries):\\n g[l].append(r)\\n ans[i] = bfs(i)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n List<Integer>[] g = new ArrayList[n - 1];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int i = 0; i < n - 1; i++) {\\n g[i].add(i + 1);\\n }\\n\\n int[] ans = new int[queries.length];\\n int[] vis = new int[n - 1];\\n for (int i = 0; i < queries.length; i++) {\\n g[queries[i][0]].add(queries[i][1]);\\n ans[i] = bfs(i + 1, g, vis, n);\\n }\\n return ans;\\n }\\n\\n private int bfs(int i, List<Integer>[] g, int[] vis, int n) {\\n List<Integer> q = new ArrayList<>();\\n q.add(0);\\n for (int step = 1; ; step++) {\\n List<Integer> tmp = q;\\n q = new ArrayList<>();\\n for (int x : tmp) {\\n for (int y : g[x]) {\\n if (y == n - 1) {\\n return step;\\n }\\n if (vis[y] != i) {\\n vis[y] = i;\\n q.add(y);\\n }\\n }\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<vector<int>> g(n - 1);\\n for (int i = 0; i < n - 1; i++) {\\n g[i].push_back(i + 1);\\n }\\n vector<int> vis(n - 1, -1);\\n\\n auto bfs = [&](int i) -> int {\\n vector<int> q = {0};\\n for (int step = 1; ; step++) {\\n vector<int> nxt;\\n for (int x : q) {\\n for (int y : g[x]) {\\n if (y == n - 1) {\\n return step;\\n }\\n if (vis[y] != i) {\\n vis[y] = i;\\n nxt.push_back(y);\\n }\\n }\\n }\\n q = move(nxt);\\n }\\n };\\n\\n vector<int> ans(queries.size());\\n for (int i = 0; i < queries.size(); i++) {\\n g[queries[i][0]].push_back(queries[i][1]);\\n ans[i] = bfs(i);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\n g := make([][]int, n-1)\\n for i := range g {\\n g[i] = append(g[i], i+1)\\n }\\n\\n vis := make([]int, n-1)\\n bfs := func(i int) int {\\n q := []int{0}\\n for step := 1; ; step++ {\\n tmp := q\\n q = nil\\n for _, x := range tmp {\\n for _, y := range g[x] {\\n if y == n-1 {\\n return step\\n }\\n if vis[y] != i {\\n vis[y] = i\\n q = append(q, y)\\n }\\n }\\n }\\n }\\n }\\n\\n ans := make([]int, len(queries))\\n for i, q := range queries {\\n g[q[0]] = append(g[q[0]], q[1])\\n ans[i] = bfs(i + 1)\\n }\\n return ans\\n}\\n
\\n定义 $f[i]$ 为从 $0$ 到 $i$ 的最短路。
\\n用 $\\\\textit{from}[i]$ 记录额外添加的边的终点是 $i$,起点列表是 $\\\\textit{from}[i]$。
\\n我们可以从 $i-1$ 到 $i$,也可以从 $\\\\textit{from}[i][j]$ 到 $i$,这些位置作为转移来源,用其 $f$ 值 $+1$ 更新 $f[i]$ 的最小值。
\\n初始值:$f[i] = i$。
\\n答案:$f[n-1]$。
\\n设添加的边为 $l\\\\to r$,只有当 $f[l]+1 < f[r]$ 时才更新 DP。
\\n###py
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n frm = [[] for _ in range(n)]\\n f = list(range(n))\\n ans = []\\n for l, r in queries:\\n frm[r].append(l)\\n if f[l] + 1 < f[r]:\\n f[r] = f[l] + 1\\n for i in range(r + 1, n):\\n f[i] = min(f[i], f[i - 1] + 1, min((f[j] for j in frm[i]), default=inf) + 1)\\n ans.append(f[-1])\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n List<Integer>[] from = new ArrayList[n];\\n Arrays.setAll(from, i -> new ArrayList<>());\\n int[] f = new int[n];\\n for (int i = 1; i < n; i++) {\\n f[i] = i;\\n }\\n\\n int[] ans = new int[queries.length];\\n for (int qi = 0; qi < queries.length; qi++) {\\n int l = queries[qi][0];\\n int r = queries[qi][1];\\n from[r].add(l);\\n if (f[l] + 1 < f[r]) {\\n f[r] = f[l] + 1;\\n for (int i = r + 1; i < n; i++) {\\n f[i] = Math.min(f[i], f[i - 1] + 1);\\n for (int j : from[i]) {\\n f[i] = Math.min(f[i], f[j] + 1);\\n }\\n }\\n }\\n ans[qi] = f[n - 1];\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<vector<int>> from(n);\\n vector<int> f(n);\\n iota(f.begin(), f.end(), 0);\\n\\n vector<int> ans(queries.size());\\n for (int qi = 0; qi < queries.size(); qi++) {\\n int l = queries[qi][0], r = queries[qi][1];\\n from[r].push_back(l);\\n if (f[l] + 1 < f[r]) {\\n f[r] = f[l] + 1;\\n for (int i = r + 1; i < n; i++) {\\n f[i] = min(f[i], f[i - 1] + 1);\\n for (int j : from[i]) {\\n f[i] = min(f[i], f[j] + 1);\\n }\\n }\\n }\\n ans[qi] = f[n - 1];\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\n from := make([][]int, n)\\n f := make([]int, n)\\n for i := 1; i < n; i++ {\\n f[i] = i\\n }\\n\\n ans := make([]int, len(queries))\\n for qi, q := range queries {\\n l, r := q[0], q[1]\\n from[r] = append(from[r], l)\\n if f[l]+1 < f[r] {\\n f[r] = f[l] + 1\\n for i := r + 1; i < n; i++ {\\n f[i] = min(f[i], f[i-1]+1)\\n for _, j := range from[i] {\\n f[i] = min(f[i], f[j]+1)\\n }\\n }\\n }\\n ans[qi] = f[n-1]\\n }\\n return ans\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:BFS 暴力。每次加边后,重新跑一遍 BFS,求出从 $0$ 到 $n-1$ 的最短路。\\n\\n细节\\n\\n为避免反复创建 $\\\\textit{vis}$ 数组,可以在 $\\\\textit{vis}$ 中保存当前节点是第几次询问访问的。\\n\\n###py\\n\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n g = [[i + 1] for i in range(n - 1…","guid":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-i//solution/liang-chong-fang-fa-bfs-dppythonjavacgo-mgunf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T14:16:37.330Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"预处理(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-ymcf","content":"用一个长为 $8$ 的数组存放偏移向量,前 $4$ 个表示上下左右四个方向,后 $4$ 个表示斜向的四个方向。
\\n用一个大小为 $n^2\\\\times 2$ 的数组 $s$ 预处理元素和,其中 $s[v][0]$ 为 $\\\\texttt{adjacentSum}(v)$ 的结果,$s[v][1]$ 为 $\\\\texttt{diagonalSum}(v)$ 的结果。这可以在初始化时,遍历 $\\\\textit{grid}[i][j]$ 以及偏移向量,累加每个元素的相邻元素之和计算出来。
\\n\\n\\n注:也可以在 $\\\\textit{grid}$ 外面加一圈 $0$,这样无需判断下标越界。
\\n
###py
\\nDIRS = ((-1, 0), (1, 0), (0, -1), (0, 1), (1, 1), (-1, 1), (-1, -1), (1, -1))\\n\\nclass neighborSum:\\n def __init__(self, grid: List[List[int]]):\\n n = len(grid)\\n s = [[0, 0] for _ in range(n * n)]\\n for i, row in enumerate(grid):\\n for j, v in enumerate(row):\\n for k, (dx, dy) in enumerate(DIRS):\\n x, y = i + dx, j + dy\\n if 0 <= x < n and 0 <= y < n:\\n s[v][k // 4] += grid[x][y]\\n self.s = s\\n\\n def adjacentSum(self, value: int) -> int:\\n return self.s[value][0]\\n\\n def diagonalSum(self, value: int) -> int:\\n return self.s[value][1]\\n
\\n###java
\\nclass neighborSum {\\n private static final int[][] DIRS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}};\\n\\n private final int[][] s;\\n\\n public neighborSum(int[][] grid) {\\n int n = grid.length;\\n s = new int[n * n][2];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n int v = grid[i][j];\\n for (int k = 0; k < 8; k++) {\\n int x = i + DIRS[k][0];\\n int y = j + DIRS[k][1];\\n if (0 <= x && x < n && 0 <= y && y < n) {\\n s[v][k / 4] += grid[x][y];\\n }\\n }\\n }\\n }\\n }\\n\\n public int adjacentSum(int value) {\\n return s[value][0];\\n }\\n\\n public int diagonalSum(int value) {\\n return s[value][1];\\n }\\n}\\n
\\n###cpp
\\nclass neighborSum {\\n static constexpr int dirs[8][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}};\\n vector<array<int, 2>> s;\\npublic:\\n neighborSum(vector<vector<int>>& grid) {\\n int n = grid.size();\\n s.resize(n * n);\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n int v = grid[i][j];\\n for (int k = 0; k < 8; k++) {\\n int x = i + dirs[k][0], y = j + dirs[k][1];\\n if (0 <= x && x < n && 0 <= y && y < n) {\\n s[v][k / 4] += grid[x][y];\\n }\\n }\\n }\\n }\\n }\\n\\n int adjacentSum(int value) {\\n return s[value][0];\\n }\\n\\n int diagonalSum(int value) {\\n return s[value][1];\\n }\\n};\\n
\\n###go
\\nvar dirs = []struct{ x, y int }{{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}}\\n\\ntype neighborSum [][2]int\\n\\nfunc Constructor(grid [][]int) neighborSum {\\nn := len(grid)\\ns := make(neighborSum, n*n)\\nfor i, row := range grid {\\nfor j, v := range row {\\nfor k, d := range dirs {\\nx, y := i+d.x, j+d.y\\nif 0 <= x && x < n && 0 <= y && y < n {\\ns[v][k/4] += grid[x][y]\\n}\\n}\\n}\\n}\\nreturn s\\n}\\n\\nfunc (s neighborSum) AdjacentSum(value int) int {\\nreturn s[value][0]\\n}\\n\\nfunc (s neighborSum) DiagonalSum(value int) int {\\nreturn s[value][1]\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用一个长为 $8$ 的数组存放偏移向量,前 $4$ 个表示上下左右四个方向,后 $4$ 个表示斜向的四个方向。 用一个大小为 $n^2\\\\times 2$ 的数组 $s$ 预处理元素和,其中 $s[v][0]$ 为 $\\\\texttt{adjacentSum}(v)$ 的结果,$s[v][1]$ 为 $\\\\texttt{diagonalSum}(v)$ 的结果。这可以在初始化时,遍历 $\\\\textit{grid}[i][j]$ 以及偏移向量,累加每个元素的相邻元素之和计算出来。\\n\\n注:也可以在 $\\\\textit{grid}$ 外面加一圈 $0…","guid":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-ymcf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T09:59:17.619Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3242. 设计相邻元素求和服务","url":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/3242-she-ji-xiang-lin-yuan-su-qiu-he-fu-5h9s7","content":"二维数组的大小是 $n \\\\times n$,元素范围是 $[0, n^2 - 1]$ 且元素各不相同,因此从 $0$ 到 $n^2 - 1$ 的每个整数在二维数组中各出现一次。
\\n为了计算二维数组中的特定元素的边相邻的元素之和与角相邻的元素之和,需要定位到该元素在二维数组中的位置,然后计算该位置的边相邻的元素之和或角相邻的元素之和,因此需要使用哈希表记录每个元素在二维数组中的位置。
\\n构造方法中,得到二维数组 $\\\\textit{grid}$ 的边长 $n$,遍历二维数组 $\\\\textit{grid}$ 将每个元素与对应位置存入哈希表。
\\n对于方法 $\\\\textit{adjacentSum}$,从哈希表中定位到元素 $\\\\textit{value}$ 在二维数组中的位置,然后计算该位置的边相邻的最多四个元素之和,只考虑在二维数组的行列下标范围中的边相邻元素。
\\n对于方法 $\\\\textit{diagonalSum}$,从哈希表中定位到元素 $\\\\textit{value}$ 在二维数组中的位置,然后计算该位置的角相邻的最多四个元素之和,只考虑在二维数组的行列下标范围中的边相邻元素。
\\n###Java
\\nclass NeighborSum {\\n private static int[][] adjacentDirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n private static int[][] diagonalDirs = {{-1, -1}, {-1, 1}, {1, -1}, {1, 1}};\\n private int n;\\n private int[][] grid;\\n private int[][] positions;\\n\\n public NeighborSum(int[][] grid) {\\n this.n = grid.length;\\n this.grid = grid;\\n this.positions = new int[n * n][];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n positions[grid[i][j]] = new int[]{i, j};\\n }\\n }\\n }\\n\\n public int adjacentSum(int value) {\\n int sum = 0;\\n int[] position = positions[value];\\n for (int[] dir : adjacentDirs) {\\n int newRow = position[0] + dir[0], newCol = position[1] + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {\\n sum += grid[newRow][newCol];\\n }\\n }\\n return sum;\\n }\\n\\n public int diagonalSum(int value) {\\n int sum = 0;\\n int[] position = positions[value];\\n for (int[] dir : diagonalDirs) {\\n int newRow = position[0] + dir[0], newCol = position[1] + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {\\n sum += grid[newRow][newCol];\\n }\\n }\\n return sum;\\n }\\n}\\n
\\n###C#
\\npublic class NeighborSum {\\n private static int[][] adjacentDirs = {new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1}};\\n private static int[][] diagonalDirs = {new int[]{-1, -1}, new int[]{-1, 1}, new int[]{1, -1}, new int[]{1, 1}};\\n private int n;\\n private int[][] grid;\\n private int[][] positions;\\n\\n public NeighborSum(int[][] grid) {\\n this.n = grid.Length;\\n this.grid = grid;\\n this.positions = new int[n * n][];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n positions[grid[i][j]] = new int[]{i, j};\\n }\\n }\\n }\\n\\n public int AdjacentSum(int value) {\\n int sum = 0;\\n int[] position = positions[value];\\n foreach (int[] dir in adjacentDirs) {\\n int newRow = position[0] + dir[0], newCol = position[1] + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {\\n sum += grid[newRow][newCol];\\n }\\n }\\n return sum;\\n }\\n\\n public int DiagonalSum(int value) {\\n int sum = 0;\\n int[] position = positions[value];\\n foreach (int[] dir in diagonalDirs) {\\n int newRow = position[0] + dir[0], newCol = position[1] + dir[1];\\n if (newRow >= 0 && newRow < n && newCol >= 0 && newCol < n) {\\n sum += grid[newRow][newCol];\\n }\\n }\\n return sum;\\n }\\n}\\n
\\n时间复杂度:构造方法的时间复杂度是 $O(n^2)$,方法 $\\\\textit{adjacentSum}$ 和 $\\\\textit{diagonalSum}$ 的时间复杂度是 $O(1)$,其中 $n$ 是二维数组 $\\\\textit{grid}$ 的行数和列数。构造方法初始化二维数组 $\\\\textit{grid}$ 和哈希表的时间是 $O(n^2)$,方法 $\\\\textit{adjacentSum}$ 和 $\\\\textit{diagonalSum}$ 得到特定元素的位置并计算该位置的有限个相邻元素之和的时间是 $O(1)$。
\\n空间复杂度:$O(n^2)$,其中 $n$ 是二维数组 $\\\\textit{grid}$ 的行数和列数。存储二维数组 $\\\\textit{grid}$ 和哈希表的空间是 $O(n^2)$。
\\n由于题目保证添加的边(捷径)不会交叉,从贪心的角度看,遇到捷径就走捷径是最优的。
\\n把目光放在边上。
\\n初始有 $n-1$ 条边,我们在 $0\\\\rightarrow 1$ 这条边上,目标是到达 $(n-2)\\\\rightarrow (n-1)$ 这条边,并把这条边走完。
\\n处理 $\\\\textit{queries}$ 之前,需要走 $n-1$ 条边。
\\n连一条从 $2$ 到 $4$ 的边,意味着什么?
\\n相当于把 $2\\\\rightarrow 3$ 这条边和 $3\\\\rightarrow 4$ 这条边合并成一条边。现在从起点到终点需要 $3$ 条边。
\\n连一条从 $0$ 到 $2$ 的边,意味着什么?
\\n相当于把 $0\\\\rightarrow 1$ 这条边和 $1\\\\rightarrow 2$ 这条边合并成一条边。现在从起点到终点需要 $2$ 条边。
\\n用并查集实现边的合并。初始化一个大小为 $n-1$ 的并查集,并查集中的节点 $i$ 表示题目的边 $i \\\\rightarrow (i+1)$。(相当于给每条边编号 $0,1,2,\\\\dots n-2$。)
\\n连一条从 $L$ 到 $R$ 的边,相当于把并查集中的节点 $L,L+1,L+2\\\\cdots, R-2$ 合并到并查集中的节点 $R-1$ 上。
\\n合并的同时,维护并查集连通块个数。
\\n答案就是每次合并后的并查集连通块个数。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n fa = list(range(n - 1))\\n\\n # 非递归并查集\\n def find(x: int) -> int:\\n rt = x\\n while fa[rt] != rt:\\n rt = fa[rt]\\n while fa[x] != rt:\\n fa[x], x = rt, fa[x]\\n return rt\\n\\n ans = []\\n cnt = n - 1 # 并查集连通块个数\\n for l, r in queries:\\n fr = find(r - 1)\\n i = find(l)\\n while i < r - 1:\\n cnt -= 1\\n fa[i] = fr\\n i = find(i + 1)\\n ans.append(cnt)\\n return ans\\n
\\n###java
\\nclass UnionFind {\\n public final int[] fa;\\n\\n public UnionFind(int size) {\\n fa = new int[size];\\n for (int i = 1; i < size; i++) {\\n fa[i] = i;\\n }\\n }\\n\\n public int find(int x) {\\n if (fa[x] != x) {\\n fa[x] = find(fa[x]);\\n }\\n return fa[x];\\n }\\n}\\n\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n UnionFind uf = new UnionFind(n - 1);\\n int[] ans = new int[queries.length];\\n int cnt = n - 1; // 并查集连通块个数\\n for (int qi = 0; qi < queries.length; qi++) {\\n int l = queries[qi][0];\\n int r = queries[qi][1] - 1;\\n int fr = uf.find(r);\\n for (int i = uf.find(l); i < r; i = uf.find(i + 1)) {\\n uf.fa[i] = fr;\\n cnt--;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> fa(n - 1);\\n iota(fa.begin(), fa.end(), 0);\\n\\n // 非递归并查集\\n auto find = [&](int x) -> int {\\n int rt = x;\\n while (fa[rt] != rt) {\\n rt = fa[rt];\\n }\\n while (fa[x] != rt) {\\n int tmp = fa[x];\\n fa[x] = rt;\\n x = tmp;\\n }\\n return rt;\\n };\\n\\n vector<int> ans(queries.size());\\n int cnt = n - 1; // 并查集连通块个数\\n for (int qi = 0; qi < queries.size(); qi++) {\\n int l = queries[qi][0], r = queries[qi][1] - 1;\\n int fr = find(r);\\n for (int i = find(l); i < r; i = find(i + 1)) {\\n fa[i] = fr;\\n cnt--;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\nfa := make([]int, n-1)\\nfor i := range fa {\\nfa[i] = i\\n}\\n// 非递归并查集\\nfind := func(x int) int {\\nrt := x\\nfor fa[rt] != rt {\\nrt = fa[rt]\\n}\\nfor fa[x] != rt {\\nfa[x], x = rt, fa[x]\\n}\\nreturn rt\\n}\\n\\nans := make([]int, len(queries))\\ncnt := n - 1 // 并查集连通块个数\\nfor qi, q := range queries {\\nl, r := q[0], q[1]-1\\nfr := find(r)\\nfor i := find(l); i < r; i = find(i + 1) {\\nfa[i] = fr\\ncnt--\\n}\\nans[qi] = cnt\\n}\\nreturn ans\\n}\\n
\\ni = find(l)
以及 i = find(i + 1)
跳过。由于数组的特殊性,每次合并的复杂度为 $\\\\mathcal{O}(1)$。定义 $\\\\textit{nxt}[i]$ 表示 $i$ 指向的最右节点编号,这里 $0\\\\le i \\\\le n-2$。
\\n初始值 $\\\\textit{nxt}[i]=i+1$。
\\n连一条从 $L$ 到 $R$ 的边,分类讨论:
\\n和方法一一样,维护一个 $\\\\textit{cnt}$ 变量,每把一个 $\\\\textit{nxt}[i]$ 置为 $0$,就把 $\\\\textit{cnt}$ 减一。
\\n###py
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n ans = []\\n nxt = list(range(1, n))\\n cnt = n - 1\\n for l, r in queries:\\n if 0 < nxt[l] < r:\\n i = nxt[l]\\n while i < r:\\n cnt -= 1\\n nxt[i], i = 0, nxt[i]\\n nxt[l] = r\\n ans.append(cnt)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n int[] nxt = new int[n - 1];\\n for (int i = 0; i < n - 1; i++) {\\n nxt[i] = i + 1;\\n }\\n\\n int[] ans = new int[queries.length];\\n int cnt = n - 1;\\n for (int qi = 0; qi < queries.length; qi++) {\\n int l = queries[qi][0];\\n int r = queries[qi][1];\\n if (nxt[l] > 0 && nxt[l] < r) {\\n for (int i = nxt[l]; i < r;) {\\n cnt--;\\n int tmp = nxt[i];\\n nxt[i] = 0;\\n i = tmp;\\n }\\n nxt[l] = r;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> nxt(n - 1);\\n iota(nxt.begin(), nxt.end(), 1);\\n\\n vector<int> ans(queries.size());\\n int cnt = n - 1;\\n for (int qi = 0; qi < queries.size(); qi++) {\\n int l = queries[qi][0], r = queries[qi][1];\\n if (nxt[l] && nxt[l] < r) {\\n for (int i = nxt[l]; i < r;) {\\n cnt--;\\n int tmp = nxt[i];\\n nxt[i] = 0;\\n i = tmp;\\n }\\n nxt[l] = r;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\nnxt := make([]int, n-1)\\nfor i := range nxt {\\nnxt[i] = i + 1\\n}\\n\\nans := make([]int, len(queries))\\ncnt := n - 1\\nfor qi, q := range queries {\\nl, r := q[0], q[1]\\nif nxt[l] > 0 && nxt[l] < r {\\nfor i := nxt[l]; i < r; i, nxt[i] = nxt[i], 0 {\\ncnt--\\n}\\nnxt[l] = r\\n}\\nans[qi] = cnt\\n}\\nreturn ans\\n}\\n
\\n也可以把 $\\\\textit{nxt}[i]$ 置为 $r$,这样可以把进入循环和继续循环的逻辑合并成一个:当 $\\\\textit{nxt}[i]<r$ 时进入循环/继续循环。
\\n###py
\\nclass Solution:\\n def shortestDistanceAfterQueries(self, n: int, queries: List[List[int]]) -> List[int]:\\n ans = []\\n nxt = list(range(1, n))\\n cnt = n - 1\\n for i, r in queries:\\n while nxt[i] < r:\\n cnt -= 1\\n nxt[i], i = r, nxt[i]\\n ans.append(cnt)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int[] shortestDistanceAfterQueries(int n, int[][] queries) {\\n int[] nxt = new int[n - 1];\\n for (int i = 0; i < n - 1; i++) {\\n nxt[i] = i + 1;\\n }\\n\\n int[] ans = new int[queries.length];\\n int cnt = n - 1;\\n for (int qi = 0; qi < queries.length; qi++) {\\n int i = queries[qi][0];\\n int r = queries[qi][1];\\n while (nxt[i] < r) {\\n cnt--;\\n int tmp = nxt[i];\\n nxt[i] = r;\\n i = tmp;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> nxt(n - 1);\\n iota(nxt.begin(), nxt.end(), 1);\\n\\n vector<int> ans(queries.size());\\n int cnt = n - 1;\\n for (int qi = 0; qi < queries.size(); qi++) {\\n int i = queries[qi][0], r = queries[qi][1];\\n while (nxt[i] < r) {\\n cnt--;\\n int tmp = nxt[i];\\n nxt[i] = r;\\n i = tmp;\\n }\\n ans[qi] = cnt;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc shortestDistanceAfterQueries(n int, queries [][]int) []int {\\nnxt := make([]int, n-1)\\nfor i := range nxt {\\nnxt[i] = i + 1\\n}\\n\\nans := make([]int, len(queries))\\ncnt := n - 1\\nfor qi, q := range queries {\\nfor i, r := q[0], q[1]; nxt[i] < r; i, nxt[i] = nxt[i], r {\\ncnt--\\n}\\nans[qi] = cnt\\n}\\nreturn ans\\n}\\n
\\ncnt--
至多执行 $\\\\mathcal{O}(n)$ 次,所以二重循环是 $\\\\mathcal{O}(n+q)$ 的时间。更多相似题目,见下面数据结构题单中的「数组上的并查集」和「区间并查集」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:区间并查集 由于题目保证添加的边(捷径)不会交叉,从贪心的角度看,遇到捷径就走捷径是最优的。\\n\\n把目光放在边上。\\n\\n初始有 $n-1$ 条边,我们在 $0\\\\rightarrow 1$ 这条边上,目标是到达 $(n-2)\\\\rightarrow (n-1)$ 这条边,并把这条边走完。\\n\\n处理 $\\\\textit{queries}$ 之前,需要走 $n-1$ 条边。\\n\\n连一条从 $2$ 到 $4$ 的边,意味着什么?\\n\\n相当于把 $2\\\\rightarrow 3$ 这条边和 $3\\\\rightarrow 4$ 这条边合并成一条边。现在从起点到终点需要 $3$ 条边。…","guid":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii//solution/qu-jian-bing-cha-ji-pythonjavacgo-by-end-a9k7","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T05:15:35.522Z","media":[{"url":"https://pic.leetcode.cn/1722747389-ZsMpqd-w409c-1.jpg","type":"photo","width":576,"height":99,"blurhash":"LiOp*|%M~qof~qWBD%of%MayIUj["},{"url":"https://pic.leetcode.cn/1722747344-UibNQD-w409c.jpg","type":"photo","width":576,"height":99,"blurhash":"LiOp*|%M~qof~qWBD%of%MayIUj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【澳洲麦老师】【平凡人9行简单代码】【每行注解】【你也可以】","url":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii//solution/ao-zhou-mai-lao-shi-tan-xin-ping-fan-ren-dmtl","content":"【澳洲麦老师】【平凡人9行简单代码】【每行注解】【你也可以】","description":"【澳洲麦老师】【平凡人9行简单代码】【每行注解】【你也可以】","guid":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii//solution/ao-zhou-mai-lao-shi-tan-xin-ping-fan-ren-dmtl","author":"Michael_Dr","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T05:08:26.960Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟即可,在初始化遍历时创建值对","url":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/mo-ni-ji-ke-zai-chu-shi-hua-bian-li-shi-rakfa","content":"\\n\\nProblem: 100378. 设计相邻元素求和服务
\\n
###Python3
\\n# 由于查询函数传入的是值不是位置,为了效率我们应该,利用值做hash\\n# 由于n的最大值为10,这里我们可以直接开一个n^2大小的hash值数组 memo[i][0]表示相邻和 memo[i][1]表示对角和 \\n\\nclass neighborSum:\\n\\n def __init__(self, grid: List[List[int]]):\\n n = len(grid)\\n memo = [[0]*2 for _ in range(n**2)]\\n for i,row in enumerate(grid):\\n for j,num in enumerate(row):\\n memo[num][0] = (grid[i+1][j] if i+1<n else 0) + (grid[i-1][j] if i-1>=0 else 0) + (grid[i][j+1] if j+1<n else 0) + (grid[i][j-1] if j-1>=0 else 0)\\n memo[num][1] = (grid[i+1][j+1] if i+1<n and j+1<n else 0) + (grid[i-1][j-1] if i-1>=0 and j-1>=0 else 0) + (grid[i-1][j+1] if i-1>=0 and j+1<n else 0) + (grid[i+1][j-1] if i+1<n and j-1>=0 else 0)\\n self.n = n\\n self.memo = memo\\n\\n def adjacentSum(self, value: int) -> int:\\n if value>=0 and value<(self.n)**2:\\n return self.memo[value][0]\\n\\n\\n def diagonalSum(self, value: int) -> int:\\n if value>=0 and value<(self.n)**2:\\n return self.memo[value][1]\\n\\n\\n# Your neighborSum object will be instantiated and called as such:\\n# obj = neighborSum(grid)\\n# param_1 = obj.adjacentSum(value)\\n# param_2 = obj.diagonalSum(value)\\n
\\n","description":"Problem: 100378. 设计相邻元素求和服务 ###Python3\\n\\n# 由于查询函数传入的是值不是位置,为了效率我们应该,利用值做hash\\n# 由于n的最大值为10,这里我们可以直接开一个n^2大小的hash值数组 memo[i][0]表示相邻和 memo[i][1]表示对角和 \\n\\nclass neighborSum:\\n\\n def __init__(self, grid: List[List[int]]):\\n n = len(grid)\\n memo = [[0]*2 for _ in range(n**2…","guid":"https://leetcode.cn/problems/design-neighbor-sum-service//solution/mo-ni-ji-ke-zai-chu-shi-hua-bian-li-shi-rakfa","author":"funny-vvilburzoz","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T04:26:36.133Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"BFS","url":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-i//solution/bfs-by-tsreaper-ou61","content":"每次修改之后,从 $0$ 开始 BFS 到 $(n - 1)$ 即可。复杂度 $\\\\mathcal{O}(q(n + q))$。
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n vector<int> e[n];\\n for (int i = 1; i < n; i++) e[i - 1].push_back(i);\\n\\n auto bfs = [&]() {\\n int dis[n];\\n memset(dis, -1, sizeof(dis));\\n queue<int> q;\\n q.push(0); dis[0] = 0;\\n while (!q.empty()) {\\n int sn = q.front(); q.pop();\\n for (int fn : e[sn]) if (dis[fn] == -1) {\\n q.push(fn); dis[fn] = dis[sn] + 1;\\n }\\n }\\n return dis[n - 1];\\n };\\n\\n vector<int> ans;\\n for (auto &qry : queries) {\\n e[qry[0]].push_back(qry[1]);\\n ans.push_back(bfs());\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:BFS 每次修改之后,从 $0$ 开始 BFS 到 $(n - 1)$ 即可。复杂度 $\\\\mathcal{O}(q(n + q))$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n vector不存在两个查询 i
和 j
使得 queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1]
,这个条件是什么意思?
其实这个条件是说,如果把一条道路 $u \\\\to v$ 看成区间 $[u, v)$,那么所有区间要么互相包含,要么不相交,不存在相交但不包含的情况。
\\n这是一个很好的性质,能够帮助我们通过贪心快速求出最短路。对于任意一条从 $0$ 到 $(n - 1)$ 的路径,如果里面存在多个小区间可以被一个大区间包含,那么直接把它们替换成这个大区间,答案会变得更好。
\\n因此我们可以用一个 set
维护当前的最短路包含哪些区间。加入一个区间后,如果它能够包含一些小区间,则从 set
里把这些小区间踢掉。答案就是这个 set
的大小。
复杂度 $\\\\mathcal{O}((n + q)\\\\log n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> shortestDistanceAfterQueries(int n, vector<vector<int>>& queries) {\\n typedef pair<int, int> pii;\\n // 用一个 set 维护当前的最短路包含哪些区间\\n set<pii> st;\\n // 先把初始的 i - 1 -> i 都加进来\\n for (int i = 1; i < n; i++) st.insert(pii(i - 1, i));\\n\\n vector<int> ans;\\n for (auto &qry : queries) {\\n int l = qry[0], r = qry[1];\\n // 判断这个区间是否包含最短路里的其它区间\\n // 如果当前最短路里存在区间 [l\', r\') 满足 l\' == l 且 r\' < r,那么新区间 [l, r) 肯定包含它\\n // 否则新区间肯定已经被包含,没有加入的必要\\n auto it = st.lower_bound(pii(l, -1));\\n if (it != st.end() && it->first == l && it->second < r) {\\n // 踢掉所有新区间 [l, r) 包含的老区间\\n while (it != st.end() && it->first < r) it = st.erase(it);\\n st.insert(pii(l, r));\\n }\\n // 答案就是 set 的大小\\n ans.push_back(st.size());\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:贪心 & 模拟 不存在两个查询 i 和 j 使得 queries[i][0] < queries[j][0] < queries[i][1] < queries[j][1],这个条件是什么意思?\\n\\n其实这个条件是说,如果把一条道路 $u \\\\to v$ 看成区间 $[u, v)$,那么所有区间要么互相包含,要么不相交,不存在相交但不包含的情况。\\n\\n这是一个很好的性质,能够帮助我们通过贪心快速求出最短路。对于任意一条从 $0$ 到 $(n - 1)$ 的路径,如果里面存在多个小区间可以被一个大区间包含,那么直接把它们替换成这个大区间,答案会变得更好。\\n\\n因此…","guid":"https://leetcode.cn/problems/shortest-distance-after-road-addition-queries-ii//solution/tan-xin-mo-ni-by-tsreaper-muwk","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T04:10:30.342Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分别计算(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i//solution/fen-bie-ji-suan-pythonjavacgo-by-endless-771t","content":"先计算所有行变成回文最少需要翻转多少次。
\\n也就是对于每一行 $\\\\textit{row}$,计算这一行变成回文最少需要翻转多少次。
\\n也就是累加 $\\\\textit{row}[j]\\\\ne \\\\textit{row}[n-1-j]$ 的个数,其中 $0\\\\le j \\\\le \\\\lfloor n/2 \\\\rfloor$。
\\n对于列,统计方式同理。
\\n两种情况取最小值,即为答案。
\\n###py
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n diff_row = 0\\n for row in grid:\\n for j in range(len(row) // 2):\\n if row[j] != row[-1 - j]:\\n diff_row += 1\\n\\n diff_col = 0\\n for col in zip(*grid):\\n for i in range(len(grid) // 2):\\n if col[i] != col[-1 - i]:\\n diff_col += 1\\n\\n return min(diff_row, diff_col)\\n
\\n###java
\\nclass Solution {\\n public int minFlips(int[][] grid) {\\n int m = grid.length;\\n int n = grid[0].length;\\n\\n int diffRow = 0;\\n for (int[] row : grid) {\\n for (int j = 0; j < n / 2; j++) {\\n if (row[j] != row[n - 1 - j]) {\\n diffRow++;\\n }\\n }\\n }\\n\\n int diffCol = 0;\\n for (int j = 0; j < n; j++) {\\n for (int i = 0; i < m / 2; i++) {\\n if (grid[i][j] != grid[m - 1 - i][j]) {\\n diffCol++;\\n }\\n }\\n }\\n\\n return Math.min(diffRow, diffCol);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n\\n int diff_row = 0;\\n for (auto& row : grid) {\\n for (int j = 0; j < n / 2; j++) {\\n diff_row += row[j] != row[n - 1 - j];\\n }\\n }\\n\\n int diff_col = 0;\\n for (int j = 0; j < n; j++) {\\n for (int i = 0; i < m / 2; i++) {\\n diff_col += grid[i][j] != grid[m - 1 - i][j];\\n }\\n }\\n\\n return min(diff_row, diff_col);\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize, n = gridColSize[0];\\n\\n int diff_row = 0;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n if (grid[i][j] != grid[i][n - 1 - j]) {\\n diff_row++;\\n }\\n }\\n }\\n\\n int diff_col = 0;\\n for (int j = 0; j < n; j++) {\\n for (int i = 0; i < m / 2; i++) {\\n if (grid[i][j] != grid[m - 1 - i][j]) {\\n diff_col++;\\n }\\n }\\n }\\n\\n return MIN(diff_row, diff_col);\\n}\\n
\\n###go
\\nfunc minFlips(grid [][]int) int {\\n m, n := len(grid), len(grid[0])\\n\\n diffRow := 0\\n for _, row := range grid {\\n for j := 0; j < n/2; j++ {\\n if row[j] != row[n-1-j] {\\n diffRow++\\n }\\n }\\n }\\n\\n diffCol := 0\\n for j := 0; j < n; j++ {\\n for i, row := range grid[:m/2] {\\n if row[j] != grid[m-1-i][j] {\\n diffCol++\\n }\\n }\\n }\\n\\n return min(diffRow, diffCol)\\n}\\n
\\n###js
\\nvar minFlips = function(grid) {\\n const m = grid.length, n = grid[0].length;\\n\\n let diffRow = 0;\\n for (const row of grid) {\\n for (let j = 0; j < n / 2; j++) {\\n if (row[j] !== row[n - 1 - j]) {\\n diffRow++;\\n }\\n }\\n }\\n\\n let diffCol = 0;\\n for (let j = 0; j < n; j++) {\\n for (let i = 0; i < m / 2; i++) {\\n if (grid[i][j] !== grid[m - 1 - i][j]) {\\n diffCol++;\\n }\\n }\\n }\\n\\n return Math.min(diffRow, diffCol);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_flips(grid: Vec<Vec<i32>>) -> i32 {\\n let m = grid.len();\\n let n = grid[0].len();\\n\\n let mut diff_row = 0;\\n for row in &grid {\\n for j in 0..n / 2 {\\n if row[j] != row[n - 1 - j] {\\n diff_row += 1;\\n }\\n }\\n }\\n\\n let mut diff_col = 0;\\n for j in 0..n {\\n for i in 0..m / 2 {\\n if grid[i][j] != grid[m - 1 - i][j] {\\n diff_col += 1;\\n }\\n }\\n }\\n\\n diff_row.min(diff_col)\\n }\\n}\\n
\\nzip
的开销。欢迎关注 B站@灵茶山艾府
\\n","description":"先计算所有行变成回文最少需要翻转多少次。 也就是对于每一行 $\\\\textit{row}$,计算这一行变成回文最少需要翻转多少次。\\n\\n也就是累加 $\\\\textit{row}[j]\\\\ne \\\\textit{row}[n-1-j]$ 的个数,其中 $0\\\\le j \\\\le \\\\lfloor n/2 \\\\rfloor$。\\n\\n对于列,统计方式同理。\\n\\n两种情况取最小值,即为答案。\\n\\n###py\\n\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n diff_row = 0…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i//solution/fen-bie-ji-suan-pythonjavacgo-by-endless-771t","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T01:27:14.804Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计个数(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/tong-ji-ge-shu-pythonjavacgo-by-endlessc-gll7","content":"遍历 $\\\\textit{pick}$,用一个 $n\\\\times 11$ 大小的矩阵,统计每个玩家得到的每种颜色的球的个数。
\\n然后遍历每个玩家,如果该玩家至少有一种颜色的球大于玩家编号,则把答案加一。
\\n###py
\\nclass Solution:\\n def winningPlayerCount(self, n: int, pick: List[List[int]]) -> int:\\n cnts = [[0] * 11 for _ in range(n)]\\n for x, y in pick:\\n cnts[x][y] += 1\\n\\n ans = 0\\n for i, cnt in enumerate(cnts):\\n if any(c > i for c in cnt):\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int winningPlayerCount(int n, int[][] pick) {\\n int[][] cnts = new int[n][11];\\n for (int[] p : pick) {\\n cnts[p[0]][p[1]]++;\\n }\\n\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int c : cnts[i]) {\\n if (c > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int winningPlayerCount(int n, vector<vector<int>>& pick) {\\n vector<array<int, 11>> cnts(n);\\n for (auto& p : pick) {\\n cnts[p[0]][p[1]]++;\\n }\\n\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int c : cnts[i]) {\\n if (c > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint winningPlayerCount(int n, int** pick, int pickSize, int* pickColSize) {\\n int (*cnts)[11] = calloc(n, sizeof(int[11]));\\n for (int i = 0; i < pickSize; i++) {\\n int* p = pick[i];\\n cnts[p[0]][p[1]]++;\\n }\\n\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < 11; j++) {\\n if (cnts[i][j] > i) {\\n ans++;\\n break;\\n }\\n }\\n }\\n\\n free(cnts);\\n return ans;\\n}\\n
\\n###go
\\nfunc winningPlayerCount(n int, pick [][]int) (ans int) {\\n cnts := make([][11]int, n)\\n for _, p := range pick {\\n cnts[p[0]][p[1]]++\\n }\\n\\n for i, cnt := range cnts {\\n for _, c := range cnt {\\n if c > i {\\n ans++\\n break\\n }\\n }\\n }\\n return\\n}\\n
\\n###js
\\nvar winningPlayerCount = function(n, pick) {\\n const cnts = Array.from({ length: n }, () => Array(11).fill(0));\\n for (const [x, y] of pick) {\\n cnts[x][y]++;\\n }\\n\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n if (cnts[i].some(c => c > i)) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn winning_player_count(n: i32, pick: Vec<Vec<i32>>) -> i32 {\\n let mut cnts = vec![[0; 11]; n as usize];\\n for p in pick {\\n cnts[p[0] as usize][p[1] as usize] += 1;\\n }\\n\\n let mut ans = 0;\\n for (i, cnt) in cnts.iter().enumerate() {\\n if cnt.iter().any(|&c| c > i) {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn winning_player_count(n: i32, pick: Vec<Vec<i32>>) -> i32 {\\n let mut cnts = vec![[0; 11]; n as usize];\\n for p in pick {\\n cnts[p[0] as usize][p[1] as usize] += 1;\\n }\\n\\n cnts.iter()\\n .enumerate()\\n .filter(|(i, cnt)| cnt.iter().any(|c| c > i))\\n .count() as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历 $\\\\textit{pick}$,用一个 $n\\\\times 11$ 大小的矩阵,统计每个玩家得到的每种颜色的球的个数。 然后遍历每个玩家,如果该玩家至少有一种颜色的球大于玩家编号,则把答案加一。\\n\\n###py\\n\\nclass Solution:\\n def winningPlayerCount(self, n: int, pick: List[List[int]]) -> int:\\n cnts = [[0] * 11 for _ in range(n)]\\n for x, y in pick:\\n cnts…","guid":"https://leetcode.cn/problems/find-the-number-of-winning-players//solution/tong-ji-ge-shu-pythonjavacgo-by-endlessc-gll7","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T01:18:29.173Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分类讨论(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/fen-lei-tao-lun-pythonjavacgo-by-endless-jl6a","content":"为方便描述,把 $\\\\textit{grid}$ 简称为 $a$。
\\n由于所有行和列都必须是回文的,所以要满足
\\n$$
\\na[i][j] = a[i][n-1-j] = a[m-1-i][j] = a[m-1-i][n-1-j]
\\n$$
也就是这四个数要么都是 $0$,要么都是 $1$。其中 $0\\\\le i \\\\le \\\\lfloor m/2 \\\\rfloor,\\\\ 0\\\\le j \\\\le \\\\lfloor n/2 \\\\rfloor$。
\\n设
\\n$$
\\n\\\\textit{cnt}_1 = a[i][j] + a[i][n-1-j] + a[m-1-i][j] + a[m-1-i][n-1-j]
\\n$$
把这四个数都变成 $0$ 需要翻转 $\\\\textit{cnt}_1$ 次,都变成 $1$ 需要翻转 $4-\\\\textit{cnt}_1$ 次。
\\n两种情况取最小值,把
\\n$$
\\n\\\\min(\\\\textit{cnt}_1, 4-\\\\textit{cnt}_1)
\\n$$
加入答案。
\\n分类讨论:
\\n如何处理正中间一排和正中间一列,是本题的重点。
\\n首先,如果 $m$ 和 $n$ 都是奇数,那么正中央的格子 $(\\\\lfloor m/2 \\\\rfloor, \\\\lfloor n/2 \\\\rfloor)$ 必须是 $0$,把其元素值加入答案。
\\n然后统计正中间一排(如果 $m$ 是奇数)和正中间一列(如果 $n$ 是奇数)的格子:
\\n这 $\\\\textit{diff}$ 对 $0$ 和 $1$ 必须翻转其中一个数,所以答案至少要增加 $\\\\textit{diff}$。什么情况下,可以只增加 $\\\\textit{diff}$?
\\n分类讨论:
\\n综上所述:
\\n具体请看 视频讲解 第三题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def minFlips(self, a: List[List[int]]) -> int:\\n m, n = len(a), len(a[0])\\n ans = 0\\n for i in range(m // 2):\\n row, row2 = a[i], a[-1 - i]\\n for j in range(n // 2):\\n cnt1 = row[j] + row[-1 - j] + row2[j] + row2[-1 - j]\\n ans += min(cnt1, 4 - cnt1) # 全为 1 或全为 0\\n\\n if m % 2 and n % 2:\\n # 正中间的数必须是 0\\n ans += a[m // 2][n // 2]\\n\\n diff = cnt1 = 0\\n if m % 2:\\n # 统计正中间这一排\\n row = a[m // 2]\\n for j in range(n // 2):\\n if row[j] != row[-1 - j]:\\n diff += 1\\n else:\\n cnt1 += row[j] * 2\\n if n % 2:\\n # 统计正中间这一列\\n for i in range(m // 2):\\n if a[i][n // 2] != a[-1 - i][n // 2]:\\n diff += 1\\n else:\\n cnt1 += a[i][n // 2] * 2\\n\\n return ans + (diff if diff else cnt1 % 4)\\n
\\n###java
\\nclass Solution {\\n public int minFlips(int[][] a) {\\n int m = a.length;\\n int n = a[0].length;\\n int ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];\\n ans += Math.min(cnt1, 4 - cnt1); // 全为 1 或全为 0\\n }\\n }\\n\\n if (m % 2 > 0 && n % 2 > 0) {\\n // 正中间的数必须是 0\\n ans += a[m / 2][n / 2];\\n }\\n\\n int diff = 0;\\n int cnt1 = 0;\\n if (m % 2 > 0) {\\n // 统计正中间这一排\\n for (int j = 0; j < n / 2; j++) {\\n if (a[m / 2][j] != a[m / 2][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += a[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2 > 0) {\\n // 统计正中间这一列\\n for (int i = 0; i < m / 2; i++) {\\n if (a[i][n / 2] != a[m - 1 - i][n / 2]) {\\n diff++;\\n } else {\\n cnt1 += a[i][n / 2] * 2;\\n }\\n }\\n }\\n\\n return ans + (diff > 0 ? diff : cnt1 % 4);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& a) {\\n int m = a.size(), n = a[0].size();\\n int ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];\\n ans += min(cnt1, 4 - cnt1); // 全为 1 或全为 0\\n }\\n }\\n\\n if (m % 2 && n % 2) {\\n // 正中间的数必须是 0\\n ans += a[m / 2][n / 2];\\n }\\n\\n int diff = 0, cnt1 = 0;\\n if (m % 2) {\\n // 统计正中间这一排\\n for (int j = 0; j < n / 2; j++) {\\n if (a[m / 2][j] != a[m / 2][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += a[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2) {\\n // 统计正中间这一列\\n for (int i = 0; i < m / 2; i++) {\\n if (a[i][n / 2] != a[m - 1 - i][n / 2]) {\\n diff++;\\n } else {\\n cnt1 += a[i][n / 2] * 2;\\n }\\n }\\n }\\n\\n return ans + (diff ? diff : cnt1 % 4);\\n }\\n};\\n
\\n###c
\\nint minFlips(int** a, int gridSize, int* gridColSize) {\\n int m = gridSize, n = gridColSize[0];\\n int ans = 0;\\n for (int i = 0; i < m / 2; i++) {\\n for (int j = 0; j < n / 2; j++) {\\n int cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];\\n ans += cnt1 < 4 - cnt1 ? cnt1 : 4 - cnt1; // 全为 1 或全为 0\\n }\\n }\\n\\n if (m % 2 && n % 2) {\\n // 正中间的数必须是 0\\n ans += a[m / 2][n / 2];\\n }\\n\\n int diff = 0, cnt1 = 0;\\n if (m % 2) {\\n // 统计正中间这一排\\n for (int j = 0; j < n / 2; j++) {\\n if (a[m / 2][j] != a[m / 2][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += a[m / 2][j] * 2;\\n }\\n }\\n }\\n if (n % 2) {\\n // 统计正中间这一列\\n for (int i = 0; i < m / 2; i++) {\\n if (a[i][n / 2] != a[m - 1 - i][n / 2]) {\\n diff++;\\n } else {\\n cnt1 += a[i][n / 2] * 2;\\n }\\n }\\n }\\n \\n return ans + (diff ? diff : cnt1 % 4);\\n}\\n
\\n###go
\\nfunc minFlips(a [][]int) (ans int) {\\nm, n := len(a), len(a[0])\\nfor i, row := range a[:m/2] {\\nrow2 := a[m-1-i]\\nfor j, x := range row[:n/2] {\\ncnt1 := x + row[n-1-j] + row2[j] + row2[n-1-j]\\nans += min(cnt1, 4-cnt1) // 全为 1 或全为 0\\n}\\n}\\n\\nif m%2 > 0 && n%2 > 0 {\\n// 正中间的数必须是 0\\nans += a[m/2][n/2]\\n}\\n\\ndiff, cnt1 := 0, 0\\nif m%2 > 0 {\\n// 统计正中间这一排\\nrow := a[m/2]\\nfor j, x := range row[:n/2] {\\nif x != row[n-1-j] {\\ndiff++\\n} else {\\ncnt1 += x * 2\\n}\\n}\\n}\\nif n%2 > 0 {\\n// 统计正中间这一列\\nfor i, row := range a[:m/2] {\\nif row[n/2] != a[m-1-i][n/2] {\\ndiff++\\n} else {\\ncnt1 += row[n/2] * 2\\n}\\n}\\n}\\n\\nif diff > 0 {\\nans += diff\\n} else {\\nans += cnt1 % 4\\n}\\nreturn\\n}\\n
\\n###js
\\nvar minFlips = function(a) {\\n const m = a.length, n = a[0].length;\\n let ans = 0;\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n const cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];\\n ans += Math.min(cnt1, 4 - cnt1); // 全为 1 或全为 0\\n }\\n }\\n\\n if (m % 2 && n % 2) {\\n // 正中间的数必须是 0\\n ans += a[Math.floor(m / 2)][Math.floor(n / 2)];\\n }\\n\\n let diff = 0, cnt1 = 0;\\n if (m % 2) {\\n // 统计正中间这一排\\n for (let j = 0; j < Math.floor(n / 2); j++) {\\n if (a[Math.floor(m / 2)][j] !== a[Math.floor(m / 2)][n - 1 - j]) {\\n diff++;\\n } else {\\n cnt1 += a[Math.floor(m / 2)][j] * 2;\\n }\\n }\\n }\\n if (n % 2) {\\n // 统计正中间这一列\\n for (let i = 0; i < Math.floor(m / 2); i++) {\\n if (a[i][Math.floor(n / 2)] !== a[m - 1 - i][Math.floor(n / 2)]) {\\n diff++;\\n } else {\\n cnt1 += a[i][Math.floor(n / 2)] * 2;\\n }\\n }\\n }\\n\\n return ans + (diff ? diff : cnt1 % 4);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_flips(a: Vec<Vec<i32>>) -> i32 {\\n let m = a.len();\\n let n = a[0].len();\\n let mut ans = 0;\\n for i in 0..m / 2 {\\n for j in 0..n / 2 {\\n let cnt1 = a[i][j] + a[i][n - 1 - j] + a[m - 1 - i][j] + a[m - 1 - i][n - 1 - j];\\n ans += cnt1.min(4 - cnt1); // 全为 1 或全为 0\\n }\\n }\\n\\n if m % 2 == 1 && n % 2 == 1 {\\n // 正中间的数必须是 0\\n ans += a[m / 2][n / 2];\\n }\\n\\n let mut diff = 0;\\n let mut cnt1 = 0;\\n if m % 2 == 1 {\\n // 统计正中间这一排\\n for j in 0..n / 2 {\\n if a[m / 2][j] != a[m / 2][n - 1 - j] {\\n diff += 1;\\n } else {\\n cnt1 += a[m / 2][j] * 2;\\n }\\n }\\n }\\n if n % 2 == 1 {\\n // 统计正中间这一列\\n for i in 0..m / 2 {\\n if a[i][n / 2] != a[m - 1 - i][n / 2] {\\n diff += 1;\\n } else {\\n cnt1 += a[i][n / 2] * 2;\\n }\\n }\\n }\\n\\n ans + if diff != 0 { diff } else { cnt1 % 4 }\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析:行和列都是回文 为方便描述,把 $\\\\textit{grid}$ 简称为 $a$。\\n\\n由于所有行和列都必须是回文的,所以要满足\\n\\n$$\\n a[i][j] = a[i][n-1-j] = a[m-1-i][j] = a[m-1-i][n-1-j]\\n $$\\n\\n也就是这四个数要么都是 $0$,要么都是 $1$。其中 $0\\\\le i \\\\le \\\\lfloor m/2 \\\\rfloor,\\\\ 0\\\\le j \\\\le \\\\lfloor n/2 \\\\rfloor$。\\n\\n设\\n\\n$$\\n \\\\textit{cnt}_1 = a[i][j] + a[i][n-1-j] + a[m-1-i][j…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/fen-lei-tao-lun-pythonjavacgo-by-endless-jl6a","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T01:11:46.421Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"直接遍历计算,Python简洁两行代码","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i//solution/zhi-jie-bian-li-ji-suan-pythonjian-ji-li-p6dy","content":"\\n\\nProblem: 100387. 最少翻转次数使二进制矩阵回文 I
\\n
[TOC]
\\n直接遍历计算。
\\n执行用时分布461ms击败100.00%;消耗内存分布44.01MB击败100.00%
\\n###C
\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize >> 1, n = * gridColSize >> 1, i, j, cnt1 = 0, cnt2 = 0;\\n for (i = 0; i < gridSize; ++ i)\\n for (j = 0; j < n; ++ j)\\n if (grid[i][j] != grid[i][* gridColSize - j - 1]) ++ cnt1;\\n for (j = 0; j < * gridColSize; ++ j)\\n for (i = 0; i < m; ++ i)\\n if (grid[gridSize - i - 1][j] != grid[i][j]) ++ cnt2;\\n return cnt1 < cnt2 ? cnt1 : cnt2;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n m, n = len(grid) >> 1, len(grid[0]) >> 1\\n return min(sum(sum(a != b for a, b in zip(row[: n], row[: - n - 1: - 1])) for row in grid),\\n sum(sum(a != b for a, b in zip(col[: m], col[: - m - 1: - 1])) for col in zip(*grid)))\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100387. 最少翻转次数使二进制矩阵回文 I [TOC]\\n\\n直接遍历计算。\\n\\n执行用时分布461ms击败100.00%;消耗内存分布44.01MB击败100.00%\\n\\n###C\\n\\nint minFlips(int** grid, int gridSize, int* gridColSize) {\\n int m = gridSize >> 1, n = * gridColSize >> 1, i, j, cnt1 = 0, cnt2 = 0;\\n for (i = 0; i < gridSize; ++ i)…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-i//solution/zhi-jie-bian-li-ji-suan-pythonjian-ji-li-p6dy","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-04T00:05:16.892Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 + 计数 + 分情况讨论","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/tan-xin-ji-shu-fen-qing-kuang-tao-lun-by-ejm9","content":"\\n\\nProblem: 100385. 最少翻转次数使二进制矩阵回文 II
\\n
[TOC]
\\n行列都要是回文,翻译过来如下图:
\\n
\\n对于矩形中的位置,分成四种情况:
很明显题目如果没有 且矩阵中 1 的数目可以被 4 整除
这个要求,那就直接贪心即可:
0
比较多就对应位置就全变成0
1
比较多对应位置就全部变成1
如果解决 且矩阵中 1 的数目可以被 4 整除
?
\\n还是分情况讨论,还是下图:
\\n
4
整除,所以没影响0
,否则就会出现奇数个1
了00
,不变11
,不变01
或10
,改变其中一个值,可以变成00
或者11
,假设都变成00
分别统计绿色和蓝色中的t00,t11,t01
个数,分类讨论如下:
t11
有偶数对,那满足且矩阵中 1 的数目可以被 4 整除
t11
有奇数对,那就不满足,还缺两个1
:\\nt01 >= 1
,由于假设都变成00
,那就取一个出来变成11
,操作数是不变的。t01 == 0
,那就只能把t11
中其中一个11
,改成00
了,操作数加2
更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def minFlips(self, grid: List[List[int]]) -> int:\\n n,m = len(grid),len(grid[0])\\n res = 0\\n \\n # 如果是奇数行或列,先不看中间的行和列\\n for i in range(n//2):\\n for j in range(m//2):\\n one,zero = 0,0\\n for num in [grid[i][j],grid[i][m-j-1],grid[n-i-1][j],grid[n-i-1][m-j-1]]:\\n if num:\\n one += 1\\n else:\\n zero += 1\\n # 贪心,全变0或者全变1\\n res += min(one,zero)\\n \\n # 奇数行和奇数列,最中间必须为0\\n if n&1 and m&1 and grid[n//2][m//2]:\\n res += 1\\n \\n # 统计奇数行和奇数列的 00,01,11的数目\\n t00,t11,t01 = 0,0,0\\n # 奇数行\\n if n&1:\\n i = n // 2\\n for j in range(m//2):\\n tmp = grid[i][j]+grid[i][m-j-1]\\n if not tmp:\\n t00 += 1\\n elif tmp == 2:\\n t11 += 1\\n else:\\n res += 1\\n t01 += 1\\n # 奇数列\\n if m&1:\\n j = m // 2\\n for i in range(n//2):\\n tmp = grid[i][j] + grid[n-i-1][j]\\n if not tmp:\\n t00 += 1\\n elif tmp == 2:\\n t11 += 1\\n else:\\n res += 1\\n t01 += 1\\n\\n # 1个数被4整除\\n if t11 % 2 == 0:\\n return res\\n \\n # 还差 2 个 1\\n # 把1个t01 变成11\\n if t01 >= 1:\\n return res\\n # 把1个t11 变成00\\n else:\\n return res + 2\\n
\\n","description":"Problem: 100385. 最少翻转次数使二进制矩阵回文 II [TOC]\\n\\n行列都要是回文,翻译过来如下图:\\n \\n 对于矩形中的位置,分成四种情况:\\n\\n黄色,每一个格子都会有三个位置与其对应相等:\\n \\n 如四个角,值必须相等,即全是0或者1,才能构成回文\\n绿色和蓝色,每一个格子都会有一个位置与其对应相等:\\n \\n 如上图\\n红色,只有自己。\\n贪心\\n\\n很明显题目如果没有 且矩阵中 1 的数目可以被 4 整除 这个要求,那就直接贪心即可:\\n\\n0比较多就对应位置就全变成0\\n1比较多对应位置就全部变成1\\n计数\\n\\n如果解决 且矩阵中 1 的数目可以被 4 整除 ?…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/tan-xin-ji-shu-fen-qing-kuang-tao-lun-by-ejm9","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-03T16:39:28.687Z","media":[{"url":"https://pic.leetcode.cn/1722702017-rEjXVo-image.png","type":"photo","width":582,"height":209,"blurhash":"LkQv]L%OtA%4xtj[j[j[^nR%R%WU"},{"url":"https://pic.leetcode.cn/1722702145-JhvWkD-image.png","type":"photo","width":605,"height":243,"blurhash":"LKSPX+-xWC-@-:j=fQj]-=fTj[fO"},{"url":"https://pic.leetcode.cn/1722702273-qrxwpA-image.png","type":"photo","width":598,"height":214,"blurhash":"LXQTS}~DWVSwtQofa|WV~DEJoL%2"},{"url":"https://pic.leetcode.cn/1722702017-rEjXVo-image.png","type":"photo","width":582,"height":209,"blurhash":"LkQv]L%OtA%4xtj[j[j[^nR%R%WU"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 分类讨论","url":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/mei-ju-fen-lei-tao-lun-by-tsreaper-x8yz","content":"每一行和每一列都是回文的,这个条件是什么意思?
\\n我们首先考虑 $n$ 和 $m$ 都是偶数的情况,也就是轴对称中心在两行 / 两列之间。每一行和每一列都是回文的,就是要求 grid[i][j] == grid[i][m - 1 - j] == grid[n - 1 - i][j] == grid[n - 1 - i][m - 1 - j]
。
可以发现,只要我们确定了矩阵左上角四分之一的格子,剩下的格子由于对称性的要求将会自动确定。由于每种元素都会在左上、右上、左下、右下的四个对应位置各出现一次,所以矩阵中 $1$ 的数目可以被 $4$ 整除的要求将自动满足,我们只需要求出将所有对应位置的元素改成一样的最小操作数即可。
\\n接下来我们考虑 $n$ 和 $m$ 中恰有一个是奇数的情况,也就是轴对称中心位于某一行 / 某一列。考虑行数 $n$ 是奇数,那么轴对称中心所在的这一行 $i$ 由于 i == n - 1 - i
,只需要满足 grid[i][j] == grid[i][m - 1 - j]
即可。那么这一行 $1$ 的数目对 $4$ 取模可能是 $0$,可能是 $2$。那如果不是 $4$ 的倍数怎么办?
我们可以看下这一行有没有 grid[i][j] != grid[i][m - 1 - j]
的格子。如果有,那么我们必须花一次操作把它改成两个 $0$ 或者两个 $1$。如果改成两个 $0$,那么这一行 $1$ 的数目不影响;如果改成两个 $1$,那么这一行 $1$ 的数目将增加 $2$。
因此,如果这一行 grid[i][j] == grid[i][m - 1 - j]
的格子中,$1$ 的数目对 $4$ 取模等于 $2$,那么我们可以将一对 grid[i][j] != grid[i][m - 1 - j]
的格子都改成 $1$,剩下的都改成 $0$,这样 $1$ 的数目就是 $4$ 的倍数了。那如果不存在 grid[i][j] != grid[i][m - 1 - j]
的格子怎么办?那只好花两次操作,把两个 $1$ 都改成 $0$ 了。
最后一种情况是 $n$ 和 $m$ 都是奇数。这时候矩阵的中心不受任何限制。然而,因为其它位置的 $1$ 的数量加起来是偶数,所以矩阵的中心必须取 $0$,否则 $1$ 的数量将变成奇数,不能被 $4$ 整除。
\\n将以上所有情况的操作数量加起来即可。复杂度 $\\\\mathcal{O}(nm)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int minFlips(vector<vector<int>>& grid) {\\n int n = grid.size(), m = grid[0].size(), ans = 0;\\n\\n // 情况一:处理有四个对应位置的格子\\n for (int i = 0, ii = n - 1; i < ii; i++, ii--) for (int j = 0, jj = m - 1; j < jj; j++, jj--) {\\n int sm = grid[i][j] + grid[i][jj] + grid[ii][j] + grid[ii][jj];\\n ans += min(sm, 4 - sm);\\n }\\n\\n // 情况二:处理在对称轴上的格子\\n int one = 0, diff = 0;\\n if (n & 1) {\\n int i = n / 2;\\n for (int j = 0, jj = m - 1; j < jj; j++, jj--) {\\n if (grid[i][j] == grid[i][jj] && grid[i][j] == 1) one++;\\n else if (grid[i][j] != grid[i][jj]) diff++;\\n }\\n }\\n if (m & 1) {\\n int j = m / 2;\\n for (int i = 0, ii = n - 1; i < ii; i++, ii--) {\\n if (grid[i][j] == grid[ii][j] && grid[i][j] == 1) one++;\\n else if (grid[i][j] != grid[ii][j]) diff++;\\n }\\n }\\n ans += diff;\\n // 如果没有对称不同的格子,1 的数量又不被 4 整除,那只能花两次操作,把两个 1 都变成 0\\n if (diff == 0 && one % 2 == 1) ans += 2;\\n\\n // 情况三:处理矩阵中心的格子\\n if (n % 2 == 1 && m % 2 == 1 && grid[n / 2][m / 2] == 1) ans++;\\n\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 & 分类讨论 每一行和每一列都是回文的,这个条件是什么意思?\\n\\n情况一\\n\\n我们首先考虑 $n$ 和 $m$ 都是偶数的情况,也就是轴对称中心在两行 / 两列之间。每一行和每一列都是回文的,就是要求 grid[i][j] == grid[i][m - 1 - j] == grid[n - 1 - i][j] == grid[n - 1 - i][m - 1 - j]。\\n\\n可以发现,只要我们确定了矩阵左上角四分之一的格子,剩下的格子由于对称性的要求将会自动确定。由于每种元素都会在左上、右上、左下、右下的四个对应位置各出现一次,所以矩阵中 $1…","guid":"https://leetcode.cn/problems/minimum-number-of-flips-to-make-binary-grid-palindromic-ii//solution/mei-ju-fen-lei-tao-lun-by-tsreaper-x8yz","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-08-03T16:34:29.380Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"完美处理圆心在矩形外的情况(不用求交点、不涉及精度问题)","url":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/wan-mei-chu-li-yuan-xin-zai-ju-xing-wai-zeh32","content":"总体思路,是并查集。只要给定的圆,能经由 矩形内 的区域,将矩形的 左上轮廓 和 右下轮廓 连接起来即可。
\\n难点是,有些圆可能圆心在矩形外,然后通过矩形外的区域将两个轮廓连接起来,需要排除。
\\n排除思路:首先通过思考可以得出以下结论:
\\n设两个圆的相交的区域为阴影。
\\n若阴影 完全不在 矩形内,如下图所示,则两个圆在矩形内的区域里无相交,因此两个圆不能通过并查集合并。
\\n
若阴影的 一部分在 矩形内,如下图所示,那么将这两个圆在并查集中合并与否,对答案没有影响。
\\n\\n\\n分四种情况讨论。
\\n
\\nA. 两个圆 都 和矩形的 左上轮廓 相交;
\\nB. 两个圆 都 与矩形的 右下轮廓 相交;
\\nC. 两个圆 都 同时与两个轮廓相交;
\\nD. 一个圆与矩形的任一轮廓(左上或右下)相交,另一个圆同时与两个轮廓相交
\\n这四种情况中,两个圆合并与否,都不影响最终结果。
若阴影的 全部都在 矩形内,如下图所示,那么应当将两个圆在并查集中合并。
\\n
根据上述结论可以得出一个推论:只要阴影区的 任取一点:
\\n也就是说,只要在阴影内任取一点,判断其是否在矩形内就可以判断是否可以合并。
\\n那么取哪个点好呢?如果取两个圆的交点,那么可能在判断交点是否在矩形内时出现精度问题。因此不妨按下面的方法取点。设圆 $1$ 的中心为 $O_1$,坐标为 $(x_1, y_1)$ ,半径为 $r_1$;圆 $2$ 的中心为 $O_2$,坐标为 $(x_2, y_2)$,半径为 $r_2$。那么,所取的点 $C$ 为 $O_1O_2$ 连线中,按照 $r_1 : r_2$ 比例分割的点(如下图所示),其坐标如下:
\\n$$\\\\displaystyle{x_c = x_1 + \\\\frac{r_1}{r_1 + r_2}(x_2 - x_1) = \\\\frac{x_1r_2 + x_2r_1}{r_1 + r_2}}$$
\\n$$\\\\displaystyle{y_c = y_1 + \\\\frac{r_1}{r_1 + r_2}(y_2 - y_1) = \\\\frac{y_1r_2 + y_2r_1}{r1 + r_2}}$$
\\n下面证明当两个圆存在相交的阴影部分时,点 $C$ 在阴影部分内。
\\n由于 $x_c$ 和 $y_c$ 不涉及开平方,故比较 $0 \\\\le x_c \\\\le X$ 无需开方,不涉及精度问题;比较 $0 \\\\le y_c \\\\le Y$ 同理。因此可以完美解决精度问题。
\\n代码:
\\n###python
\\nclass Solution:\\n def canReachCorner(self, X: int, Y: int, circles: List[List[int]]) -> bool:\\n n = len(circles)\\n fa = [-1] * (n+2)\\n\\n def find(x):\\n if fa[x] == -1: return x\\n fa[x] = find(fa[x])\\n return fa[x]\\n\\n def merge(x, y):\\n x, y = find(x), find(y)\\n if x != y:\\n fa[x] = y\\n\\n def in_circle(x, y, r, x0, y0):\\n return (x0 - x) * (x0 - x) + (y0 - y) * (y0 - y) <= r * r\\n\\n def cross_vertical(x, y, r, x0, y1, y2):\\n if y1 >= y:\\n return in_circle(x, y, r, x0, y1)\\n if y2 <= y:\\n return in_circle(x, y, r, x0, y2)\\n return abs(x0 - x) <= r\\n\\n def cross_horizontal(x, y, r, x1, x2, y0):\\n if x1 >= x:\\n return in_circle(x, y, r, x1, y0)\\n if x2 <= x:\\n return in_circle(x, y, r, x2, y0)\\n return abs(y0 - y) <= r\\n \\n for i1, c1 in enumerate(circles):\\n x1, y1, r1 = c1\\n # 和 (0,0), (0, Y) 相交\\n if cross_vertical(x1, y1, r1, 0, 0, Y): merge(i1, n)\\n # 和 (0, Y), (X, Y) 相交\\n if cross_horizontal(x1, y1, r1, 0, X, Y): merge(i1, n)\\n # 和 (0, 0), (X, 0) 相交\\n if cross_horizontal(x1, y1, r1, 0, X, 0): merge(i1, n+1)\\n # 和 (X, 0), (X, Y) 相交\\n if cross_vertical(x1, y1, r1, X, 0, Y): merge(i1, n+1)\\n\\n for i2, c2 in enumerate(circles):\\n if i2 <= i1: continue\\n x2, y2, r2 = c2\\n if (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1) <= (r1 + r2) * (r1 + r2):\\n xm = x1 * r2 + x2 * r1\\n ym = y1 * r2 + y2 * r1\\n s = r1 + r2\\n if xm >= 0 and ym >= 0 and xm <= X * s and ym <= Y * s:\\n merge(i1, i2)\\n\\n return find(n) != find(n+1)\\n
\\n","description":"总体思路,是并查集。只要给定的圆,能经由 矩形内 的区域,将矩形的 左上轮廓 和 右下轮廓 连接起来即可。 难点是,有些圆可能圆心在矩形外,然后通过矩形外的区域将两个轮廓连接起来,需要排除。\\n\\n排除思路:首先通过思考可以得出以下结论:\\n\\n设两个圆的相交的区域为阴影。\\n\\n若阴影 完全不在 矩形内,如下图所示,则两个圆在矩形内的区域里无相交,因此两个圆不能通过并查集合并。\\n\\n\\n若阴影的 一部分在 矩形内,如下图所示,那么将这两个圆在并查集中合并与否,对答案没有影响。\\n\\n分四种情况讨论。\\n A. 两个圆 都 和矩形的 左上轮廓 相交;\\n B. 两个圆 都 与矩形的…","guid":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/wan-mei-chu-li-yuan-xin-zai-ju-xing-wai-zeh32","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-31T17:18:45.722Z","media":[{"url":"https://pic.leetcode.cn/1722445881-SNbwkz-image.png","type":"photo","width":318,"height":279,"blurhash":"LYQ0p^x]xt%M0Oazt6azoffQfQfQ"},{"url":"https://pic.leetcode.cn/1722445899-oYzPSF-image.png","type":"photo","width":318,"height":234,"blurhash":"LXO|z~x]oex]0Oazj[az-.j[ayj["},{"url":"https://pic.leetcode.cn/1722445912-rnvpwp-image.png","type":"photo","width":318,"height":245,"blurhash":"LfP7OtbJ~U%MxuIpWCxZ-ooeRkoe"},{"url":"https://pic.leetcode.cn/1722445968-hAzHuh-image.png","type":"photo","width":353,"height":239,"blurhash":"LAS$r,~qxt~q~qoLj[j[-oofRkRl"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3233. 统计不是特殊数字的数字数量","url":"https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special//solution/3233-tong-ji-bu-shi-te-shu-shu-zi-de-shu-enhg","content":"特殊数字满足恰好有两个小于其自身的因数,加上其自身之后恰好有三个因数。由于任何正整数都有因数 $1$,因此特殊数字满足恰好有一个大于 $1$ 且小于其自身的因数。
\\n对于任意正整数 $x$,考虑其因数的情况。
\\n当 $x = 1$ 时,只有一个因数 $1$。
\\n当 $x > 1$ 时,$1$ 和 $x$ 都是 $x$ 的因数,因此 $x$ 至少有两个因数。对于 $x$ 的每个因数 $y$,满足 $y > 0$ 且 $x \\\\bmod y = 0$,因此 $z = \\\\dfrac{x}{y}$ 也是 $x$ 的因数,满足 $y \\\\times z = x$。分别考虑如下情况。
\\n如果 $y^2 < x$,则 $y < \\\\sqrt{x} < z$。
\\n如果 $y^2 > x$,则 $z < \\\\sqrt{x} < y$。
\\n如果 $y^2 = x$,则 $y = z = \\\\sqrt{x}$。
\\n因此正整数 $x$ 的所有因数中,小于 $\\\\sqrt{x}$ 的因数个数与大于 $\\\\sqrt{x}$ 的因数个数相等且分别对应,两个因数对应的含义是两个因数的乘积等于 $x$,因此正整数 $x$ 的不等于 $\\\\sqrt{x}$ 的因数个数是偶数。当 $x$ 不是完全平方数时,$x$ 的因数个数是偶数;当 $x$ 是完全平方数时,$\\\\sqrt{x}$ 是 $x$ 的因数,$x$ 的因数个数是奇数。
\\n由于特殊数字恰好有三个因数,因此特殊数字一定是大于 $1$ 的完全平方数。
\\n对于大于 $1$ 的完全平方数 $x$,记 $r = \\\\sqrt{x}$,则 $r$ 是大于 $1$ 的正整数。分别考虑 $r$ 是质数和合数的情况。
\\n当 $r$ 是质数时,$x$ 恰好有三个因数:$1$、$r$ 和 $r^2$,因此 $x$ 是特殊数字。
\\n当 $r$ 是合数时,存在大于 $1$ 的正整数 $s$ 和 $t$ 满足 $r = s \\\\times t$,$x$ 至少有如下因数:$1$、$s$、$s \\\\times t$、$s^2 \\\\times t$ 和 $s^2 \\\\times t^2$,因此 $x$ 不是特殊数字。
\\n因此,一个数字是特殊数字等价于该数字是质数的平方。
\\n为了计算区间 $[l, r]$ 中的非特殊数字的数字数量,需要计算区间 $[l, r]$ 中的特殊数字的数量 $\\\\textit{special}$,然后计算非特殊数字的数字数量 $r - l + 1 - \\\\textit{special}$。
\\n计算区间 $[l, r]$ 中的特殊数字的数量等价于计算区间 $\\\\big[\\\\big\\\\lceil \\\\sqrt{l} \\\\big\\\\rceil, \\\\big\\\\lfloor \\\\sqrt{r} \\\\big\\\\rfloor\\\\big]$ 中的质数的数量。记 $\\\\textit{lowerBound} = \\\\big\\\\lceil \\\\sqrt{l} \\\\big\\\\rceil$,$\\\\textit{upperBound} = \\\\big\\\\lfloor \\\\sqrt{r} \\\\big\\\\rfloor$,则需要计算区间 $[\\\\textit{lowerBound}, \\\\textit{upperBound}]$ 中的质数的数量。需要首先得到不超过 $\\\\textit{upperBound}$ 的所有质数,然后计算答案。
\\n筛选质数有两种常见的做法,分别是埃氏筛和欧拉筛。
\\n埃拉托斯特尼筛法,简称埃氏筛,是由希腊数学家埃拉托斯特尼提出的筛选质数的算法。埃氏筛的原理是:对于质数 $p$,所有大于 $p$ 的 $p$ 的倍数都是合数,根据该性质移除所有的合数。
\\n使用埃氏筛得到不超过 $\\\\textit{upperBound}$ 的所有质数的做法是:使用列表记录从 $2$ 到 $\\\\textit{upperBound}$ 的所有整数,每次保留列表中的最小整数 $p$,将 $p$ 保留,并将列表中的所有大于 $p$ 的 $p$ 的倍数都删除,当没有更多的整数可以删除时,剩下的数就是不超过 $\\\\textit{upperBound}$ 的所有质数。
\\n实现方面,可以使用一个标记数组记录每个整数是否为质数。遍历过程中有一点可以优化,对于两个不同的质数 $p$ 和 $q$,其中 $p < q$,当遍历到 $q$ 时,所有大于 $p$ 的 $p$ 的倍数已经被标记为合数,包括 $p \\\\times q$ 也已经被标记为合数,因此遍历 $q$ 的倍数时不需要再次遍历小于 $q^2$ 的整数,而是可以从 $q^2$ 开始遍历 $q$ 的倍数。
\\n得到不超过 $\\\\textit{upperBound}$ 的所有质数之后,计算区间 $[\\\\textit{lowerBound}, \\\\textit{upperBound}]$ 中的质数的数量 $\\\\textit{special}$,即为区间 $[l, r]$ 中的特殊数字的数量,区间 $[l, r]$ 中的非特殊数字的数字数量是 $r - l + 1 - \\\\textit{special}$。
\\n###Java
\\nclass Solution {\\n public int nonSpecialCount(int l, int r) {\\n int lowerBound = (int) Math.ceil(Math.sqrt(l));\\n int upperBound = (int) Math.floor(Math.sqrt(r));\\n boolean[] isPrime = new boolean[upperBound + 1];\\n Arrays.fill(isPrime, true);\\n isPrime[0] = false;\\n isPrime[1] = false;\\n for (int i = 2; i * i <= upperBound; i++) {\\n if (isPrime[i]) {\\n for (int j = i * i; j <= upperBound; j += i) {\\n isPrime[j] = false;\\n }\\n }\\n }\\n int special = 0;\\n for (int i = lowerBound; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n special++;\\n }\\n }\\n return r - l + 1 - special;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NonSpecialCount(int l, int r) {\\n int lowerBound = (int) Math.Ceiling(Math.Sqrt(l));\\n int upperBound = (int) Math.Floor(Math.Sqrt(r));\\n bool[] isPrime = new bool[upperBound + 1];\\n Array.Fill(isPrime, true);\\n isPrime[0] = false;\\n isPrime[1] = false;\\n for (int i = 2; i * i <= upperBound; i++) {\\n if (isPrime[i]) {\\n for (int j = i * i; j <= upperBound; j += i) {\\n isPrime[j] = false;\\n }\\n }\\n }\\n int special = 0;\\n for (int i = lowerBound; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n special++;\\n }\\n }\\n return r - l + 1 - special;\\n }\\n}\\n
\\n时间复杂度:$O(\\\\sqrt{r} \\\\log \\\\log \\\\sqrt{r})$,其中 $r$ 是给定的区间上界。埃氏筛的时间复杂度是 $O(\\\\sqrt{r} \\\\log \\\\log \\\\sqrt{r})$,遍历给定区间中的所有质数的时间是 $O(\\\\sqrt{r})$,因此时间复杂度是 $O(\\\\sqrt{r} \\\\log \\\\log \\\\sqrt{r})$。
\\n空间复杂度:$O(\\\\sqrt{r})$,其中 $r$ 是给定的区间上界。标记数组的空间是 $O(\\\\sqrt{r})$。
\\n埃氏筛存在重复标记的情况。如果一个合数有多个不同的质因数,则会被标记多次,例如 $30$ 会被 $2$、$3$、$5$ 分别标记一次。欧拉筛可以避免重复标记,确保每个合数只被标记一次。
\\n欧拉筛又称线性筛,其原理和埃氏筛相似,和埃氏筛的区别在于欧拉筛会对每个整数的倍数做标记,而不只是对质数的倍数做标记。欧拉筛需要维护一个质数列表,对于 $2 \\\\le i \\\\le \\\\textit{upperBound}$,当遍历到整数 $i$ 时,执行如下操作。
\\n如果 $i$ 是质数,即 $i$ 没有被标记为合数,则将 $i$ 加入质数列表。
\\n从小到大遍历质数列表,对于每个质数 $\\\\textit{prime}$,当 $i \\\\times \\\\textit{prime} \\\\le \\\\textit{upperBound}$ 时,执行如下操作。
\\n将 $i \\\\times \\\\textit{prime}$ 标记为合数。
\\n如果 $i$ 能被 $\\\\textit{prime}$ 整除,则结束遍历质数列表。
\\n欧拉筛的核心思想是:如果 $x$ 能被 $\\\\textit{prime}$ 整除,记 $\\\\textit{prime}\'$ 为大于 $\\\\textit{prime}$ 的质数,$y = x \\\\times \\\\textit{prime}\'$,则有 $y = \\\\dfrac{x}{\\\\textit{prime}} \\\\times \\\\textit{prime}\' \\\\times \\\\textit{prime}$,当遍历到 $\\\\dfrac{x}{\\\\textit{prime}} \\\\times \\\\textit{prime}\'$ 时,$y$ 会被质数 $\\\\textit{prime}$ 标记,由于 $\\\\textit{prime} < \\\\textit{prime}\'$,因此 $y$ 会被其最小的质因数标记。由于欧拉筛可以确保每个合数被其最小的质因数标记,因此可以确保每个合数只被标记一次。
\\n得到不超过 $\\\\textit{upperBound}$ 的所有质数之后,计算区间 $[\\\\textit{lowerBound}, \\\\textit{upperBound}]$ 中的质数的数量 $\\\\textit{special}$,即为区间 $[l, r]$ 中的特殊数字的数量,区间 $[l, r]$ 中的非特殊数字的数字数量是 $r - l + 1 - \\\\textit{special}$。
\\n###Java
\\nclass Solution {\\n public int nonSpecialCount(int l, int r) {\\n int lowerBound = (int) Math.ceil(Math.sqrt(l));\\n int upperBound = (int) Math.floor(Math.sqrt(r));\\n List<Integer> primes = new ArrayList<Integer>();\\n boolean[] isPrime = new boolean[upperBound + 1];\\n Arrays.fill(isPrime, true);\\n isPrime[0] = false;\\n isPrime[1] = false;\\n for (int i = 2; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n primes.add(i);\\n }\\n for (int prime : primes) {\\n if (i * prime <= upperBound) {\\n isPrime[i * prime] = false;\\n if (i % prime == 0) {\\n break;\\n }\\n } else {\\n break;\\n }\\n }\\n }\\n int special = 0;\\n for (int i = lowerBound; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n special++;\\n }\\n }\\n return r - l + 1 - special;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NonSpecialCount(int l, int r) {\\n int lowerBound = (int) Math.Ceiling(Math.Sqrt(l));\\n int upperBound = (int) Math.Floor(Math.Sqrt(r));\\n IList<int> primes = new List<int>();\\n bool[] isPrime = new bool[upperBound + 1];\\n Array.Fill(isPrime, true);\\n isPrime[0] = false;\\n isPrime[1] = false;\\n for (int i = 2; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n primes.Add(i);\\n }\\n foreach (int prime in primes) {\\n if (i * prime <= upperBound) {\\n isPrime[i * prime] = false;\\n if (i % prime == 0) {\\n break;\\n }\\n } else {\\n break;\\n }\\n }\\n }\\n int special = 0;\\n for (int i = lowerBound; i <= upperBound; i++) {\\n if (isPrime[i]) {\\n special++;\\n }\\n }\\n return r - l + 1 - special;\\n }\\n}\\n
\\n时间复杂度:$O(\\\\sqrt{r})$,其中 $r$ 是给定的区间上界。欧拉筛的时间复杂度是 $O(\\\\sqrt{r})$,遍历给定区间中的所有质数的时间是 $O(\\\\sqrt{r})$,因此时间复杂度是 $O(\\\\sqrt{r})$。
\\n空间复杂度:$O(\\\\sqrt{r})$,其中 $r$ 是给定的区间上界。标记数组和质数列表的空间是 $O(\\\\sqrt{r})$。
\\n设 $s_1$ 为 $\\\\textit{nums}$ 中的所有个位数之和,$s_2$ 为 $\\\\textit{nums}$ 中的所有两位数之和。注意题目保证只有个位数和两位数。
\\n小红若要获胜,必须满足 $s_1 > s_2$ 或者 $s_2 > s_1$,即
\\n$$
\\ns_1 \\\\ne s_2
\\n$$
代码实现时,可以令 $s = s_1 - s_2$,即累加 $\\\\textit{nums}$ 的所有元素,把其中的两位数变成相反数累加。这样最后只需判断 $s\\\\ne 0$ 即可。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def canAliceWin(self, nums: List[int]) -> bool:\\n return sum(x if x < 10 else -x for x in nums) != 0\\n
\\n###java
\\nclass Solution {\\n public boolean canAliceWin(int[] nums) {\\n int s = 0;\\n for (int x : nums) {\\n s += x < 10 ? x : -x;\\n }\\n return s != 0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canAliceWin(vector<int>& nums) {\\n int s = 0;\\n for (int x : nums) {\\n s += x < 10 ? x : -x;\\n }\\n return s != 0;\\n }\\n};\\n
\\n###c
\\nbool canAliceWin(int* nums, int numsSize) {\\n int s = 0;\\n for (int i = 0; i < numsSize; i++) {\\n s += nums[i] < 10 ? nums[i] : -nums[i];\\n }\\n return s != 0;\\n}\\n
\\n###go
\\nfunc canAliceWin(nums []int) bool {\\ns := 0\\nfor _, x := range nums {\\nif x < 10 {\\ns += x\\n} else {\\ns -= x\\n}\\n}\\nreturn s != 0\\n}\\n
\\n###js
\\nvar canAliceWin = function(nums) {\\n let s = 0;\\n for (const x of nums) {\\n s += x < 10 ? x : -x;\\n }\\n return s !== 0;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_alice_win(nums: Vec<i32>) -> bool {\\n nums.iter().map(|&x| if x < 10 { x } else { -x }).sum::<i32>() != 0\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"设 $s_1$ 为 $\\\\textit{nums}$ 中的所有个位数之和,$s_2$ 为 $\\\\textit{nums}$ 中的所有两位数之和。注意题目保证只有个位数和两位数。 小红若要获胜,必须满足 $s_1 > s_2$ 或者 $s_2 > s_1$,即\\n\\n$$\\n s_1 \\\\ne s_2\\n $$\\n\\n代码实现时,可以令 $s = s_1 - s_2$,即累加 $\\\\textit{nums}$ 的所有元素,把其中的两位数变成相反数累加。这样最后只需判断 $s\\\\ne 0$ 即可。\\n\\n具体请看 视频讲解,欢迎点赞关注~\\n\\n###py\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/find-if-digit-game-can-be-won//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-i5b5","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-28T05:29:58.416Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"预处理质数+O(1)回答(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special//solution/yu-chu-li-zhi-shu-o1hui-da-pythonjavacgo-7xaq","content":"正难则反,统计区间 $[l,r]$ 内有多少个特殊数字。
\\n这等价于区间 $[0,r]$ 内的特殊数字个数,减去区间 $[0,l-1]$ 内的特殊数字个数。
\\n根据题意,只有质数的平方 $p^2$ 才是特殊数字,因为 $p^2$ 恰好有两个真因数 $1$ 和 $p$。而其他的数,$1$ 没有真因数,质数只有 $1$ 个真因数,不是 $1$ 不是质数也不是质数平方的数有至少三个真因数。
\\n所以区间 $[0,i]$ 内的特殊数字个数等于:
\\n预处理 $\\\\lfloor\\\\sqrt{10^9}\\\\rfloor = 31622$ 内的质数,然后用前缀和计算 $[0,i]$ 中的质数个数 $\\\\pi(i)$,那么区间 $[l,r]$ 内的特殊数字个数就是
\\n$$
\\n\\\\pi(\\\\lfloor\\\\sqrt{r}\\\\rfloor) - \\\\pi(\\\\lfloor\\\\sqrt{l-1}\\\\rfloor)
\\n$$
答案为区间内的整数个数,减去区间内的特殊数字个数,即
\\n$$
\\nr-l+1 - \\\\left(\\\\pi(\\\\lfloor\\\\sqrt{r}\\\\rfloor) - \\\\pi(\\\\lfloor\\\\sqrt{l-1}\\\\rfloor)\\\\right)
\\n$$
如何筛质数?请看 视频讲解 第二题,欢迎点赞关注~
\\n###py
\\nMX = 31622\\npi = [0] * (MX + 1)\\nfor i in range(2, MX + 1):\\n if pi[i] == 0: # i 是质数\\n pi[i] = pi[i - 1] + 1\\n for j in range(i * i, MX + 1, i):\\n pi[j] = -1 # 标记 i 的倍数为合数\\n else:\\n pi[i] = pi[i - 1]\\n\\nclass Solution:\\n def nonSpecialCount(self, l: int, r: int) -> int:\\n return r - l + 1 - (pi[isqrt(r)] - pi[isqrt(l - 1)])\\n
\\n###java
\\nclass Solution {\\n private static final int MX = 31622;\\n private static final int[] PI = new int[MX + 1];\\n\\n static {\\n for (int i = 2; i <= MX; i++) {\\n if (PI[i] == 0) { // i 是质数\\n PI[i] = PI[i - 1] + 1;\\n for (int j = i * i; j <= MX; j += i) { // 注:如果 MX 比较大,小心 i*i 溢出\\n PI[j] = -1; // 标记 i 的倍数为合数\\n }\\n } else {\\n PI[i] = PI[i - 1];\\n }\\n }\\n }\\n\\n public int nonSpecialCount(int l, int r) {\\n return r - l + 1 - (PI[(int) Math.sqrt(r)] - PI[(int) Math.sqrt(l - 1)]);\\n }\\n}\\n
\\n###cpp
\\nconst int MX = 31622;\\nint pi[MX + 1];\\n\\nauto init = [] {\\n for (int i = 2; i <= MX; i++) {\\n if (pi[i] == 0) { // i 是质数\\n pi[i] = pi[i - 1] + 1;\\n for (int j = i * i; j <= MX; j += i) { // 注:如果 MX 比较大,小心 i*i 溢出\\n pi[j] = -1; // 标记 i 的倍数为合数\\n }\\n } else {\\n pi[i] = pi[i - 1];\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int nonSpecialCount(int l, int r) {\\n return r - l + 1 - (pi[(int) sqrt(r)] - pi[(int) sqrt(l - 1)]);\\n }\\n};\\n
\\n###go
\\nconst mx = 31622\\nvar pi [mx + 1]int\\n\\nfunc init() {\\n for i := 2; i <= mx; i++ {\\n if pi[i] == 0 { // i 是质数\\n pi[i] = pi[i-1] + 1\\n for j := i * i; j <= mx; j += i {\\n pi[j] = -1 // 标记 i 的倍数为合数\\n }\\n } else {\\n pi[i] = pi[i-1]\\n }\\n }\\n}\\n\\nfunc nonSpecialCount(l, r int) int {\\n cntR := pi[int(math.Sqrt(float64(r)))]\\n cntL := pi[int(math.Sqrt(float64(l-1)))]\\n return r - l + 1 - (cntR - cntL)\\n}\\n
\\n更多数学题目,见下面的数学题单。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"正难则反,统计区间 $[l,r]$ 内有多少个特殊数字。 这等价于区间 $[0,r]$ 内的特殊数字个数,减去区间 $[0,l-1]$ 内的特殊数字个数。\\n\\n根据题意,只有质数的平方 $p^2$ 才是特殊数字,因为 $p^2$ 恰好有两个真因数 $1$ 和 $p$。而其他的数,$1$ 没有真因数,质数只有 $1$ 个真因数,不是 $1$ 不是质数也不是质数平方的数有至少三个真因数。\\n\\n所以区间 $[0,i]$ 内的特殊数字个数等于:\\n\\n区间 $[0,\\\\lfloor\\\\sqrt{i}\\\\rfloor]$ 中的质数个数。\\n\\n预处理 $\\\\lfloor\\\\sqrt{10…","guid":"https://leetcode.cn/problems/find-the-count-of-numbers-which-are-not-special//solution/yu-chu-li-zhi-shu-o1hui-da-pythonjavacgo-7xaq","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-28T05:24:36.464Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"BFS or 并查集","url":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/bfs-or-bing-cha-ji-by-tsreaper-ga61","content":"注:本题解没有考虑圆从矩形外部连接两个边界的情况(然而标程也没有考虑),预判力扣之后会在题面中增加条件。
\\n正难则反。因为“不经过圆的路径”很难处理,我们反过来想“在圆边界或内部的路径”有怎样的性质。
\\n考虑一条完全在圆边界或内部的路径,如果我们用一把剪刀沿着这条路径剪开,能把矩形剪成两半,而且矩形的左下角和右下角在不同的一半里,那么我们就无法从矩形的左下角,不经过圆的边界或内部,到达右上角。
\\n怎样的路径才能剪开矩形,并分离两个角落呢?有四种可能性:从矩形下边界剪到上边界、左边界剪到右边界、下边界剪到左边界、右边界剪到上边界。所以,我们需要寻找一条从某个矩形边界到另一个矩形边界,且只经过圆边界或内部的路径。
\\n如何从矩形边界走到圆上(或反过来)呢?这需要边界的线段和圆相交。如何从一个圆走到另一个圆呢?这需要两个圆有交点。
\\n如何判断线段和圆相交?当然这个问题有更通用的算法,不过在本题中,因为线段只有水平或竖直线段,我们可以处理得简单一点。首先圆到直线的距离必须不大于半径,但即使满足这个条件,圆也可能处于线段的旁边而不和它相交。所以还需要满足“线段其中一个端点在圆内”,或“圆心的横(纵)坐标在两个端点之间”的条件。
\\n如何判断两圆有交点?这个比较简单,只需要圆心之间的距离不大于两圆半径之和即可。
\\n因此,我们可以将相交的图形之间连边,构造一张无向图。看在这张无向图里,能否从某个矩形边界,沿着连边走到另一个矩形边界。可以用 BFS 或并查集解决这个问题。
\\n复杂度 $\\\\mathcal{O}(n^2)$。
\\n其实本题是一个比较经典的套路:平面图的最小割等于其对偶图的最短路。感兴趣的读者也可以继续练习以下题目。
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canReachCorner(int X, int Y, vector<vector<int>>& C) {\\n // 求点 (xa, ya) 到 (xb, yb) 距离的平方\\n auto dis2 = [&](long long xa, long long ya, long long xb, long long yb) {\\n return (xa - xb) * (xa - xb) + (ya - yb) * (ya - yb);\\n };\\n\\n // 判断第 idx 个圆是否和水平线 (xa, y) -> (xb, y) 相交\\n auto checkH = [&](int xa, int xb, int y, int idx) {\\n // 首先判断圆心到直线的距离\\n int d = abs(C[idx][1] - y);\\n if (d > C[idx][2]) return false;\\n // 圆心是否在两个端点之间\\n if (C[idx][0] >= xa && C[idx][0] <= xb) return true;\\n // 某个端点是否在圆内\\n if (dis2(xa, y, C[idx][0], C[idx][1]) <= 1LL * C[idx][2] * C[idx][2]) return true;\\n if (dis2(xb, y, C[idx][0], C[idx][1]) <= 1LL * C[idx][2] * C[idx][2]) return true;\\n return false;\\n };\\n\\n // 判断第 idx 个圆是否和竖直线 (x, ya) -> (x, yb) 相交\\n auto checkV = [&](int ya, int yb, int x, int idx) {\\n // 首先判断圆心到直线的距离\\n int d = abs(C[idx][0] - x);\\n if (d > C[idx][2]) return false;\\n // 圆心是否在两个端点之间\\n if (C[idx][1] >= ya && C[idx][1] <= yb) return true;\\n // 某个端点是否在圆内\\n if (dis2(x, ya, C[idx][0], C[idx][1]) <= 1LL * C[idx][2] * C[idx][2]) return true;\\n if (dis2(x, yb, C[idx][0], C[idx][1]) <= 1LL * C[idx][2] * C[idx][2]) return true;\\n return false;\\n };\\n\\n // 构建无向图,我们给下、右、上、左边界编号为 0 ~ 3,第 i 个圆编号为 i + 4\\n int n = C.size();\\n vector<int> e[n + 4];\\n // 圆和矩形边界连边\\n for (int i = 0; i < n; i++) {\\n if (checkH(0, X, 0, i)) e[i + 4].push_back(0), e[0].push_back(i + 4);\\n if (checkV(0, Y, X, i)) e[i + 4].push_back(1), e[1].push_back(i + 4);\\n if (checkH(0, X, Y, i)) e[i + 4].push_back(2), e[2].push_back(i + 4);\\n if (checkV(0, Y, 0, i)) e[i + 4].push_back(3), e[3].push_back(i + 4);\\n }\\n // 两圆之间连边\\n for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++)\\n if (dis2(C[i][0], C[i][1], C[j][0], C[j][1]) <= 1LL * (C[i][2] + C[j][2]) * (C[i][2] + C[j][2]))\\n e[i + 4].push_back(j + 4), e[j + 4].push_back(i + 4);\\n\\n // BFS 判断能否从 S 走到 T\\n auto bfs = [&](int S, int T) {\\n bool vis[n + 4];\\n memset(vis, 0, sizeof(vis));\\n queue<int> q;\\n q.push(S); vis[S] = true;\\n while (!q.empty()) {\\n int sn = q.front(); q.pop();\\n for (int fn : e[sn]) if (!vis[fn]) {\\n q.push(fn); vis[fn] = true;\\n }\\n }\\n return vis[T];\\n };\\n \\n // 四种从边界剪开的情况\\n if (bfs(0, 2) || bfs(0, 3) || bfs(1, 2) || bfs(1, 3)) return false;\\n return true;\\n }\\n};\\n
\\n","description":"解法:BFS or 并查集 注:本题解没有考虑圆从矩形外部连接两个边界的情况(然而标程也没有考虑),预判力扣之后会在题面中增加条件。\\n\\n正难则反。因为“不经过圆的路径”很难处理,我们反过来想“在圆边界或内部的路径”有怎样的性质。\\n\\n考虑一条完全在圆边界或内部的路径,如果我们用一把剪刀沿着这条路径剪开,能把矩形剪成两半,而且矩形的左下角和右下角在不同的一半里,那么我们就无法从矩形的左下角,不经过圆的边界或内部,到达右上角。\\n\\n怎样的路径才能剪开矩形,并分离两个角落呢?有四种可能性:从矩形下边界剪到上边界、左边界剪到右边界、下边界剪到左边界、右边界剪到上边界。所以…","guid":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/bfs-or-bing-cha-ji-by-tsreaper-ga61","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-28T04:57:25.019Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图解】转换成图上 DFS + 避免浮点数的做法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/deng-jie-zhuan-huan-bing-cha-ji-pythonja-yf9y","content":"如果从矩形【上边界/左边界】到矩形【右边界/下边界】的路被圆堵死,则无法从矩形左下角移动到矩形右上角。
\\n怎么判断呢?
\\n首先考虑圆心都在矩形内部的情况。如果圆和圆相交或相切,则相当于在两个圆之间架起了一座桥。如果圆和矩形边界相交或相切,则相当于在矩形边界和圆之间架起了一座桥。如果可以从矩形【上边界/左边界】通过桥到达矩形【右边界/下边界】,则说明路被堵死,无法从矩形左下角移动到矩形右上角。
\\n也可以把桥理解成切割线,如果能把从矩形左下角到矩形右上角的路径切断,则无法从矩形左下角移动到矩形右上角。
\\n用图论的术语来说,就是把圆抽象成节点,在相交或相切的节点之间连边,得到一张无向图。如果从与【上边界/左边界】相交的节点出发,DFS 这张图,到达与【右边界/下边界】相交的节点,则说明无法从矩形左下角移动到矩形右上角。
\\n需要注意,本题没有保证圆心一定在矩形内部,如何处理这种情况呢?
\\n注:把两圆的两个交点连起来,该线段与 $O_1O_2$ 相交得到的交点作为点 $A$ 也可以,但这种情况点 $A$ 横纵坐标的分母会是一个 $10^{18}$ 数量级的数,在与 $\\\\textit{X}$ 或 $\\\\textit{Y}$ 相乘时会产生 $10^{27}$ 数量级的数,超出了 64 位整数的范围,需要用大整数实现,更麻烦。
\\n如何判断圆是否与矩形边界相交相切?
\\n从与矩形【上边界/左边界】相交/相切的圆开始 DFS。
\\n如果当前 DFS 到了圆 $i$:
\\n最后,如果最外层调用 DFS 的地方收到了 $\\\\texttt{true}$,则表示无法从矩形左下角移动到矩形右上角,返回 $\\\\texttt{false}$。
\\n代码实现时,可以在递归之前,特判圆包含矩形左下角或者矩形右上角的情况,此时可以直接返回 $\\\\texttt{false}$。
\\n###py
\\nclass Solution:\\n def canReachCorner(self, X: int, Y: int, circles: List[List[int]]) -> bool:\\n # 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\n def in_circle(ox: int, oy: int, r: int, x: int, y: int) -> bool:\\n return (ox - x) * (ox - x) + (oy - y) * (oy - y) <= r * r\\n\\n vis = [False] * len(circles)\\n def dfs(i: int) -> bool:\\n x1, y1, r1 = circles[i]\\n # 圆 i 是否与矩形右边界/下边界相交相切\\n if y1 <= Y and abs(x1 - X) <= r1 or \\\\\\n x1 <= X and y1 <= r1 or \\\\\\n x1 > X and in_circle(x1, y1, r1, X, 0):\\n return True\\n vis[i] = True\\n for j, (x2, y2, r2) in enumerate(circles):\\n # 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if not vis[j] and \\\\\\n (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) and \\\\\\n x1 * r2 + x2 * r1 < (r1 + r2) * X and \\\\\\n y1 * r2 + y2 * r1 < (r1 + r2) * Y and \\\\\\n dfs(j):\\n return True\\n return False\\n\\n for i, (x, y, r) in enumerate(circles):\\n # 圆 i 包含矩形左下角 or\\n # 圆 i 包含矩形右上角 or\\n # 圆 i 与矩形上边界/左边界相交相切\\n if in_circle(x, y, r, 0, 0) or \\\\\\n in_circle(x, y, r, X, Y) or \\\\\\n not vis[i] and (x <= X and abs(y - Y) <= r or\\n y <= Y and x <= r or\\n y > Y and in_circle(x, y, r, 0, Y)) and dfs(i):\\n return False\\n return True\\n
\\n###java
\\nclass Solution {\\n public boolean canReachCorner(int X, int Y, int[][] circles) {\\n boolean[] vis = new boolean[circles.length];\\n for (int i = 0; i < circles.length; i++) {\\n long x = circles[i][0], y = circles[i][1], r = circles[i][2];\\n if (inCircle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n inCircle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && Math.abs(y - Y) <= r ||\\n y <= Y && x <= r ||\\n y > Y && inCircle(x, y, r, 0, Y)) && dfs(i, X, Y, circles, vis)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n // 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\n private boolean inCircle(long ox, long oy, long r, long x, long y) {\\n return (ox - x) * (ox - x) + (oy - y) * (oy - y) <= r * r;\\n }\\n\\n private boolean dfs(int i, int X, int Y, int[][] circles, boolean[] vis) {\\n long x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if (y1 <= Y && Math.abs(x1 - X) <= r1 ||\\n x1 <= X && y1 <= r1 ||\\n x1 > X && inCircle(x1, y1, r1, X, 0)) {\\n return true;\\n }\\n vis[i] = true;\\n for (int j = 0; j < circles.length; j++) {\\n long x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if (!vis[j] &&\\n (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * X &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * Y &&\\n dfs(j, X, Y, circles, vis)) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n // 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\n bool in_circle(long long ox, long long oy, long long r, long long x, long long y) {\\n return (ox - x) * (ox - x) + (oy - y) * (oy - y) <= r * r;\\n }\\n\\npublic:\\n bool canReachCorner(int X, int Y, vector<vector<int>>& circles) {\\n int n = circles.size();\\n vector<int> vis(n);\\n auto dfs = [&](auto&& dfs, int i) -> bool {\\n long long x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if (y1 <= Y && abs(x1 - X) <= r1 ||\\n x1 <= X && y1 <= r1 ||\\n x1 > X && in_circle(x1, y1, r1, X, 0)) {\\n return true;\\n }\\n vis[i] = true;\\n for (int j = 0; j < n; j++) {\\n long long x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if (!vis[j] && (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * X &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * Y &&\\n dfs(dfs, j)) {\\n return true;\\n }\\n }\\n return false;\\n };\\n for (int i = 0; i < n; i++) {\\n long long x = circles[i][0], y = circles[i][1], r = circles[i][2];\\n if (in_circle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n in_circle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && abs(y - Y) <= r ||\\n y <= Y && x <= r ||\\n y > Y && in_circle(x, y, r, 0, Y)) && dfs(dfs, i)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n
\\n###c
\\n// 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\nbool inCircle(long long ox, long long oy, long long r, long long x, long long y) {\\n return (ox - x) * (ox - x) + (oy - y) * (oy - y) <= r * r;\\n}\\n\\nbool dfs(int i, int X, int Y, int** circles, int circlesSize, bool* vis) {\\n long long x1 = circles[i][0], y1 = circles[i][1], r1 = circles[i][2];\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if (y1 <= Y && abs(x1 - X) <= r1 ||\\n x1 <= X && y1 <= r1 ||\\n x1 > X && inCircle(x1, y1, r1, X, 0)) {\\n return true;\\n }\\n vis[i] = true;\\n for (int j = 0; j < circlesSize; j++) {\\n long long x2 = circles[j][0], y2 = circles[j][1], r2 = circles[j][2];\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if (!vis[j] && (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * X &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * Y &&\\n dfs(j, X, Y, circles, circlesSize, vis)) {\\n return true;\\n }\\n }\\n return false;\\n}\\n\\nbool canReachCorner(int X, int Y, int** circles, int circlesSize, int* circlesColSize) {\\n bool* vis = calloc(circlesSize, sizeof(bool));\\n for (int i = 0; i < circlesSize; i++) {\\n long long x = circles[i][0], y = circles[i][1], r = circles[i][2];\\n if (inCircle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n inCircle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && abs(y - Y) <= r ||\\n y <= Y && x <= r ||\\n y > Y && inCircle(x, y, r, 0, Y)) && dfs(i, X, Y, circles, circlesSize, vis)) {\\n free(vis);\\n return false;\\n }\\n }\\n free(vis);\\n return true;\\n}\\n
\\n###go
\\n// 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\nfunc inCircle(ox, oy, r, x, y int) bool {\\n return (ox-x)*(ox-x)+(oy-y)*(oy-y) <= r*r\\n}\\n\\nfunc canReachCorner(X, Y int, circles [][]int) bool {\\n vis := make([]bool, len(circles))\\n var dfs func(int) bool\\n dfs = func(i int) bool {\\n x1, y1, r1 := circles[i][0], circles[i][1], circles[i][2]\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if y1 <= Y && abs(x1-X) <= r1 || x1 <= X && y1 <= r1 || x1 > X && inCircle(x1, y1, r1, X, 0) {\\n return true\\n }\\n vis[i] = true\\n for j, c := range circles {\\n x2, y2, r2 := c[0], c[1], c[2]\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if !vis[j] && (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2) <= (r1+r2)*(r1+r2) &&\\n x1*r2+x2*r1 < (r1+r2)*X &&\\n y1*r2+y2*r1 < (r1+r2)*Y &&\\n dfs(j) {\\n return true\\n }\\n }\\n return false\\n }\\n for i, c := range circles {\\n x, y, r := c[0], c[1], c[2]\\n if inCircle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n inCircle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && abs(y-Y) <= r || y <= Y && x <= r || y > Y && inCircle(x, y, r, 0, Y)) && dfs(i) {\\n return false\\n }\\n }\\n return true\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\n###js
\\nvar canReachCorner = function(X, Y, circles) {\\n // 判断点 (x, y) 是否在圆 (ox, oy, r) 内\\n function inCircle(ox, oy, r, x, y) {\\n return BigInt(ox - x) * BigInt(ox - x) +\\n BigInt(oy - y) * BigInt(oy - y) <= BigInt(r) * BigInt(r);\\n }\\n\\n const BX = BigInt(X), BY = BigInt(Y);\\n const vis = new Array(circles.length).fill(false);\\n function dfs(i) {\\n let [x1, y1, r1] = circles[i];\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if (y1 <= Y && Math.abs(x1 - X) <= r1 ||\\n x1 <= X && y1 <= r1 ||\\n x1 > X && inCircle(x1, y1, r1, X, 0)) {\\n return true;\\n }\\n x1 = BigInt(x1);\\n y1 = BigInt(y1);\\n r1 = BigInt(r1);\\n vis[i] = true;\\n for (let j = 0; j < circles.length; j++) {\\n if (!vis[j]) {\\n let [x2, y2, r2] = circles[j];\\n x2 = BigInt(x2);\\n y2 = BigInt(y2);\\n r2 = BigInt(r2);\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if ((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * BX &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * BY &&\\n dfs(j)) {\\n return true;\\n }\\n }\\n }\\n return false;\\n }\\n\\n for (let i = 0; i < circles.length; i++) {\\n const [x, y, r] = circles[i];\\n if (inCircle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n inCircle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && Math.abs(y - Y) <= r ||\\n y <= Y && x <= r ||\\n y > Y && inCircle(x, y, r, 0, Y)) && dfs(i)) {\\n return false;\\n }\\n }\\n return true;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_reach_corner(x_corner: i32, y_corner: i32, circles: Vec<Vec<i32>>) -> bool {\\n let X = x_corner as i64;\\n let Y = y_corner as i64;\\n let mut vis = vec![false; circles.len()];\\n for i in 0..circles.len() {\\n let x = circles[i][0] as i64;\\n let y = circles[i][1] as i64;\\n let r = circles[i][2] as i64;\\n if Self::in_circle(x, y, r, 0, 0) || // 圆 i 包含矩形左下角\\n Self::in_circle(x, y, r, X, Y) || // 圆 i 包含矩形右上角\\n // 圆 i 是否与矩形上边界/左边界相交相切\\n !vis[i] && (x <= X && (y - Y).abs() <= r ||\\n y <= Y && x <= r ||\\n y > Y && Self::in_circle(x, y, r, 0, Y)) && Self::dfs(i, X, Y, &circles, &mut vis) {\\n return false;\\n }\\n }\\n true\\n }\\n\\n // 判断点 (x,y) 是否在圆 (ox,oy,r) 内\\n fn in_circle(ox: i64, oy: i64, r: i64, x: i64, y: i64) -> bool {\\n (ox - x) * (ox - x) + (oy - y) * (oy - y) <= r * r\\n }\\n\\n fn dfs(i: usize, x: i64, y: i64, circles: &Vec<Vec<i32>>, vis: &mut Vec<bool>) -> bool {\\n let x1 = circles[i][0] as i64;\\n let y1 = circles[i][1] as i64;\\n let r1 = circles[i][2] as i64;\\n // 圆 i 是否与矩形右边界/下边界相交相切\\n if y1 <= y && (x1 - x).abs() <= r1 ||\\n x1 <= x && y1 <= r1 ||\\n x1 > x && Self::in_circle(x1, y1, r1, x, 0) {\\n return true;\\n }\\n vis[i] = true;\\n for (j, c2) in circles.iter().enumerate() {\\n let x2 = c2[0] as i64;\\n let y2 = c2[1] as i64;\\n let r2 = c2[2] as i64;\\n // 在两圆相交相切的前提下,点 A 是否严格在矩形内\\n if !vis[j] && (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2) <= (r1 + r2) * (r1 + r2) &&\\n x1 * r2 + x2 * r1 < (r1 + r2) * x &&\\n y1 * r2 + y2 * r1 < (r1 + r2) * y &&\\n Self::dfs(j, x, y, circles, vis) {\\n return true;\\n }\\n }\\n false\\n }\\n}\\n
\\n注:本题也可以用并查集实现,但效率不如 DFS。
\\n更多相似题目,见下面图论题单中的「DFS」和数据结构题单中的「并查集」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"如果从矩形【上边界/左边界】到矩形【右边界/下边界】的路被圆堵死,则无法从矩形左下角移动到矩形右上角。 怎么判断呢?\\n\\n首先考虑圆心都在矩形内部的情况。如果圆和圆相交或相切,则相当于在两个圆之间架起了一座桥。如果圆和矩形边界相交或相切,则相当于在矩形边界和圆之间架起了一座桥。如果可以从矩形【上边界/左边界】通过桥到达矩形【右边界/下边界】,则说明路被堵死,无法从矩形左下角移动到矩形右上角。\\n\\n也可以把桥理解成切割线,如果能把从矩形左下角到矩形右上角的路径切断,则无法从矩形左下角移动到矩形右上角。\\n\\n用图论的术语来说,就是把圆抽象成节点…","guid":"https://leetcode.cn/problems/check-if-the-rectangle-corner-is-reachable//solution/deng-jie-zhuan-huan-bing-cha-ji-pythonja-yf9y","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-28T04:41:06.265Z","media":[{"url":"https://pic.leetcode.cn/1722649636-dihkoU-lc3235-c.png","type":"photo","width":1814,"height":9030,"blurhash":"LMR:HF%h%M%L?wt7RkWCI9V@t7bI"},{"url":"https://pic.leetcode.cn/1722579370-cPlOGI-lc3235-2-c.png","type":"photo","width":1646,"height":1999,"blurhash":"LBS6St_4?v.8^+oe%Lt6xuWVNGof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟","url":"https://leetcode.cn/problems/find-if-digit-game-can-be-won//solution/mo-ni-by-tsreaper-eyrr","content":"按题意模拟即可。复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canAliceWin(vector<int>& nums) {\\n int sm = 0, a = 0, b = 0;\\n for (int x : nums) {\\n sm += x;\\n if (x < 10) a += x;\\n else b += x;\\n }\\n return a > sm - a || b > sm - b;\\n }\\n};\\n
\\n","description":"解法:模拟 按题意模拟即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n bool canAliceWin(vector只有质数的平方才是特殊数字。因此先预处理出小于等于 $\\\\sqrt{10^9}$ 的质数以及它们的平方,就可以利用二分算出 $[l, r]$ 内特殊数字的数量。
\\n复杂度 $\\\\mathcal{O}(\\\\sqrt{r}\\\\log r + \\\\log r)$,前者是质数筛法的复杂度,后者是二分的复杂度。
\\n###cpp
\\n#define MAGIC ((int) 1e5)\\nbool inited = false, flag[MAGIC + 10];\\nvector<long long> vec;\\n\\n// 力扣是用同一个 class 对象多次测试,所以先全局预处理出特殊数字\\nvoid init() {\\n if (inited) return;\\n inited = true;\\n memset(flag, 0, sizeof(flag));\\n for (int i = 2; i <= MAGIC; i++) if (!flag[i]) {\\n vec.push_back(1LL * i * i);\\n for (int j = i * 2; j <= MAGIC; j += i) flag[j] = true;\\n }\\n}\\n\\nclass Solution {\\npublic:\\n int nonSpecialCount(int l, int r) {\\n init();\\n // 二分算出特殊数字的数量\\n int R = upper_bound(vec.begin(), vec.end(), r) - vec.begin();\\n int L = lower_bound(vec.begin(), vec.end(), l) - vec.begin();\\n return (r - l + 1) - (R - L);\\n }\\n};\\n
\\n","description":"解法:数学 只有质数的平方才是特殊数字。因此先预处理出小于等于 $\\\\sqrt{10^9}$ 的质数以及它们的平方,就可以利用二分算出 $[l, r]$ 内特殊数字的数量。\\n\\n复杂度 $\\\\mathcal{O}(\\\\sqrt{r}\\\\log r + \\\\log r)$,前者是质数筛法的复杂度,后者是二分的复杂度。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\n#define MAGIC ((int) 1e5)\\nbool inited = false, flag[MAGIC + 10];\\nvector点击图片放大查看:
\\nclass Solution:\\n def nthPersonGetsNthSeat(self, n: int) -> float:\\n return 1 if n == 1 else 0.5\\n
\\nclass Solution {\\n public double nthPersonGetsNthSeat(int n) {\\n return n == 1 ? 1 : 0.5;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n double nthPersonGetsNthSeat(int n) {\\n return n == 1 ? 1 : 0.5;\\n }\\n};\\n
\\ndouble nthPersonGetsNthSeat(int n) {\\n return n == 1 ? 1 : 0.5;\\n}\\n
\\nfunc nthPersonGetsNthSeat(n int) float64 {\\n if n == 1 {\\n return 1\\n }\\n return 0.5\\n}\\n
\\nvar nthPersonGetsNthSeat = function(n) {\\n return n == 1 ? 1 : 0.5;\\n};\\n
\\nimpl Solution {\\n pub fn nth_person_gets_nth_seat(n: i32) -> f64 {\\n if n == 1 { 1.0 } else { 0.5 }\\n }\\n}\\n
\\n之前写过一篇图解,推导 $f(n)$ 的过程有些类似。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"点击图片放大查看: class Solution:\\n def nthPersonGetsNthSeat(self, n: int) -> float:\\n return 1 if n == 1 else 0.5\\n\\nclass Solution {\\n public double nthPersonGetsNthSeat(int n) {\\n return n == 1 ? 1 : 0.5;\\n }\\n}\\n\\nclass Solution {\\npublic:\\n double nthPersonGetsNthSeat…","guid":"https://leetcode.cn/problems/airplane-seat-assignment-probability//solution/tu-jie-mei-xiang-ming-bai-yi-zhang-tu-mi-8bn4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-27T03:05:46.847Z","media":[{"url":"https://pic.leetcode.cn/1727755614-jyvuRU-lc1227-2-c.png","type":"photo","width":2435,"height":2772,"blurhash":"L9SigQ_3-;~q-;tRt7ayxus:IUM{"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"记忆化搜索->递推","url":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/ji-yi-hua-sou-suo-di-tui-by-yi-cheng-8i-m8qb","content":"灵神dp题单打卡
\\n先找子问题,举个例子
\\ns = \\"abcbdd\\" 从后往前思考,\\"dd\\"是回文串,问题变为求\\"abcd\\"是否是两个回文\\n串组成的,问题变为了子问题。\\n
\\n在上面我们看出了我们需要两个参数,一个是参数$i$,代表了$s[0..i]$,一个$j$,代表了剩余的回文串个数
\\n那么可以定义$dfs(i,j)$代表了$s[0..i]$能否分割成$j$段回文串。
\\n$$dfs(i,j) \\\\ ||= \\\\ dfs(k - 1,j - 1) \\\\ && \\\\ ispalindrome(s[k...i])$$
\\n其中$j - 2 < k <= i $,所有$k$取与.
边界条件:
\\n\\n\\n我们如果每次判断是否回文,那么太慢了,可以预处理,利用的是中心扩散法,不会的可以见此题:https://leetcode.cn/problems/palindromic-substrings/
\\n
class Solution:\\n def checkPartitioning(self, s: str) -> bool:\\n n = len(s)\\n \\"\\"\\"\\n dfs(i,j)代表了s[0..i]能否分割成j段\\n k >= j - 1\\n dfs(i,j) = dfs(k - 1,j - 1) && ispalindrome(s[k...i]) j - 2 < k <= i 所有k取与\\n if i + 1 == j,return true\\n if j == 1 return ispalindrome(s[0...i])\\n \\"\\"\\"\\n # 预处理是否回文\\n ispalindrome = [[False] * n for _ in range(n)]\\n for i in range(2 * n - 1):\\n l,r = i // 2,i // 2 + i % 2\\n while l >= 0 and r < n and s[l] == s[r]:\\n ispalindrome[l][r] = True\\n l -= 1\\n r += 1\\n @cache\\n def dfs(i: int,j: int) -> int:\\n if i + 1 == j:\\n return True\\n if j == 1:\\n return ispalindrome[0][i]\\n res = False\\n for k in range(i,j - 2,-1):\\n res = res or (ispalindrome[k][i] and dfs(k - 1,j - 1))\\n return res\\n return dfs(n - 1,3)\\n
\\n###Python3
\\nclass Solution:\\n def checkPartitioning(self, s: str) -> bool:\\n n = len(s)\\n \\"\\"\\"\\n dfs(i,j)代表了s[0..i]能否分割成j段\\n k >= j - 1\\n\\n if i + 1 == j,return true\\n if j == 1 return ispalindrome(s[0...i])\\n \\"\\"\\"\\n # 预处理是否回文\\n ispalindrome = [[False] * n for _ in range(n)]\\n for i in range(2 * n - 1):\\n l,r = i // 2,i // 2 + i % 2\\n while l >= 0 and r < n and s[l] == s[r]:\\n ispalindrome[l][r] = True\\n l -= 1\\n r += 1\\n f = [[True] * (3 + 1) for _ in range(n)]\\n for i in range(n):\\n f[i][1] = ispalindrome[0][i]\\n for j in range(2,3 + 1):\\n for i in range(1,n):\\n f[i][j] = False\\n for k in range(i,j - 2,-1):\\n f[i][j] = f[i][j] or (ispalindrome[k][i] and f[k - 1][j - 1])\\n return f[n - 1][3]\\n
\\n","description":"灵神dp题单打卡 先找子问题,举个例子\\n\\ns = \\"abcbdd\\" 从后往前思考,\\"dd\\"是回文串,问题变为求\\"abcd\\"是否是两个回文\\n串组成的,问题变为了子问题。\\n\\n\\n在上面我们看出了我们需要两个参数,一个是参数$i$,代表了$s[0..i]$,一个$j$,代表了剩余的回文串个数\\n\\n那么可以定义$dfs(i,j)$代表了$s[0..i]$能否分割成$j$段回文串。\\n $$dfs(i,j) \\\\ ||= \\\\ dfs(k - 1,j - 1) \\\\ && \\\\ ispalindrome(s[k...i])$$\\n 其中$j - 2 < k <= i $,所有$k$取与…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/ji-yi-hua-sou-suo-di-tui-by-yi-cheng-8i-m8qb","author":"yi-cheng-8i","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-26T05:03:31.054Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种解法,附保姆级的变色图示模拟 (C++ / Python / Java / Kotlin)","url":"https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii//solution/bao-mu-ji-de-bian-se-tu-shi-mo-ni-c-pyth-zn6z","content":"我们对符合条件的元素计数,然后放入指定位置即可。
\\n###C++
\\nclass Solution {\\npublic:\\n int removeDuplicates(vector<int>& nums) {\\n if(nums.size() <= 2) return nums.size();\\n\\n int cnt = 2; // 先计入 nums[0], nums[1]\\n int v1 = nums[0]; // 前面第二个元素\\n int v2 = nums[1]; // 前面第一个元素\\n\\n for(int i = 2; i < nums.size(); i++){\\n // nums[i] != v2, 说明不是重复两次以上的后续元素。\\n // 因为数组有序,v1 <= v2。nums[i] 如果 == v1,那么也 == v2\\n if(nums[i] != v1) {\\n cnt++; // 计数\\n nums[cnt - 1] = nums[i]; // 放入 cnt - 1 处\\n // 以上两行可以合并简化,但个人更倾向于可读性\\n }\\n v1 = v2; \\n v2 = nums[i];\\n }\\n\\n return cnt;\\n }\\n};\\n
\\n###C
\\n// C 实现\\nint removeDuplicates(int* nums, int numsSize) {\\n if(numsSize <= 2) return numsSize;\\n\\n int cnt = 2; // 先计入 nums[0], nums[1]\\n int v1 = nums[0]; // 前面第二个元素\\n int v2 = nums[1]; // 前面第一个元素\\n\\n for(int i = 2; i < numsSize; i++){\\n // nums[i] != v2, 说明不是重复两次以上的后续元素。\\n // 因为数组有序,v1 <= v2。nums[i] 如果 == v1,那么也 == v2\\n if(nums[i] != v1) { \\n cnt++; // 计数\\n nums[cnt - 1] = nums[i]; // 放入 cnt - 1 处\\n // 以上两行可以合并简化,但个人更倾向于可读性\\n }\\n v1 = v2; \\n v2 = nums[i];\\n }\\n\\n return cnt;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def removeDuplicates(self, nums: List[int]) -> int:\\n if len(nums) <= 2:\\n return len(nums)\\n\\n cnt = 2 # 先计入 nums[0], nums[1]\\n v1 = nums[0] # 前面第二个元素\\n v2 = nums[1] # 前面第一个元素\\n\\n for i in range(2, len(nums)):\\n # nums[i] != v2, 说明不是重复两次以上的后续元素。\\n # 因为数组有序,v1 <= v2。nums[i] 如果 == v1,那么也 == v2\\n if nums[i] != v1:\\n cnt += 1 # 计数\\n nums[cnt - 1] = nums[i] # 放入 cnt - 1 处\\n # 以上两行可以调换简化,但个人更倾向于可读性\\n\\n v1 = v2\\n v2 = nums[i]\\n\\n return cnt\\n
\\n###Java
\\n// Java 实现\\nclass Solution {\\n public int removeDuplicates(int[] nums) {\\n if(nums.length <= 2) return nums.length;\\n\\n int cnt = 2; // 先计入 nums[0], nums[1]\\n int v1 = nums[0]; // 前面第二个元素\\n int v2 = nums[1]; // 前面第一个元素\\n\\n for(int i = 2; i < nums.length; i++){\\n // nums[i] != v2, 说明不是重复两次以上的后续元素。\\n // 因为数组有序,v1 <= v2。nums[i] 如果 == v1,那么也 == v2\\n if(nums[i] != v1) {\\n cnt++; // 计数\\n nums[cnt - 1] = nums[i]; // 放入 cnt - 1 处\\n // 以上两行可以合并简化,但个人更倾向于可读性\\n }\\n v1 = v2; \\n v2 = nums[i];\\n }\\n\\n return cnt;\\n }\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun removeDuplicates(nums: IntArray): Int {\\n if(nums.size <= 2) return nums.size\\n\\n var cnt = 2 // 先计入 nums[0], nums[1]\\n var v1 = nums[0] // 前面第二个元素\\n var v2 = nums[1] // 前面第一个元素\\n\\n for(i in 2..<nums.size){\\n // nums[i] != v2, 说明不是重复两次以上的后续元素。\\n // 因为数组有序,v1 <= v2。nums[i] 如果 == v1,那么也 == v2\\n if(nums[i] != v1) { \\n cnt++ // 计数\\n nums[cnt - 1] = nums[i] // 放入 cnt - 1 处\\n // 以上两行可以合并简化,但个人更倾向于可读性\\n }\\n v1 = v2 \\n v2 = nums[i]\\n }\\n\\n return cnt\\n }\\n}\\n
\\n能否在 if
处直接判断 nums[i] != nums[i-2]
呢?我把答案放在评论区。
先看代码再看过程模拟。虽然思路更难一点,但对学习循环不变量很有帮助。
\\n###C++
\\nclass Solution {\\npublic:\\n int removeDuplicates(vector<int>& nums) {\\n if (nums.size() <= 2) return nums.size();\\n\\n int l = 2;\\n\\n for (int r = 2; r < nums.size(); r++) {\\n if (nums[l - 2] != nums[r]) {\\n // 可以合并成 nums[l++] = nums[r]\\n nums[l] = nums[r];\\n l++;\\n }\\n }\\n\\n return l;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def removeDuplicates(self, nums):\\n if len(nums) <= 2:\\n return len(nums)\\n\\n l = 2\\n\\n for r in range(2, len(nums)):\\n if nums[l - 2] != nums[r]:\\n nums[l] = nums[r]\\n l += 1\\n\\n return l\\n
\\n###Java
\\nclass Solution {\\n public int removeDuplicates(int[] nums) {\\n if (nums.length <= 2) return nums.length;\\n\\n int l = 2;\\n\\n for (int r = 2; r < nums.length; r++) {\\n if (nums[l - 2] != nums[r]) {\\n // 可以合并成 nums[l++] = nums[r]\\n nums[l] = nums[r];\\n l++;\\n }\\n }\\n\\n return l;\\n }\\n}\\n
\\n###Kotlin
\\nclass Solution {\\n fun removeDuplicates(nums: IntArray): Int {\\n if(nums.size <= 2) return nums.size \\n\\n var l = 2\\n \\n for(r in 2..<nums.size){\\n if(nums[l - 2] != nums[r]){\\n // 可以合并成 nums[l++] = nums[r]\\n nums[l] = nums[r]\\n l++ \\n }\\n }\\n\\n return l\\n }\\n}\\n
\\n模拟一遍过程。
\\n取数组示例和初始双指针如下所示,我们会依次缩减蓝色区域,跳过已重复两次以上的元素,并扩展绿色区域(已安置好的元素), 同时使 $l$ 保持在绿色区域的右一格。
\\n上图中满足 nums[l-2] != [nums[r]
,我们可以执行 nums[l] = nums[r]
并使 $l, r$ 同步右移,如下所示,这可视为偏移量为 0 的无效偏移。
上图中 nums[l-2] == nums[r]
,因为数组是非递减排序,所以也一定满足 nums[l-1] == nums[r]
,即 nums[r]
为重复两次以上的元素。
我们可以使 $r$ 右移直至 nums[l-2] != nums[r]
,如下所示,中间的无色区域是该无效的。
现在令 nums[l] = nums[r]
后 $l$ 右移, $r$ 右移。
\\n
以此类推,寻找 nums[l-2] != nums[r]
的情况。
以下皆为个人所著,兼顾了职场面试和本硕阶段的学术考试。
\\n点赞关注不迷路。祝君早日上岸,飞黄腾达!
\\n","description":"我们对符合条件的元素计数,然后放入指定位置即可。 ###C++\\n\\nclass Solution {\\npublic:\\n int removeDuplicates(vector给定正整数 $n$ 和 $k$,要求选择 $n$ 的二进制表示中的值为 $1$ 的位改成 $0$,使得 $n$ 等于 $k$。当 $n$ 可以变成 $k$ 时,应满足 $k$ 的二进制表示中的每个 $1$ 所在位对应的 $n$ 的该位值等于 $1$,等价于 $(n ~&~ k) = k$。
\\n当 $(n ~&~ k) = k$ 时,$n$ 和 $k$ 的二进制表示中的值不同的每一位都满足 $n$ 在该位的值等于 $1$ 且 $k$ 在该位的值等于 $0$,因此更改次数等于 $n$ 和 $k$ 的二进制表示中的值不同的位的数量,即 $n \\\\oplus k$ 的二进制表示中的 $1$ 的数量,计算 $n \\\\oplus k$ 的二进制表示中的 $1$ 的位数并返回该位数。
\\n当 $(n ~&~ k) \\\\ne k$ 时,不能将 $n$ 变成 $k$,返回 $-1$。
\\n###Java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (n & k) == k ? Integer.bitCount(n ^ k) : -1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinChanges(int n, int k) {\\n return (n & k) == k ? BitCount(n ^ k) : -1;\\n }\\n\\n public int BitCount(int num) {\\n uint bits = (uint) num;\\n bits = bits - ((bits >> 1) & 0x55555555);\\n bits = (bits & 0x33333333) + ((bits >> 2) & 0x33333333);\\n bits = (bits + (bits >> 4)) & 0x0f0f0f0f;\\n bits = (bits + (bits >> 8)) & 0x00ff00ff;\\n bits = (bits + (bits >> 16)) & 0x0000ffff;\\n return (int) bits;\\n }\\n}\\n
\\n时间复杂度:$O(1)$。这里将计算一个整数的二进制表示中的 $1$ 的位数的时间视为 $O(1)$。
\\n空间复杂度:$O(1)$。
\\n\\n\\nProblem: 100372. 使两个整数相等的位更改次数
\\n
[TOC]
\\n位运算。
\\n执行用时分布0ms击败100.00%;消耗内存分布8.01MB击败100.00%
\\n###C
\\nint minChanges(int n, int k) {\\n if ((n | k) != n) return -1;\\n int ans = 0;\\n for (int a = n ^ k; a; a &= a - 1)\\n if (a) ++ ans;\\n return ans;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return (n ^ k).bit_count() if n | k == n else -1\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100372. 使两个整数相等的位更改次数 [TOC]\\n\\n位运算。\\n\\n执行用时分布0ms击败100.00%;消耗内存分布8.01MB击败100.00%\\n\\n###C\\n\\nint minChanges(int n, int k) {\\n if ((n | k) != n) return -1;\\n int ans = 0;\\n for (int a = n ^ k; a; a &= a - 1)\\n if (a) ++ ans;\\n return ans;\\n}\\n\\n\\n###Python3\\n\\nclass Solution:…","guid":"https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal//solution/wei-yun-suan-c0ms-pythonyi-xing-by-admir-0d0j","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-22T01:15:05.389Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学 只需要一行代码","url":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/shu-xue-zhi-xu-yao-yi-xing-dai-ma-by-ftm-6i8v","content":"\\n\\nProblem: 100375. 求出硬币游戏的赢家
\\n
[TOC]
\\n先计算一共可以拿出几次115,奇数先手赢偶数后手赢
\\n拿出115只能 75 + 40,所以算有多少个(1,4)在(x,y)里面
\\n###Python
\\nclass Solution(object):\\n def losingPlayer(self, x, y):\\n \\"\\"\\"\\n :type x: int\\n :type y: int\\n :rtype: str\\n \\"\\"\\"\\n return \'Alice\'if min(x,y/4)%2 else \'Bob\'\\n
\\n","description":"Problem: 100375. 求出硬币游戏的赢家 [TOC]\\n\\n先计算一共可以拿出几次115,奇数先手赢偶数后手赢\\n\\n拿出115只能 75 + 40,所以算有多少个(1,4)在(x,y)里面\\n\\n时间复杂度: $O(1)$\\n空间复杂度: $O(0)$\\n\\n###Python\\n\\nclass Solution(object):\\n def losingPlayer(self, x, y):\\n \\"\\"\\"\\n :type x: int\\n :type y: int\\n :rtype: str…","guid":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/shu-xue-zhi-xu-yao-yi-xing-dai-ma-by-ftm-6i8v","author":"ftMk6YPk2D","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-21T17:57:29.387Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(1) 位运算做法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal//solution/o1-wei-yun-suan-zuo-fa-pythonjavacgo-by-3lg19","content":"\\n从集合的角度理解,每次操作相当于去掉集合 $n$ 中的一个元素。
\\n要能把 $n$ 变成 $k$,$k$ 必须是 $n$ 的子集。如果不是,返回 $-1$。
\\n如果 $k$ 是 $n$ 的子集,答案为从 $n$ 中去掉 $k$ 后的集合大小,即 $n\\\\oplus k$ 的二进制中的 $1$ 的个数。
\\n\\n\\n注:也可以计算 $n-k$ 的二进制中的 $1$ 的个数。
\\n
具体请看 视频讲解,欢迎点赞关注~
\\n如果 $n$ 和 $k$ 的交集是 $k$,那么 $k$ 就是 $n$ 的子集。
\\n交集就是位运算中的 AND(&
)。
###py
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return -1 if n & k != k else (n ^ k).bit_count()\\n
\\n###java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (n & k) != k ? -1 : Integer.bitCount(n ^ k);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n return (n & k) != k ? -1 : __builtin_popcount(n ^ k);\\n }\\n};\\n
\\n###c
\\nint minChanges(int n, int k) {\\n return (n & k) != k ? -1 : __builtin_popcount(n ^ k);\\n}\\n
\\n###go
\\nfunc minChanges(n, k int) int {\\nif n&k != k {\\nreturn -1\\n}\\nreturn bits.OnesCount(uint(n ^ k))\\n}\\n
\\n###js
\\nvar minChanges = function(n, k) {\\n return (n & k) !== k ? -1 : bitCount32(n ^ k);\\n};\\n\\nfunction bitCount32(n) {\\n n = n - ((n >> 1) & 0x55555555);\\n n = (n & 0x33333333) + ((n >> 2) & 0x33333333);\\n return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_changes(n: i32, k: i32) -> i32 {\\n if n & k != k {\\n return -1;\\n }\\n (n ^ k).count_ones() as _\\n }\\n}\\n
\\n如果 $n$ 和 $k$ 的并集是 $n$,那么 $k$ 就是 $n$ 的子集。
\\n并集就是位运算中的 OR(|
)。
###py
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return -1 if n | k != n else (n ^ k).bit_count()\\n
\\n###java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (n | k) != n ? -1 : Integer.bitCount(n ^ k);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n return (n | k) != n ? -1 : __builtin_popcount(n ^ k);\\n }\\n};\\n
\\n###c
\\nint minChanges(int n, int k) {\\n return (n | k) != n ? -1 : __builtin_popcount(n ^ k);\\n}\\n
\\n###go
\\nfunc minChanges(n, k int) int {\\nif n|k != n {\\nreturn -1\\n}\\nreturn bits.OnesCount(uint(n ^ k))\\n}\\n
\\n###js
\\nvar minChanges = function(n, k) {\\n return (n | k) !== n ? -1 : bitCount32(n ^ k);\\n};\\n\\nfunction bitCount32(n) {\\n n = n - ((n >> 1) & 0x55555555);\\n n = (n & 0x33333333) + ((n >> 2) & 0x33333333);\\n return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_changes(n: i32, k: i32) -> i32 {\\n if n | k != n {\\n return -1;\\n }\\n (n ^ k).count_ones() as _\\n }\\n}\\n
\\n如果 $k$ 去掉 $n$ 中所有元素后,变成了空集,那么 $k$ 就是 $n$ 的子集。
\\n写成代码,如果 (k & ~n) == 0
,那么 $k$ 就是 $n$ 的子集。
###py
\\nclass Solution:\\n def minChanges(self, n: int, k: int) -> int:\\n return -1 if k & ~n else (n ^ k).bit_count()\\n
\\n###java
\\nclass Solution {\\n public int minChanges(int n, int k) {\\n return (k & ~n) > 0 ? -1 : Integer.bitCount(n ^ k);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minChanges(int n, int k) {\\n return k & ~n ? -1 : __builtin_popcount(n ^ k);\\n }\\n};\\n
\\n###c
\\nint minChanges(int n, int k) {\\n return k & ~n ? -1 : __builtin_popcount(n ^ k);\\n}\\n
\\n###go
\\nfunc minChanges(n, k int) int {\\nif k&^n > 0 {\\nreturn -1\\n}\\nreturn bits.OnesCount(uint(n ^ k))\\n}\\n
\\n###js
\\nvar minChanges = function(n, k) {\\n return k & ~n ? -1 : bitCount32(n ^ k);\\n};\\n\\nfunction bitCount32(n) {\\n n = n - ((n >> 1) & 0x55555555);\\n n = (n & 0x33333333) + ((n >> 2) & 0x33333333);\\n return ((n + (n >> 4) & 0xF0F0F0F) * 0x1010101) >> 24;\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_changes(n: i32, k: i32) -> i32 {\\n if k & !n > 0 {\\n return -1;\\n }\\n (n ^ k).count_ones() as _\\n }\\n}\\n
\\n更多相似题目,见下面的位运算题单。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"请先阅读:从集合论到位运算,常见位运算技巧分类总结! 从集合的角度理解,每次操作相当于去掉集合 $n$ 中的一个元素。\\n\\n要能把 $n$ 变成 $k$,$k$ 必须是 $n$ 的子集。如果不是,返回 $-1$。\\n\\n如果 $k$ 是 $n$ 的子集,答案为从 $n$ 中去掉 $k$ 后的集合大小,即 $n\\\\oplus k$ 的二进制中的 $1$ 的个数。\\n\\n注:也可以计算 $n-k$ 的二进制中的 $1$ 的个数。\\n\\n具体请看 视频讲解,欢迎点赞关注~\\n\\n写法一\\n\\n如果 $n$ 和 $k$ 的交集是 $k$,那么 $k$ 就是 $n$ 的子集。\\n\\n交集就是位运算中的…","guid":"https://leetcode.cn/problems/number-of-bit-changes-to-make-two-integers-equal//solution/o1-wei-yun-suan-zuo-fa-pythonjavacgo-by-3lg19","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-21T04:09:30.008Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(1) 数学做法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/o1-shu-xue-zuo-fa-pythonjavacgo-by-endle-427f","content":"因为 $10$ 的倍数不可能等于 $115$,所以面额为 $10$ 的硬币不能单独选,至少要选 $1$ 个面额为 $75$ 的硬币。
\\n又由于 $75\\\\cdot 2=150>115$,所以面额为 $75$ 的硬币要恰好选 $1$ 个。
\\n由于 $75+10\\\\cdot 4 = 115$,所以面额为 $10$ 的硬币要恰好选 $4$ 个。
\\n\\n\\n本质上来说,我们在求解二元一次不定方程 $75a+10b=115$,它有唯一正整数解 $a=1,b=4$。
\\n
如果一开始 Alice 就没法选,或者偶数轮后 Alice 没法选,那么 Bob 胜出,否则 Alice 胜出。
\\n设 $k = \\\\min(x, \\\\lfloor y/4 \\\\rfloor)$,这是能玩的回合数。
\\n判断 $k$ 的奇偶性,奇数 Alice 胜,偶数 Bob 胜。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def losingPlayer(self, x: int, y: int) -> str:\\n return \\"Alice\\" if min(x, y // 4) % 2 else \\"Bob\\"\\n
\\n###java
\\nclass Solution {\\n public String losingPlayer(int x, int y) {\\n return Math.min(x, y / 4) % 2 != 0 ? \\"Alice\\" : \\"Bob\\";\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string losingPlayer(int x, int y) {\\n return min(x, y / 4) % 2 ? \\"Alice\\" : \\"Bob\\";\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nconst char* losingPlayer(int x, int y) {\\n return MIN(x, y / 4) % 2 ? \\"Alice\\" : \\"Bob\\";\\n}\\n
\\n###go
\\nfunc losingPlayer(x, y int) string {\\nreturn [2]string{\\"Bob\\", \\"Alice\\"}[min(x, y/4)%2]\\n}\\n
\\n###js
\\nvar losingPlayer = function(x, y) {\\n return Math.min(x, Math.floor(y / 4)) % 2 ? \\"Alice\\" : \\"Bob\\";\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn losing_player(x: i32, y: i32) -> String {\\n if x.min(y / 4) % 2 != 0 {\\n \\"Alice\\".to_string()\\n } else {\\n \\"Bob\\".to_string()\\n }\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 因为 $10$ 的倍数不可能等于 $115$,所以面额为 $10$ 的硬币不能单独选,至少要选 $1$ 个面额为 $75$ 的硬币。\\n\\n又由于 $75\\\\cdot 2=150>115$,所以面额为 $75$ 的硬币要恰好选 $1$ 个。\\n\\n由于 $75+10\\\\cdot 4 = 115$,所以面额为 $10$ 的硬币要恰好选 $4$ 个。\\n\\n本质上来说,我们在求解二元一次不定方程 $75a+10b=115$,它有唯一正整数解 $a=1,b=4$。\\n\\n如果一开始 Alice 就没法选,或者偶数轮后 Alice 没法选,那么 Bob 胜出,否则 Alice…","guid":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/o1-shu-xue-zuo-fa-pythonjavacgo-by-endle-427f","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-21T00:28:10.860Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学","url":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/shu-xue-by-tsreaper-1vy9","content":"用 $75$ 和 $10$ 组成 $115$ 只有一种方式,即 $75 + 10 \\\\times 4$。因此每次玩家肯定拿一个 $75$ 和四个 $10$。
\\n所以游戏能进行的轮数就是 $\\\\min(x, \\\\lfloor \\\\frac{y}{4} \\\\rfloor)$,如果轮数是奇数则 Alice 获胜,否则 Bob 获胜。
\\n复杂度 $\\\\mathcal{O}(1)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n string losingPlayer(int x, int y) {\\n int r = min(x, y / 4);\\n if (r & 1) return \\"Alice\\";\\n else return \\"Bob\\";\\n }\\n};\\n
\\n","description":"解法:数学 用 $75$ 和 $10$ 组成 $115$ 只有一种方式,即 $75 + 10 \\\\times 4$。因此每次玩家肯定拿一个 $75$ 和四个 $10$。\\n\\n所以游戏能进行的轮数就是 $\\\\min(x, \\\\lfloor \\\\frac{y}{4} \\\\rfloor)$,如果轮数是奇数则 Alice 获胜,否则 Bob 获胜。\\n\\n复杂度 $\\\\mathcal{O}(1)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n string losingPlayer(int x, int y) {\\n int…","guid":"https://leetcode.cn/problems/find-the-winning-player-in-coin-game//solution/shu-xue-by-tsreaper-1vy9","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-20T16:25:45.706Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"根据值域动态规划(Python)","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-ii//solution/gen-ju-zhi-yu-dong-tai-gui-hua-python-by-w1ql","content":"拿到这道题最先想到的是动态规划。每切一刀以后都会得到独立的小蛋糕,这完全符合动态规划子问题与原问题形式类似的条件。然而如果简单地定义状态$f[i1][i2][j1][j2]$为切割$i1\\\\cdots i2, j1\\\\cdots j2$区域内矩形所需最小代价,则复杂度过高,无法通过。
\\n分析易知,横切和竖切的代价是与其位置无关的,即$horizontalCut$和$verticalCut$中元素的顺序是不重要的,因此可以对其任意重新排列。
\\n对于横切/竖切,由于竖向/横向已经切割的块数不减,所以将代价更大的操作放在前面是更优的。进一步地,需要将代价相同的切割放在一起操作。
\\n注意到$horizontalCut$和$verticalCut$的元素大小范围为$1e3$,因此可以根据值域对其计数并降序排序,并设计如下动态规划:
\\n定义$dp[h][v]$为当前待进行横向切割代价为$h$、竖向切割代价为$v$时的最小代价。由于按代价从大到小切割,$h$和$v$降序遍历,因此$dp[h][v]$可以由$dp[h+1][v]$和$dp[h][v+1]$转移得到,状态转移方程见代码。初始条件为$dp[\\\\max(horizontalCut)][\\\\max(verticalCut)]=0$,所求即为$dp[0][0]$。
\\n###Python
\\nclass Solution:\\n def minimumCost(self, m: int, n: int, hcut: List[int], vcut: List[int]) -> int:\\n mxhcut, mxvcut = max(hcut, default=0), max(vcut, default=0)\\n # 计数\\n hcnt, vcnt = [0] * (mxhcut+2), [0] * (mxvcut+2)\\n for h in hcut:\\n hcnt[h] += 1\\n for v in vcut:\\n vcnt[v] += 1\\n # 后缀和维护横向和竖向已经切割的块数\\n hsuf, vsuf = [0] * (mxhcut+2), [0] * (mxvcut+2)\\n for h in range(mxhcut, -1, -1):\\n hsuf[h] = hsuf[h+1] + hcnt[h]\\n for v in range(mxvcut, -1, -1):\\n vsuf[v] = vsuf[v+1] + vcnt[v]\\n\\n dp = [[inf] * (mxvcut+2) for _ in range(mxhcut+2)]\\n # 不需要特判 h+1 和 v+1 是否越界的初始化方式\\n dp[mxhcut+1][mxvcut] = dp[mxhcut][mxvcut+1] = 0\\n for h in range(mxhcut, -1, -1):\\n for v in range(mxvcut, -1, -1):\\n # Python 可能需要手写 min 才能避免超时\\n dp[h][v] = min(dp[h+1][v] + hcnt[h+1] * (h+1) * (vsuf[v+1]+1),\\n dp[h][v+1] + vcnt[v+1] * (v+1) * (hsuf[h+1]+1))\\n return dp[0][0]\\n
\\n\\n\\nProblem: 3216. 交换后字典序最小的字符串
\\n
[TOC]
\\n\\n\\n相邻求差判断奇偶。
\\n
\\n\\n相邻求差。差大于0,说明后值较小可以交换;差为偶数,说明两数奇偶性相同。
\\n
###Java
\\nclass Solution {\\n public String getSmallestString(String s) {\\n StringBuilder sb = new StringBuilder();\\n // 是否已发生一次交互\\n boolean hasSwitch = false;\\n char preC = \'0\';\\n int len = s.length();\\n for (int i = 0; i < len; i++) {\\n char currentC = s.charAt(i);\\n if (i == 0 || hasSwitch) {\\n sb.append(currentC);\\n } else {\\n // 相邻求差。差大于0,说明后值较小可以交换;差为偶数,说明两数奇偶性相同\\n int diff = preC - currentC;\\n if (diff > 0 && (diff & 1) == 0) {\\n hasSwitch = true;\\n // 尾部前插\\n sb.insert(sb.length() - 1, currentC);\\n } else {\\n sb.append(currentC);\\n }\\n }\\n preC = currentC;\\n }\\n return sb.toString();\\n }\\n}\\n
\\n","description":"Problem: 3216. 交换后字典序最小的字符串 [TOC]\\n\\n相邻求差判断奇偶。\\n\\n相邻求差。差大于0,说明后值较小可以交换;差为偶数,说明两数奇偶性相同。\\n\\n时间复杂度: $O(1)$\\n空间复杂度: $O(1)$\\n\\n###Java\\n\\nclass Solution {\\n public String getSmallestString(String s) {\\n StringBuilder sb = new StringBuilder();\\n // 是否已发生一次交互\\n boolean…","guid":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap//solution/3216-jiao-huan-hou-zi-dian-xu-zui-xiao-d-5z6j","author":"fa-xing-bu-luan","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-15T04:21:05.408Z","media":[{"url":"https://pic.leetcode.cn/1721017233-urkdsM-%E6%8D%95%E8%8E%B7.JPG","type":"photo","width":706,"height":433,"blurhash":"L25Oc{rGtkxu?]Q:tjofu1MLpGWU"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"暴力枚举 记忆化搜索dp","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/xiao-bai-bao-li-mei-ju-dp-by-tt-forever-81pxs","content":"\\n\\nProblem: 100361. 切蛋糕的最小总开销 I
\\n
[TOC]
\\n###C++
\\nclass Solution {\\npublic:\\n int minimumCost(int m, int n, vector<int>& h, vector<int>& v) {\\n int memo[m+1][n+1][m+1][n+1];\\n memset(memo, 0x3f, sizeof(memo));\\n int INF = memo[0][0][0][0];\\n function< int (int,int,int,int)> dfs = [&](int x1, int y1, int x2, int y2) -> int {\\n if(x1 == x2 - 1 && y1 == y2 - 1) return 0;\\n int &ans = memo[x1][y1][x2][y2];\\n if( ans != INF) return ans;\\n\\n //x dir\\n for (int i = x1 + 1; i < x2; ++i) {\\n int cur = h[i - 1];\\n cur += dfs(x1,y1, i, y2);\\n cur += dfs(i,y1, x2, y2);\\n ans = min(ans, cur);\\n }\\n //y dir\\n for (int i = y1 + 1; i < y2; ++i) {\\n int cur = v[i - 1];\\n cur += dfs(x1,y1, x2, i);\\n cur += dfs(x1,i, x2, y2);\\n ans = min(ans, cur);\\n }\\n return ans;\\n };\\n return dfs(0, 0, m, n);\\n }\\n};\\n\\n
\\n\\n\\nProblem: 100352. 交换后字典序最小的字符串
\\n
[TOC]
\\n字符遍历比较
\\n执行用时分布0ms击败100.00%;消耗内存分布7.77MB击败100.00%
\\n###C
\\nchar* getSmallestString(char* s) {\\n for (char * c = s + 1; * c; ++ c)\\n if (((* (c - 1) ^ * c) & 1) == 0 && * (c - 1) > * c) {\\n * (c - 1) ^= * c, *c ^= * (c - 1), * (c - 1) ^= * c;\\n break;\\n }\\n return s;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def getSmallestString(self, s: str) -> str:\\n s = list(s)\\n for i, (x, y) in enumerate(pairwise(s)):\\n if (ord(x) ^ ord(y)) & 1 == 0 and x > y:\\n s[i], s[i + 1] = s[i + 1], s[i]\\n break\\n return \'\'.join(s)\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100352. 交换后字典序最小的字符串 [TOC]\\n\\n字符遍历比较\\n\\n执行用时分布0ms击败100.00%;消耗内存分布7.77MB击败100.00%\\n\\n###C\\n\\nchar* getSmallestString(char* s) {\\n for (char * c = s + 1; * c; ++ c)\\n if (((* (c - 1) ^ * c) & 1) == 0 && * (c - 1) > * c) {\\n * (c - 1) ^= * c, *c ^= * (c - 1), * (c…","guid":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap//solution/zi-fu-bi-jiao-by-admiring-meninskyuli-jb5i","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T07:35:15.596Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(m^2*n^2*(m+n)) 二维棋盘分割动态规划问题","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/om2n2mn-er-wei-qi-pan-fen-ge-dong-tai-gu-r5en","content":"O(m^2*n^2*(m+n)) 二维棋盘分割动态规划问题","description":"O(m^2*n^2*(m+n)) 二维棋盘分割动态规划问题","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/om2n2mn-er-wei-qi-pan-fen-ge-dong-tai-gu-r5en","author":"981377660LMT","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T04:25:37.077Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 + 记忆化搜索","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/tan-xin-ji-yi-hua-sou-suo-by-mipha-2022-dgyi","content":"\\n\\nProblem: 100361. 切蛋糕的最小总开销 I
\\n
[TOC]
\\n对于两个数组horizontalCut
和verticalCut
简称h
和v
,若v
数组已经切了j
次,则当切h[i]
刀时,cost
为h[i] * (j+1)
。
很明显,要使总cost
最小,,对于两个数组,cost
花费越大的那一行或者那一列,应该优先切除,因此先从大到小排序预处理。
horizontalCut.sort(reverse=True)\\nverticalCut.sort(reverse=True)\\n
\\ndfs(i,j)
代表两个数组切分别切到第i
刀与第j
刀的最小cost
。
@cache\\n def dfs(i,j):\\n if i == m and j == n:\\n return 0\\n \\n # 只能切 j\\n if i == m:\\n return dfs(i,j+1) + verticalCut[j] * (i+1)\\n \\n # 只能切 i\\n if j == n:\\n return dfs(i+1,j) + horizontalCut[i] * (j+1)\\n \\n # 切 i 或 切 j\\n return min(dfs(i,j+1) + verticalCut[j] * (i+1),dfs(i+1,j) + horizontalCut[i] * (j+1))\\n
\\n当然这题是脑筋急转弯,两个数组,谁大切谁,但不会证明。但T3
这个数据量用记忆化搜索肯定能过
更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def minimumCost(self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]) -> int:\\n horizontalCut.sort(reverse=True)\\n verticalCut.sort(reverse=True)\\n m -= 1\\n n -= 1\\n @cache\\n def dfs(i,j):\\n if i == m and j == n:\\n return 0\\n \\n if i == m:\\n return dfs(i,j+1) + verticalCut[j] * (i+1)\\n \\n if j == n:\\n return dfs(i+1,j) + horizontalCut[i] * (j+1)\\n \\n return min(dfs(i,j+1) + verticalCut[j] * (i+1),dfs(i+1,j) + horizontalCut[i] * (j+1))\\n \\n return dfs(0,0)\\n
\\n","description":"Problem: 100361. 切蛋糕的最小总开销 I [TOC]\\n\\n贪心\\n\\n对于两个数组horizontalCut和verticalCut简称h和v,若v数组已经切了j次,则当切h[i]刀时,cost为h[i] * (j+1)。\\n\\n很明显,要使总cost最小,,对于两个数组,cost花费越大的那一行或者那一列,应该优先切除,因此先从大到小排序预处理。\\n\\nhorizontalCut.sort(reverse=True)\\nverticalCut.sort(reverse=True)\\n\\n记忆化搜索\\n\\ndfs(i,j)代表两个数组切分别切到第i刀与第j刀的最小c…","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/tan-xin-ji-yi-hua-sou-suo-by-mipha-2022-dgyi","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T04:15:29.958Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心,附思考题(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap//solution/tan-xin-pythonjavacgo-by-endlesscheng-jqga","content":"只能交换一次。要使字典序最小,需要满足下面两个要求:
\\n细节:字符对应的数字的奇偶性,等于字符的 ASCII 值的奇偶性。
\\n具体请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def getSmallestString(self, s: str) -> str:\\n t = list(s)\\n for i in range(1, len(t)):\\n x, y = t[i - 1], t[i]\\n if x > y and ord(x) % 2 == ord(y) % 2:\\n t[i - 1], t[i] = y, x\\n break\\n return \'\'.join(t)\\n
\\n###java
\\nclass Solution {\\n public String getSmallestString(String s) {\\n char[] t = s.toCharArray();\\n for (int i = 1; i < t.length; i++) {\\n char x = t[i - 1];\\n char y = t[i];\\n if (x > y && x % 2 == y % 2) {\\n t[i - 1] = y;\\n t[i] = x;\\n break;\\n }\\n }\\n return new String(t);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string getSmallestString(string s) {\\n for (int i = 1; i < s.length(); i++) {\\n char x = s[i - 1], y = s[i];\\n if (x > y && x % 2 == y % 2) {\\n swap(s[i - 1], s[i]);\\n break;\\n }\\n }\\n return s;\\n }\\n};\\n
\\n###c
\\nchar* getSmallestString(char* s) {\\n for (int i = 1; s[i]; i++) {\\n char x = s[i - 1], y = s[i];\\n if (x > y && x % 2 == y % 2) {\\n s[i - 1] = y;\\n s[i] = x;\\n break;\\n }\\n }\\n return s;\\n}\\n
\\n###go
\\nfunc getSmallestString(s string) string {\\n t := []byte(s)\\n for i := 1; i < len(t); i++ {\\n x, y := t[i-1], t[i]\\n if x > y && x%2 == y%2 {\\n t[i-1], t[i] = y, x\\n break\\n }\\n }\\n return string(t)\\n}\\n
\\n###js
\\nvar getSmallestString = function(s) {\\n const t = s.split(\'\');\\n for (let i = 1; i < t.length; i++) {\\n const x = t[i - 1], y = t[i];\\n if (x > y && x.charCodeAt(0) % 2 === y.charCodeAt(0) % 2) {\\n t[i - 1] = y;\\n t[i] = x;\\n break;\\n }\\n }\\n return t.join(\'\');\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn get_smallest_string(mut s: String) -> String {\\n unsafe {\\n let t = s.as_bytes_mut();\\n for i in 1..t.len() {\\n let x = t[i - 1];\\n let y = t[i];\\n if x > y && x % 2 == y % 2 {\\n t[i - 1] = y;\\n t[i] = x;\\n break;\\n }\\n }\\n s\\n }\\n }\\n}\\n
\\n如果交换的是任意两位呢?
\\n见 670. 最大交换。
\\n更多相似题目,见 贪心题单 中的「§3.1 字典序最小/最大」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"只能交换一次。要使字典序最小,需要满足下面两个要求: 交换的两个数字,左边必须大于右边,否则交换不会使字典序变小。\\n交换的位置越靠左越好。比如示例 1 的 $45320$,交换 $5$ 和 $3$ 得到 $43520$,而交换更靠右的 $2$ 和 $0$ 得到 $45302$,这比 $43520$ 更大。\\n\\n细节:字符对应的数字的奇偶性,等于字符的 ASCII 值的奇偶性。\\n\\n具体请看 视频讲解,欢迎点赞关注~\\n\\n###py\\n\\nclass Solution:\\n def getSmallestString(self, s: str) -> str:…","guid":"https://leetcode.cn/problems/lexicographically-smallest-string-after-a-swap//solution/tan-xin-pythonjavacgo-by-endlesscheng-jqga","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T04:11:29.795Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图解】逆向思维+最小生成树(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/fei-bao-li-tan-xin-zuo-fa-pythonjavacgo-mae7e","content":"本题和 3219. 切蛋糕的最小总开销 II 是一样的,请看 我的题解。
\\n","description":"本题和 3219. 切蛋糕的最小总开销 II 是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-i//solution/fei-bao-li-tan-xin-zuo-fa-pythonjavacgo-mae7e","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T04:09:21.489Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图解】逆向思维+最小生成树(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-ii//solution/tan-xin-ji-qi-zheng-ming-jiao-huan-lun-z-ivtn","content":"根据最小生成树的 Kruskal 算法,先把边权从小到大排序,然后遍历边,如果边的两个点属于不同连通块,则合并。
\\n在上图中:
\\n一般地,我们用双指针计算答案:
\\n###py
\\nclass Solution:\\n def minimumCost(self, m: int, n: int, horizontalCut: List[int], verticalCut: List[int]) -> int:\\n horizontalCut.sort()\\n verticalCut.sort()\\n ans = i = j = 0\\n for _ in range(m + n - 2):\\n if j == n - 1 or i < m - 1 and horizontalCut[i] < verticalCut[j]:\\n ans += horizontalCut[i] * (n - j) # 上下连边\\n i += 1\\n else:\\n ans += verticalCut[j] * (m - i) # 左右连边\\n j += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long minimumCost(int m, int n, int[] horizontalCut, int[] verticalCut) {\\n Arrays.sort(horizontalCut); // 下面倒序遍历\\n Arrays.sort(verticalCut);\\n long ans = 0;\\n int i = 0;\\n int j = 0;\\n while (i < m - 1 || j < n - 1) {\\n if (j == n - 1 || i < m - 1 && horizontalCut[i] < verticalCut[j]) {\\n ans += horizontalCut[i++] * (n - j); // 上下连边\\n } else {\\n ans += verticalCut[j++] * (m - i); // 左右连边\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long minimumCost(int m, int n, vector<int>& horizontalCut, vector<int>& verticalCut) {\\n ranges::sort(horizontalCut);\\n ranges::sort(verticalCut);\\n long long ans = 0;\\n int i = 0, j = 0;\\n while (i < m - 1 || j < n - 1) {\\n if (j == n - 1 || i < m - 1 && horizontalCut[i] < verticalCut[j]) {\\n ans += horizontalCut[i++] * (n - j); // 上下连边\\n } else {\\n ans += verticalCut[j++] * (m - i); // 左右连边\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nlong long minimumCost(int m, int n, int* horizontalCut, int horizontalSize, int* verticalCut, int verticalSize) {\\n qsort(horizontalCut, horizontalSize, sizeof(int), cmp);\\n qsort(verticalCut, verticalSize, sizeof(int), cmp);\\n long long ans = 0;\\n int i = 0, j = 0;\\n while (i < m - 1 || j < n - 1) {\\n if (j == n - 1 || i < m - 1 && horizontalCut[i] < verticalCut[j]) {\\n ans += horizontalCut[i++] * (n - j); // 上下连边\\n } else {\\n ans += verticalCut[j++] * (m - i); // 左右连边\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc minimumCost(m, n int, horizontalCut, verticalCut []int) (ans int64) {\\nslices.Sort(horizontalCut)\\nslices.Sort(verticalCut)\\ni, j := 0, 0\\nfor range m + n - 2 {\\nif j == n-1 || i < m-1 && horizontalCut[i] < verticalCut[j] {\\nans += int64(horizontalCut[i] * (n - j)) // 上下连边\\ni++\\n} else {\\nans += int64(verticalCut[j] * (m - i)) // 左右连边\\nj++\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar minimumCost = function(m, n, horizontalCut, verticalCut) {\\n horizontalCut.sort((a, b) => a - b);\\n verticalCut.sort((a, b) => a - b);\\n let ans = 0, i = 0, j = 0;\\n while (i < m - 1 || j < n - 1) {\\n if (j === n - 1 || i < m - 1 && horizontalCut[i] < verticalCut[j]) {\\n ans += horizontalCut[i++] * (n - j); // 上下连边\\n } else {\\n ans += verticalCut[j++] * (m - i); // 左右连边\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_cost(m: i32, n: i32, mut horizontal_cut: Vec<i32>, mut vertical_cut: Vec<i32>) -> i64 {\\n let m = m as usize;\\n let n = n as usize;\\n horizontal_cut.sort_unstable();\\n vertical_cut.sort_unstable();\\n let mut ans = 0;\\n let mut i = 0;\\n let mut j = 0;\\n for _ in 0..m + n - 2 {\\n if j == n - 1 || i < m - 1 && horizontal_cut[i] < vertical_cut[j] {\\n ans += (horizontal_cut[i] * (n - j) as i32) as i64; // 上下连边\\n i += 1;\\n } else {\\n ans += (vertical_cut[j] * (m - i) as i32) as i64; // 左右连边\\n j += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据最小生成树的 Kruskal 算法,先把边权从小到大排序,然后遍历边,如果边的两个点属于不同连通块,则合并。 在上图中:\\n\\n由于 $1$ 最小,把第一、二排的节点上下连边,也就是把 $3$ 条边权为 $1$ 的边加入生成树。\\n对于 $3$,由于第一、二排的节点已经上下连边,所以只需把 $2$ 条边权为 $3$ 的边加入生成树。\\n$5$ 同理,把 $2$ 条边权为 $5$ 的边加入生成树。\\n最后,对于 $7$,此时只剩下两个连通块,只需把 $1$ 条边权为 $7$ 的边加入生成树。\\n\\n一般地,我们用双指针计算答案:\\n\\n从小到大排序两个数组。初始化…","guid":"https://leetcode.cn/problems/minimum-cost-for-cutting-cake-ii//solution/tan-xin-ji-qi-zheng-ming-jiao-huan-lun-z-ivtn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-14T04:07:56.316Z","media":[{"url":"https://pic.leetcode.cn/1734769130-IchLwc-lc3219-c.png","type":"photo","width":2313,"height":3929,"blurhash":"LDSPX_xu%M~q?bt7WBay%MofIURi"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"logtrick","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-ii//solution/logtrick-by-crazy-mendelcaf-btlo","content":"logtrick是我从灵神视频中学习到的,此文章介绍logtrick用法与实践,以及灵神视频中未提到的,我本人总结出来的小技巧
\\nlogtrick通常用于求 子数组(gcd,lcm,&,|)后的max或者min或者计数问题
\\nlogtrick主要是解决子数组问题,所以在此我们先引出子数组问题
\\n就是求 f(l,r)的所有操作的(最优值 或者 计数值) 1<=l<=r<=n
\\n类似这个公式:
\\n$$
\\n\\\\sum_{l=1}^{n} \\\\sum_{r=l}^{n} f(l, r)
\\n$$
\\n但不一定是求和,应是题目要求的求(最优值 或者 计数值)等
**这个f即函数 是求区间[l,r]完成题目特定操作后 得到的特定值 **
\\n比如说:f(l,r),f函数是求min的 那么就是求[l,r]区间中最小的一个值
\\n计算子数组,有一种可行思路是把注意力放在右端点,通过不断移动右端点,在移动的时候利用前面的统计结果算出移动右端点后的结果,从而得出所有子区间的答案。
\\n这种方法经典套路有 前缀和,logtrick
\\n今天我们重点是logtrick
\\nlogtrick是基于O(n^2)的计算子数组问题 利用(|,&,lcm,gcd)等性质 优化的一种套路亦或者是算法
\\n先介绍o(n^2)的算法
\\n###c++
\\nfor(int i = 0; i < n;i++){\\nfor(int j = i - 1;j >= 0;j--){\\na[j] ?= a[i]; //此问号可以为gcd lcm | &\\n}\\n}\\n
\\n这个算法挺清晰的我们研究一下过程,然后考虑优化
\\n比如说 求a的子数组按位&等于k的子数组的总和
\\n给出数据与对应二进制:
\\n###c++
\\n 7 3 4 1\\n111 011 100 001 \\n
\\n操作流程:
\\n第一次:\\n 7 3 4 1\\n 111 011 100 001\\n\\n存储:a[0]\\n111\\n\\n第二次:\\n 7 3 4 1\\n 111 011 100 001\\n\\n存储: a[0]&a[1] a[1]\\n011 011\\n\\n第三次:\\n 7 3 4 1\\n 111 011 100 001\\n\\n存储: a[0]&a[1]&a[2] a[1]&a[2] a[2]\\n000 000 100\\n\\n第四次:\\n 7 3 4 1\\n 111 011 100 001\\n\\n存储: a[0]&a[1]&a[2]&a[3] a[1]&a[2]&a[3] a[2]&a[3] a[3]\\n000 000 000 001\\n
\\n可以发现这样枚举右端点更新左端点可以保证求出所有的子数组的运算结果
\\n优化:
\\n这是最最最重点的了我们发现第三次到第四次过程中是没有变化的,为什么呢,因为&越多数&的的总结局是越来越小
\\n而&不变就是说明了我这个数已经不足以让当前位置变小,又因为当前位置的前面都是与当前位置&过的,所以不能让当前位置变小也自然不会让前面位置再变小,这点很难理解用灵神的话来讲就是看做是集和的关系,我后面枚举的这个数设为x & 当前这个数设为y然后 y不变小, 意味这 y 一定是x的子集 而y前面的数都&过y 说明都是与y的交集了,与x交就相当于没有交; 很难理解借助图形理解
\\n图形
\\n我们设当前枚举的是x 然后 x&y=y , 然后y前面的数为z z&y设为 t
\\n前面的数组的记录的数t是这样的
\\n{:width=400}
y是x的子集所以加入x长这样
\\n{:width=400}
虚线部分是x,可以看到加入x是不会对y和y前面的t产生影响的 因为 x与y与z的交集还是t
\\n所以当我们遇到 a[j] & a[i] == a[j] 时 直接break ,不必管前面的数
\\n就因为这个小优化我们的时间复杂度可以从
\\nO(n^2) 变为 O(nlogU)
\\nu为数组中最大值
\\n因为我们一个数不断缩小就加强了成为x的子集的可能性,就更可能的触及 a[j] & a[i] == a[j] 这个条件,而最坏也是log级别的缩小,此为logtrick精髓所在
\\n除了& 还有| gcd lcm 殊途同归读者自己思考
\\n###c++
\\nfor(int i = 1; i <= n;i++){\\n int x = nums[i];\\n //这里可以进行一些操作\\n for(int j = i - 1;j >= 1;j--){\\n if((nums[j] 操作 nums[i]) = ?){\\n break;\\n }\\n nums[j] 操作= nums[i];\\n }\\n 然后进行二分或者一些操作\\n}\\n
\\nlogtrick 配合 map使用有出其不意的效果,甚至说更加无脑与方便,下面题目有讲
\\n源于灵神总结的题单
\\n3097. 或值至少为 K 的最短子数组 II - 力扣(LeetCode)
\\n思路: 因为如果 nums[j] | nums[i] == nums[i] 就代表前面都不会再变大,所以直接结束,很经典的板子题
\\n###c++
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int mi = 0x3f3f3f3f;\\n int n = nums.size();\\n for(int i = 0; i < n;i++){\\n if(nums[i] >= k){\\n return 1;\\n }\\n for(int j= i - 1; j >= 0 && (nums[j] | nums[i]) != nums[j];j--){\\n nums[j] |= nums[i];\\n if(nums[j]>=k){\\n mi = min(mi,i-j+1);\\n }\\n }\\n }\\n return mi==0x3f3f3f3f ? -1 : mi;\\n }\\n};\\n
\\n2411. 按位或最大的最小子数组长度 - 力扣(LeetCode)
\\n思路:和上题类似
\\n###C++
\\nclass Solution {\\npublic:\\n vector<int> smallestSubarrays(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> ans(n,1);\\n for(int i = 0; i < n;i++){\\n int x= nums[i];\\n for(int j = i - 1 ;j >= 0 && (nums[j] | nums[i]) != nums[j];j--) {\\n nums[j]|=nums[i],ans[j] = i - j + 1;\\n }\\n }\\n return ans;\\n\\n }\\n};\\n
\\n3209. 子数组按位与值为 K 的数目 - 力扣(LeetCode)
\\n思路: 该题更快做法应该是二分,但是这里我们用无脑的map计数直接无视二分,让问题难度直线下降
\\n###c++
\\nclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, int k) {\\n long long ans = 0;\\n int n = nums.size();\\n map<int,int> h;\\n for(int i = 0;i<n;i++){\\n int x = nums[i];\\n h[x]++;\\n for(int j = i - 1; j >= 0 && (nums[j] & nums[i]) != nums[j];j--){\\n h[nums[j]]--;\\n nums[j]&=nums[i];\\n h[nums[j]]++;\\n }\\n ans+=h[k];\\n\\n }\\n return ans;\\n }\\n};\\n
\\n1521. 找到最接近目标值的函数值 - 力扣(LeetCode)
\\n思路: 简单板子运用
\\n###c++
\\nclass Solution {\\npublic:\\n int closestToTarget(vector<int>& nums, int target) {\\n int ans = 0x3f3f3f3f;\\n for(int i = 0;i < nums.size();i++){\\n ans = min(ans,abs(nums[i] - target));\\n for(int j = i - 1;j >= 0;j--){\\n if((nums[j]&nums[i]) == nums[j]) break;\\n nums[j]&=nums[i];\\n ans = min(ans,abs(nums[j] - target));\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n思路:计数问题带个map 会好解决很多
\\n###c++
\\nclass Solution {\\npublic:\\n int subarrayBitwiseORs(vector<int>& a) {\\n int n = a.size();\\n map<int,int> h;\\n for(int i = 0;i<n;i++){\\n h[a[i]]++;\\n for(int j = i - 1; j >= 0;j--){\\n if((a[j]|a[i])==a[j]) break;\\n h[a[j]|a[i]]++;\\n a[j]|=a[i];\\n }\\n }\\n return h.size();\\n }\\n};\\n
\\n思路: 运用map然后累加到res中
\\n###c++
\\nconst int N = 200005;\\nint n,m;\\nmap<int,int> res;\\nint a[N];\\nvoid solve(){\\n\\ncin >> n;\\nfor(int i = 1; i <= n;i++){\\ncin >> a[i];\\n}\\nmap<int,int> h;\\nfor(int i = 1; i <= n;i++){\\nh[a[i]]++;\\nfor(int j = i - 1; j >=1;j--){\\nif(__gcd(a[i],a[j]) == a[j]){\\nbreak;\\n}else{\\nh[a[j]]--;\\nif(h[a[j]]==0) h.erase(a[j]);\\na[j] = __gcd(a[i],a[j]);\\nh[a[j]]++;\\n}\\n}\\nfor(auto p:h) res[p.first]+=p.second;\\n}\\nint q;\\ncin >> q;\\nwhile(q--){\\nint t;\\ncin >> t;\\ncout << res[t] <<endl;\\n}\\n\\n}\\n
\\n此题是我做到目前为止 logtrick中最难的题,但是其实也不是很难啦,就是相对别的题目比较难,就是要灵活掌握变形
\\n\\n思路:思维+logtrick,首先我们要清楚要贪心修改数字必定是把数字修改成1,因为这样才不会影响后面的数,然后就是如果修改了前面的子数组的一个元素,前面的子数组就全部没用了,直接map.claer(),然后做logtrick时候j那一维度为不用遍历到前面了
\\n###c++
\\nconst int N = 200005;\\nint n,m;\\nmap<int,int> res;\\nint a[N];\\nvoid solve(){\\n\\ncin >> n;\\nfor(int i = 1; i <= n;i++){\\ncin >> a[i];\\n}\\nmap<int,set<int> > h;\\nint cnt = 0;\\nint lt = 1;\\nfor(int i = 1; i <= n;i++){\\nh[a[i]].insert(i);\\nfor(int j = i - 1;j>=lt;j--){\\nif(__gcd(a[j],a[i])==a[j]){\\nbreak;\\n}\\nh[a[j]].erase(j);\\nif(h[a[j]].size()==0) h.erase(a[j]);\\nh[__gcd(a[j],a[i])].insert(j);\\na[j]=__gcd(a[j],a[i]);\\n}\\n//i-j+1 == k\\nbool f = false;\\nfor(auto &[k,se]:h){\\nif(se.count(i - k + 1)){\\ncnt++;\\nf = true;\\nbreak;\\n}\\n}\\nif(f){\\nmap<int,set<int> > h2;\\nh=h2;\\nh[1].insert(i);\\nlt=i+1;\\n}\\ncout << cnt << \\" \\";\\n}\\n\\n}\\n
\\n最后感谢@灵茶山艾府
\\n基础起源于灵神的教学,题单也是灵神那里找的
我们需要一个数据结构维护可预约的座位:
\\n最小堆完美符合上述要求:
\\n###py
\\nclass SeatManager:\\n def __init__(self, n: int):\\n self.available = list(range(1, n + 1)) # 有序数组无需堆化\\n\\n def reserve(self) -> int:\\n return heappop(self.available)\\n\\n def unreserve(self, seatNumber: int) -> None:\\n heappush(self.available, seatNumber)\\n
\\n###java
\\nclass SeatManager {\\n private final PriorityQueue<Integer> available = new PriorityQueue<>();\\n\\n public SeatManager(int n) {\\n for (int i = 1; i <= n; i++) {\\n available.add(i);\\n }\\n }\\n\\n public int reserve() {\\n return available.poll();\\n }\\n\\n public void unreserve(int seatNumber) {\\n available.add(seatNumber);\\n }\\n}\\n
\\n###cpp
\\nclass SeatManager {\\n priority_queue<int, vector<int>, greater<>> available;\\n\\npublic:\\n SeatManager(int n) {\\n for (int i = 1; i <= n; i++) {\\n available.push(i);\\n }\\n }\\n\\n int reserve() {\\n int seatNumber = available.top();\\n available.pop();\\n return seatNumber;\\n }\\n\\n void unreserve(int seatNumber) {\\n available.push(seatNumber);\\n }\\n};\\n
\\n###go
\\ntype SeatManager struct {\\n sort.IntSlice // 继承 Len, Less, Swap\\n}\\n\\nfunc Constructor(n int) SeatManager {\\n m := SeatManager{make([]int, n)}\\n for i := range m.IntSlice {\\n m.IntSlice[i] = i + 1\\n }\\n // 有序数组无需堆化\\n return m\\n}\\n\\nfunc (m *SeatManager) Reserve() int {\\n return heap.Pop(m).(int)\\n}\\n\\nfunc (m *SeatManager) Unreserve(seatNumber int) {\\n heap.Push(m, seatNumber)\\n}\\n\\nfunc (m *SeatManager) Push(v any) { m.IntSlice = append(m.IntSlice, v.(int)) }\\nfunc (m *SeatManager) Pop() any { a := m.IntSlice; v := a[len(a)-1]; m.IntSlice = a[:len(a)-1]; return v }\\n
\\n###js
\\nvar SeatManager = function(n) {\\n this.available = new MinPriorityQueue();\\n for (let i = 1; i <= n; i++) {\\n this.available.enqueue(i);\\n }\\n};\\n\\nSeatManager.prototype.reserve = function() {\\n return this.available.dequeue().element;\\n};\\n\\nSeatManager.prototype.unreserve = function(seatNumber) {\\n this.available.enqueue(seatNumber);\\n};\\n
\\n###rust
\\nuse std::collections::BinaryHeap;\\n\\nstruct SeatManager {\\n available: BinaryHeap<i32>,\\n}\\n\\nimpl SeatManager {\\n fn new(n: i32) -> Self {\\n let mut available = BinaryHeap::new();\\n for i in 1..=n {\\n available.push(-i); // 取相反数,变成最小堆\\n }\\n Self { available }\\n }\\n\\n fn reserve(&mut self) -> i32 {\\n -self.available.pop().unwrap()\\n }\\n\\n fn unreserve(&mut self, seat_number: i32) {\\n self.available.push(-seat_number);\\n }\\n}\\n
\\n方法一的时空复杂度均和 $n$ 有关,如果把数据范围改成 $n\\\\le 10^9$,就会超时/爆内存。这种情况下要怎么做?
\\n想象有一个空房间,一开始没有椅子。
\\n如果有人进入了房间($\\\\texttt{reserve}$),我们可以添加一把新的椅子给人坐(如果没有空出来的椅子)。
\\n如果有人离开了椅子($\\\\texttt{unreserve}$),后面来的人不需要新的椅子,直接坐空出来的椅子就行。直到所有椅子都被坐满,此时必须要添加一把新的椅子给人坐。
\\n用一个变量 $\\\\textit{seats}$ 表示目前房间内有多少把椅子,初始值为 $0$。用一个最小堆 $\\\\textit{available}$ 维护空出来的椅子编号,初始为空。
\\n###py
\\nclass SeatManager:\\n def __init__(self, _: int):\\n self.seats = 0 # 一开始没有椅子\\n self.available = []\\n\\n def reserve(self) -> int:\\n if self.available: # 有空出来的椅子\\n return heappop(self.available) # 坐编号最小的\\n self.seats += 1 # 添加一把新的椅子\\n return self.seats\\n\\n def unreserve(self, seatNumber: int) -> None:\\n heappush(self.available, seatNumber) # 有人离开了椅子\\n
\\n###java
\\nclass SeatManager {\\n private final PriorityQueue<Integer> available = new PriorityQueue<>();\\n private int seats;\\n\\n public SeatManager(int n) {\\n }\\n\\n public int reserve() {\\n if (!available.isEmpty()) { // 有空出来的椅子\\n return available.poll(); // 坐编号最小的\\n }\\n return ++seats; // 添加一把新的椅子\\n }\\n\\n public void unreserve(int seatNumber) {\\n available.add(seatNumber); // 有人离开了椅子\\n }\\n}\\n
\\n###cpp
\\nclass SeatManager {\\n int seats = 0;\\n priority_queue<int, vector<int>, greater<>> available;\\n\\npublic:\\n SeatManager(int) {}\\n\\n int reserve() {\\n if (!available.empty()) { // 有空出来的椅子\\n int seatNumber = available.top(); // 坐编号最小的\\n available.pop();\\n return seatNumber;\\n }\\n return ++seats; // 添加一把新的椅子\\n }\\n\\n void unreserve(int seatNumber) {\\n available.push(seatNumber); // 有人离开了椅子\\n }\\n};\\n
\\n###go
\\ntype SeatManager struct {\\n sort.IntSlice // 继承 Len, Less, Swap\\n seats int\\n}\\n\\nfunc Constructor(int) SeatManager {\\n return SeatManager{}\\n}\\n\\nfunc (m *SeatManager) Reserve() int {\\n if len(m.IntSlice) > 0 { // 有空出来的椅子\\n return heap.Pop(m).(int) // 坐编号最小的\\n }\\n m.seats += 1 // 添加一把新的椅子\\n return m.seats\\n}\\n\\nfunc (m *SeatManager) Unreserve(seatNumber int) {\\n heap.Push(m, seatNumber) // 有人离开了椅子\\n}\\n\\nfunc (m *SeatManager) Push(v any) { m.IntSlice = append(m.IntSlice, v.(int)) }\\nfunc (m *SeatManager) Pop() any { a := m.IntSlice; v := a[len(a)-1]; m.IntSlice = a[:len(a)-1]; return v }\\n
\\n###js
\\nvar SeatManager = function(_) {\\n this.seats = 0; // 一开始没有椅子\\n this.available = new MinPriorityQueue();\\n};\\n\\nSeatManager.prototype.reserve = function() {\\n if (!this.available.isEmpty()) { // 有空出来的椅子\\n return this.available.dequeue().element; // 坐编号最小的\\n }\\n return ++this.seats; // 添加一把新的椅子\\n};\\n\\nSeatManager.prototype.unreserve = function(seatNumber) {\\n this.available.enqueue(seatNumber); // 有人离开了椅子\\n};\\n
\\n###rust
\\nuse std::collections::BinaryHeap;\\n\\nstruct SeatManager {\\n seats: i32,\\n available: BinaryHeap<i32>,\\n}\\n\\nimpl SeatManager {\\n fn new(_: i32) -> Self {\\n Self { seats: 0, available: BinaryHeap::new() }\\n }\\n\\n fn reserve(&mut self) -> i32 {\\n if let Some(seat) = self.available.pop() { // 有空出来的椅子\\n -seat // 坐编号最小的\\n } else {\\n self.seats += 1; // 添加一把新的椅子\\n self.seats\\n }\\n }\\n\\n fn unreserve(&mut self, seat_number: i32) {\\n self.available.push(-seat_number); // 有人离开了椅子\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:维护可预约的座位 我们需要一个数据结构维护可预约的座位:\\n\\n$\\\\texttt{reserve}$:查找并删除最小值。\\n$\\\\texttt{unreserve}$:添加元素。\\n\\n最小堆完美符合上述要求:\\n\\n初始化:把 $1,2,3,\\\\cdots,n$ 全部入堆。\\n$\\\\texttt{reserve}$:弹出并返回堆顶。\\n$\\\\texttt{unreserve}$:把 $\\\\textit{seatNumber}$ 入堆。\\n\\n###py\\n\\nclass SeatManager:\\n def __init__(self, n: int…","guid":"https://leetcode.cn/problems/seat-reservation-manager//solution/liang-chong-fang-fa-wei-hu-ke-yu-yue-de-tmub8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-11T02:46:32.138Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举右,维护左,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/best-sightseeing-pair//solution/mei-ju-you-wei-hu-zuo-jian-ji-xie-fa-pyt-x385","content":"下文把 $\\\\textit{values}$ 简记为 $v$。
\\n得分
\\n$$
\\nv_i + v_j + i - j
\\n$$
可以变形为
\\n$$
\\n(v_i + i) + (v_j - j)
\\n$$
我们可以枚举 $j$,同时维护在 $j$ 左边的 $v_i + i$ 的最大值 $\\\\textit{mx}$,然后用 $\\\\textit{mx} + v_j - j$ 更新答案的最大值。
\\n示例 1 的计算过程如下:
\\n$j$ | \\n$v_j$ | \\n$v_j + j$ | \\n$\\\\textit{mx}$ | \\n$v_j - j$ | \\n$\\\\textit{mx} + v_j - j$ | \\n
---|---|---|---|---|---|
$0$ | \\n$8$ | \\n$8$ | \\n$-\\\\infty$ | \\n$8$ | \\n$-\\\\infty$ | \\n
$1$ | \\n$1$ | \\n$2$ | \\n$8$ | \\n$0$ | \\n$8$ | \\n
$2$ | \\n$5$ | \\n$7$ | \\n$8$ | \\n$3$ | \\n$11$ | \\n
$3$ | \\n$2$ | \\n$5$ | \\n$8$ | \\n$-1$ | \\n$7$ | \\n
$4$ | \\n$6$ | \\n$10$ | \\n$8$ | \\n$2$ | \\n$10$ | \\n
$j=2$ 时可以计算出最大的得分 $11$。
\\n注意 $j=0$ 时,由于 $j$ 左边没有数,所以 $\\\\textit{mx}$ 初始化成 $-\\\\infty$。
\\n不过,把 $\\\\textit{mx}$ 初始化成 $0$ 也是可以的。由于题目保证数组中至少有两个数且 $v_i \\\\ge 1$,所以答案至少为 $v_0 + v_1 + 0 - 1 \\\\ge v_0$。把 $\\\\textit{mx}$ 初始化成 $0$ 会导致答案一开始更新为 $v_0$,由于我们不会计算出比 $v_0$ 还小的答案,所以把 $\\\\textit{mx}$ 初始化成 $0$ 在本题数据范围下是正确的。
\\n也可以把 $\\\\textit{mx}$ 初始化成 $v_0 + 0 = v_0$,然后从 $j=1$ 开始遍历数组。
\\n###py
\\nclass Solution:\\n def maxScoreSightseeingPair(self, values: List[int]) -> int:\\n ans = mx = 0 # mx 表示 j 左边的 values[i] + i 的最大值\\n for j, v in enumerate(values):\\n ans = max(ans, mx + v - j)\\n mx = max(mx, v + j)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int maxScoreSightseeingPair(int[] values) {\\n int ans = 0;\\n int mx = values[0]; // j 左边的 values[i] + i 的最大值\\n for (int j = 1; j < values.length; j++) {\\n ans = Math.max(ans, mx + values[j] - j);\\n mx = Math.max(mx, values[j] + j);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxScoreSightseeingPair(vector<int>& values) {\\n int ans = 0, mx = values[0]; // mx 表示 j 左边的 values[i] + i 的最大值\\n for (int j = 1; j < values.size(); j++) {\\n ans = max(ans, mx + values[j] - j);\\n mx = max(mx, values[j] + j);\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint maxScoreSightseeingPair(int* values, int valuesSize) {\\n int ans = 0, mx = values[0]; // mx 表示 j 左边的 values[i] + i 的最大值\\n for (int j = 1; j < valuesSize; j++) {\\n ans = MAX(ans, mx + values[j] - j);\\n mx = MAX(mx, values[j] + j);\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc maxScoreSightseeingPair(values []int) (ans int) {\\n mx := 0 // j 左边的 values[i] + i 的最大值\\n for j, v := range values {\\n ans = max(ans, mx+v-j)\\n mx = max(mx, v+j)\\n }\\n return\\n}\\n
\\n###js
\\nvar maxScoreSightseeingPair = function(values) {\\n let ans = 0, mx = values[0]; // mx 表示 j 左边的 values[i] + i 的最大值\\n for (let j = 1; j < values.length; j++) {\\n ans = Math.max(ans, mx + values[j] - j);\\n mx = Math.max(mx, values[j] + j);\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn max_score_sightseeing_pair(values: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut mx = 0; // j 左边的 values[i] + i 的最大值\\n for (j, &v) in values.iter().enumerate() {\\n ans = ans.max(mx + v - j as i32);\\n mx = mx.max(v + j as i32);\\n }\\n ans\\n }\\n}\\n
\\n所有 $v_i + v_j + i - j$ 之和是多少?其中 $i<j$。
\\n欢迎在评论区分享你的思路/代码。
\\n见 数据结构题单 的第零章:枚举右,维护左。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"下文把 $\\\\textit{values}$ 简记为 $v$。 得分\\n\\n$$\\n v_i + v_j + i - j\\n $$\\n\\n可以变形为\\n\\n$$\\n (v_i + i) + (v_j - j)\\n $$\\n\\n我们可以枚举 $j$,同时维护在 $j$ 左边的 $v_i + i$ 的最大值 $\\\\textit{mx}$,然后用 $\\\\textit{mx} + v_j - j$ 更新答案的最大值。\\n\\n示例 1 的计算过程如下:\\n\\n$j$\\t $v_j$\\t $v_j + j$\\t $\\\\textit{mx}$\\t $v_j - j$\\t $\\\\textit{mx} + v_j - j$ $0$\\t $8$…","guid":"https://leetcode.cn/problems/best-sightseeing-pair//solution/mei-ju-you-wei-hu-zuo-jian-ji-xie-fa-pyt-x385","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-09T08:53:56.189Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"菜鸟回溯法解法(代码随想录c++风格)","url":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros//solution/cai-niao-hui-su-fa-jie-fa-dai-ma-sui-xia-tihv","content":"菜鸟回溯法解法(代码随想录c++风格)","description":"菜鸟回溯法解法(代码随想录c++风格)","guid":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros//solution/cai-niao-hui-su-fa-jie-fa-dai-ma-sui-xia-tihv","author":"ao-li-gei-shao-nian-f","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-07T06:10:29.011Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3211. 生成不含相邻零的二进制字符串","url":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros//solution/3211-sheng-cheng-bu-han-xiang-lin-ling-d-magb","content":"可以使用回溯得到所有长度为 $n$ 的有效字符串。
\\n字符串的每个位置都需要填入 $0$ 或 $1$。对于 $0 \\\\le i < n$,如果 $i = 0$ 或字符串的下标 $i - 1$ 处填入 $1$,则字符串的下标 $i$ 处可以填入 $0$ 或 $1$,否则字符串的下标 $i$ 处只能填入 $1$。
\\n上述操作可以确保得到有效字符串,当得到一个长度为 $n$ 的有效字符串时,将该字符串添加到结果列表中。回溯结束之后即可得到所有长度为 $n$ 的有效字符串。
\\n###Java
\\nclass Solution {\\n List<String> binaryStrings = new ArrayList<String>();\\n StringBuffer sb = new StringBuffer();\\n int n;\\n\\n public List<String> validStrings(int n) {\\n this.n = n;\\n backtrack(0);\\n return binaryStrings;\\n }\\n\\n public void backtrack(int index) {\\n if (index == n) {\\n binaryStrings.add(sb.toString());\\n } else {\\n int prev = index > 0 ? sb.charAt(index - 1) - \'0\' : -1;\\n for (int i = 0; i <= 1; i++) {\\n if (i != 0 || prev != 0) {\\n sb.append(i);\\n backtrack(index + 1);\\n sb.setLength(index);\\n }\\n }\\n }\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n IList<string> binaryStrings = new List<string>();\\n StringBuilder sb = new StringBuilder();\\n int n;\\n\\n public IList<string> ValidStrings(int n) {\\n this.n = n;\\n Backtrack(0);\\n return binaryStrings;\\n }\\n\\n public void Backtrack(int index) {\\n if (index == n) {\\n binaryStrings.Add(sb.ToString());\\n } else {\\n int prev = index > 0 ? sb[index - 1] - \'0\' : -1;\\n for (int i = 0; i <= 1; i++) {\\n if (i != 0 || prev != 0) {\\n sb.Append(i);\\n Backtrack(index + 1);\\n sb.Length--;\\n }\\n }\\n }\\n }\\n}\\n
\\n时间复杂度:$O(n \\\\times 2^n)$,其中 $n$ 是给定的有效字符串长度。有效字符串个数不超过 $2^n$,每个有效字符串添加到答案中的时间是 $O(n)$,因此时间复杂度是 $O(n \\\\times 2^n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是给定的有效字符串长度。存储当前有效字符串的临时空间和递归调用栈空间是 $O(n)$。注意返回值不计入空间复杂度。
\\n长度为 $n$ 的二进制字符串对应的整数的范围是 $[0, 2^n - 1]$,可以判断范围 $[0, 2^n - 1]$ 中的每个整数的 $n$ 位二进制表示是否满足任意两个相邻位中至少有一个 $1$。
\\n对于整数 $i$,判断其 $n$ 位二进制表示是否满足任意两个相邻位中至少有一个 $1$ 的方法如下。
\\n计算 $i$ 的二进制表示取反后的数与 $2^n - 1$ 的按位与运算的结果 $\\\\textit{currBits}$,则 $\\\\textit{currBits}$ 为 $i$ 的最低 $n$ 位取反后的结果。
\\n计算 $\\\\textit{currBits} ~&~ (\\\\textit{currBits} >> 1)$ 的值,如果该值为 $0$ 则 $i$ 的 $n$ 位二进制表示满足任意两个相邻位中至少有一个 $1$。
\\n如果整数 $i$ 的 $n$ 位二进制表示满足任意两个相邻位中至少有一个 $1$,则将整数 $i$ 转换成 $n$ 位二进制表示并添加到结果中。
\\n遍历结束之后,即可得到所有长度为 $n$ 的有效字符串。
\\n###Java
\\nclass Solution {\\n public List<String> validStrings(int n) {\\n List<String> binaryStrings = new ArrayList<String>();\\n int total = 1 << n;\\n int mask = total - 1;\\n for (int i = 0; i < total; i++) {\\n int currBits = ~i & mask;\\n if ((currBits & (currBits >> 1)) == 0) {\\n binaryStrings.add(getBinaryString(i, n));\\n }\\n }\\n return binaryStrings;\\n }\\n\\n public String getBinaryString(int num, int n) {\\n char[] arr = new char[n];\\n for (int i = n - 1; i >= 0; i--, num /= 2) {\\n arr[i] = (char) (\'0\' + num % 2);\\n }\\n return new String(arr);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public IList<string> ValidStrings(int n) {\\n IList<string> binaryStrings = new List<string>();\\n int total = 1 << n;\\n int mask = total - 1;\\n for (int i = 0; i < total; i++) {\\n int currBits = ~i & mask;\\n if ((currBits & (currBits >> 1)) == 0) {\\n binaryStrings.Add(GetBinaryString(i, n));\\n }\\n }\\n return binaryStrings;\\n }\\n\\n public string GetBinaryString(int num, int n) {\\n char[] arr = new char[n];\\n for (int i = n - 1; i >= 0; i--, num /= 2) {\\n arr[i] = (char) (\'0\' + num % 2);\\n }\\n return new string(arr);\\n }\\n}\\n
\\n时间复杂度:$O(n \\\\times 2^n)$,其中 $n$ 是给定的有效字符串长度。有效字符串个数不超过 $2^n$,每个有效字符串添加到答案中的时间是 $O(n)$,因此时间复杂度是 $O(n \\\\times 2^n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是给定的有效字符串长度。存储当前有效字符串的临时空间是 $O(n)$。注意返回值不计入空间复杂度。
\\n对于一个有效的字符串,相邻两个字符,必有一个为1。
\\n###c++
\\nvector<string> validStrings(int n) {\\n vector<string> res;\\n function<void(string &)> dfs = [&](string &s) {\\n if (s.size() == n) {\\n res.push_back(s);\\n return;\\n }\\n s.push_back(\'1\'), dfs(s);\\n s.pop_back();\\n if (s.back() == \'1\') {\\n s.push_back(\'0\'), dfs(s);\\n s.pop_back();\\n }\\n };\\n string s = \\"0\\";\\n dfs(s);\\n s = \\"1\\";\\n dfs(s);\\n return res;\\n}\\n
\\n","description":"对于一个有效的字符串,相邻两个字符,必有一个为1。 ###c++\\n\\nvector生成所有长为 $n$ 的二进制字符串,要求字符串不包含 $\\\\texttt{\\"00\\"}$。
\\n类似 17. 电话号码的字母组合,用递归生成所有长为 $n$ 的符合要求的字符串。如果你没有写过这题,可以看 视频讲解【基础算法精讲 14】。
\\n本题枚举字符串的位置 $i$ 填 $1$ 还是 $0$:
\\n从 $i=0$ 开始递归,到 $i=n$ 结束。
\\n⚠注意:由于字符串长度固定为 $n$,本题不需要恢复现场,直接覆盖之前填的数据就行。
\\n###py
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n ans = []\\n path = [\'\'] * n\\n\\n def dfs(i: int) -> None:\\n if i == n:\\n ans.append(\'\'.join(path)) # 注意 join 需要 O(n) 时间\\n return\\n\\n # 填 1\\n path[i] = \'1\'\\n dfs(i + 1)\\n\\n # 填 0\\n if i == 0 or path[i - 1] == \'1\':\\n path[i] = \'0\' # 直接覆盖\\n dfs(i + 1)\\n\\n dfs(0)\\n return ans\\n
\\n###java
\\nclass Solution {\\n public List<String> validStrings(int n) {\\n List<String> ans = new ArrayList<>();\\n char[] path = new char[n];\\n dfs(0, n, path, ans);\\n return ans;\\n }\\n\\n private void dfs(int i, int n, char[] path, List<String> ans) {\\n if (i == n) {\\n ans.add(new String(path));\\n return;\\n }\\n\\n // 填 1\\n path[i] = \'1\';\\n dfs(i + 1, n, path, ans);\\n\\n // 填 0\\n if (i == 0 || path[i - 1] == \'1\') {\\n path[i] = \'0\'; // 直接覆盖\\n dfs(i + 1, n, path, ans);\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<string> validStrings(int n) {\\n vector<string> ans;\\n string path(n, 0);\\n\\n auto dfs = [&](auto&& dfs, int i) -> void {\\n if (i == n) {\\n ans.push_back(path); // 注意这里复制了一份 path,需要 O(n) 时间\\n return;\\n }\\n\\n // 填 1\\n path[i] = \'1\';\\n dfs(dfs, i + 1);\\n\\n // 填 0\\n if (i == 0 || path[i - 1] == \'1\') {\\n path[i] = \'0\'; // 直接覆盖\\n dfs(dfs, i + 1);\\n }\\n };\\n\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc validStrings(n int) (ans []string) {\\n path := make([]byte, n)\\n var dfs func(i int)\\n dfs = func(i int) {\\n if i == n {\\n ans = append(ans, string(path)) // 注意 string(path) 需要 O(n) 时间\\n return\\n }\\n\\n // 填 1\\n path[i] = \'1\'\\n dfs(i + 1)\\n\\n // 填 0\\n if i == 0 || path[i-1] == \'1\' {\\n path[i] = \'0\' // 直接覆盖\\n dfs(i + 1)\\n }\\n }\\n dfs(0)\\n return\\n}\\n
\\n###js
\\nvar validStrings = function(n) {\\n const ans = [];\\n const path = Array(n);\\n\\n function dfs(i) {\\n if (i === n) {\\n ans.push(path.join(\'\')); // 注意 join 需要 O(n) 时间\\n return;\\n }\\n\\n // 填 1\\n path[i] = \'1\';\\n dfs(i + 1);\\n\\n // 填 0\\n if (i === 0 || path[i - 1] === \'1\') {\\n path[i] = \'0\'; // 直接覆盖\\n dfs(i + 1);\\n }\\n }\\n\\n dfs(0);\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn valid_strings(n: i32) -> Vec<String> {\\n fn dfs(i: usize, path: &mut Vec<char>, ans: &mut Vec<String>) {\\n if i == path.len() {\\n ans.push(path.iter().collect());\\n return;\\n }\\n\\n // 填 1\\n path[i] = \'1\';\\n dfs(i + 1, path, ans);\\n\\n // 填 0\\n if i == 0 || path[i - 1] == \'1\' {\\n path[i] = \'0\'; // 直接覆盖\\n dfs(i + 1, path, ans);\\n }\\n }\\n\\n let mut ans = vec![];\\n let mut path = vec![\'\\\\0\'; n as usize];\\n dfs(0, &mut path, &mut ans);\\n ans\\n }\\n}\\n
\\n枚举 $[0,2^n-1]$ 中的 $i$,如果 $i$ 的长为 $n$ 的二进制中,没有相邻的 $0$,那么将其二进制字符串加入答案。
\\n怎么判断二进制中是否有相邻的 $0$?
\\n我们可以把 $i$ 取反(保留低 $n$ 位),记作 $x$。问题变成:判断 $x$ 中是否有相邻的 $1$。
\\n需要一个一个地遍历二进制数 $x$ 的每一位吗?
\\n不需要,我们可以用 x & (x >> 1)
来判断,如果这个值不为零,则说明 $x$ 中有相邻的 $1$,反之没有。例如 $x=110$,右移一位得 $011$,可以发现这两个二进制数的次低位都是 $1$,所以计算 AND 的结果必然不为 $0$。
代码实现时,可以直接枚举取反后的值 $x$,如果 x & (x >> 1)
等于 $0$,就把 $x$ 取反后的值(也就是 $i$)加入答案。
如何取反?
\\nmask = (1 << n) - 1
。x ^ mask
,由于 $0$ 和 $1$ 异或后变成了 $1$,$1$ 和 $1$ 异或后变成了 $0$,所以 $x$ 的低 $n$ 位都取反了。具体请看 视频讲解 第二题,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n ans = []\\n mask = (1 << n) - 1\\n for x in range(1 << n):\\n if (x >> 1) & x == 0:\\n # 0{n}b 表示长为 n 的有前导零的二进制\\n ans.append(f\\"{x ^ mask:0{n}b}\\")\\n return ans\\n
\\n###py
\\nclass Solution:\\n def validStrings(self, n: int) -> List[str]:\\n mask = (1 << n) - 1\\n return [f\\"{x ^ mask:0{n}b}\\" for x in range(1 << n) if (x >> 1) & x == 0]\\n
\\n###java
\\nclass Solution {\\n public List<String> validStrings(int n) {\\n List<String> ans = new ArrayList<>();\\n int mask = (1 << n) - 1;\\n for (int x = 0; x < (1 << n); x++) {\\n if (((x >> 1) & x) == 0) {\\n int i = x ^ mask;\\n // 一种生成前导零的写法:在 i 前面插入 1<<n,转成字符串后再去掉插入的 1<<n\\n ans.add(Integer.toBinaryString((1 << n) | i).substring(1));\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<string> validStrings(int n) {\\n vector<string> ans;\\n int mask = (1 << n) - 1;\\n for (int x = 0; x < (1 << n); x++) {\\n if (((x >> 1) & x) == 0) {\\n ans.push_back(bitset<18>(x ^ mask).to_string().substr(18 - n));\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc validStrings(n int) (ans []string) {\\n mask := 1<<n - 1\\n for x := range 1 << n {\\n if x>>1&x == 0 {\\n ans = append(ans, fmt.Sprintf(\\"%0*b\\", n, x^mask))\\n }\\n }\\n return\\n}\\n
\\n###js
\\nvar validStrings = function(n) {\\n const ans = [];\\n const mask = (1 << n) - 1;\\n for (let x = 0; x < (1 << n); x++) {\\n if (((x >> 1) & x) === 0) {\\n ans.push(_.padStart((x ^ mask).toString(2), n, \'0\'));\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n fn valid_strings(n: i32) -> Vec<String> {\\n let mask = (1 << n) - 1;\\n (0..1 << n)\\n .filter(|&x| (x >> 1) & x == 0)\\n .map(|x| format!(\\"{:0w$b}\\", x ^ mask, w = n as usize))\\n .collect()\\n }\\n}\\n
\\n你能想出一个更快的枚举方式吗?如何直接跳到下一个合法的二进制数?
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 生成所有长为 $n$ 的二进制字符串,要求字符串不包含 $\\\\texttt{\\"00\\"}$。\\n\\n方法一:回溯(爆搜)\\n\\n类似 17. 电话号码的字母组合,用递归生成所有长为 $n$ 的符合要求的字符串。如果你没有写过这题,可以看 视频讲解【基础算法精讲 14】。\\n\\n本题枚举字符串的位置 $i$ 填 $1$ 还是 $0$:\\n\\n填 $1$,没有任何约束,可以直接填。\\n填 $0$,需要满足 $i=0$,或者 $i-1$ 这个位置(前一个位置)填的是 $1$。\\n填完后,继续往下递归,考虑 $i+1$ 怎么填。\\n\\n从 $i=0$ 开始递归,到 $i=n$ 结束。\\n\\n⚠注…","guid":"https://leetcode.cn/problems/generate-binary-strings-without-adjacent-zeros//solution/wei-yun-suan-zuo-fa-pythonjavacgo-by-end-6lbt","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-07T04:08:26.898Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 做法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/alternating-groups-i//solution/on-zuo-fa-pythonjavacgo-by-endlesscheng-fofz","content":"本题和 3208. 交替组 II 是一样的,令 $k=3$ 即可,请看 我的题解。
\\n","description":"本题和 3208. 交替组 II 是一样的,令 $k=3$ 即可,请看 我的题解。","guid":"https://leetcode.cn/problems/alternating-groups-i//solution/on-zuo-fa-pythonjavacgo-by-endlesscheng-fofz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-06T23:43:54.441Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/alternating-groups-ii//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-fzva","content":"本题是环形数组,请先完成普通数组的版本:3101. 交替子数组计数(我的题解)
\\n把数组复制一份拼接起来,和 3101 题一样,遍历数组的同时,维护以 $i$ 为右端点的交替子数组的长度 $\\\\textit{cnt}$。
\\n如果 $i\\\\ge n$ 且 $\\\\textit{cnt}\\\\ge k$,那么 $i$ 就是一个长为 $k$ 的交替子数组的右端点,答案加一。注意这里要判断 $i\\\\ge n$,从而避免重复统计。
\\n代码实现时,不需要复制数组,而是用 $i\\\\bmod n$ 的方式取到对应的值。
\\n具体请看 视频讲解 第三题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int], k: int) -> int:\\n n = len(colors)\\n ans = cnt = 0\\n for i in range(n * 2):\\n if i > 0 and colors[i % n] == colors[(i - 1) % n]:\\n cnt = 0\\n cnt += 1\\n if i >= n and cnt >= k:\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int numberOfAlternatingGroups(int[] colors, int k) {\\n int n = colors.length;\\n int ans = 0;\\n int cnt = 0;\\n for (int i = 0; i < n * 2; i++) {\\n if (i > 0 && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 0;\\n }\\n cnt++;\\n if (i >= n && cnt >= k) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors, int k) {\\n int n = colors.size();\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n * 2; i++) {\\n if (i > 0 && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 0;\\n }\\n cnt++;\\n ans += i >= n && cnt >= k;\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint numberOfAlternatingGroups(int* colors, int n, int k) {\\n int ans = 0, cnt = 0;\\n for (int i = 0; i < n * 2; i++) {\\n if (i > 0 && colors[i % n] == colors[(i - 1) % n]) {\\n cnt = 0;\\n }\\n cnt++;\\n if (i >= n && cnt >= k) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc numberOfAlternatingGroups(colors []int, k int) (ans int) {\\nn := len(colors)\\ncnt := 0\\nfor i := 0; i < n*2; i++ {\\nif i > 0 && colors[i%n] == colors[(i-1)%n] {\\ncnt = 0\\n}\\ncnt++\\nif i >= n && cnt >= k {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar numberOfAlternatingGroups = function(colors, k) {\\n const n = colors.length;\\n let ans = 0, cnt = 0;\\n for (let i = 0; i < n * 2; i++) {\\n if (i > 0 && colors[i % n] === colors[(i - 1) % n]) {\\n cnt = 0;\\n }\\n cnt++;\\n if (i >= n && cnt >= k) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn number_of_alternating_groups(colors: Vec<i32>, k: i32) -> i32 {\\n let n = colors.len();\\n let mut ans = 0;\\n let mut cnt = 0;\\n for i in 0..n * 2 {\\n if i > 0 && colors[i % n] == colors[(i - 1) % n] {\\n cnt = 0;\\n }\\n cnt += 1;\\n if i >= n && cnt >= k {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本题是环形数组,请先完成普通数组的版本:3101. 交替子数组计数(我的题解) 把数组复制一份拼接起来,和 3101 题一样,遍历数组的同时,维护以 $i$ 为右端点的交替子数组的长度 $\\\\textit{cnt}$。\\n\\n如果 $i\\\\ge n$ 且 $\\\\textit{cnt}\\\\ge k$,那么 $i$ 就是一个长为 $k$ 的交替子数组的右端点,答案加一。注意这里要判断 $i\\\\ge n$,从而避免重复统计。\\n\\n代码实现时,不需要复制数组,而是用 $i\\\\bmod n$ 的方式取到对应的值。\\n\\n具体请看 视频讲解 第三题,欢迎点赞关注~\\n\\n###py\\n\\nclass…","guid":"https://leetcode.cn/problems/alternating-groups-ii//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-fzva","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-06T23:41:35.069Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"滑动窗口","url":"https://leetcode.cn/problems/alternating-groups-i//solution/hua-dong-chuang-kou-by-tsreaper-fnfq","content":"枚举组的开头,那么组中间的 $(k - 2)$ 个元素都需要满足“与两边的颜色不同”的条件。预处理哪些元素和两边的颜色不同,再用滑动窗口统计中间的 $(k - 2)$ 个元素中,有几个满足该条件即可。复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors, int K) {\\n int n = colors.size();\\n // 预处理哪些元素与两边颜色不同\\n int f[n];\\n for (int i = 0; i < n; i++) {\\n int x = colors[(i - 1 + n) % n];\\n int y = colors[i];\\n int z = colors[(i + 1) % n];\\n if (x != y && y != z) f[i] = 1;\\n else f[i] = 0;\\n }\\n\\n // 滑动窗口\\n int sm = 0;\\n for (int i = 1; i + 1 < K; i++) sm += f[i];\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n if (sm == K - 2) ans++;\\n sm -= f[(i + 1) % n];\\n sm += f[(i + K - 1) % n];\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:滑动窗口 枚举组的开头,那么组中间的 $(k - 2)$ 个元素都需要满足“与两边的颜色不同”的条件。预处理哪些元素和两边的颜色不同,再用滑动窗口统计中间的 $(k - 2)$ 个元素中,有几个满足该条件即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector枚举组的开头,那么组中间的 $(k - 2)$ 个元素都需要满足“与两边的颜色不同”的条件。预处理哪些元素和两边的颜色不同,再用滑动窗口统计中间的 $(k - 2)$ 个元素中,有几个满足该条件即可。复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector<int>& colors, int K) {\\n int n = colors.size();\\n // 预处理哪些元素与两边颜色不同\\n int f[n];\\n for (int i = 0; i < n; i++) {\\n int x = colors[(i - 1 + n) % n];\\n int y = colors[i];\\n int z = colors[(i + 1) % n];\\n if (x != y && y != z) f[i] = 1;\\n else f[i] = 0;\\n }\\n\\n // 滑动窗口\\n int sm = 0;\\n for (int i = 1; i + 1 < K; i++) sm += f[i];\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n if (sm == K - 2) ans++;\\n sm -= f[(i + 1) % n];\\n sm += f[(i + K - 1) % n];\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:滑动窗口 枚举组的开头,那么组中间的 $(k - 2)$ 个元素都需要满足“与两边的颜色不同”的条件。预处理哪些元素和两边的颜色不同,再用滑动窗口统计中间的 $(k - 2)$ 个元素中,有几个满足该条件即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int numberOfAlternatingGroups(vector\\n\\nProblem: 100351. 交替组 II
\\n
[TOC]
\\n最近的每日一题才出过类似的
\\n3101. 交替子数组计数
colors = colors * 2
,看到环直接拼接就对了。
只要存在交替,右指针r
就右移,否则左指针l
指向r
指针。
# 满足题意\\n if r - l >= k:\\n # 保证起点不超过第一轮\\n if r - k < n_half:\\n res += 1\\n
\\n更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def numberOfAlternatingGroups(self, colors: List[int], k: int) -> int:\\n res = 0\\n n_half = len(colors)\\n # 数组拼接,模拟环\\n colors = colors * 2\\n l,r = 0,1\\n n = len(colors)\\n while r < n and l < n_half:\\n if colors[r] != colors[r-1]:\\n r += 1\\n else:\\n l = r\\n r += 1\\n \\n # 满足题意\\n if r - l >= k:\\n # 保证起点不超过第一轮\\n if r - k < n_half:\\n res += 1\\n # 超过了,剪枝\\n else:\\n break\\n # print(l,r,k,n_half)\\n \\n return res\\n
\\n","description":"Problem: 100351. 交替组 II [TOC]\\n\\n最近的每日一题才出过类似的\\n 3101. 交替子数组计数\\n\\n数组拼接模拟环\\n\\ncolors = colors * 2,看到环直接拼接就对了。\\n\\n双指针\\n\\n只要存在交替,右指针r就右移,否则左指针l指向r指针。\\n\\n判断条件\\n # 满足题意\\n if r - l >= k:\\n # 保证起点不超过第一轮\\n if r - k < n_half:\\n res += 1\\n\\n\\n更多…","guid":"https://leetcode.cn/problems/alternating-groups-ii//solution/shuang-zhi-zhen-by-mipha-2022-x4ak","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-06T16:44:06.411Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一目了然的图示分析,附多语言","url":"https://leetcode.cn/problems/find-the-town-judge//solution/yi-mu-liao-ran-de-tu-shi-fen-xi-fu-duo-y-nuqd","content":"我们可以将其抽象为图结构,如下所示。顶点 3 表示法官,其余为百姓。信任关系用边表示,比如边 $1 \\\\rightarrow 3$ 表示 1 信任 3。
\\n
顶点 1245 之间可能也存在边,我们先不用管。
\\n此处补充一下出度入度的概念:
\\n根据题意,如果小镇法官存在,那么:
\\n$n$ 为点数,所以在图结构中,如果存在小镇法官,只有他对应的顶点满足:
\\n综上,我们遍历边集,对每条边的起点和终点统计出度入度就可以了。
\\n###C++
\\nclass Solution {\\npublic:\\n int findJudge(int n, vector<vector<int>>& trust) {\\n // 出度数组,因为点数分布在 1~n, 所以此处多存储一个顶点 0,方便处理。对于下面的入度数组同理。 \\n vector<int> outDegrees(n + 1); \\n // 入度数组\\n vector<int> inDegrees(n + 1);\\n\\n // 遍历边集 trust\\n for (auto& edge : trust) {\\n int u = edge[0], v = edge[1]; // 起点和终点\\n outDegrees[u]++; // 起点出度 + 1\\n inDegrees[v]++; // 终点入度 + 1\\n }\\n\\n // 忽略顶点 0,寻找出度为 0,入度为 n-1 的顶点。\\n for (int v = 1; v <= n; ++v) {\\n if (outDegrees[v] == 0 && inDegrees[v] == n - 1){\\n return v;\\n }\\n }\\n\\n // 小镇法官不存在\\n return -1;\\n }\\n};\\n
\\n###C
\\nint findJudge(int n, int** trust, int trustSize, int* trustColSize){\\n // 出度数组,因为点数分布在 1~n, 所以此处多存储一个顶点 0,方便处理。对于下面的入度数组同理。 \\n int* outDegrees = (int *)malloc(sizeof(int)*(n+1));\\n // 入度数组\\n int* inDegrees = (int *)malloc(sizeof(int)*(n+1));\\n memset(inDegrees, 0, sizeof(int)*(n+1));\\n memset(outDegrees, 0, sizeof(int)*(n+1));\\n\\n // 遍历边集 trust\\n for (int i = 0; i < trustSize; ++i) {\\n int u = trust[i][0], v = trust[i][1]; // 起点和终点\\n outDegrees[u]++; // 起点出度 + 1\\n inDegrees[v]++; // 终点入度 + 1\\n }\\n \\n // 忽略顶点 0,寻找出度为 0,入度为 n-1 的顶点。\\n for (int v = 1; v <= n; ++v) {\\n if (outDegrees[v] == 0 && inDegrees[v] == n - 1) {\\n return v;\\n }\\n }\\n\\n // 小镇法官不存在\\n return -1;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def findJudge(self, n: int, trust: List[List[int]]) -> int:\\n # 出度数组,因为点数分布在 1~n, 所以此处多存储一个顶点 0,方便处理。对于下面的入度数组同理。 \\n out_degrees = [0] * (n + 1)\\n # 入度数组 \\n in_degrees = [0] * (n + 1) \\n\\n # 遍历边集 trust\\n for u, v in trust:\\n out_degrees[u] += 1 # 起点 u 出度 + 1\\n in_degrees[v] += 1 # 终点 v 入度 + 1\\n\\n # 忽略顶点 0,寻找出度为 0,入度为 n-1 的顶点。\\n for v in range(1, n + 1):\\n if out_degrees[v] == 0 and in_degrees[v] == n - 1:\\n return v\\n\\n # 小镇法官不存在\\n return -1\\n
\\nclass Solution:\\n def findJudge(self, n: int, trust: List[List[int]]) -> int:\\n outDegrees = Counter(start for start, _ in trust)\\n inDegrees = Counter(end for _, end in trust)\\n return next((i for i in range(1, n + 1) if outDegrees[i] == 0 and inDegrees[i] == n - 1), -1)\\n
\\n###Java
\\nclass Solution {\\n public int findJudge(int n, int[][] trust) {\\n // 出度数组,因为点数分布在 1~n, 所以此处多存储一个顶点 0,方便处理。对于下面的入度数组同理。 \\n int[] outDegrees = new int[n + 1];\\n // 入度数组\\n int[] inDegrees = new int[n + 1]; \\n \\n // 遍历边集 trust\\n for (int[] edge : trust) {\\n int u = edge[0], v = edge[1]; // 起点和终点\\n outDegrees[u]++; // 起点出度 + 1\\n inDegrees[v]++; // 终点入度 + 1\\n }\\n\\n // 忽略顶点 0,寻找出度为 0,入度为 n-1 的顶点。\\n for (int v = 1; v <= n; v++) {\\n if (outDegrees[v] == 0 && inDegrees[v] == n - 1) {\\n return v;\\n }\\n }\\n \\n // 小镇法官不存在\\n return -1;\\n }\\n}\\n
\\n###kotlin
\\nclass Solution {\\n fun findJudge(n: Int, trust: Array<IntArray>): Int {\\n // 出度数组,因为点数分布在 1~n, 所以此处多存储一个顶点 0,方便处理。对于下面的入度数组同理。\\n val outDegrees = IntArray(n+1) \\n // 入度数组\\n val inDegrees = IntArray(n+1) \\n\\n // 遍历边集 trust\\n for((u, v) in trust){ \\n outDegrees[u]++ // 起点 u 出度 + 1\\n inDegrees[v]++ // 终点 v 入度 + 1\\n }\\n\\n // 忽略顶点 0,寻找出度为 0,入度为 n-1 的顶点。\\n return (1..n).firstOrNull{ \\n outDegrees[it] == 0 && inDegrees[it] == n-1 \\n }\\n ?: -1\\n }\\n}\\n
\\n读者不熟悉 $\\\\Theta$ 视为 $O$ 即可。
\\n记 $n$ 为点数,$m$ 为边数。
\\n时间复杂度:$\\\\Theta(n + m)$。此处不能化简,因为小镇法官可能不存在,我们不能确定 $m$ 与 $n$ 的大小关系。
\\n空间复杂度:$\\\\Theta(n)$
\\n借助理论知识能较方便地处理很多模型,这和数学中的函数很像,也是理论科学的魅力所在。也希望读者能重视基础知识,不用急于做题。很多题目中所要求解的就是学术中的术语,比如“岛屿数量”这道经典题目求的就是连通分量数目。
\\n以下皆为个人所著,兼顾了职场面试和本硕阶段的学术考试。
\\n点赞关注不迷路,祝各位早日上岸,飞黄腾达!
\\n","description":"我们可以将其抽象为图结构,如下所示。顶点 3 表示法官,其余为百姓。信任关系用边表示,比如边 $1 \\\\rightarrow 3$ 表示 1 信任 3。 顶点 1245 之间可能也存在边,我们先不用管。\\n\\n此处补充一下出度入度的概念:\\n\\n出度:从某点出发的边数\\n入度:指向某点的边数\\n\\n\\n\\n根据题意,如果小镇法官存在,那么:\\n\\n小镇法官不会信任任何人。\\n每个人(除了小镇法官)都信任这位小镇法官。\\n只有一个人同时满足属性 1 和属性 2。\\n\\n\\n\\n$n$ 为点数,所以在图结构中,如果存在小镇法官,只有他对应的顶点满足:\\n\\n出度为 $0$\\n入度为 $n - 1$…","guid":"https://leetcode.cn/problems/find-the-town-judge//solution/yi-mu-liao-ran-de-tu-shi-fen-xi-fu-duo-y-nuqd","author":"shawxing-kwok","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-07-06T15:18:55.692Z","media":[{"url":"https://pic.leetcode.cn/1719456955-QHOdEB-1718423325-dVaLMt-Screenshot%202024-06-15%20at%2011.40.28%20AM.png","type":"photo","width":1600,"height":514}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3200. 三角形的最大高度","url":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/3200-san-jiao-xing-de-zui-da-gao-du-by-s-m0l3","content":"三角形的首行可能放置红色球或蓝色球,当首行颜色确定之后,三角形的每一行的颜色都可以确定。
\\n对于两种情况,分别枚举每一行,计算是否可以使用指定颜色的球完整放置该行,可以使用指定颜色的球完整放置的最大行数即为三角形的最大高度。
\\n###Java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(getMaxHeight(new int[]{red, blue}), getMaxHeight(new int[]{blue, red}));\\n }\\n\\n public int getMaxHeight(int[] counts) {\\n int height = 0;\\n int position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxHeightOfTriangle(int red, int blue) {\\n return Math.Max(GetMaxHeight(new int[]{red, blue}), GetMaxHeight(new int[]{blue, red}));\\n }\\n\\n public int GetMaxHeight(int[] counts) {\\n int height = 0;\\n int position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n vector<int> counts1 = {red, blue};\\n vector<int> counts2 = {blue, red};\\n return max(getMaxHeight(counts1), getMaxHeight(counts2));\\n }\\n\\n int getMaxHeight(vector<int>& counts) {\\n int height = 0;\\n int position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n return max(self.getMaxHeight([red, blue]), self.getMaxHeight([blue, red]))\\n\\n def getMaxHeight(self, counts: List[int]) -> int:\\n height = 0\\n position = 0\\n while height + 1 <= counts[position]:\\n height += 1\\n counts[position] -= height\\n position ^= 1\\n return height\\n
\\n###C
\\nint getMaxHeight(int* counts) {\\n int height = 0;\\n int position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n}\\n\\nint maxHeightOfTriangle(int red, int blue) {\\n int* counts1 = (int*) malloc(sizeof(int) * 2);\\n counts1[0] = red;\\n counts1[1] = blue;\\n int* counts2 = (int*) malloc(sizeof(int) * 2);\\n counts2[0] = blue;\\n counts2[1] = red;\\n return fmax(getMaxHeight(counts1), getMaxHeight(counts2));\\n}\\n
\\n###Go
\\nfunc maxHeightOfTriangle(red int, blue int) int {\\n counts1 := [2]int{red, blue}\\n counts2 := [2]int{blue, red}\\n return max(getMaxHeight(counts1), getMaxHeight(counts2))\\n}\\n\\nfunc getMaxHeight(counts [2]int) int {\\n height := 0\\n position := 0\\n for height + 1 <= counts[position] {\\n height++\\n counts[position] -= height\\n position ^= 1\\n }\\n return height\\n}\\n
\\n###JavaScript
\\nvar maxHeightOfTriangle = function(red, blue) {\\n return Math.max(getMaxHeight([red, blue]), getMaxHeight([blue, red]));\\n};\\n\\nvar getMaxHeight = function(counts) {\\n let height = 0;\\n let position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n};\\n
\\n###TypeScript
\\nfunction maxHeightOfTriangle(red: number, blue: number): number {\\n return Math.max(getMaxHeight([red, blue]), getMaxHeight([blue, red]));\\n};\\n\\nfunction getMaxHeight(counts: number[]): number {\\n let height = 0;\\n let position = 0;\\n while (height + 1 <= counts[position]) {\\n height++;\\n counts[position] -= height;\\n position ^= 1;\\n }\\n return height;\\n};\\n
\\n时间复杂度:$O(\\\\textit{red} + \\\\textit{blue})$,其中 $\\\\textit{red}$ 和 $\\\\textit{blue}$ 分别是给定的红色球和蓝色球的数量。对于两种情况分别需要枚举可能的最大行数,可能的最大行数是 $O(\\\\textit{red} + \\\\textit{blue})$。
\\n空间复杂度:$O(1)$。
\\n假设三角形有 $p$ 个奇数行和 $q$ 个偶数行,则奇数行和偶数行的球的数量计算如下。
\\n奇数行:$\\\\sum_{i = 1}^p (2i - 1) = \\\\dfrac{(1 + 2p - 1) \\\\times p}{2} = \\\\dfrac{2p^2}{2} = p^2$。
\\n偶数行:$\\\\sum_{i = 1}^q (2i) = \\\\dfrac{(2 + 2q) \\\\times q}{2} = \\\\dfrac{2q^2 + 2q}{2} = q^2 + q$。
\\n如果给定的奇数行和偶数行的球的数量上限分别是 $\\\\textit{odd}$ 和 $\\\\textit{even}$,非负整数 $p$ 和 $q$ 的取值范围计算如下。
\\n奇数行:$p^2 \\\\le \\\\textit{odd}$,$-\\\\sqrt{\\\\textit{odd}} \\\\le p \\\\le \\\\sqrt{\\\\textit{odd}}$,由于 $p$ 是非负整数,因此 $0 \\\\le p \\\\le \\\\lfloor \\\\sqrt{\\\\textit{odd}} \\\\rfloor$。
\\n偶数行:$q^2 + q \\\\le \\\\textit{even}$,$\\\\dfrac{-1 - \\\\sqrt{4 \\\\times \\\\textit{even} + 1}}{2} \\\\le q \\\\le \\\\dfrac{-1 + \\\\sqrt{4 \\\\times \\\\textit{even} + 1}}{2}$,由于 $q$ 是非负整数,因此 $0 \\\\le q \\\\le \\\\Big\\\\lfloor \\\\dfrac{-1 + \\\\sqrt{4 \\\\times \\\\textit{even} + 1}}{2} \\\\Big\\\\rfloor$。
\\n为了使三角形的高度最大,奇数行和偶数行的数量都应该取最大值。记 $\\\\textit{oddRows} = \\\\lfloor \\\\sqrt{\\\\textit{odd}} \\\\rfloor$,$\\\\textit{evenRows} = \\\\Big\\\\lfloor \\\\dfrac{-1 + \\\\sqrt{4 \\\\times \\\\textit{even} + 1}}{2} \\\\Big\\\\rfloor$,三角形的最大高度计算如下。
\\n当 $\\\\textit{oddRows} \\\\le \\\\textit{evenRows}$ 时,三角形最多有 $\\\\textit{oddRows}$ 个奇数行和 $\\\\textit{oddRows}$ 个偶数行,因此三角形的最大高度是 $\\\\textit{oddRows} \\\\times 2$。
\\n当 $\\\\textit{oddRows} > \\\\textit{evenRows}$ 时,三角形最多有 $\\\\textit{evenRows} + 1$ 个奇数行和 $\\\\textit{evenRows}$ 个偶数行,因此三角形的最大高度是 $\\\\textit{evenRows} \\\\times 2 + 1$。
\\n###Java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(getMaxHeight(red, blue), getMaxHeight(blue, red));\\n }\\n\\n public int getMaxHeight(int odd, int even) {\\n int oddRows = (int) Math.sqrt(odd);\\n int evenRows = (int) ((-1 + Math.sqrt(4 * even + 1)) / 2);\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxHeightOfTriangle(int red, int blue) {\\n return Math.Max(GetMaxHeight(red, blue), GetMaxHeight(blue, red));\\n }\\n\\n public int GetMaxHeight(int odd, int even) {\\n int oddRows = (int) Math.Sqrt(odd);\\n int evenRows = (int) ((-1 + Math.Sqrt(4 * even + 1)) / 2);\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n return max(getMaxHeight(red, blue), getMaxHeight(blue, red));\\n }\\n\\n int getMaxHeight(int odd, int even) {\\n int oddRows = sqrt(odd);\\n int evenRows = (-1 + sqrt(4 * even + 1)) / 2;\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n return max(self.getMaxHeight(red, blue), self.getMaxHeight(blue, red))\\n\\n def getMaxHeight(self, odd: int, even: int) -> int:\\n oddRows = int(sqrt(odd))\\n evenRows = int((-1 + sqrt(4 * even + 1)) // 2)\\n return oddRows * 2 if oddRows <= evenRows else evenRows * 2 + 1\\n
\\n###C
\\nint getMaxHeight(int odd, int even) {\\n int oddRows = sqrt(odd);\\n int evenRows = (-1 + sqrt(4 * even + 1)) / 2;\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n}\\n\\nint maxHeightOfTriangle(int red, int blue) {\\n return fmax(getMaxHeight(red, blue), getMaxHeight(blue, red));\\n}\\n
\\n###Go
\\nfunc maxHeightOfTriangle(red int, blue int) int {\\n return max(getMaxHeight(red, blue), getMaxHeight(blue, red))\\n}\\n\\nfunc getMaxHeight(odd int, even int) int {\\n oddRows := int(math.Sqrt(float64(odd)))\\n evenRows := int((-1 + math.Sqrt(float64(4 * even + 1))) / 2)\\n if oddRows <= evenRows {\\n return oddRows * 2\\n } else {\\n return evenRows * 2 + 1\\n }\\n}\\n
\\n###JavaScript
\\nvar maxHeightOfTriangle = function(red, blue) {\\n return Math.max(getMaxHeight(red, blue), getMaxHeight(blue, red));\\n}\\n\\nvar getMaxHeight = function(odd, even) {\\n let oddRows = Math.floor(Math.sqrt(odd));\\n let evenRows = Math.floor((-1 + Math.sqrt(4 * even + 1)) / 2);\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n};\\n
\\n###TypeScript
\\nfunction maxHeightOfTriangle(red: number, blue: number): number {\\n return Math.max(getMaxHeight(red, blue), getMaxHeight(blue, red));\\n}\\n\\nfunction getMaxHeight(odd: number, even: number) {\\n let oddRows = Math.floor(Math.sqrt(odd));\\n let evenRows = Math.floor((-1 + Math.sqrt(4 * even + 1)) / 2);\\n return oddRows <= evenRows ? oddRows * 2 : evenRows * 2 + 1;\\n};\\n
\\n代码中使用了 $\\\\texttt{Math.sqrt}$ 方法,该方法调用了本地方法 $\\\\texttt{StrictMath.sqrt}$,因此其时间与空间复杂度取决于本地方法的实现,这里不具体分析。
\\n","description":"思路和算法 三角形的首行可能放置红色球或蓝色球,当首行颜色确定之后,三角形的每一行的颜色都可以确定。\\n\\n对于两种情况,分别枚举每一行,计算是否可以使用指定颜色的球完整放置该行,可以使用指定颜色的球完整放置的最大行数即为三角形的最大高度。\\n\\n代码\\n\\n###Java\\n\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(getMaxHeight(new int[]{red, blue}), getMaxHeight(new…","guid":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/3200-san-jiao-xing-de-zui-da-gao-du-by-s-m0l3","author":"stormsunshine","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-30T06:22:48.479Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举 / O(1) 数学公式(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/o1-shu-xue-gong-shi-pythonjavacgo-by-end-t2ht","content":"用一个长为 $2$ 的 $\\\\textit{cnt}$ 数组,分别记录偶数行和奇数行分别放了多少个球。
\\n从 $i=1$ 开始枚举,如果 $i$ 是奇数则把 $\\\\textit{cnt}[1]$ 增加 $i$,如果 $i$ 是偶数则把 $\\\\textit{cnt}[0]$ 增加 $i$。
\\n增加后,如果
\\n这两个条件都成立,说明无法把第 $i$ 行填满,返回 $i-1$。
\\n###py
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n cnt = [0, 0]\\n for i in count(1):\\n cnt[i % 2] += i\\n if (cnt[0] > red or cnt[1] > blue) and (cnt[0] > blue or cnt[1] > red):\\n return i - 1\\n
\\n###java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n int[] cnt = new int[2];\\n for (int i = 1; ; i++) {\\n cnt[i % 2] += i;\\n if ((cnt[0] > red || cnt[1] > blue) && (cnt[0] > blue || cnt[1] > red)) {\\n return i - 1;\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int cnt[2]{};\\n for (int i = 1; ; i++) {\\n cnt[i % 2] += i;\\n if ((cnt[0] > red || cnt[1] > blue) && (cnt[0] > blue || cnt[1] > red)) {\\n return i - 1;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc maxHeightOfTriangle(red, blue int) int {\\ncnt := [2]int{}\\nfor i := 1; ; i++ {\\ncnt[i%2] += i\\nif (cnt[0] > red || cnt[1] > blue) && (cnt[0] > blue || cnt[1] > red) {\\nreturn i - 1\\n}\\n}\\n}\\n
\\n奇数行放红球,偶数行放蓝球;或者奇数行放蓝球,偶数行放红球。
\\n计算最多能放多少排。两种情况取最大值。
\\n设奇数行有 $k$ 行,那么需要
\\n$$
\\n1+3+5+\\\\cdots + (2k-1) = k^2
\\n$$
个球。(等差数列求和公式)
\\n假设我们有 $n$ 个球,那么有
\\n$$
\\nn\\\\ge k^2
\\n$$
解得
\\n$$
\\nk \\\\le \\\\left\\\\lfloor\\\\sqrt n\\\\right\\\\rfloor
\\n$$
设偶数行有 $k$ 行,那么需要
\\n$$
\\n2+4+6+\\\\cdots + 2k = k^2 + k
\\n$$
个球。(等差数列求和公式)
\\n假设我们有 $n$ 个球,那么有
\\n$$
\\nn\\\\ge k^2 + k
\\n$$
解得
\\n$$
\\nk \\\\le \\\\left\\\\lfloor\\\\dfrac{\\\\sqrt{4n+1}-1}{2}\\\\right\\\\rfloor
\\n$$
设有 $\\\\textit{odd}$ 个奇数行,$\\\\textit{even}$ 个偶数行,那么总行数为
\\n$$
\\n\\\\begin{cases}
\\n2\\\\cdot \\\\textit{even} + 1, & odd > even \\\\
\\n2\\\\cdot \\\\textit{odd}, & \\\\text{otherwise} \\\\
\\n\\\\end{cases}
\\n$$
具体请看 视频讲解,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def maxHeightOfTriangle(self, red: int, blue: int) -> int:\\n def f(n: int, m: int) -> int:\\n odd = isqrt(n)\\n even = int((sqrt(m * 4 + 1) - 1) / 2)\\n return even * 2 + 1 if odd > even else odd * 2\\n return max(f(red, blue), f(blue, red))\\n
\\n###java
\\nclass Solution {\\n public int maxHeightOfTriangle(int red, int blue) {\\n return Math.max(f(red, blue), f(blue, red));\\n }\\n\\n private int f(int n, int m) {\\n int odd = (int) Math.sqrt(n);\\n int even = (int) ((Math.sqrt(m * 4 + 1) - 1) / 2);\\n return odd > even ? even * 2 + 1 : odd * 2;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n int f(int n, int m) {\\n int odd = sqrt(n);\\n int even = (sqrt(m * 4 + 1) - 1) / 2;\\n return odd > even ? even * 2 + 1 : odd * 2;\\n }\\n\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n return max(f(red, blue), f(blue, red));\\n }\\n};\\n
\\n###go
\\nfunc f(n, m int) int {\\nodd := int(math.Sqrt(float64(n)))\\neven := int((math.Sqrt(float64(m*4+1)) - 1) / 2)\\nif odd > even {\\nreturn even*2 + 1\\n}\\nreturn odd * 2\\n}\\n\\nfunc maxHeightOfTriangle(red, blue int) int {\\nreturn max(f(red, blue), f(blue, red))\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举 用一个长为 $2$ 的 $\\\\textit{cnt}$ 数组,分别记录偶数行和奇数行分别放了多少个球。\\n\\n从 $i=1$ 开始枚举,如果 $i$ 是奇数则把 $\\\\textit{cnt}[1]$ 增加 $i$,如果 $i$ 是偶数则把 $\\\\textit{cnt}[0]$ 增加 $i$。\\n\\n增加后,如果\\n\\n$\\\\textit{cnt}[0] > \\\\textit{red}$ 或者 $\\\\textit{cnt}[1] > \\\\textit{blue}$\\n$\\\\textit{cnt}[0] > \\\\textit{blue}$ 或者 $\\\\textit{cnt}[1]…","guid":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/o1-shu-xue-gong-shi-pythonjavacgo-by-end-t2ht","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-30T04:31:58.631Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 模拟","url":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/mei-ju-mo-ni-by-tsreaper-llca","content":"枚举第一行是红色还是蓝色,再按题意模拟即可。复杂度 $\\\\mathcal{O}(\\\\sqrt{r} + \\\\sqrt{b})$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int ans = 0;\\n // 枚举第一行是红色还是蓝色\\n for (int d = 0; d < 2; d++) {\\n int cnt[2] = {red, blue};\\n // i 表示现在第几行,now 表示现在什么颜色\\n for (int i = 1, now = d; ; i++, now ^= 1) {\\n if (cnt[now] < i) break;\\n ans = max(ans, i);\\n cnt[now] -= i;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 & 模拟 枚举第一行是红色还是蓝色,再按题意模拟即可。复杂度 $\\\\mathcal{O}(\\\\sqrt{r} + \\\\sqrt{b})$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int ans = 0;\\n // 枚举第一行是红色还是蓝色\\n for (int d = 0; d < 2; d++) {\\n int cnt[2] = {red…","guid":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/mei-ju-mo-ni-by-tsreaper-llca","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-30T04:13:36.657Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简洁写法","url":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/jian-ji-xie-fa-by-yzq789-v-wi9j","content":"\\n\\nProblem: 100340. 三角形的最大高度
\\n
[TOC]
\\n\\n\\n不管怎么说,三角形的形状是不变的,把奇数行累加到odd里,偶数行累加到even里。然后对于唯二的两种情况分别进行判断
\\n
###C++
\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int i = 0, odd = 0, even = 0;\\n for (i = 1; ; i++) {\\n if (i % 2) {\\n odd += i;\\n } else {\\n even += i;\\n }\\n if (!((odd <= red && even <= blue) || (even <= red && odd <= blue))) break;\\n }\\n return i - 1;\\n }\\n};\\n
\\n","description":"Problem: 100340. 三角形的最大高度 [TOC]\\n\\n不管怎么说,三角形的形状是不变的,把奇数行累加到odd里,偶数行累加到even里。然后对于唯二的两种情况分别进行判断\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int maxHeightOfTriangle(int red, int blue) {\\n int i = 0, odd = 0, even = 0;\\n for (i = 1; ; i++) {\\n if (i % 2) {…","guid":"https://leetcode.cn/problems/maximum-height-of-a-triangle//solution/jian-ji-xie-fa-by-yzq789-v-wi9j","author":"yzq789-v","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-30T04:12:08.946Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"维护一个变量用于记录总的翻转次数,看不懂你捶我","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/wei-hu-yi-ge-bian-liang-yong-yu-ji-lu-zo-ixy9","content":"维护一个变量用于记录总的翻转次数
\\n两次翻转可以看做没有翻转,故只有正反两面,用布尔变量即可
\\n枚举所有元素,每次遍历到一个元素
\\n若当前位置是翻转状态,则判断是不是 0, 不是就加一次翻转
\\n若当前位置是未翻转状态,则判断是不是 1,不是就加一次翻转
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n boolean isUp = true;\\n int n = nums.length,ans=0;\\n for(int i=0;i<n;i++){\\n if(isUp){//未翻转\\n if(nums[i] != 1){\\n ans++;\\n isUp = !isUp;\\n }\\n }else{//翻转\\n if(nums[i] == 1){\\n ans++;\\n isUp = !isUp;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n","description":"维护一个变量用于记录总的翻转次数 两次翻转可以看做没有翻转,故只有正反两面,用布尔变量即可\\n\\n枚举所有元素,每次遍历到一个元素\\n\\n若当前位置是翻转状态,则判断是不是 0, 不是就加一次翻转\\n\\n若当前位置是未翻转状态,则判断是不是 1,不是就加一次翻转\\n\\n###Java\\n\\nclass Solution {\\n public int minOperations(int[] nums) {\\n boolean isUp = true;\\n int n = nums.length,ans=0;\\n for(int i=0…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/wei-hu-yi-ge-bian-liang-yong-yu-ji-lu-zo-ixy9","author":"adoring-eulermrv","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-24T07:13:40.939Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序计算","url":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/pai-xu-ji-suan-by-admiring-meninskyuli-cbs1","content":"\\n\\nProblem: 100342. 最小元素和最大元素的最小平均值
\\n
[TOC]
\\n排序,遍历计算
\\n执行用时分布0ms击败100.00%;消耗内存分布5.70MB击败100.00%
\\n###C
\\nint cmp(const void * a, const void * b) {\\n return * (int *) a - * (int *) b;\\n}\\ndouble minimumAverage(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int mi = nums[0] + nums[numsSize - 1];\\n for (int i = 1, j = numsSize - 2; i < j; ) \\n mi = fmin(mi, nums[i ++] + nums[j --]);\\n return mi / 2.0;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n n = len(nums) >> 1\\n return min(x + y for x, y in zip(nums[: n], nums[: n - 1: -1])) / 2.0\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100342. 最小元素和最大元素的最小平均值 [TOC]\\n\\n排序,遍历计算\\n\\n执行用时分布0ms击败100.00%;消耗内存分布5.70MB击败100.00%\\n\\n###C\\n\\nint cmp(const void * a, const void * b) {\\n return * (int *) a - * (int *) b;\\n}\\ndouble minimumAverage(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int…","guid":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/pai-xu-ji-suan-by-admiring-meninskyuli-cbs1","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-24T00:38:15.349Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python 模拟 38ms","url":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/python-mo-ni-38ms-by-nrib8zib57-vp68","content":"\\n\\nProblem: 100342. 最小元素和最大元素的最小平均值
\\n
[TOC]
\\n###Python3
\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n n=len(nums)\\n ans=inf\\n for i in range(n//2):\\n tmp=nums[i]+nums[n-1-i]\\n ans=min(ans,tmp/2)\\n return ans\\n
\\n","description":"Problem: 100342. 最小元素和最大元素的最小平均值 [TOC]\\n\\n###Python3\\n\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n n=len(nums)\\n ans=inf\\n for i in range(n//2):\\n tmp=nums[i]+nums[n-1-i]\\n ans=min(ans,tmp/2)…","guid":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/python-mo-ni-38ms-by-nrib8zib57-vp68","author":"nriB8ZIB57","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-23T06:36:35.122Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3191. 使二进制数组全部等于 1 的最少操作次数 I","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/3191-shi-er-jin-zhi-shu-zu-quan-bu-deng-l2cjv","content":"用 $n$ 表示数组 $\\\\textit{nums}$ 的长度,则数组 $\\\\textit{nums}$ 中的长度为 $3$ 的子数组有 $n - 2$ 个,最左边的子数组的下标范围是 $[0, 2]$,最右边的子数组的下标范围是 $[n - 3, n - 1]$。从左到右依次遍历每个长度为 $3$ 的子数组,对于 $0 \\\\le i \\\\le n - 3$,当遍历到下标范围 $[i, i + 2]$ 的子数组时,下标范围 $[0, i - 1]$ 中的元素都在当前子数组的左侧,因此执行反转操作时不会反转下标范围 $[0, i - 1]$ 中的元素,此时如果 $\\\\textit{nums}[i] = 0$ 则必须反转下标范围 $[i, i + 2]$ 的子数组才能将 $\\\\textit{nums}[i]$ 变成 $1$。
\\n根据上述分析,可以得到将数组 $\\\\textit{nums}$ 中的所有元素都变成 $1$ 的反转方法:从左到右依次遍历每个长度为 $3$ 的子数组,对于 $0 \\\\le i \\\\le n - 3$,当遍历到下标范围 $[i, i + 2]$ 的子数组时,如果 $\\\\textit{nums}[i] = 0$,则反转当前下标范围的子数组,否则不反转当前下标范围的子数组。遍历所有长度为 $3$ 的子数组之后,如果数组 $\\\\textit{nums}$ 中的所有元素都是 $1$ 则返回操作次数,否则返回 $-1$。
\\n上述反转方法中,每个子数组都反转 $1$ 次或 $0$ 次,且所有的反转操作都是必要的,即对于一个需要反转的子数组,如果不反转,则该子数组的最左边的元素是 $0$,无法达到数组中不存在 $0$ 的目标。因此,按照上述反转方法执行反转操作之后,如果数组 $\\\\textit{nums}$ 中的所有元素都是 $1$,则使用最少次数的反转使数组 $\\\\textit{nums}$ 中不存在 $0$,否则无法使数组 $\\\\textit{nums}$ 中不存在 $0$。
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int operations = 0;\\n int n = nums.length;\\n for (int i = 0; i <= n - 3; i++) {\\n if (nums[i] == 0) {\\n for (int j = 0; j < 3; j++) {\\n nums[i + j] ^= 1;\\n }\\n operations++;\\n }\\n }\\n for (int i = n - 2; i < n; i++) {\\n if (nums[i] == 0) {\\n return -1;\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int operations = 0;\\n int n = nums.Length;\\n for (int i = 0; i <= n - 3; i++) {\\n if (nums[i] == 0) {\\n for (int j = 0; j < 3; j++) {\\n nums[i + j] ^= 1;\\n }\\n operations++;\\n }\\n }\\n for (int i = n - 2; i < n; i++) {\\n if (nums[i] == 0) {\\n return -1;\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n时间复杂度:$O(nk)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$k$ 是每次反转的子数组的长度,$k = 3$。需要遍历数组 $\\\\textit{nums}$ 一次,由于每次反转连续 $k$ 个元素,因此时间复杂度是 $O(nk)$。
\\n空间复杂度:$O(1)$。
\\n考虑更一般的问题:给定正整数 $k$,每次选择数组中任意连续 $k$ 个元素反转,计算将数组中所有元素变为 $1$ 的最少操作次数。这道题为 $k = 3$ 的特例。
\\n如果直接模拟反转操作,则每个子数组的反转时间是 $O(k)$,因此时间复杂度是 $O(nk)$。该时间复杂度可以优化。
\\n反转一个下标范围的子数组,等价于该下标范围中的每个元素的操作次数加 $1$,因此可以使用差分数组降低时间复杂度。定义差分数组 $\\\\textit{diffs}$,其中 $\\\\textit{diffs}[i]$ 表示 $\\\\textit{nums}[i]$ 和 $\\\\textit{nums}[i - 1]$ 的操作次数之差,这里规定 $\\\\textit{nums}[-1]$ 的操作次数之差是 $0$。
\\n对于 $0 \\\\le i \\\\le n - 2$,如果反转下标范围 $[i, i + 2]$ 的子数组,则下标范围 $[i, i + 2]$ 中的每个元素的操作次数加 $1$,因此将 $\\\\textit{diffs}[i]$ 的值加 $1$,将 $\\\\textit{diffs}[i + 3]$ 的值减 $1$。特别地,当 $i = n - 3$ 时,$i + 3$ 超出数组下标范围,因此不更新 $\\\\textit{diffs}[i + 3]$ 的值。对于 $0 \\\\le i < n$,差分数组 $\\\\textit{diffs}$ 的下标范围 $[0, i]$ 的前缀和即为 $\\\\textit{nums}[i]$ 的操作次数。
\\n用 $\\\\textit{operations}$ 表示整个数组中的操作次数,用 $\\\\textit{curr}$ 表示当前元素的操作次数,用 $[i, j - 1]$ 表示长度为 $3$ 的子数组,初始时 $\\\\textit{operations} = \\\\textit{curr} = 0$,$i = 0$,$j = 3$。对于 $0 \\\\le i < n$,从左到右遍历数组 $\\\\textit{nums}$ 的每个下标对 $(i, j)$,遍历过程中保持 $j - i = 3$,执行如下操作。
\\n将 $\\\\textit{diffs}[i]$ 的值加到 $\\\\textit{curr}$,更新后的 $\\\\textit{curr}$ 表示 $\\\\textit{nums}[i]$ 的操作次数。
\\n经过 $\\\\textit{curr}$ 次反转之后,$\\\\textit{nums}[i]$ 的值为 $(\\\\textit{nums}[i] + \\\\textit{curr}) \\\\bmod 2$,如果值为 $1$ 则不需要反转,如果值为 $0$ 则需要反转,反转处理如下。
\\n如果 $j > n$,则从下标 $i$ 到数组末尾的元素个数少于 $3$,因此不存在以 $i$ 作为开始下标且长度为 $3$ 的子数组,无法经过反转使数组 $\\\\textit{nums}$ 中不存在 $0$,返回 $-1$。
\\n如果 $j \\\\le n$,则反转下标范围 $[i, j - 1]$ 的子数组,将 $\\\\textit{operations}$ 和 $\\\\textit{curr}$ 的值各加 $1$,将 $\\\\textit{diffs}[i]$ 的值加 $1$,将 $\\\\textit{diffs}[j]$ 的值减 $1$(如果 $j = n$ 则不更新 $\\\\textit{diffs}[j]$ 的值)。
\\n遍历结束之后,如果未返回 $-1$,则 $\\\\textit{operations}$ 即为使数组 $\\\\textit{nums}$ 中不存在 $0$ 的最少操作次数。
\\n使用差分数组实现,则对于每个长度为 $3$ 的子数组只需要更新差分数组中的两个元素值,每个子数组的处理时间是 $O(1)$,总时间复杂度是 $O(n)$。
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int operations = 0;\\n int curr = 0;\\n int n = nums.length;\\n int[] diffs = new int[n];\\n for (int i = 0, j = 3; i < n; i++, j++) {\\n curr += diffs[i];\\n if ((nums[i] + curr) % 2 == 0) {\\n if (j > n) {\\n return -1;\\n }\\n operations++;\\n curr++;\\n diffs[i]++;\\n if (j < n) {\\n diffs[j]--;\\n }\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int operations = 0;\\n int curr = 0;\\n int n = nums.Length;\\n int[] diffs = new int[n];\\n for (int i = 0, j = 3; i < n; i++, j++) {\\n curr += diffs[i];\\n if ((nums[i] + curr) % 2 == 0) {\\n if (j > n) {\\n return -1;\\n }\\n operations++;\\n curr++;\\n diffs[i]++;\\n if (j < n) {\\n diffs[j]--;\\n }\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组 $\\\\textit{nums}$ 一次,对于每个元素的操作时间是 $O(1)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要创建长度为 $n$ 的差分数组。
\\n从左到右遍历长度为 $3$ 的子数组,实质是用长度为 $3$ 的定长滑动窗口从左到右遍历。这道题也可以使用滑动窗口的思想得到最少操作次数。
\\n为了计算一个元素的操作次数,需要使用队列存储反转的子数组的开始下标。用 $\\\\textit{operations}$ 表示整个数组中的操作次数,用 $[i, j]$ 表示长度为 $3$ 的定长滑动窗口,初始时 $\\\\textit{operations} = 0$,$i = 0$,$j = 2$。对于 $0 \\\\le i < n$,从左到右遍历数组 $\\\\textit{nums}$ 的每个下标对 $(i, j)$,遍历过程中保持 $j - i = 2$,执行如下操作。
\\n当前子数组的首个元素 $\\\\textit{nums}[i]$ 只会受到以下标范围 $[i - 2, i - 1]$ 中的下标作为开始下标的子数组的反转的影响,因此需要确保队列中的下标都不小于 $i - 2$。如果队列不为空且队首下标等于 $i - 3$,则将队首下标出队列。
\\n此时队列中的下标都是经过反转的子数组的开始下标,且这些子数组的反转都会使元素 $\\\\textit{nums}[i]$ 反转,因此 $\\\\textit{nums}[i]$ 的操作次数是队列中的下标个数。用 $\\\\textit{size}$ 表示队列中的下标个数,则经过反转之后,$\\\\textit{nums}[i]$ 的值为 $(\\\\textit{nums}[i] + \\\\textit{size}) \\\\bmod 2$,如果值为 $1$ 则不需要反转,如果值为 $0$ 则需要反转,反转处理如下。
\\n如果 $j \\\\ge n$,则从下标 $i$ 到数组末尾的元素个数少于 $3$,因此不存在以 $i$ 作为开始下标且长度为 $3$ 的子数组,无法经过反转使数组 $\\\\textit{nums}$ 中不存在 $0$,返回 $-1$。
\\n如果 $j < n$,则反转下标范围 $[i, j]$ 的子数组,将 $i$ 入队列,将 $\\\\textit{operations}$ 的值加 $1$。
\\n遍历结束之后,如果未返回 $-1$,则 $\\\\textit{operations}$ 即为使数组 $\\\\textit{nums}$ 中不存在 $0$ 的最少操作次数。
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int operations = 0;\\n int n = nums.length;\\n Queue<Integer> queue = new ArrayDeque<Integer>();\\n for (int i = 0, j = 2; i < n; i++, j++) {\\n if (!queue.isEmpty() && queue.peek() == i - 3) {\\n queue.poll();\\n }\\n if ((nums[i] + queue.size()) % 2 == 0) {\\n if (j >= n) {\\n return -1;\\n }\\n queue.offer(i);\\n operations++;\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums) {\\n int operations = 0;\\n int n = nums.Length;\\n Queue<int> queue = new Queue<int>();\\n for (int i = 0, j = 2; i < n; i++, j++) {\\n if (queue.Count > 0 && queue.Peek() == i - 3) {\\n queue.Dequeue();\\n }\\n if ((nums[i] + queue.Count) % 2 == 0) {\\n if (j >= n) {\\n return -1;\\n }\\n queue.Enqueue(i);\\n operations++;\\n }\\n }\\n return operations;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组 $\\\\textit{nums}$ 一次,对于每个元素的操作时间是 $O(1)$。
\\n空间复杂度:$O(k)$,其中 $k$ 是每次反转的子数组的长度,$k = 3$。空间复杂度主要取决于队列,队列中的元素个数不超过 $k$。
\\n为方便计算,把 $\\\\textit{nums}$ 从小到大排序。
\\n排序后,对于 $i=0,1,2,\\\\cdots,n/2$,计算 $\\\\textit{nums}[i]+\\\\textit{nums}[n-1-i]$ 的最小值。最后,返回最小值除以 $2$ 的结果。
\\n最后除以 $2$,这样可以避免在循环中做浮点运算,只在最后返回时做一次浮点运算。
\\n注意题目保证 $n$ 是偶数。
\\n###py
\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort()\\n return min(nums[i] + nums[-1 - i] for i in range(len(nums) // 2)) / 2\\n
\\n###java
\\nclass Solution {\\n public double minimumAverage(int[] nums) {\\n Arrays.sort(nums);\\n int ans = Integer.MAX_VALUE;\\n int n = nums.length;\\n for (int i = 0; i < n / 2; i++) {\\n ans = Math.min(ans, nums[i] + nums[n - 1 - i]);\\n }\\n return ans / 2.0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n double minimumAverage(vector<int>& nums) {\\n ranges::sort(nums);\\n int n = nums.size();\\n int ans = INT_MAX;\\n for (int i = 0; i < n / 2; ++i) {\\n ans = min(ans, nums[i] + nums[n - 1 - i]);\\n }\\n return ans / 2.0;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\ndouble minimumAverage(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int ans = INT_MAX;\\n for (int i = 0; i < numsSize / 2; i++) {\\n ans = MIN(ans, nums[i] + nums[numsSize - 1 - i]);\\n }\\n return ans / 2.0;\\n}\\n
\\n###go
\\nfunc minimumAverage(nums []int) float64 {\\n slices.Sort(nums)\\n ans := math.MaxInt\\n for i, n := 0, len(nums); i < n/2; i++ {\\n ans = min(ans, nums[i]+nums[n-1-i])\\n }\\n return float64(ans) / 2\\n}\\n
\\n###js
\\nvar minimumAverage = function(nums) {\\n nums.sort((a, b) => a - b);\\n let n = nums.length;\\n let ans = Infinity;\\n for (let i = 0; i < n / 2; i++) {\\n ans = Math.min(ans, nums[i] + nums[n - 1 - i]);\\n }\\n return ans / 2;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_average(mut nums: Vec<i32>) -> f64 {\\n nums.sort_unstable();\\n (0..nums.len() / 2)\\n .map(|i| nums[i] + nums[nums.len() - 1 - i])\\n .min()\\n .unwrap() as f64 / 2.0\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"为方便计算,把 $\\\\textit{nums}$ 从小到大排序。 排序后,对于 $i=0,1,2,\\\\cdots,n/2$,计算 $\\\\textit{nums}[i]+\\\\textit{nums}[n-1-i]$ 的最小值。最后,返回最小值除以 $2$ 的结果。\\n\\n最后除以 $2$,这样可以避免在循环中做浮点运算,只在最后返回时做一次浮点运算。\\n\\n注意题目保证 $n$ 是偶数。\\n\\n###py\\n\\nclass Solution:\\n def minimumAverage(self, nums: List[int]) -> float:\\n nums.sort…","guid":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/pai-xu-bian-li-pythonjavacgo-by-endlessc-x155","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-23T04:20:10.393Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序","url":"https://leetcode.cn/problems/minimum-average-of-smallest-and-largest-elements//solution/pai-xu-by-tsreaper-vrfl","content":"将 nums
排序后,第 $i$ 次操作的 minElement
即为 nums[i]
,maxElement
即为 nums[n - 1 - i]
。因此答案就是 min(nums[i] + nums[n - 1 - i]) / 2
。
复杂度 $\\\\mathcal{O}(n \\\\log n)$,主要是排序的复杂度。
\\n###cpp
\\nclass Solution {\\npublic:\\n double minimumAverage(vector<int>& nums) {\\n int n = nums.size();\\n sort(nums.begin(), nums.end());\\n int ans = 1e9;\\n for (int i = 0; i < n / 2; i++) ans = min(ans, nums[i] + nums[n - 1 - i]);\\n return ans / 2.0;\\n }\\n};\\n
\\n","description":"解法:排序 将 nums 排序后,第 $i$ 次操作的 minElement 即为 nums[i],maxElement 即为 nums[n - 1 - i]。因此答案就是 min(nums[i] + nums[n - 1 - i]) / 2。\\n\\n复杂度 $\\\\mathcal{O}(n \\\\log n)$,主要是排序的复杂度。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n double minimumAverage(vector请看 本题视频讲解 第四题,欢迎点赞关注!
\\n看示例 1,要求构造逆序对为 $2$ 的排列。
\\n讨论最后一个数填什么:
\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解决。
\\n\\n\\n注:动态规划有「选或不选」和「枚举选哪个」两种基本思考方式。在做题时,可根据题目要求,选择适合题目的一种来思考。本题用到的是「枚举选哪个」。
\\n
因为要解决的问题都形如「前 $i$ 个数的逆序对为 $j$ 时的排列个数」,所以用它作为本题的状态定义 $\\\\textit{dfs}(i,j)$。
\\n直接考虑第 $i$ 个数 $\\\\textit{perm}[i]$ 和前面 $\\\\textit{perm}[0]$ 到 $\\\\textit{perm}[i-1]$ 可以组成的逆序对的个数:
\\n累加得
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\sum_{k=0}^{\\\\min(i,j)}\\\\textit{dfs}(i-1,j-k)
\\n$$
其中 $\\\\min(i,j)$ 是因为前面只有 $i$ 个数,至多和 $\\\\textit{perm}[i]$ 组成 $i$ 个逆序对。
\\n⚠注意:我们不需要知道每个位置具体填了什么数。无论之前填了什么数,只要 $\\\\textit{perm}[i]$ 填的是剩余元素的最大值,那么 $k$ 就是 $0$;只要 $\\\\textit{perm}[i]$ 填的是剩余元素的次大值,那么 $k$ 就是 $1$;依此类推。
\\n除此以外,设 $\\\\textit{req}[i]$ 是前 $i$ 个数的逆序对个数(没有要求就是 $-1$),如果 $\\\\textit{req}[i-1]\\\\ge 0$,则无需枚举 $k$,分类讨论:
\\n递归边界:$\\\\textit{dfs}(0,0)=1$,此时找到了一个符合要求的排列。
\\n递归入口:$\\\\textit{dfs}(n-1,\\\\textit{req}[n-1])$,也就是答案。
\\n根据题意,$\\\\textit{req}[0]$ 一定为 $0$。代码实现时,可以在递归之前判断 $\\\\textit{req}[0] > 0$ 的情况,如果满足则直接返回 $0$。
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。
具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n###py
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n MOD = 1_000_000_007\\n req = [-1] * n\\n req[0] = 0\\n for end, cnt in requirements:\\n req[end] = cnt\\n if req[0]:\\n return 0\\n\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i == 0:\\n return 1\\n r = req[i - 1]\\n if r >= 0:\\n return dfs(i - 1, r) if r <= j <= i + r else 0\\n return sum(dfs(i - 1, j - k) for k in range(min(i, j) + 1)) % MOD\\n return dfs(n - 1, req[-1])\\n
\\n###java
\\nclass Solution {\\n public int numberOfPermutations(int n, int[][] requirements) {\\n int[] req = new int[n];\\n Arrays.fill(req, -1);\\n req[0] = 0;\\n int m = 0;\\n for (int[] p : requirements) {\\n req[p[0]] = p[1];\\n m = Math.max(m, p[1]);\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n\\n int[][] memo = new int[n][m + 1];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(n - 1, req[n - 1], req, memo);\\n }\\n\\n private int dfs(int i, int j, int[] req, int[][] memo) {\\n if (i == 0) {\\n return 1;\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j];\\n }\\n int res = 0;\\n int r = req[i - 1];\\n if (r >= 0) {\\n if (j >= r && j - i <= r) {\\n res = dfs(i - 1, r, req, memo);\\n }\\n } else {\\n for (int k = 0; k <= Math.min(i, j); k++) {\\n res = (res + dfs(i - 1, j - k, req, memo)) % 1_000_000_007;\\n }\\n }\\n return memo[i][j] = res; // 记忆化\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n vector<int> req(n, -1);\\n req[0] = 0;\\n for (auto& p : requirements) {\\n req[p[0]] = p[1];\\n }\\n if (req[0]) {\\n return 0;\\n }\\n\\n int m = ranges::max(req);\\n vector<vector<int>> memo(n, vector<int>(m + 1, -1)); // -1 表示没有计算过\\n auto dfs = [&](auto&& dfs, int i, int j) -> int {\\n if (i == 0) {\\n return 1;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n res = 0;\\n if (int r = req[i - 1]; r >= 0) {\\n if (j >= r && j - i <= r) {\\n res = dfs(dfs, i - 1, r);\\n }\\n } else {\\n for (int k = 0; k <= min(i, j); k++) {\\n res = (res + dfs(dfs, i - 1, j - k)) % MOD;\\n }\\n }\\n return res;\\n };\\n return dfs(dfs, n - 1, req[n - 1]);\\n }\\n};\\n
\\n###go
\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\nconst mod = 1_000_000_007\\nreq := make([]int, n)\\nfor i := 1; i < n; i++ {\\nreq[i] = -1\\n}\\nfor _, p := range requirements {\\nreq[p[0]] = p[1]\\n}\\nif req[0] > 0 {\\nreturn 0\\n}\\n\\nm := slices.Max(req)\\nmemo := make([][]int, n)\\nfor i := range memo {\\nmemo[i] = make([]int, m+1)\\nfor j := range memo[i] {\\nmemo[i][j] = -1\\n}\\n}\\nvar dfs func(int, int) int\\ndfs = func(i, j int) (res int) {\\nif i == 0 {\\nreturn 1\\n}\\np := &memo[i][j]\\nif *p != -1 {\\nreturn *p\\n}\\ndefer func() { *p = res }()\\nif r := req[i-1]; r >= 0 {\\nif j < r || j-i > r {\\nreturn 0\\n}\\nreturn dfs(i-1, r)\\n}\\nfor k := 0; k <= min(i, j); k++ {\\nres += dfs(i-1, j-k)\\n}\\nreturn res % mod\\n}\\nreturn dfs(n-1, req[n-1])\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示前 $i$ 个数的逆序对为 $j$ 时的排列个数。
\\n如果 $\\\\textit{req}[i-1]<0$,相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i][j] = \\\\sum_{k=0}^{\\\\min(i,j)}f[i-1][j-k]
\\n$$
如果 $r=\\\\textit{req}[i-1]\\\\ge 0$,递推式为
\\n$$
\\nf[i][j] =
\\n\\\\begin{cases}
\\nf[i-1][r],\\\\ &r\\\\le j \\\\le i+r\\\\
\\n0,\\\\ &\\\\text{otherwise}\\\\
\\n\\\\end{cases}
\\n$$
初始值 $f[0][0]=1$,翻译自递归边界 $\\\\textit{dfs}(0,0)=1$。
\\n答案为 $f[n-1][\\\\textit{req}[n-1]]$,翻译自递归入口 $\\\\textit{dfs}(n-1,\\\\textit{req}[n-1])$。
\\n###py
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n MOD = 1_000_000_007\\n req = [-1] * n\\n req[0] = 0\\n for end, cnt in requirements:\\n req[end] = cnt\\n if req[0]:\\n return 0\\n\\n m = max(req)\\n f = [[0] * (m + 1) for _ in range(n)]\\n f[0][0] = 1\\n for i in range(1, n):\\n mx = m if req[i] < 0 else req[i]\\n r = req[i - 1]\\n if r >= 0:\\n for j in range(r, min(i + r, mx) + 1):\\n f[i][j] = f[i - 1][r]\\n else:\\n for j in range(mx + 1):\\n f[i][j] = sum(f[i - 1][j - k] for k in range(min(i, j) + 1)) % MOD\\n return f[-1][req[-1]]\\n
\\n###java
\\nclass Solution {\\n public int numberOfPermutations(int n, int[][] requirements) {\\n final int MOD = 1_000_000_007;\\n int[] req = new int[n];\\n Arrays.fill(req, -1);\\n req[0] = 0;\\n int m = 0;\\n for (int[] p : requirements) {\\n req[p[0]] = p[1];\\n m = Math.max(m, p[1]);\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n\\n int[][] f = new int[n][m + 1];\\n f[0][0] = 1;\\n for (int i = 1; i < n; i++) {\\n int mx = req[i] < 0 ? m : req[i];\\n int r = req[i - 1];\\n if (r >= 0) {\\n for (int j = r; j <= Math.min(i + r, mx); j++) {\\n f[i][j] = f[i - 1][r];\\n }\\n } else {\\n for (int j = 0; j <= mx; j++) {\\n for (int k = 0; k <= Math.min(i, j); k++) {\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % MOD;\\n }\\n }\\n }\\n }\\n return f[n - 1][req[n - 1]];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n vector<int> req(n, -1);\\n req[0] = 0;\\n for (auto& p : requirements) {\\n req[p[0]] = p[1];\\n }\\n if (req[0]) {\\n return 0;\\n }\\n\\n int m = ranges::max(req);\\n vector<vector<int>> f(n, vector<int>(m + 1));\\n f[0][0] = 1;\\n for (int i = 1; i < n; i++) {\\n int mx = req[i] < 0 ? m : req[i];\\n if (int r = req[i - 1]; r >= 0) {\\n for (int j = r; j <= min(i + r, mx); j++) {\\n f[i][j] = f[i - 1][r];\\n }\\n } else {\\n for (int j = 0; j <= mx; j++) {\\n for (int k = 0; k <= min(i, j); k++) {\\n f[i][j] = (f[i][j] + f[i - 1][j - k]) % MOD;\\n }\\n }\\n }\\n }\\n return f[n - 1][req[n - 1]];\\n }\\n};\\n
\\n###go
\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\nconst mod = 1_000_000_007\\nreq := make([]int, n)\\nfor i := 1; i < n; i++ {\\nreq[i] = -1\\n}\\nfor _, p := range requirements {\\nreq[p[0]] = p[1]\\n}\\nif req[0] > 0 {\\nreturn 0\\n}\\n\\nm := slices.Max(req)\\nf := make([][]int, n)\\nfor i := range f {\\nf[i] = make([]int, m+1)\\n}\\nf[0][0] = 1\\nfor i := 1; i < n; i++ {\\nmx := m\\nif req[i] >= 0 {\\nmx = req[i]\\n}\\nif r := req[i-1]; r >= 0 {\\nfor j := r; j <= min(i+r, mx); j++ {\\nf[i][j] = f[i-1][r]\\n}\\n} else {\\nfor j := 0; j <= mx; j++ {\\nfor k := 0; k <= min(i, j); k++ {\\nf[i][j] = (f[i][j] + f[i-1][j-k]) % mod\\n}\\n}\\n}\\n}\\nreturn f[n-1][req[n-1]]\\n}\\n
\\n和式
\\n$$
\\n\\\\sum_{k=0}^{\\\\min(i,j)}f[i-1][j-k]
\\n$$
可以用 前缀和 优化。
\\n观察上面的状态转移方程,在计算 $f[i]$ 时,只会用到 $f[i-1]$,不会用到比 $i-1$ 更早的状态。
\\n因此可以去掉第一个维度,反复利用同一个长为 $m+1$ 的一维数组。
\\n代码实现时,前缀和可以直接保存在 $f$ 中。先计算前缀和,再利用前缀和计算和式(子数组和)。
\\n关于取模的技巧,见 模运算的世界:当加减乘除遇上取模。
\\n###py
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n MOD = 1_000_000_007\\n req = [-1] * n\\n req[0] = 0\\n for end, cnt in requirements:\\n req[end] = cnt\\n if req[0]:\\n return 0\\n\\n m = max(req)\\n f = [0] * (m + 1)\\n f[0] = 1\\n for i in range(1, n):\\n mx = m if req[i] < 0 else req[i]\\n r = req[i - 1]\\n if r >= 0:\\n for j in range(m + 1):\\n f[j] = f[r] if r <= j <= min(i + r, mx) else 0\\n else:\\n for j in range(1, mx + 1): # 计算前缀和\\n f[j] = (f[j] + f[j - 1]) % MOD\\n for j in range(mx, i, -1): # 计算子数组和\\n f[j] = (f[j] - f[j - i - 1]) % MOD\\n return f[req[-1]]\\n
\\n###java
\\nclass Solution {\\n public int numberOfPermutations(int n, int[][] requirements) {\\n final int MOD = 1_000_000_007;\\n int[] req = new int[n];\\n Arrays.fill(req, -1);\\n req[0] = 0;\\n int m = 0;\\n for (int[] p : requirements) {\\n req[p[0]] = p[1];\\n m = Math.max(m, p[1]);\\n }\\n if (req[0] > 0) {\\n return 0;\\n }\\n\\n int[] f = new int[m + 1];\\n f[0] = 1;\\n for (int i = 1; i < n; i++) {\\n int mx = req[i] < 0 ? m : req[i];\\n int r = req[i - 1];\\n if (r >= 0) {\\n Arrays.fill(f, 0, r, 0);\\n Arrays.fill(f, r + 1, Math.min(i + r, mx) + 1, f[r]);\\n Arrays.fill(f, Math.min(i + r, mx) + 1, m + 1, 0);\\n } else {\\n for (int j = 1; j <= mx; j++) { // 计算前缀和\\n f[j] = (f[j] + f[j - 1]) % MOD;\\n }\\n for (int j = mx; j > i; j--) { // 计算子数组和\\n f[j] = (f[j] - f[j - i - 1] + MOD) % MOD;\\n }\\n }\\n }\\n return f[req[n - 1]];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n vector<int> req(n, -1);\\n req[0] = 0;\\n for (auto& p : requirements) {\\n req[p[0]] = p[1];\\n }\\n if (req[0]) {\\n return 0;\\n }\\n\\n int m = ranges::max(req);\\n vector<int> f(m + 1);\\n f[0] = 1;\\n for (int i = 1; i < n; i++) {\\n int mx = req[i] < 0 ? m : req[i];\\n if (int r = req[i - 1]; r >= 0) {\\n fill(f.begin(), f.begin() + r, 0);\\n fill(f.begin() + r + 1, f.begin() + min(i + r, mx) + 1, f[r]);\\n fill(f.begin() + min(i + r, mx) + 1, f.end(), 0);\\n } else {\\n for (int j = 1; j <= mx; j++) { // 计算前缀和\\n f[j] = (f[j] + f[j - 1]) % MOD;\\n }\\n for (int j = mx; j > i; j--) { // 计算子数组和\\n f[j] = (f[j] - f[j - i - 1] + MOD) % MOD;\\n }\\n }\\n }\\n return f[req[n - 1]];\\n }\\n};\\n
\\n###go
\\nfunc numberOfPermutations(n int, requirements [][]int) int {\\nconst mod = 1_000_000_007\\nreq := make([]int, n)\\nfor i := 1; i < n; i++ {\\nreq[i] = -1\\n}\\nfor _, p := range requirements {\\nreq[p[0]] = p[1]\\n}\\nif req[0] > 0 {\\nreturn 0\\n}\\n\\nm := slices.Max(req)\\nf := make([]int, m+1)\\nf[0] = 1\\nfor i := 1; i < n; i++ {\\nmx := m\\nif req[i] >= 0 {\\nmx = req[i]\\n}\\nif r := req[i-1]; r >= 0 {\\nclear(f[:r])\\nfor j := r + 1; j <= min(i+r, mx); j++ {\\nf[j] = f[r]\\n}\\nclear(f[min(i+r, mx)+1:])\\n} else {\\nfor j := 1; j <= mx; j++ { // 计算前缀和\\nf[j] = (f[j] + f[j-1]) % mod\\n}\\nfor j := mx; j > i; j-- { // 计算子数组和\\nf[j] = (f[j] - f[j-i-1] + mod) % mod\\n}\\n}\\n}\\nreturn f[req[n-1]]\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"请看 本题视频讲解 第四题,欢迎点赞关注! 一、寻找子问题\\n\\n看示例 1,要求构造逆序对为 $2$ 的排列。\\n\\n讨论最后一个数填什么:\\n\\n填 $2$,那么前面不会有比 $2$ 大的数,这意味着剩下的 $n-1=2$ 个数的逆序对等于 $2-0=2$。\\n填 $1$,那么前面一定有 $1$ 个比 $1$ 大的数,这意味着剩下的 $n-1=2$ 个数的逆序对等于 $2-1=1$。\\n填 $0$,那么前面一定有 $2$ 个比 $0$ 大的数,这意味着剩下的 $n-1=2$ 个数的逆序对等于 $2-2=0$。\\n\\n这些问题都是和原问题相似的、规模更小的子问题,可以用递归解…","guid":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/jiao-ni-yi-bu-bu-si-kao-dpcong-ji-yi-hua-974t","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-23T01:32:45.506Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"从左到右操作,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/cong-zuo-dao-you-cao-zuo-jian-ji-xie-fa-yzcde","content":"由于 $\\\\textit{nums}[i]$ 会被发生在 $i$ 左侧的操作影响,我们先从最左边的 $\\\\textit{nums}[0]$ 开始思考。
\\n讨论是否要在 $i=0$ 处操作:
\\n对后续元素来说,由于反转偶数次等于没反转,所以只需考虑操作次数的奇偶性。
\\n一般地,设遍历到 $x=\\\\textit{nums}[i]$ 时,之前执行了 $k$ 次操作,分类讨论:
\\n问:为什么这样做是对的?
\\n答:
\\n问:题目要求的「最少」体现在哪里?
\\n答:对同一个 $i$ 至多选择一次,就可以做到最少的操作次数。
\\n具体请看 视频讲解 第三题,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n k = 0\\n for x in nums:\\n if x == k % 2: # 必须操作\\n k += 1\\n return k\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int k = 0;\\n for (int x : nums) {\\n if (x == k % 2) { // 必须操作\\n k++;\\n }\\n }\\n return k;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int k = 0;\\n for (int x : nums) {\\n if (x == k % 2) { // 必须操作\\n k++;\\n }\\n }\\n return k;\\n }\\n};\\n
\\n###c
\\nint minOperations(int* nums, int numsSize) {\\n int k = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == k % 2) { // 必须操作\\n k++;\\n }\\n }\\n return k;\\n}\\n
\\n###go
\\nfunc minOperations(nums []int) (k int) {\\nfor _, x := range nums {\\nif x == k%2 { // 必须操作\\nk++\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar minOperations = function(nums) {\\n let k = 0;\\n for (const x of nums) {\\n if (x === k % 2) { // 必须操作\\n k++;\\n }\\n }\\n return k;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>) -> i32 {\\n let mut k = 0;\\n for x in nums {\\n if x == k % 2 { // 必须操作\\n k += 1;\\n }\\n }\\n k\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 由于 $\\\\textit{nums}[i]$ 会被发生在 $i$ 左侧的操作影响,我们先从最左边的 $\\\\textit{nums}[0]$ 开始思考。\\n\\n讨论是否要在 $i=0$ 处操作:\\n\\n如果 $\\\\textit{nums}[0]=1$,不需要操作,问题变成剩下 $n-1$ 个数的子问题。\\n如果 $\\\\textit{nums}[0]=0$,一定要操作,问题变成剩下 $n-1$ 个数(在操作次数是 $1$ 的情况下)的子问题。\\n\\n对后续元素来说,由于反转偶数次等于没反转,所以只需考虑操作次数的奇偶性。\\n\\n一般地,设遍历到 $x=\\\\textit{nums}[i…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/cong-zuo-dao-you-cao-zuo-jian-ji-xie-fa-yzcde","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T23:23:56.264Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"从左到右修改(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/cong-zuo-dao-you-xiu-gai-pythonjavacgo-b-k38u","content":"给定一个 $01$ 数组 $\\\\textit{nums}$,每次操作,你可以:
\\n返回把 $\\\\textit{nums}$ 全变成 $1$ 的最小操作次数,如果无法做到则返回 $-1$。
\\n讨论是否需要对 $i=0$ 执行操作:
\\n接下来,讨论是否需要对 $i=1$ 执行操作,处理方式同上。
\\n依此类推,一直到 $i=n-3$ 处理完后,还剩下 $\\\\textit{nums}[n-2]$ 和 $\\\\textit{nums}[n-1]$,这两个数必须都等于 $1$,否则无法达成题目要求。
\\n###py
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n ans = 0\\n for i in range(len(nums) - 2):\\n if nums[i] == 0: # 必须操作\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n ans += 1\\n return ans if nums[-2] and nums[-1] else -1\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums) {\\n int n = nums.length;\\n int ans = 0;\\n for (int i = 0; i < n - 2; i++) {\\n if (nums[i] == 0) { // 必须操作\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return nums[n - 2] != 0 && nums[n - 1] != 0 ? ans : -1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int n = nums.size();\\n int ans = 0;\\n for (int i = 0; i < n - 2; i++) {\\n if (nums[i] == 0) { // 必须操作\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return nums[n - 2] && nums[n - 1] ? ans : -1;\\n }\\n};\\n
\\n###c
\\nint minOperations(int* nums, int n) {\\n int ans = 0;\\n for (int i = 0; i < n - 2; i++) {\\n if (nums[i] == 0) { // 必须操作\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return nums[n - 2] && nums[n - 1] ? ans : -1;\\n}\\n
\\n###go
\\nfunc minOperations(nums []int) (ans int) {\\nn := len(nums)\\nfor i, x := range nums[:n-2] {\\nif x == 0 { // 必须操作\\nnums[i+1] ^= 1\\nnums[i+2] ^= 1\\nans++\\n}\\n}\\nif nums[n-2] == 0 || nums[n-1] == 0 {\\nreturn -1\\n}\\nreturn\\n}\\n
\\n###js
\\nvar minOperations = function(nums) {\\n const n = nums.length;\\n let ans = 0;\\n for (let i = 0; i < n - 2; i++) {\\n if (nums[i] === 0) { // 必须操作\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans++;\\n }\\n }\\n return nums[n - 2] && nums[n - 1] ? ans : -1;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_operations(mut nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let mut ans = 0;\\n for i in 0..n - 2 {\\n if nums[i] == 0 { // 必须操作\\n nums[i + 1] ^= 1;\\n nums[i + 2] ^= 1;\\n ans += 1;\\n }\\n }\\n\\n if nums[n - 2] != 0 && nums[n - 1] != 0 {\\n ans\\n } else {\\n -1\\n }\\n }\\n}\\n
\\n把题目中的 $3$ 替换成 $k$,其中 $1\\\\le k \\\\le n$,你能想出一个与 $k$ 无关的 $\\\\mathcal{O}(n)$ 做法吗?
\\n见 CF1955E,枚举 $k$,变成上述思考题。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 给定一个 $01$ 数组 $\\\\textit{nums}$,每次操作,你可以:\\n\\n选一个 $[0,n-3]$ 中的下标 $i$,把 $\\\\textit{nums}[i],\\\\textit{nums}[i+1],\\\\textit{nums}[i+2]$ 都反转,即异或 $1$。\\n\\n返回把 $\\\\textit{nums}$ 全变成 $1$ 的最小操作次数,如果无法做到则返回 $-1$。\\n\\n思路\\n\\n讨论是否需要对 $i=0$ 执行操作:\\n\\n如果 $\\\\textit{nums}[0]=1$,不需要操作,问题变成剩下 $n-1$ 个数的子问题。\\n如果 $\\\\textit…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/cong-zuo-dao-you-xiu-gai-pythonjavacgo-b-k38u","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T23:15:51.373Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心思路,遍历遇 0 就操作","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/tan-xin-si-lu-bian-li-pan-duan-ji-shu-by-3n9n","content":"\\n\\nProblem: 100344. 使二进制数组全部等于 1 的最少操作次数 I
\\n
[TOC]
\\n贪心思路,遍历数组,遇到 0 就进行操作,并计数操作次数。
\\n小技巧: 遇到 nums[ i ] == 0 时,只要对 nums[ i + 1 ]、nums[ i + 2 ] 进行操作异或运算就可以了,nums[ i ] 就别计算了,后续不会再遍历到 nums[ i ] 的,这样循环体里少一次计算。
\\n执行用时分布126ms击败100.00%;消耗内存分布14.20MB击败100.00%
\\n###C
\\nint minOperations(int* nums, int numsSize) {\\n int ans = 0, i = 0; \\n for (numsSize -= 2; i < numsSize; ++ i)\\n if (nums[i] == 0)\\n nums[i + 1] ^= 1, nums[i + 2] ^= 1, ++ ans;\\n return nums[i] && nums[++ i] ? ans : -1; \\n}\\n
\\n###Python3
\\nclass Solution:\\n def minOperations(self, nums: List[int]) -> int:\\n ans = 0\\n for i in range(len(nums) - 2):\\n if nums[i] == 0:\\n nums[i + 1] ^= 1\\n nums[i + 2] ^= 1\\n ans += 1\\n return ans if nums[-2 :] == [1, 1] else -1\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100344. 使二进制数组全部等于 1 的最少操作次数 I [TOC]\\n\\n贪心思路,遍历数组,遇到 0 就进行操作,并计数操作次数。\\n\\n小技巧: 遇到 nums[ i ] == 0 时,只要对 nums[ i + 1 ]、nums[ i + 2 ] 进行操作异或运算就可以了,nums[ i ] 就别计算了,后续不会再遍历到 nums[ i ] 的,这样循环体里少一次计算。\\n\\n执行用时分布126ms击败100.00%;消耗内存分布14.20MB击败100.00%\\n\\n###C\\n\\nint minOperations(int* nums…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-i//solution/tan-xin-si-lu-bian-li-pan-duan-ji-shu-by-3n9n","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T22:34:24.779Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/mei-ju-by-tsreaper-pf84","content":"把从下标 $i$ 开始的后缀全部反转,称为对下标 $i$ 进行操作。
\\n首先,对一个下标进行两次操作,等于没有操作。因此每个下标最多操作一次。
\\n其次,如果 nums[0] == 0
,那么只能对下标 $0$ 进行操作才能让它变成 $1$。确定了是否要对下标 $0$ 进行操作之后,问题就变成了 nums[1..n - 1]
的,长度为 $(n - 1)$ 的子问题。
因此从左到右枚举每个位置,看要不要进行操作即可。这里有个小问题:如果对前面进行了 $x$ 次操作,那么 nums[i]
的值现在是多少?答案是 nums[i] xor (x mod 2)
。
复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums) {\\n int n = nums.size();\\n int ans = 0, now = 0;\\n for (int i = 0; i < n; i++) if ((nums[i] ^ now) == 0) ans++, now ^= 1;\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 把从下标 $i$ 开始的后缀全部反转,称为对下标 $i$ 进行操作。\\n\\n首先,对一个下标进行两次操作,等于没有操作。因此每个下标最多操作一次。\\n\\n其次,如果 nums[0] == 0,那么只能对下标 $0$ 进行操作才能让它变成 $1$。确定了是否要对下标 $0$ 进行操作之后,问题就变成了 nums[1..n - 1] 的,长度为 $(n - 1)$ 的子问题。\\n\\n因此从左到右枚举每个位置,看要不要进行操作即可。这里有个小问题:如果对前面进行了 $x$ 次操作,那么 nums[i] 的值现在是多少?答案是 nums[i] xor (x mod 2…","guid":"https://leetcode.cn/problems/minimum-operations-to-make-binary-array-elements-equal-to-one-ii//solution/mei-ju-by-tsreaper-pf84","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T17:53:03.148Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"递推","url":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/di-tui-by-tsreaper-9bqb","content":"本题解的下标从 $0$ 开始。
\\n设 $f(i, j)$ 表示从 $0$ 到 $i$ 的排列中恰有 $j$ 个逆序对的排列数。为了得到递推方程,我们考虑下标 $i$ 的数 $a_i$ 和前面 $i$ 个数的大小关系。如果 $a_i$ 比其中 $d$ 个数小,那么将会引入额外 $d$ 个逆序对。因此递推方程即为
\\n$$
\\nf(i, j) = \\\\sum\\\\limits_{d = 0}^i f(i - 1, j - d)
\\n$$
当然,题目里还引入了一些限制,要求长度为 $p_t$ 的前缀逆序对数恰为 $c_t$。那么遇到 $i = p_t$ 时,我们只计算 $f(p_t, j = c_t)$ 的值,其它的 $f(p_t, j) = 0$ 即可。
\\n直接计算递推方程的复杂度是 $\\\\mathcal{O}(n^2m)$ 的,其中 $m$ 是逆序对数的最大值。因为本题数据范围很小也能通过。实际上,很容易利用前缀和将复杂度优化至 $\\\\mathcal{O}(nm)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int numberOfPermutations(int n, vector<vector<int>>& requirements) {\\n int m = 0;\\n for (auto &req : requirements) m = max(m, req[1]);\\n\\n int A[n];\\n memset(A, -1, sizeof(A));\\n for (auto &req : requirements) A[req[0]] = req[1];\\n if (A[0] > 0) return 0;\\n\\n const int MOD = 1e9 + 7;\\n long long f[n][m + 1];\\n memset(f, 0, sizeof(f));\\n f[0][0] = 1;\\n for (int i = 1; i < n; i++) {\\n int L = 0, R = m;\\n if (A[i] >= 0) L = R = A[i];\\n for (int j = L; j <= R; j++) for (int det = 0; det <= i && det <= j; det++) f[i][j] = (f[i][j] + f[i - 1][j - det]) % MOD;\\n }\\n\\n return f[n - 1][A[n - 1]];\\n }\\n};\\n
\\n","description":"解法:递推 本题解的下标从 $0$ 开始。\\n\\n设 $f(i, j)$ 表示从 $0$ 到 $i$ 的排列中恰有 $j$ 个逆序对的排列数。为了得到递推方程,我们考虑下标 $i$ 的数 $a_i$ 和前面 $i$ 个数的大小关系。如果 $a_i$ 比其中 $d$ 个数小,那么将会引入额外 $d$ 个逆序对。因此递推方程即为\\n\\n$$\\n f(i, j) = \\\\sum\\\\limits_{d = 0}^i f(i - 1, j - d)\\n $$\\n\\n当然,题目里还引入了一些限制,要求长度为 $p_t$ 的前缀逆序对数恰为 $c_t$。那么遇到 $i = p_t$ 时,我们只计算…","guid":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/di-tui-by-tsreaper-9bqb","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T17:52:47.081Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"滑动窗口优化 dp","url":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/hua-dong-chuang-kou-you-hua-dp-by-mipha-7zetx","content":"\\n\\nProblem: 100333. 统计逆序对的数目
\\n
[TOC]
\\n假设构造了一个长度为n
序列L
,L
中包含[0,n-1]
,且不重复。
\\nrequirements[i] = [end,cnt]
翻译过来就是L[:end+1]
中的逆序对刚好有cnt
个,假设该序列L
满足所有的requirements
,则结果+1
,求有多少种序列满足。
tag
思路这种结果要求mod
的,一般就是组合数学+逆元
或者记忆化搜索
或者dp
,从数据量来看:
\\n$$
\\n2 <= n <= 300 \\\\
\\n0 <= cnt_i <= 400
\\n$$
\\ndp
或者记忆化搜索
应该能解决。
\\n而且题目其实已经给出了思考方向,每一个requirements[h]
,满足的区间不是序列中间的某个区间,而是前缀区间,那么dp
的第一维度就可以是前i
个数组成的序列。
dp[i][t]
,代表序列只包含[0,i]
时的逆序对个数为t
时有多少种情况。
\\n那么新增加一个i
值时,逆序对会有什么变化:
\\n假设上一个状态的某个序列是 [0,2,1]
,很明显逆序对个数只有1
对(2,1)
。
\\n当新增一个3
时,插入到上个序列中会有以下情况:
[0,2,1,3] => 1 对逆序对\\n[0,2,3,1] => 2 对逆序对,多了(3,1)\\n[0,3,2,1] => 3 对逆序对,多了(3,2) (3,1)\\n[3,0,2,1] => 4 对逆序对,多了(3,0) (3,2) (3,1)\\n
\\n很明显新插入一个值i
,逆序对的个数会增加[0,i]
对。
因此转移方程有两种情况:
\\ni
在某个i,cnt = requirements[h]
中时,只有dp[i][cnt]
是有可能有值的,其他dp[i][t != cnt]
必定等于0
,因为不满足requirements[h]
。 # 只更新 dp[i][cnt]\\n # 枚举可增加的逆序对个数 d\\n for d in range(i+1):\\n if t >= d:\\n dp[i][t] += dp[i-1][t-d]\\n else:\\n break\\n dp[i][t] %= mod\\n
\\ni
不在某个i,t = requirements[h]
中时,那么dp[i][j]
都有可能有值,需要遍历\\n # 枚举逆序对个数\\n for t in range(m+1):\\n # 枚举可增加的逆序对个数 d\\n for d in range(i+1):\\n if t >= d:\\n dp[i][t] += dp[i-1][t-d]\\n else:\\n break\\n dp[i][t] %= mod\\n
\\ni = 3
: dp[i][0] = dp[i-1][0]\\n dp[i][1] = dp[i-1][0] + dp[i-1][1]\\n dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2]\\n dp[i][3] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3]\\n dp[i][4] = dp[i-1][1] + dp[i-1][2] + dp[i-1][3] + dp[i-1][4]\\n
\\n可以发现,随着t
的增加,上个状态其实就是一个滑动窗口求和的过程。 # 滑动窗口求和\\n total = 0\\n # 枚举逆序对个数\\n for t in range(m+1):\\n total += dp[i-1][t]\\n if t - (i+1) >= 0:\\n total -= dp[i-1][t-(i+1)]\\n total %= mod\\n dp[i][t] = total\\n
\\n更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\nclass Solution:\\n def numberOfPermutations(self, n: int, requirements: List[List[int]]) -> int:\\n mod = int(1e9+7)\\n # end 前缀个数升序\\n requirements.sort()\\n # 最多m对逆序对\\n m = requirements[-1][1]\\n # 到第i个数,共有j个逆序对的情况数\\n dp = [[0] * (m+1) for _ in range(n)]\\n \\n j = 0\\n if requirements[0][0] == 0:\\n if requirements[0][1] != 0:\\n return 0\\n\\n j += 1\\n\\n dp[0][0] = 1\\n # 到第i个数\\n for i in range(1,n):\\n # 当前位有要求,那就只有dp[i][t]是有值的,其它肯定没有值\\n if requirements[j][0] == i:\\n t = requirements[j][1]\\n j += 1\\n # 只更新 dp[i][t]\\n # 枚举可增加的逆序对个数 d\\n for d in range(i+1):\\n if t >= d:\\n dp[i][t] += dp[i-1][t-d]\\n else:\\n break\\n dp[i][t] %= mod\\n # 当前位没有要求,那就更新所有t\\n else:\\n \'\'\'\\n 滑动窗口优化\\n i-1,最多增加i对逆序对\\n i = 3\\n dp[i][0] = dp[i-1][0]\\n dp[i][1] = dp[i-1][0] + dp[i-1][1]\\n dp[i][2] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2]\\n dp[i][3] = dp[i-1][0] + dp[i-1][1] + dp[i-1][2] + dp[i-1][3]\\n dp[i][4] = dp[i-1][1] + dp[i-1][2] + dp[i-1][3] + dp[i-1][4]\\n \'\'\'\\n # 滑动窗口求和\\n total = 0\\n # 枚举逆序对个数\\n for t in range(m+1):\\n total += dp[i-1][t]\\n if t - (i+1) >= 0:\\n total -= dp[i-1][t-(i+1)]\\n total %= mod\\n dp[i][t] = total\\n \\n return dp[-1][-1]\\n
\\n","description":"Problem: 100333. 统计逆序对的数目 [TOC]\\n\\n假设构造了一个长度为n序列L,L中包含[0,n-1],且不重复。\\n requirements[i] = [end,cnt]翻译过来就是L[:end+1]中的逆序对刚好有cnt个,假设该序列L满足所有的requirements,则结果+1,求有多少种序列满足。\\n\\ndp\\n选择 tag 思路\\n\\n这种结果要求mod的,一般就是组合数学+逆元或者记忆化搜索或者dp,从数据量来看:\\n $$\\n 2 <= n <= 300 \\\\\\n 0 <= cnt_i <= 400\\n $$\\n dp或者记忆化搜索应该能解决。\\n 而且题目其…","guid":"https://leetcode.cn/problems/count-the-number-of-inversions//solution/hua-dong-chuang-kou-you-hua-dp-by-mipha-7zetx","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-22T16:18:20.151Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3184. 构成整天的下标对数目 I","url":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-i//solution/3184-gou-cheng-zheng-tian-de-xia-biao-du-uwv9","content":"最直观的思路是遍历数组 $\\\\textit{hours}$ 中的所有满足 $i < j$ 的下标对 $(i, j)$ 并计算满足 $(\\\\textit{hours}[i] + \\\\textit{hours}[j]) \\\\bmod 24 = 0$ 的下标对的数目。遍历结束之后,即可得到构成整天的下标对数目。
\\n###Java
\\nclass Solution {\\n static final int HOURS_EACH_DAY = 24;\\n\\n public int countCompleteDayPairs(int[] hours) {\\n int pairs = 0;\\n int length = hours.length;\\n for (int i = 0; i < length; i++) {\\n for (int j = i + 1; j < length; j++) {\\n if ((hours[i] + hours[j]) % HOURS_EACH_DAY == 0) {\\n pairs++;\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const int HOURS_EACH_DAY = 24;\\n\\n public int CountCompleteDayPairs(int[] hours) {\\n int pairs = 0;\\n int length = hours.Length;\\n for (int i = 0; i < length; i++) {\\n for (int j = i + 1; j < length; j++) {\\n if ((hours[i] + hours[j]) % HOURS_EACH_DAY == 0) {\\n pairs++;\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是数组 $\\\\textit{hours}$ 的长度。需要遍历的下标对的数目是 $O(n^2)$,每个下标对的判断时间是 $O(1)$。
\\n空间复杂度:$O(1)$。
\\n为了计算数组 $\\\\textit{hours}$ 中的元素和是 $24$ 的整数倍的下标对的数目,需要计算数组 $\\\\textit{hours}$ 中的每个元素除以 $24$ 的余数,然后根据每个余数对应的元素个数计算数组 $\\\\textit{hours}$ 中的元素和是 $24$ 的整数倍的下标对的数目。以下用 $\\\\textit{counts}$ 表示除以 $24$ 的余数是 $i$ 的元素的数目,其中 $0 \\\\le i < 24$。
\\n如果有两个元素除以 $24$ 的余数分别是 $x$ 和 $y$ 且满足 $(x + y) \\\\bmod 24 = 0$,其中 $0 \\\\le x \\\\le y < 24$,则可能有以下情况。
\\n两个元素除以 $24$ 的余数不同,$1 \\\\le x \\\\le 11$,$13 \\\\le y \\\\le 23$,元素和是 $24$ 的整数倍的下标对的数目是 $\\\\textit{counts}[x] \\\\times \\\\textit{counts}[y]$。
\\n两个元素除以 $24$ 的余数相同且都是 $0$,元素和是 $24$ 的整数倍的下标对的数目是 $\\\\dfrac{\\\\textit{counts}[0] \\\\times (\\\\textit{counts}[0] - 1)}{2}$。
\\n两个元素除以 $24$ 的余数相同且都是 $12$,元素和是 $24$ 的整数倍的下标对的数目是 $\\\\dfrac{\\\\textit{counts}[12] \\\\times (\\\\textit{counts}[12] - 1)}{2}$。
\\n分别计算每种情况的元素和是 $24$ 的整数倍的下标对的数目,即可得到数组 $\\\\textit{hours}$ 中的元素和是 $24$ 的整数倍的下标对的数目。
\\n###Java
\\nclass Solution {\\n static final int HOURS_EACH_DAY = 24;\\n\\n public int countCompleteDayPairs(int[] hours) {\\n int[] counts = new int[HOURS_EACH_DAY];\\n for (int num : hours) {\\n counts[num % HOURS_EACH_DAY]++;\\n }\\n int pairs = 0;\\n for (int i = 1, j = HOURS_EACH_DAY - 1; i < j; i++, j--) {\\n pairs += counts[i] * counts[j];\\n }\\n pairs += counts[0] * (counts[0] - 1) / 2;\\n pairs += counts[HOURS_EACH_DAY / 2] * (counts[HOURS_EACH_DAY / 2] - 1) / 2;\\n return pairs;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const int HOURS_EACH_DAY = 24;\\n\\n public int CountCompleteDayPairs(int[] hours) {\\n int[] counts = new int[HOURS_EACH_DAY];\\n foreach (int num in hours) {\\n counts[num % HOURS_EACH_DAY]++;\\n }\\n int pairs = 0;\\n for (int i = 1, j = HOURS_EACH_DAY - 1; i < j; i++, j--) {\\n pairs += counts[i] * counts[j];\\n }\\n pairs += counts[0] * (counts[0] - 1) / 2;\\n pairs += counts[HOURS_EACH_DAY / 2] * (counts[HOURS_EACH_DAY / 2] - 1) / 2;\\n return pairs;\\n }\\n}\\n
\\n时间复杂度:$O(n + m)$,其中 $n$ 是数组 $\\\\textit{hours}$ 的长度,$m$ 是不同的余数个数,$m = 24$。需要遍历数组一次计算每个余数对应的元素个数,然后需要遍历每个余数一次计算元素和是 $24$ 的整数倍的下标对的数目。
\\n空间复杂度:$O(m)$,其中 $m$ 是不同的余数个数,$m = 24$。需要记录每个余数对应的歌曲数量。
\\n\\n\\nProblem: 100301. 构成整天的下标对数目 II
\\n
[TOC]
\\n相较上一题:100304. 构成整天的下标对数目 I
\\n\\n按题意数据量更大,直接像上一题用暴力求解不行,需要数学计算:
\\n第一步,hours 各数取 24 除的余数
\\n第二步,计数
\\n第三步,求和计算
\\n余数 cnt[1] 和 cnt[23] 能组成 cnt[1] * cnt[23] 对、 余数 cnt[2] 和 cnt[22] 能组成 cnt[2] * cnt[22] 对、... 余数 cnt[11] 和 cnt[13] 能组成 cnt[11] * cnt[13] 对
\\n特例,余数 cnt[0] 和 cnt[12] 是自身与自身形成整天对,分别是 C(cnt[0], 2) 和 C(cnt[12], 2)
\\n最后,返回总和。
\\n执行用时分布112ms击败100.00%;消耗内存分布62.54MB击败-%
\\n###Python3
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n cnt = Counter([x % 24 for x in hours])\\n return sum(cnt[i] * cnt[24 - i] for i in range(1, 12)) + comb(cnt[0], 2) + comb(cnt[12], 2)\\n
\\n###C
\\nlong long countCompleteDayPairs(int* hours, int hoursSize) {\\n long long cnt[24] = {0};\\n for (int i = 0; i < hoursSize; ++ i) ++ cnt[hours[i] % 24];\\n long long ans = (cnt[0] -- * cnt[0] >> 1) + (cnt[12] -- * cnt[12] >> 1);\\n for (int i = 1; i < 12; ++ i) ans += cnt[i] * cnt[24 - i];\\n return ans; \\n}\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100301. 构成整天的下标对数目 II [TOC]\\n\\n相较上一题:100304. 构成整天的下标对数目 I\\n\\n【题解】100304. 构成整天的下标对数目 I【题解】\\n\\n按题意数据量更大,直接像上一题用暴力求解不行,需要数学计算:\\n\\n第一步,hours 各数取 24 除的余数\\n\\n第二步,计数\\n\\n得到余数为 0 ~ 23 的计数 cnt[ i ]\\n\\n第三步,求和计算\\n\\n余数 cnt[1] 和 cnt[23] 能组成 cnt[1] * cnt[23] 对、 余数 cnt[2] 和 cnt[22] 能组成 cnt[2] * cnt[22…","guid":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-ii//solution/shu-xue-ji-shu-ji-suan-python-liang-xing-2ohb","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-16T04:43:51.874Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举右,维护左,O(n) 一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-i//solution/on-yi-ci-bian-li-pythonjavacgo-by-endles-snsu","content":"本题和周赛第二题 3185. 构成整天的下标对数目 II 是一样的,请看 我的题解。
\\n","description":"本题和周赛第二题 3185. 构成整天的下标对数目 II 是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-i//solution/on-yi-ci-bian-li-pythonjavacgo-by-endles-snsu","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-16T04:12:14.036Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举右,维护左,O(n) 一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-ii//solution/tao-lu-mei-ju-you-wei-hu-zuo-pythonjavac-3vhv","content":"借鉴 1. 两数之和 的思路,遍历 $\\\\textit{hours}$ 的同时,用一个哈希表(或者数组)记录元素的出现次数。
\\n举几个例子:
\\n一般地,对于 $\\\\textit{hours}[i]$,需要知道左边有多少个模 $24$ 是 $24-\\\\textit{hours}[i]\\\\bmod 24$ 的数。
\\n特别地,如果 $\\\\textit{hours}[i]$ 模 $24$ 是 $0$,那么需要知道左边有多少个模 $24$ 也是 $0$ 的数。
\\n这两种情况可以合并为:累加左边
\\n$$
\\n(24-\\\\textit{hours}[i]\\\\bmod 24)\\\\bmod 24
\\n$$
的出现次数。
\\n代码实现时,用一个长为 $24$ 的数组 $\\\\textit{cnt}$ 维护 $\\\\textit{hours}[i]\\\\bmod 24$ 的出现次数。
\\n请看本题 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def countCompleteDayPairs(self, hours: List[int]) -> int:\\n H = 24\\n ans = 0\\n cnt = [0] * H\\n for t in hours:\\n # 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n # 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t % H) % H]\\n cnt[t % H] += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long countCompleteDayPairs(int[] hours) {\\n final int H = 24;\\n long ans = 0;\\n int[] cnt = new int[H];\\n for (int t : hours) {\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t % H) % H];\\n cnt[t % H]++;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countCompleteDayPairs(vector<int> &hours) {\\n const int H = 24;\\n long long ans = 0;\\n int cnt[H]{};\\n for (int t : hours) {\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t % H) % H];\\n cnt[t % H]++;\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nlong long countCompleteDayPairs(int* hours, int hoursSize) {\\n const int H = 24;\\n long long ans = 0;\\n int cnt[H] = {};\\n for (int i = 0; i < hoursSize; i++) {\\n int t = hours[i] % H;\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t) % H];\\n cnt[t]++;\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc countCompleteDayPairs(hours []int) (ans int64) {\\n const H = 24\\n cnt := [H]int{}\\n for _, t := range hours {\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += int64(cnt[(H-t%H)%H])\\n cnt[t%H]++\\n }\\n return\\n}\\n
\\n###js
\\nvar countCompleteDayPairs = function(hours) {\\n const H = 24;\\n const cnt = Array(H).fill(0);\\n let ans = 0;\\n for (const t of hours) {\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t % H) % H];\\n cnt[t % H]++;\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn count_complete_day_pairs(hours: Vec<i32>) -> i64 {\\n const H: usize = 24;\\n let mut ans = 0i64;\\n let mut cnt = vec![0; H];\\n for t in hours {\\n let t = t as usize % H;\\n // 先查询 cnt,再更新 cnt,因为题目要求 i < j\\n // 如果先更新,再查询,就把 i = j 的情况也考虑进去了\\n ans += cnt[(H - t) % H] as i64;\\n cnt[t] += 1;\\n }\\n ans\\n }\\n}\\n
\\n对于有两个变量的题目,通常可以枚举其中一个变量,把它视作常量,从而转化成只有一个变量的问题。
\\n更多相似题目,见下面数据结构题单的第零章。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"借鉴 1. 两数之和 的思路,遍历 $\\\\textit{hours}$ 的同时,用一个哈希表(或者数组)记录元素的出现次数。 举几个例子:\\n\\n如果 $\\\\textit{hours}[i]=1$,那么需要知道左边有多少个模 $24$ 是 $23$ 的数,这些数加上 $1$ 都是 $24$ 的倍数。\\n如果 $\\\\textit{hours}[i]=2$,那么需要知道左边有多少个模 $24$ 是 $22$ 的数,这些数加上 $2$ 都是 $24$ 的倍数。\\n如果 $\\\\textit{hours}[i]=26$,那么需要知道左边有多少个模 $24$ 是 $22$ 的数…","guid":"https://leetcode.cn/problems/count-pairs-that-form-a-complete-day-ii//solution/tao-lu-mei-ju-you-wei-hu-zuo-pythonjavac-3vhv","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-16T04:08:55.844Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(U(logU)^2) 的解法","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-ii//solution/oulogu2-de-jie-fa-by-vclip-u8q0","content":"第一次见到正解是 bitset 优化的题,不过这题复杂度可以进一步做到 $O(U\\\\log^2U)$(但对本题来说,速度未必比 bitset 快)。
\\n显然,最优操作应该从小到大选数。
\\n将值域分为若干个桶 $[2^k,2^{k+1}),k=0,1,\\\\cdots$。一个关键性质是一个桶内最多选两个元素,因为桶内两个元素的和必定超过 $2^{k+1}$,无法再选择第三个数。由此可以想到将一个桶内的元素合并处理。
\\n然后使用生成函数,将之前所有可能得到的和表示为一个多项式,多项式里包含 $x^i$ 项表示可能得到 $i$ 这个数,选取桶内元素 $j$ 加入可以视作将多项式乘以 $x^j$。
\\n这里的关键限制是新增的数必须大于之前的和,为了体现这一点,可以规定单项式的相乘为非对称的,即 $x^ix^j$ 当 $i<j$ 时为 $x^{i+j}$,否则为 $0$,这样规定后,就可以用多项式操作来处理了。
\\n通常多项式乘法使用 $\\\\text{FFT}$ 来加速,但对于上面定义的非对称多项式乘法无法直接 $\\\\text{FFT}$,因此要用 $\\\\text{Karatsuba}$ 乘法辅助。
\\n假设要将两个度不超过 $n$ 的多项式 $A,B$ 相乘,首先将 $A,B$ 对半拆分,即 $A=A_1x^{n/2}+A_0,B=B_1x^{n/2}+B_0$,两者相乘结果为 $AB=A_1B_1x^n+A_0B_1x^{n/2}+A_0B_0$。
\\n注意上式中没有 $A_1B_0$ 项,由于非对称乘法的性质,这些项都为 $0$。上式中的 $A_1B_1,A_0B_0$ 依然是非对称乘法,继续递归计算;$A_0B_1$ 项保证了左边的次数一定小于右边,只需要用普通乘法计算就可以了,这里可以使用 $\\\\text{FFT}$ 来计算。
\\n因此单次非对称乘法的时间复杂度为 $T(n)=2T(\\\\dfrac{n}{2})+O(n\\\\log n)=O(n\\\\log^2n)$。
\\n最开始,只有 $0$ 是可取的,初始多项式为 $s=1$。之后,遍历每个桶,将桶内元素表示为多项式 $p$,然后令 $s:=s+sp+sp^2$,由于多项式的长度不断翻倍,时间瓶颈在最后一次乘法,复杂度为 $O(U\\\\log^2U)$。
\\n这类递归算法常用的一种优化方式是在长度小于一定值时直接暴力,这题由于多项式的系数是 bool
类型,暴力会更快,因此这个阈值可以设的很大,通过数学计算,可以算出这个阈值为 $\\\\Omega(w^2)$,取 $w=64$ 就是 $4096$,Python 实现实际取了 $16384$ 这个阈值。
这样的复杂度是 $O(U\\\\log U\\\\log\\\\dfrac{U}{w^2})$。
\\nPython 的实现基于 numpy
,速度很快;C++ 的实现只用了普通的 NTT,速度一般,如果换个基于蒙哥马利模乘和 simd 的实现应该快不少。
在 Leetcode 的环境测试,这个算法在 $[1,2,\\\\cdots,500000]$ 的数据下能 $1s$ 内计算出结果,比暴力的 numpy
解法快几倍,但 $50000$ 的数据量下优势不大。
###python
\\nimport numpy as np\\n\\ndef sym_mul(lhs, rhs):\\n m, n = len(lhs), len(rhs)\\n l = 1 << (m + n - 1).bit_length()\\n f_lhs = np.fft.rfft(lhs, l)\\n f_rhs = np.fft.rfft(rhs, l)\\n ans = np.fft.irfft(f_lhs * f_rhs)[:m + n]\\n return np.round(ans).astype(dtype = bool)\\n\\ndef asym_mul(lhs, rhs):\\n n = len(lhs)\\n if n <= 16384:\\n ans = np.zeros(2 * n, dtype = bool)\\n for i in np.flatnonzero(rhs):\\n ans[i:2 * i] |= lhs[:i]\\n return ans\\n else:\\n m = n // 2\\n lo = asym_mul(lhs[:m], rhs[:m])\\n hi = asym_mul(lhs[m:], rhs[m:])\\n ans = np.concatenate((lo, hi))\\n ans[m:m + n] |= sym_mul(lhs[:m], rhs[m:])\\n return ans\\n\\n\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n h = max(rewardValues).bit_length()\\n buks = [np.zeros(1 << i, dtype = bool) for i in range(h)]\\n for e in rewardValues:\\n t = e.bit_length() - 1\\n buks[t][e - (1 << t)] = True\\n cnt = np.zeros(2 << h, dtype = bool)\\n cnt[0] = True\\n for i, buk in enumerate(buks):\\n cnt[1 << i:3 << i] |= sym_mul(cnt[:1 << i], buk)\\n cnt[2 << i:4 << i] |= asym_mul(cnt[1 << i:2 << i], buk)\\n return np.flatnonzero(cnt)[-1].item()\\n
\\n###cpp
\\nconstexpr unsigned MOD = 998244353, G = 3;\\n\\nvoid sym_mul(unsigned* lhs, unsigned* rhs, unsigned* ans, unsigned len) {\\n ntt<0, G, MOD>(lhs, len);\\n ntt<0, G, MOD>(rhs, len);\\n for (unsigned i = 0;i < len;++i)\\n ans[i] = (1ull * lhs[i] * rhs[i]) % MOD;\\n ntt<1, qpow<MOD>(G, MOD - 2), MOD>(ans, len);\\n}\\n\\nvoid asym_mul(const unsigned* lhs, const unsigned* rhs, unsigned* ans, unsigned* buf, unsigned len) {\\n if (len <= 32) {\\n for (unsigned i = 0;i < len;++i)\\n for (unsigned j = i + 1;j < len;++j)\\n ans[i + j] += lhs[i] * rhs[j];\\n } else {\\n const unsigned half = len / 2;\\n asym_mul(lhs, rhs, ans, buf, half);\\n asym_mul(lhs + half, rhs + half, ans + 2 * half, buf, half);\\n std::fill_n(std::copy_n(lhs, half, buf), half, 0);\\n std::fill_n(std::copy_n(rhs + half, half, buf + len), half, 0);\\n sym_mul(buf, buf + len, buf, len);\\n for (unsigned i = 0;i < len;++i)\\n ans[i + half] += buf[i];\\n }\\n}\\n\\nclass Solution {\\npublic:\\n int maxTotalReward(const vector<int>& rewardValues) {\\n const unsigned h = 32 - __builtin_clz(ranges::max(rewardValues));\\n vector<vector<unsigned>> buks(h);\\n for (unsigned i = 0;i < h;++i)\\n buks[i].resize(1 << i, 0);\\n for (int e : rewardValues) {\\n const unsigned t = 31 - __builtin_clz(e);\\n buks[t][e - (1 << t)] = 1;\\n }\\n vector<unsigned> cnt = {1, 0};\\n vector<unsigned> tmp(2 << h);\\n for (unsigned i = 0;i < h;++i) {\\n auto& buk = buks[i];\\n cnt.resize(4 << i, 0);\\n fill_n(copy_n(cnt.data(), 1 << i, tmp.data()), 1 << i, 0);\\n fill_n(copy_n(buk.data(), 1 << i, tmp.data() + (2 << i)), 1 << i, 0);\\n sym_mul(tmp.data(), tmp.data() + (2 << i), tmp.data(), 2 << i);\\n for (unsigned j = 0;j < (2 << i);++j)\\n cnt[j + (1 << i)] = cnt[j + (1 << i)] || tmp[j];\\n asym_mul(cnt.data() + (1 << i), buk.data(), cnt.data() + (2 << i), tmp.data(), 1 << i);\\n }\\n for (unsigned i = 2 << h;i-- > 0;)\\n if (cnt[i] > 0)\\n return i;\\n return 0;\\n }\\n};\\n
\\n###cpp
\\ninline constexpr uint32_t rev(uint32_t x) {\\n x = ((x >> 1) & 0x55555555) | ((x & 0x55555555) << 1);\\n x = ((x >> 2) & 0x33333333) | ((x & 0x33333333) << 2);\\n x = ((x >> 4) & 0x0F0F0F0F) | ((x & 0x0F0F0F0F) << 4);\\n x = ((x >> 8) & 0x00FF00FF) | ((x & 0x00FF00FF) << 8);\\n return x >> 16 | x << 16;\\n}\\n\\ntemplate<uint32_t M>\\ninline constexpr uint32_t qpow(uint32_t a, uint32_t n) {\\n uint32_t ans = 1;\\n for (uint32_t i = n;i > 0;i >>= 1) {\\n if (i & 1) ans = uint64_t(ans) * a % M;\\n a = uint64_t(a) * a % M;\\n }\\n return ans;\\n}\\n\\ntemplate<bool Inv, uint32_t G, uint32_t M>\\ninline void ntt(uint32_t* seq, uint32_t n) {\\n const uint32_t n0 = 31 - __builtin_clz(n);\\n const uint32_t h = 32 - n0;\\n for (uint32_t i = 1;i < n;++i) {\\n const uint32_t ri = rev(i) >> h;\\n if (i < ri) swap(seq[i], seq[ri]);\\n }\\n for (uint32_t i = 0;i < n0;++i) {\\n const uint32_t d = 1 << i, step = qpow<M>(G, (M - 1) >> (i + 1));\\n for (uint32_t j = 0;j < n;j += d) {\\n uint32_t cur = 1;\\n for (const uint32_t k = j + d;j < k;++j) {\\n const uint32_t l = seq[j], r = uint64_t(seq[j + d]) * cur % M;\\n seq[j] = (l + r) % M;\\n seq[j + d] = (l + M - r) % M;\\n cur = uint64_t(cur) * step % M;\\n }\\n }\\n }\\n if (Inv) {\\n const uint32_t in = qpow<M>(n, M - 2);\\n for (uint32_t i = 0;i < n;++i) seq[i] = uint64_t(seq[i]) * in % M;\\n }\\n}\\n
\\n","description":"第一次见到正解是 bitset 优化的题,不过这题复杂度可以进一步做到 $O(U\\\\log^2U)$(但对本题来说,速度未必比 bitset 快)。 显然,最优操作应该从小到大选数。\\n\\n将值域分为若干个桶 $[2^k,2^{k+1}),k=0,1,\\\\cdots$。一个关键性质是一个桶内最多选两个元素,因为桶内两个元素的和必定超过 $2^{k+1}$,无法再选择第三个数。由此可以想到将一个桶内的元素合并处理。\\n\\n然后使用生成函数,将之前所有可能得到的和表示为一个多项式,多项式里包含 $x^i$ 项表示可能得到 $i$ 这个数,选取桶内元素 $j…","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-ii//solution/oulogu2-de-jie-fa-by-vclip-u8q0","author":"vclip","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T09:41:57.761Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"背包问题","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/bei-bao-wen-ti-by-jager-70-n3yc","content":"背包问题","description":"背包问题","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/bei-bao-wen-ti-by-jager-70-n3yc","author":"jager-70","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T09:12:23.114Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java/Python3 0-1背包的记忆化搜索写法+二分查找加速","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/javapython3-0-1bei-bao-de-ji-yi-hua-sou-plqn1","content":"\\n\\nProblem: 100319. 执行操作可获得的最大总奖励 I
\\n
时间复杂度:
\\n\\n\\n$O(S*n \\\\log n)$,其中$$S=2000$$,表示答案的值域大小
\\n
空间复杂度:
\\n\\n\\n$O(S*n)$
\\n
###Java
\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int n = rewardValues.length;\\n int[][] memo = new int[n][2001];\\n for (var it : memo) {\\n Arrays.fill(it, -1);\\n }\\n Arrays.sort(rewardValues);\\n return dfs(rewardValues, 0, 0, memo);\\n }\\n\\n private int dfs(int[] nums, int i, int sum, int[][] memo) {\\n if (i >= nums.length || sum > 2000) {\\n return 0;\\n }\\n if (memo[i][sum] != -1) {\\n return memo[i][sum];\\n }\\n int j1 = upper_bound(nums, nums[i]);\\n int j2 = upper_bound(nums, sum + nums[i]);\\n return memo[i][sum] = Math.max(\\n dfs(nums, j1, sum, memo),\\n nums[i] + dfs(nums, j2, sum + nums[i], memo)\\n );\\n }\\n\\n private static int upper_bound(int[] nums, int target) {\\n int l = 0, r = nums.length;\\n while (l < r) {\\n int mid = (l + r) / 2;\\n if (nums[mid] <= target) {\\n l = mid + 1;\\n } else {\\n r = mid;\\n }\\n }\\n return l;\\n }\\n}\\n
\\n###Python3
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n n = len(rewardValues)\\n rewardValues.sort()\\n @cache\\n def dfs(i, s):\\n if i >= n or s > 2000:\\n return 0\\n j1 = bisect_right(rewardValues, rewardValues[i])\\n j2 = bisect_right(rewardValues, rewardValues[i] + s)\\n return max(\\n dfs(j1, s),\\n rewardValues[i] + dfs(j2, s + rewardValues[i])\\n )\\n return dfs(0, 0)\\n
\\nint j1 = upper_bound(nums, nums[i]);
比int j1 = i + 1
更好吗?
如果测试用例中有很多重复的数字(事实的确如此),int j1 = upper_bound(nums, nums[i]);
这样做更快。考虑case:rewardValues=nums=[3]*10+[4]
,最优解是选择任意$$1$$个$$3$$和最后的$$4$$。如果你选了一个$$3$$,那么后面的$$3$$全都不能选,而且选择任意一个$$3$$都是一样的。这里用二分,可以保证只计算选择第一个$$3$$的状态。如果只是$$j1=i+1$$,那么会分别考虑以$$10$$个$$3$$作为起点的状态,这对答案是没有贡献的。
一般地,当前的和$$S \\\\geq 0$$,数组中接下来有$$m$$个$$x \\\\geq 1$$,如果选择了一个$$x$$,那么$$S\'=S+x \\\\geq x$$,后面的$$m-1$$个$$x$$都不能选,直接用二分查找跳过相等的数。
\\n","description":"Problem: 100319. 执行操作可获得的最大总奖励 I 时间复杂度:\\n\\n$O(S*n \\\\log n)$,其中$$S=2000$$,表示答案的值域大小\\n\\n空间复杂度:\\n\\n$O(S*n)$\\n\\n###Java\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int n = rewardValues.length;\\n int[][] memo = new int[n][2001];\\n for (var it…","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/javapython3-0-1bei-bao-de-ji-yi-hua-sou-plqn1","author":"1e9plus7","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T09:07:56.503Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3175. 找到连续赢 K 场比赛的第一位玩家","url":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/3175-zhao-dao-lian-xu-ying-k-chang-bi-sa-db8g","content":"数组 $\\\\textit{skills}$ 的长度是 $n$。由于数组 $\\\\textit{skills}$ 中的元素各不相同,因此每场比赛一定能分出胜负。
\\n首先考虑最简单的情形。当 $k = 1$ 时,第一回合结束后,$\\\\textit{skills}[0]$ 和 $\\\\textit{skills}[1]$ 中的较高技能等级的玩家获胜,并连续赢得 $k$ 场比赛。因此当 $k = 1$ 时,返回 $\\\\textit{skills}[0]$ 和 $\\\\textit{skills}[1]$ 中的较大的元素所在下标。
\\n当 $k > 1$ 时,常规思路是,在每场比赛之后更新数组 $\\\\textit{skills}$ 的状态,该做法在每场比赛更新数组的时间是 $O(n)$,最差情况下的时间复杂度高达 $O(n^2 + nk)$,该时间复杂度过高,需要优化。
\\n其实,并不需要对数组进行更新。在第一场比赛之后,无论玩家 $0$ 和玩家 $1$ 当中的哪个元素获胜,第二场比赛的另一个玩家一定是 $\\\\textit{skills}$ 中的下一个元素,即 $\\\\textit{skills}[2]$。当 $2 \\\\le i < n$ 时,第 $i$ 场比赛一定在第 $i - 1$ 场比赛中获胜的玩家和玩家 $i$ 之间进行,第 $i - 1$ 场比赛中获胜的玩家是数组 $\\\\textit{skills}$ 的下标范围 $[0, i - 1]$ 中的最高技能等级的玩家。
\\n因此,需要记录上一场比赛中获胜的玩家编号和玩家连续获胜的场数。用 用 $\\\\textit{maxSkillIndex}$ 表示该玩家的编号,用 $\\\\textit{consecutive}$ 表示该玩家连续获胜的场数。
\\n第一场比赛在玩家 $0$ 和玩家 $1$ 之间进行,第一场比赛之后,$\\\\textit{maxSkillIndex}$ 为 $\\\\textit{skills}[0]$ 和 $\\\\textit{skills}[1]$ 中的较大值所在下标,$\\\\textit{consecutive}$ 的值为 $1$。
\\n当 $2 \\\\le i < n$ 时,第 $i$ 场比赛在 $\\\\textit{maxSkill}$ 和 $\\\\textit{skill}$ 之间进行,可能有以下两种情况。
\\n如果 $\\\\textit{skills}[\\\\textit{maxSkillIndex}] > \\\\textit{skills}[i]$,则 $\\\\textit{maxSkillIndex}$ 不变,将 $\\\\textit{consecutive}$ 的值增加 $1$。如果 $\\\\textit{consecutive}$ 的值更新之后等于 $k$,则玩家 $\\\\textit{maxSkillIndex}$ 连续赢得 $k$ 场比赛,成为游戏的赢家,将 $\\\\textit{maxSkillIndex}$ 返回即可。
\\n如果 $\\\\textit{skills}[\\\\textit{maxSkillIndex}] < \\\\textit{skills}[i]$,则玩家 $i$ 获胜,将 $\\\\textit{maxSkillIndex}$ 的值更新为 $i$,并将 $\\\\textit{consecutive}$ 的值更新为 $1$。
\\n如果在经过 $n - 1$ 场比赛之后,仍然没有元素连续赢得 $k$ 场比赛,则不需要继续模拟游戏过程,而是可以直接得到答案。
\\n注意到游戏过程实质为打擂台的过程。无论进行多少回合,最后一次获胜的玩家一定是已经参与过游戏的玩家中的技能等级最高的玩家。在经过 $n - 1$ 场比赛之后,所有玩家都已经参与过游戏,因此第 $n - 1$ 场比赛中获胜的玩家为技能等级最高的玩家。用 $\\\\textit{maxSkillIndex}$ 表示技能等级最高的玩家编号,则之后的每一场比赛都在玩家 $\\\\textit{maxSkillIndex}$ 和其他玩家之间进行,玩家 $\\\\textit{maxSkillIndex}$ 将总是获胜,因此也将连续赢得 $k$ 场比赛。
\\n由此可以得到这道题的解法。
\\n从左到右遍历数组 $\\\\textit{skills}$ 的元素,模拟游戏过程,同时维护 $\\\\textit{skills}$ 的最大值所在下标 $\\\\textit{maxSkillIndex}$。如果存在一个玩家连续赢得 $k$ 场比赛,则将该玩家编号返回。
\\n如果遍历结束数组 $\\\\textit{skills}$ 之后,没有出现一个玩家连续赢得 $k$ 场比赛,则将 $\\\\textit{maxSkillIndex}$ 返回。
\\n###Java
\\nclass Solution {\\n public int findWinningPlayer(int[] skills, int k) {\\n int maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k == 1) {\\n return maxSkillIndex;\\n }\\n int consecutive = 1;\\n int n = skills.length;\\n for (int i = 2; i < n; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive == k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int FindWinningPlayer(int[] skills, int k) {\\n int maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k == 1) {\\n return maxSkillIndex;\\n }\\n int consecutive = 1;\\n int n = skills.Length;\\n for (int i = 2; i < n; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive == k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector<int>& skills, int k) {\\n int maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k == 1) {\\n return maxSkillIndex;\\n }\\n int consecutive = 1;\\n int n = skills.size();\\n for (int i = 2; i < n; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive == k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n }\\n};\\n
\\n###Python
\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n maxSkillIndex = 0 if skills[0] > skills[1] else 1\\n if k == 1:\\n return maxSkillIndex\\n consecutive = 1\\n n = len(skills)\\n for i in range(2, n):\\n if skills[maxSkillIndex] > skills[i]:\\n consecutive += 1\\n if consecutive == k:\\n return maxSkillIndex\\n else:\\n maxSkillIndex = i\\n consecutive = 1\\n return maxSkillIndex\\n
\\n###C
\\nint findWinningPlayer(int* skills, int skillsSize, int k) {\\n int maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k == 1) {\\n return maxSkillIndex;\\n }\\n int consecutive = 1;\\n for (int i = 2; i < skillsSize; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive == k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n}\\n
\\n###Go
\\nfunc findWinningPlayer(skills []int, k int) int {\\n maxSkillIndex := 0\\n if skills[0] < skills[1] {\\n maxSkillIndex = 1\\n }\\n if k == 1 {\\n return maxSkillIndex\\n }\\n consecutive := 1\\n n := len(skills)\\n for i := 2; i < n; i++ {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++\\n if consecutive == k {\\n return maxSkillIndex\\n }\\n } else {\\n maxSkillIndex = i\\n consecutive = 1\\n }\\n }\\n return maxSkillIndex\\n}\\n
\\n###JavaScript
\\nvar findWinningPlayer = function(skills, k) {\\n let maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k === 1) {\\n return maxSkillIndex;\\n }\\n let consecutive = 1;\\n let n = skills.length;\\n for (let i = 2; i < n; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive === k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n};\\n
\\n###TypeScript
\\nfunction findWinningPlayer(skills: number[], k: number): number {\\n let maxSkillIndex = skills[0] > skills[1] ? 0 : 1;\\n if (k === 1) {\\n return maxSkillIndex;\\n }\\n let consecutive = 1;\\n let n = skills.length;\\n for (let i = 2; i < n; i++) {\\n if (skills[maxSkillIndex] > skills[i]) {\\n consecutive++;\\n if (consecutive === k) {\\n return maxSkillIndex;\\n }\\n } else {\\n maxSkillIndex = i;\\n consecutive = 1;\\n }\\n }\\n return maxSkillIndex;\\n};\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{skills}$ 的长度。需要遍历数组 $\\\\textit{skills}$ 一次,对于每个元素的操作时间是 $O(1)$,因此总时间复杂度是 $O(n)$。
\\n空间复杂度:$O(1)$。
\\n在小数据范围的简单版本执行操作可获得的最大总奖励 I中,我们可以使用01背包写出如下代码:
\\n###Python
\\nclass Solution:\\n def maxTotalReward(self, nums: List[int]) -> int:\\n nums = sorted(set(nums))\\n dp = [False] * (nums[-1] * 2)\\n dp[0] = True\\n for x in nums:\\n for i in range(x):\\n dp[x+i] |= dp[i]\\n return next(i for i in range(len(dp)-1, -1, -1) if dp[i])\\n
\\n$nums$需要先排序,才可以更优地先选较小值、再选较大值;去重只是效率考虑。这里需要注意到,每次选完$x$以后,总价值不会超过$2x$,所以dp
数组长度只要开到$2\\\\times\\\\max(nums)$即可。
在大数据范围下,为了加快状态转移,正确做法是采用bitset,参考其他人题解。Python可以使用无限长度的int
表示bitset本就足够强大,然而力扣上的Python还有更逆天的东西:NumPy!(感谢羊神@小羊肖恩提供的灵感)
###Python
\\nimport numpy as np\\nclass Solution:\\n def maxTotalReward(self, nums: List[int]) -> int:\\n nums = sorted(set(nums))\\n dp = np.zeros(nums[-1] * 2, bool)\\n dp[0] = True\\n for x in nums:\\n dp[x:x*2] |= dp[:x]\\n return np.nonzero(dp)[0][-1].item()\\n
\\n在此不分析时间复杂度。以上代码居然只跑了300ms,甚至比直接用int
表示bitset都快!
NumPy在力扣还可以解决其他题目,即使毫无时间优势但就是能过,就连正常实现复杂度过高的算法也能用NumPy实现。如:
\\n\\n###Python
\\nimport numpy as np\\nclass Solution:\\n def transpose(self, matrix: List[List[int]]) -> List[List[int]]:\\n return np.array(matrix).T.tolist()\\n
\\n\\n###Python
\\nimport numpy as np\\nclass Solution:\\n def matrixReshape(self, mat: List[List[int]], r: int, c: int) -> List[List[int]]:\\n return mat if len(mat)*len(mat[0]) != r*c else np.array(mat).reshape(r, c).tolist()\\n
\\n\\n###Python
\\nimport numpy as np\\nclass Solution:\\n def rotate(self, matrix: List[List[int]]) -> None:\\n matrix[:] = np.rot90(np.array(matrix), 3).tolist()\\n
\\n\\n###Python
\\nimport numpy as np\\nclass Solution:\\n def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]:\\n return (1 - np.fliplr(np.array(image))).tolist()\\n
\\n\\n###Python
\\nimport numpy as np\\nclass Solution:\\n def rangeAddQueries(self, n: int, queries: List[List[int]]) -> List[List[int]]:\\n mat = np.zeros((n, n), int)\\n for r1, c1, r2, c2 in queries:\\n mat[r1:r2+1, c1:c2+1] += 1\\n return mat.tolist()\\n
\\n\\n###Python
\\nimport numpy as np\\nclass NumMatrix:\\n __slots__ = \'mat\'\\n def __init__(self, matrix: List[List[int]]):\\n self.mat = np.array(matrix, int)\\n\\n def sumRegion(self, r1: int, c1: int, r2: int, c2: int) -> int:\\n return np.sum(self.mat[r1:r2+1, c1:c2+1]).item()\\n
\\n诸如此类。
\\n","description":"在小数据范围的简单版本执行操作可获得的最大总奖励 I中,我们可以使用01背包写出如下代码: ###Python\\n\\nclass Solution:\\n def maxTotalReward(self, nums: List[int]) -> int:\\n nums = sorted(set(nums))\\n dp = [False] * (nums[-1] * 2)\\n dp[0] = True\\n for x in nums:\\n for i in range(x):…","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-ii//solution/ni-tian-numpydong-tai-gui-hua-python-by-6xx08","author":"fatalerror-i","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T05:53:40.286Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"bitset/bigint 优化 0-1 背包(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/0-1-bei-bao-by-endlesscheng-702p","content":"本题和周赛第四题是一样的,请看 我的题解。
\\n","description":"本题和周赛第四题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-i//solution/0-1-bei-bao-by-endlesscheng-702p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T04:11:48.786Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"bitset/bigint 优化 0-1 背包 + 两数之和优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-total-reward-using-operations-ii//solution/bitset-you-hua-0-1-bei-bao-by-endlessche-m1xn","content":"对于 $\\\\textit{rewardValues}$ 中的数,如果先选大的,就没法再选小的,所以按照从小到大的顺序选是最优的。
\\n把 $\\\\textit{rewardValues}$ 从小到大排序。
\\n排序后,问题类似 0-1 背包,原理请看【基础算法精讲 18】。
\\n定义 $f[i][j]$ 表示能否从 $\\\\textit{rewardValues}$ 的前 $i$ 个数中得到总奖励 $j$。
\\n设 $\\\\textit{rewardValues}$ 的第 $i$ 个数为 $v$,考虑 $v$ 选或不选:
\\n选或不选满足其一即可,所以有
\\n$$
\\nf[i][j] = f[i-1][j] \\\\vee f[i-1][j-v]
\\n$$
其中 $\\\\vee$ 即编程语言中的 ||
。
初始值 $f[0][0] = \\\\texttt{true}$。
\\n答案为最大的满足 $f[n][j]=\\\\texttt{true}$ 的 $j$。
\\n这样可以解决 3180. 执行操作可获得的最大总奖励 I,但本题数据范围更大,需要去掉第一个维度,并用 bitset 优化(也可以用 bigint)。
\\n上面的转移方程,先去掉第一个维度,得到 $f[j] = f[j] \\\\vee f[j-v]$,其中 $v\\\\le j<2v$。
\\n进一步地,把一维数组压缩成一个二进制数 $f$,其中二进制从低到高第 $j$ 位为 $1$ 表示 $f[j]=\\\\texttt{true}$,为 $0$ 表示 $f[j]=\\\\texttt{false}$。转换后 $\\\\vee$ 就是或运算(OR)了。
\\n比如 $v=3$,我们会把:
\\n这相当于取 $f$ 的低 $v$ 位,再左移 $v$ 位,然后 OR 到 $f$ 中,即编程语言中的 f |= (f & ((1 << v) - 1)) << v
,具体来说:
(1 << v) - 1
会得到一个低 $v$ 位全为 $1$ 的二进制数。f & ((1 << v) - 1)
得到 $f$ 的低 $v$ 位。(f & ((1 << v) - 1)) << v
把 f & ((1 << v) - 1)
左移 $v$ 位。f |= (f & ((1 << v) - 1)) << v
。初始值 $f=1$。
\\n答案为 $f$ 的最高位,即 $f$ 的二进制长度减一。
\\n小优化:代码实现时,可以先把 $\\\\textit{rewardValues}$ 中的重复数字去掉再 DP,毕竟无法选两个一样的数。
\\n具体请看 视频讲解,欢迎点赞关注!
\\n###py
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n f = 1\\n for v in sorted(set(rewardValues)):\\n f |= (f & ((1 << v) - 1)) << v\\n return f.bit_length() - 1\\n
\\n###java
\\n// 超时,请阅读下面的优化二和优化三\\nimport java.math.BigInteger;\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n BigInteger f = BigInteger.ONE;\\n for (int v : Arrays.stream(rewardValues).distinct().sorted().toArray()) {\\n BigInteger mask = BigInteger.ONE.shiftLeft(v).subtract(BigInteger.ONE);\\n f = f.or(f.and(mask).shiftLeft(v));\\n }\\n return f.bitLength() - 1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n ranges::sort(rewardValues);\\n rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());\\n\\n bitset<100000> f{1};\\n for (int v : rewardValues) {\\n int shift = f.size() - v;\\n // 左移 shift 再右移 shift,把所有 >= v 的比特位置 0\\n // f |= f << shift >> shift << v;\\n f |= f << shift >> (shift - v); // 简化上式\\n }\\n for (int i = rewardValues.back() * 2 - 1; ; i--) {\\n if (f.test(i)) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\n\\none := big.NewInt(1)\\nf := big.NewInt(1)\\np := new(big.Int)\\nfor _, v := range rewardValues {\\nmask := p.Sub(p.Lsh(one, uint(v)), one)\\nf.Or(f, p.Lsh(p.And(f, mask), uint(v)))\\n}\\nreturn f.BitLen() - 1\\n}\\n
\\n###go
\\nconst w = bits.UintSize\\n\\ntype bitset []uint\\n\\n// b <<= k\\nfunc (b bitset) lsh(k int) bitset {\\nshift, offset := k/w, k%w\\nif offset == 0 {\\n// Fast path\\ncopy(b[shift:], b)\\n} else {\\nfor i := len(b) - 1; i > shift; i-- {\\nb[i] = b[i-shift]<<offset | b[i-shift-1]>>(w-offset)\\n}\\nb[shift] = b[0] << offset\\n}\\nclear(b[:shift])\\nreturn b\\n}\\n\\n// 把 >= start 的清零\\nfunc (b bitset) resetRange(start int) bitset {\\ni := start / w\\nb[i] &= ^(^uint(0) << (start % w))\\nclear(b[i+1:])\\nreturn b\\n}\\n\\n// b |= c\\nfunc (b bitset) unionFrom(c bitset) {\\nfor i, v := range c {\\nb[i] |= v\\n}\\n}\\n\\nfunc (b bitset) lastIndex1() int {\\nfor i := len(b) - 1; i >= 0; i-- {\\nif b[i] != 0 {\\nreturn i*w | (bits.Len(b[i]) - 1)\\n}\\n}\\nreturn -1\\n}\\n\\nfunc maxTotalReward(rewardValues []int) int {\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\n\\nm := rewardValues[len(rewardValues)-1]\\nf := make(bitset, m*2/w+1)\\nf[0] = 1\\nfor _, v := range rewardValues {\\nf.unionFrom(slices.Clone(f).lsh(v).resetRange(v * 2))\\n}\\nreturn f.lastIndex1()\\n}\\n
\\n设 $m=\\\\max(\\\\textit{rewardValues})$,如果数组中包含 $m-1$,则答案为 $2m-1$(因为这是答案的上界),无需计算 DP。
\\n问:为什么 $2m-1$ 是答案的上界?
\\n答:如果最后一步选的数是 $x$,而 $x<m$,那么把 $x$ 替换成 $m$ 也符合要求,矛盾,所以最后一步选的一定是 $m$。在选 $m$ 之前,元素和至多是 $m-1$,选了 $m$ 之后,元素和至多是 $2m-1$。我们无法得到比 $2m-1$ 更大的元素和。
\\n###py
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n m = max(rewardValues)\\n if m - 1 in rewardValues:\\n return m * 2 - 1\\n\\n f = 1\\n for v in sorted(set(rewardValues)):\\n f |= (f & ((1 << v) - 1)) << v\\n return f.bit_length() - 1\\n
\\n###java
\\nimport java.math.BigInteger;\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int m = 0;\\n for (int v : rewardValues) {\\n m = Math.max(m, v);\\n }\\n for (int v : rewardValues) {\\n if (v == m - 1) {\\n return m * 2 - 1;\\n }\\n }\\n\\n BigInteger f = BigInteger.ONE;\\n for (int v : Arrays.stream(rewardValues).distinct().sorted().toArray()) {\\n BigInteger mask = BigInteger.ONE.shiftLeft(v).subtract(BigInteger.ONE);\\n f = f.or(f.and(mask).shiftLeft(v));\\n }\\n return f.bitLength() - 1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n int m = ranges::max(rewardValues);\\n if (ranges::find(rewardValues, m - 1) != rewardValues.end()) {\\n return m * 2 - 1;\\n }\\n\\n ranges::sort(rewardValues);\\n rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());\\n bitset<100000> f{1};\\n for (int v : rewardValues) {\\n int shift = f.size() - v;\\n // 左移 shift 再右移 shift,把所有 >= v 的比特位置 0\\n // f |= f << shift >> shift << v;\\n f |= f << shift >> (shift - v); // 简化上式\\n }\\n for (int i = m * 2 - 1;; i--) {\\n if (f.test(i)) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nm := slices.Max(rewardValues)\\nif slices.Contains(rewardValues, m-1) {\\nreturn m*2 - 1\\n}\\n\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\n\\none := big.NewInt(1)\\nf := big.NewInt(1)\\np := new(big.Int)\\nfor _, v := range rewardValues {\\nmask := p.Sub(p.Lsh(one, uint(v)), one)\\nf.Or(f, p.Lsh(p.And(f, mask), uint(v)))\\n}\\nreturn f.BitLen() - 1\\n}\\n
\\n###go
\\nconst w = bits.UintSize\\n\\ntype bitset []uint\\n\\n// b <<= k\\nfunc (b bitset) lsh(k int) bitset {\\nshift, offset := k/w, k%w\\nif offset == 0 {\\n// Fast path\\ncopy(b[shift:], b)\\n} else {\\nfor i := len(b) - 1; i > shift; i-- {\\nb[i] = b[i-shift]<<offset | b[i-shift-1]>>(w-offset)\\n}\\nb[shift] = b[0] << offset\\n}\\nclear(b[:shift])\\nreturn b\\n}\\n\\n// 把 >= start 的清零\\nfunc (b bitset) resetRange(start int) bitset {\\ni := start / w\\nb[i] &= ^(^uint(0) << (start % w))\\nclear(b[i+1:])\\nreturn b\\n}\\n\\n// b |= c\\nfunc (b bitset) unionFrom(c bitset) {\\nfor i, v := range c {\\nb[i] |= v\\n}\\n}\\n\\nfunc (b bitset) lastIndex1() int {\\nfor i := len(b) - 1; i >= 0; i-- {\\nif b[i] != 0 {\\nreturn i*w | (bits.Len(b[i]) - 1)\\n}\\n}\\nreturn -1\\n}\\n\\nfunc maxTotalReward(rewardValues []int) int {\\nm := slices.Max(rewardValues)\\nif slices.Contains(rewardValues, m-1) {\\nreturn m*2 - 1\\n}\\n\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\nf := make(bitset, m*2/w+1)\\nf[0] = 1\\nfor _, v := range rewardValues {\\nf.unionFrom(slices.Clone(f).lsh(v).resetRange(v * 2))\\n}\\nreturn f.lastIndex1()\\n}\\n
\\n如果有两个不同元素之和等于 $m-1$,也可以直接返回 $2m-1$。
\\n由于在随机数据下,几乎 100% 的概率有两数之和等于 $m-1$,而力扣又喜欢出随机数据,所以在大多数情况下,本题就是一道 1. 两数之和。
\\n###py
\\nclass Solution:\\n def maxTotalReward(self, rewardValues: List[int]) -> int:\\n m = max(rewardValues)\\n s = set()\\n for v in rewardValues:\\n if v in s:\\n continue\\n if v == m - 1 or m - 1 - v in s:\\n return m * 2 - 1\\n s.add(v)\\n\\n f = 1\\n for v in sorted(s):\\n f |= (f & ((1 << v) - 1)) << v\\n return f.bit_length() - 1\\n
\\n###java
\\nimport java.math.BigInteger;\\n\\nclass Solution {\\n public int maxTotalReward(int[] rewardValues) {\\n int m = 0;\\n for (int v : rewardValues) {\\n m = Math.max(m, v);\\n }\\n Set<Integer> set = new HashSet<>();\\n for (int v : rewardValues) {\\n if (v == m - 1) {\\n return m * 2 - 1;\\n }\\n if (set.contains(v)) {\\n continue;\\n }\\n if (set.contains(m - 1 - v)) {\\n return m * 2 - 1;\\n }\\n set.add(v);\\n }\\n\\n Arrays.sort(rewardValues);\\n int pre = 0;\\n BigInteger f = BigInteger.ONE;\\n for (int v : rewardValues) {\\n if (v == pre) {\\n continue;\\n }\\n BigInteger mask = BigInteger.ONE.shiftLeft(v).subtract(BigInteger.ONE);\\n f = f.or(f.and(mask).shiftLeft(v));\\n pre = v;\\n }\\n return f.bitLength() - 1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maxTotalReward(vector<int>& rewardValues) {\\n int m = ranges::max(rewardValues);\\n unordered_set<int> s;\\n for (int v : rewardValues) {\\n if (s.contains(v)) {\\n continue;\\n }\\n if (v == m - 1 || s.contains(m - 1 - v)) {\\n return m * 2 - 1;\\n }\\n s.insert(v);\\n }\\n\\n ranges::sort(rewardValues);\\n rewardValues.erase(unique(rewardValues.begin(), rewardValues.end()), rewardValues.end());\\n\\n bitset<100000> f{1};\\n for (int v : rewardValues) {\\n int shift = f.size() - v;\\n // 左移 shift 再右移 shift,把所有 >= v 的比特位置 0\\n // f |= f << shift >> shift << v;\\n f |= f << shift >> (shift - v); // 简化上式\\n }\\n for (int i = m * 2 - 1;; i--) {\\n if (f.test(i)) {\\n return i;\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc maxTotalReward(rewardValues []int) int {\\nm := slices.Max(rewardValues)\\nhas := map[int]bool{}\\nfor _, v := range rewardValues {\\nif v == m-1 {\\nreturn m*2 - 1\\n}\\nif has[v] {\\ncontinue\\n}\\nif has[m-1-v] {\\nreturn m*2 - 1\\n}\\nhas[v] = true\\n}\\n\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\n\\none := big.NewInt(1)\\nf := big.NewInt(1)\\np := new(big.Int)\\nfor _, v := range rewardValues {\\nmask := p.Sub(p.Lsh(one, uint(v)), one)\\nf.Or(f, p.Lsh(p.And(f, mask), uint(v)))\\n}\\nreturn f.BitLen() - 1\\n}\\n
\\n###go
\\nconst w = bits.UintSize\\n\\ntype bitset []uint\\n\\n// b <<= k\\nfunc (b bitset) lsh(k int) bitset {\\nshift, offset := k/w, k%w\\nif offset == 0 {\\n// Fast path\\ncopy(b[shift:], b)\\n} else {\\nfor i := len(b) - 1; i > shift; i-- {\\nb[i] = b[i-shift]<<offset | b[i-shift-1]>>(w-offset)\\n}\\nb[shift] = b[0] << offset\\n}\\nclear(b[:shift])\\nreturn b\\n}\\n\\n// 把 >= start 的清零\\nfunc (b bitset) resetRange(start int) bitset {\\ni := start / w\\nb[i] &= ^(^uint(0) << (start % w))\\nclear(b[i+1:])\\nreturn b\\n}\\n\\n// b |= c\\nfunc (b bitset) unionFrom(c bitset) {\\nfor i, v := range c {\\nb[i] |= v\\n}\\n}\\n\\nfunc (b bitset) lastIndex1() int {\\nfor i := len(b) - 1; i >= 0; i-- {\\nif b[i] != 0 {\\nreturn i*w | (bits.Len(b[i]) - 1)\\n}\\n}\\nreturn -1\\n}\\n\\nfunc maxTotalReward(rewardValues []int) int {\\nm := slices.Max(rewardValues)\\nhas := map[int]bool{}\\nfor _, v := range rewardValues {\\nif v == m-1 {\\nreturn m*2 - 1\\n}\\nif has[v] {\\ncontinue\\n}\\nif has[m-1-v] {\\nreturn m*2 - 1\\n}\\nhas[v] = true\\n}\\n\\nslices.Sort(rewardValues)\\nrewardValues = slices.Compact(rewardValues) // 去重\\nf := make(bitset, m*2/w+1)\\nf[0] = 1\\nfor _, v := range rewardValues {\\nf.unionFrom(slices.Clone(f).lsh(v).resetRange(v * 2))\\n}\\nreturn f.lastIndex1()\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 对于 $\\\\textit{rewardValues}$ 中的数,如果先选大的,就没法再选小的,所以按照从小到大的顺序选是最优的。\\n\\n把 $\\\\textit{rewardValues}$ 从小到大排序。\\n\\n排序后,问题类似 0-1 背包,原理请看【基础算法精讲 18】。\\n\\n定义 $f[i][j]$ 表示能否从 $\\\\textit{rewardValues}$ 的前 $i$ 个数中得到总奖励 $j$。\\n\\n设 $\\\\textit{rewardValues}$ 的第 $i$ 个数为 $v$,考虑 $v$ 选或不选:\\n\\n不选 $v$,问题变成能否从前 $i-1…","guid":"https://leetcode.cn/problems/maximum-total-reward-using-operations-ii//solution/bitset-you-hua-0-1-bei-bao-by-endlessche-m1xn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T04:10:11.439Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"打擂台(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/da-lei-tai-pythonjavacgo-by-endlesscheng-juk1","content":"本题和 5.19 的每日一题 1535. 找出数组游戏的赢家 几乎一样,请看 我的题解,把代码中的 $\\\\textit{mx}$ 改成最大值的下标即可。
\\n\\n###py
\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n max_i = win = 0\\n for i in range(1, len(skills)):\\n if skills[i] > skills[max_i]: # 打擂台,发现新的最大值\\n max_i = i\\n win = 0\\n win += 1 # 获胜回合 +1\\n if win == k: # 连续赢下 k 场比赛\\n break\\n # 如果 k 很大,那么 max_i 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return max_i\\n
\\n###java
\\nclass Solution {\\n public int findWinningPlayer(int[] skills, int k) {\\n int maxI = 0;\\n int win = 0;\\n for (int i = 1; i < skills.length && win < k; i++) {\\n if (skills[i] > skills[maxI]) { // 打擂台,发现新的最大值\\n maxI = i;\\n win = 0;\\n }\\n win++; // 获胜回合 +1\\n }\\n // 如果 k 很大,那么 maxI 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return maxI;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector<int>& skills, int k) {\\n int max_i = 0, win = 0;\\n for (int i = 1; i < skills.size() && win < k; i++) {\\n if (skills[i] > skills[max_i]) { // 打擂台,发现新的最大值\\n max_i = i;\\n win = 0;\\n }\\n win++; // 获胜回合 +1\\n }\\n // 如果 k 很大,那么 max_i 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return max_i;\\n }\\n};\\n
\\n###c
\\nint findWinningPlayer(int* skills, int n, int k) {\\n int max_i = 0, win = 0;\\n for (int i = 1; i < n && win < k; i++) {\\n if (skills[i] > skills[max_i]) { // 打擂台,发现新的最大值\\n max_i = i;\\n win = 0;\\n }\\n win++; // 获胜回合 +1\\n }\\n // 如果 k 很大,那么 max_i 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return max_i;\\n}\\n
\\n###go
\\nfunc findWinningPlayer(skills []int, k int) (maxI int) {\\n win := 0\\n for i := 1; i < len(skills) && win < k; i++ {\\n if skills[i] > skills[maxI] { // 打擂台,发现新的最大值\\n maxI = i\\n win = 0\\n }\\n win++ // 获胜回合 +1\\n }\\n // 如果 k 很大,那么 maxI 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return\\n}\\n
\\n###js
\\nvar findWinningPlayer = function(skills, k) {\\n let maxI = 0, win = 0;\\n for (let i = 1; i < skills.length && win < k; i++) {\\n if (skills[i] > skills[maxI]) { // 打擂台,发现新的最大值\\n maxI = i;\\n win = 0;\\n }\\n win++; // 获胜回合 +1\\n }\\n // 如果 k 很大,那么 maxI 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n return maxI;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn find_winning_player(skills: Vec<i32>, k: i32) -> i32 {\\n let mut max_i = 0;\\n let mut win = 0;\\n for i in 1..skills.len() {\\n if skills[i] > skills[max_i] { // 打擂台,发现新的最大值\\n max_i = i;\\n win = 0;\\n }\\n win += 1; // 获胜回合 +1\\n if win == k { // 连续赢下 k 场比赛\\n break;\\n }\\n }\\n // 如果 k 很大,那么 max_i 就是 skills 最大值的下标,毕竟最大值会一直赢下去\\n max_i as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本题和 5.19 的每日一题 1535. 找出数组游戏的赢家 几乎一样,请看 我的题解,把代码中的 $\\\\textit{mx}$ 改成最大值的下标即可。 本题视频讲解\\n\\n###py\\n\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n max_i = win = 0\\n for i in range(1, len(skills)):\\n if skills[i] > skills[max_i]: #…","guid":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/da-lei-tai-pythonjavacgo-by-endlesscheng-juk1","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-09T00:26:21.073Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历计算","url":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/yi-ci-bian-li-ji-suan-by-admiring-menins-bwvu","content":"\\n\\nProblem: 100297. 找到连续赢 K 场比赛的第一位玩家
\\n
[TOC]
\\n按题意,一次遍历计数。
\\n执行用时分布90ms击败100.00%;消耗内存分布28.92MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n win_i, win_cnt = 0 if skills[0] > skills[1] else 1, 1\\n for i, sk in enumerate(skills[2 :], start = 2):\\n if win_cnt >= k: break\\n if sk > skills[win_i]: win_i, win_cnt = i, 1\\n else: win_cnt += 1\\n return win_i\\n
\\n###C
\\nint findWinningPlayer(int* skills, int skillsSize, int k) {\\n int win_i = skills[0] > skills[1] ? 0 : 1, win_cnt = 1;\\n for (int i = 2; i < skillsSize; ++ i)\\n if (win_cnt >= k) break;\\n else if (skills[i] > skills[win_i]) win_i = i, win_cnt = 1;\\n else ++ win_cnt;\\n return win_i;\\n}\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100297. 找到连续赢 K 场比赛的第一位玩家 [TOC]\\n\\n按题意,一次遍历计数。\\n\\n执行用时分布90ms击败100.00%;消耗内存分布28.92MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def findWinningPlayer(self, skills: List[int], k: int) -> int:\\n win_i, win_cnt = 0 if skills[0] > skills[1] else 1, 1\\n for i, sk in…","guid":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/yi-ci-bian-li-ji-suan-by-admiring-menins-bwvu","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-08T23:38:26.420Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟","url":"https://leetcode.cn/problems/find-the-first-player-to-win-k-games-in-a-row//solution/mo-ni-by-tsreaper-xsnb","content":"一个直观的想法是根据题意,使用双端队列进行模拟。然而 $k$ 可能很大,怎么办呢?
\\n注意到当队列中的最大值来到队头后,它将会持续赢得比赛,不再更改位置。所以如果最大值来到队头之前,都没有人满足赢家条件,那么赢家一定是最大值。
\\n复杂度 $\\\\mathcal{O}(n)$。
\\n###cpp
\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector<int>& skills, int K) {\\n // 求队列中的最大值\\n int mx = 0;\\n for (int x : skills) mx = max(mx, x);\\n\\n // 把能力值和下标加入双端队列\\n typedef pair<int, int> pii;\\n deque<pii> q;\\n for (int i = 0; i < skills.size(); i++) q.push_back(pii(skills[i], i));\\n\\n int win = 0;\\n while (true) {\\n // 模拟比赛过程\\n pii x = q.front(); q.pop_front();\\n pii y = q.front(); q.pop_front();\\n if (x.first > y.first) {\\n win++;\\n q.push_front(x);\\n q.push_back(y);\\n } else {\\n win = 1;\\n q.push_front(y);\\n q.push_back(x);\\n }\\n // 如果队头满足赢家条件,或最大值来到了队头,则队头就是赢家\\n if (win >= K || q.front().first == mx) return q.front().second;\\n }\\n }\\n};\\n
\\n","description":"解法:模拟 一个直观的想法是根据题意,使用双端队列进行模拟。然而 $k$ 可能很大,怎么办呢?\\n\\n注意到当队列中的最大值来到队头后,它将会持续赢得比赛,不再更改位置。所以如果最大值来到队头之前,都没有人满足赢家条件,那么赢家一定是最大值。\\n\\n复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int findWinningPlayer(vector经典套路:子数组右端点固定时,对于二进制的每一位,预处理上一个 $0$ 在什么位置。子数组的 AND 只会在这些位置发生改变。所以子数组的 AND 一共只有 $\\\\log X$ 种。
\\n因此枚举子数组的右端点,再枚举 $\\\\log X$ 种 AND 值,即可。复杂度 $\\\\mathcal{O}(n\\\\log X)$,其中 $\\\\log X = 10^9$ 是值域。
\\n###c++
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int K) {\\n int n = nums.size();\\n const int MAXP = 30;\\n\\n // last[i][p]:在位置 i 的左边(含位置 i),二进制第 p 位是 0 的最近位置在哪\\n int last[n][MAXP];\\n for (int p = 0; p < MAXP; p++) {\\n if (nums[0] >> p & 1) last[0][p] = -1;\\n else last[0][p] = 0;\\n }\\n for (int i = 1; i < n; i++) for (int p = 0; p < MAXP; p++) {\\n if (nums[i] >> p & 1) last[i][p] = last[i - 1][p];\\n else last[i][p] = i;\\n }\\n\\n int ans = 1e9;\\n // 枚举子数组右端点\\n for (int i = 0; i < n; i++) {\\n // 对于二进制的每一位,拿出上一个 0 在什么位置\\n vector<int> pos;\\n for (int p = 0; p < MAXP; p++) if (last[i][p] >= 0) pos.push_back(last[i][p]);\\n sort(pos.begin(), pos.end(), greater<int>());\\n // 枚举 logX 种 AND 值\\n int now = nums[i];\\n ans = min(ans, abs(K - now));\\n for (int j : pos) {\\n now &= nums[j];\\n ans = min(ans, abs(K - now));\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"解法:枚举 经典套路:子数组右端点固定时,对于二进制的每一位,预处理上一个 $0$ 在什么位置。子数组的 AND 只会在这些位置发生改变。所以子数组的 AND 一共只有 $\\\\log X$ 种。\\n\\n因此枚举子数组的右端点,再枚举 $\\\\log X$ 种 AND 值,即可。复杂度 $\\\\mathcal{O}(n\\\\log X)$,其中 $\\\\log X = 10^9$ 是值域。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n int minimumDifference(vector怎么计算连续子数组的 OR?
\\n首先,我们有如下 $\\\\mathcal{O}(n^2)$ 的暴力算法:
\\n外层循环,从 $i=0$ 开始,从左到右遍历 $\\\\textit{nums}$。内层循环,从 $j=i-1$ 开始,从右到左遍历 $\\\\textit{nums}$,更新 $\\\\textit{nums}[j]=\\\\textit{nums}[j]\\\\ \\\\vert\\\\ \\\\textit{nums}[i]$。
\\n按照该算法,可以计算出所有子数组的 OR。注意单个元素也算子数组。
\\n为方便大家理解后续优化,先写出暴力代码:
\\n###py
\\n# 暴力算法,会超时\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n ans = inf\\n for i, x in enumerate(nums):\\n ans = min(ans, abs(x - k)) # 单个元素也算子数组\\n for j in range(i - 1, -1, -1):\\n nums[j] |= x # 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = min(ans, abs(nums[j] - k))\\n return ans\\n
\\n###java
\\n// 暴力算法,会超时\\nclass Solution {\\n public int minimumDifference(int[] nums, int k) {\\n int ans = Integer.MAX_VALUE;\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n ans = Math.min(ans, Math.abs(x - k)); // 单个元素也算子数组\\n for (int j = i - 1; j >= 0; j--) {\\n nums[j] |= x; // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = Math.min(ans, Math.abs(nums[j] - k));\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\n// 暴力算法,会超时\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n ans = min(ans, abs(x - k)); // 单个元素也算子数组\\n for (int j = i - 1; j >= 0; j--) {\\n nums[j] |= x; // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = min(ans, abs(nums[j] - k));\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n// 暴力算法,会超时\\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\\n\\nint minimumDifference(int* nums, int numsSize, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n ans = MIN(ans, abs(x - k)); // 单个元素也算子数组\\n for (int j = i - 1; j >= 0; j--) {\\n nums[j] |= x; // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = MIN(ans, abs(nums[j] - k));\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\n// 暴力算法,会超时\\nfunc minimumDifference(nums []int, k int) int {\\n ans := math.MaxInt\\n for i, x := range nums {\\n ans = min(ans, abs(x-k)) // 单个元素也算子数组\\n for j := i - 1; j >= 0; j-- {\\n nums[j] |= x // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = min(ans, abs(nums[j]-k))\\n }\\n }\\n return ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\n###js
\\n// 暴力算法,会超时\\nvar minimumDifference = function(nums, k) {\\n let ans = Infinity;\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n ans = Math.min(ans, Math.abs(x - k)); // 单个元素也算子数组\\n for (let j = i - 1; j >= 0; j--) {\\n nums[j] |= x; // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = Math.min(ans, Math.abs(nums[j] - k));\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\n// 暴力算法,会超时\\nimpl Solution {\\n pub fn minimum_difference(mut nums: Vec<i32>, k: i32) -> i32 {\\n let mut ans = i32::MAX;\\n for i in 0..nums.len() {\\n let x = nums[i];\\n ans = ans.min((x - k).abs()); // 单个元素也算子数组\\n for j in (0..i).rev() {\\n nums[j] |= x; // 现在 nums[j] = 原数组 nums[j] 到 nums[i] 的 OR\\n ans = ans.min((nums[j] - k).abs());\\n }\\n }\\n ans\\n }\\n}\\n
\\n暴力算法没有充分利用 OR 运算的性质。为了优化,我们需要考察上述过程中,这些元素之间有什么关系。
\\n为方便理解,我们从集合的角度来看上述暴力过程。
\\n\\n把二进制数看成集合,两个数的 OR 就是两个集合的并集。
\\n想一想,如果 $A_i$ 是 $A_j$ 的子集,那么内层循环还需要继续跑吗?
\\n不需要。如果 $A_i$ 已经是 $A_j$ 的子集,那么 $A_i$ 必然也是更左边的 $A_0,A_1,A_2,\\\\cdots,A_{j-1}$ 的子集。既然 $A_i$ 都已经是这些集合的子集了,那么并入操作不会改变这些集合。
\\n所以当我们发现 $A_i$ 是 $A_j$ 的子集时,就可以退出内层循环了。
\\n具体到代码,对于两个二进制数 $a$ 和 $b$,如果 $a\\\\ \\\\vert\\\\ b = a$,那么 $b$ 对应的集合是 $a$ 对应的集合的子集。
\\n具体例子可以看 视频讲解 第四题(计算的是子数组 AND)。
\\n###py
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n ans = inf\\n for i, x in enumerate(nums):\\n ans = min(ans, abs(x - k))\\n j = i - 1\\n # 如果 x 是 nums[j] 的子集,就退出循环\\n while j >= 0 and nums[j] | x != nums[j]:\\n nums[j] |= x\\n ans = min(ans, abs(nums[j] - k))\\n j -= 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minimumDifference(int[] nums, int k) {\\n int ans = Integer.MAX_VALUE;\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n ans = Math.min(ans, Math.abs(x - k));\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n ans = Math.min(ans, Math.abs(nums[j] - k));\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n ans = min(ans, abs(x - k));\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n ans = min(ans, abs(nums[j] - k));\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minimumDifference(int* nums, int numsSize, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n ans = MIN(ans, abs(x - k));\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n ans = MIN(ans, abs(nums[j] - k));\\n }\\n }\\n return ans;\\n}\\n\\n
\\n###go
\\nfunc minimumDifference(nums []int, k int) int {\\n ans := math.MaxInt\\n for i, x := range nums {\\n ans = min(ans, abs(x-k))\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n for j := i - 1; j >= 0 && nums[j]|x != nums[j]; j-- {\\n nums[j] |= x\\n ans = min(ans, abs(nums[j]-k))\\n }\\n }\\n return ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\n###js
\\nvar minimumDifference = function(nums, k) {\\n let ans = Infinity;\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n ans = Math.min(ans, Math.abs(x - k));\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n for (let j = i - 1; j >= 0 && (nums[j] | x) !== nums[j]; j--) {\\n nums[j] |= x;\\n ans = Math.min(ans, Math.abs(nums[j] - k));\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_difference(mut nums: Vec<i32>, k: i32) -> i32 {\\n let mut ans = i32::MAX;\\n for i in 0..nums.len() {\\n let x = nums[i];\\n ans = ans.min((x - k).abs());\\n let mut j = i - 1;\\n // 如果 x 是 nums[j] 的子集,就退出循环\\n while j < nums.len() && (nums[j] | x) != nums[j] {\\n nums[j] |= x;\\n ans = ans.min((nums[j] - k).abs());\\n j -= 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎在评论区发表你的思路/代码。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"怎么计算连续子数组的 OR? 首先,我们有如下 $\\\\mathcal{O}(n^2)$ 的暴力算法:\\n\\n外层循环,从 $i=0$ 开始,从左到右遍历 $\\\\textit{nums}$。内层循环,从 $j=i-1$ 开始,从右到左遍历 $\\\\textit{nums}$,更新 $\\\\textit{nums}[j]=\\\\textit{nums}[j]\\\\ \\\\vert\\\\ \\\\textit{nums}[i]$。\\n\\n$i=1$ 时,我们会把 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[1]$ 的 OR 记录在 $\\\\textit{nums}[0]$ 中。…","guid":"https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k//solution/li-yong-and-de-xing-zhi-pythonjavacgo-by-gg4d","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-02T04:14:37.854Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针 或 三分搜索","url":"https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k//solution/shuang-zhi-zhen-wei-yun-suan-ji-shu-by-m-er7a","content":"\\n\\nProblem: 100315. 找到按位与最接近 K 的子数组
\\n
[TOC]
\\nand
运算操作,有个经典特征,and
的数越多,结果越小,即固定子数组右端点r
时,子数组长度越长,and
结果越小。
因此,在固定子数组右端点r
时,假设左端点为l
:
# [l:r] 范围内做 and 操作\\ncur = AND(nums[l:r+1]) \\n# [l+1:r] 范围内做 and 操作\\nncur = AND(nums[l+1:r+1])\\n
\\n很明显,cur <= ncur
\\n由于要使abs(cur-k)
最小,即越接近k
越好,所以cur
,ncur
与k
的关系根据以下情况,对左指针是否移动进行判断:
cur <= ncur <= k
,左指针l
右移k <= cur <= ncur
,左指针l
不动,不进入左指针移动循环cur <= k <= ncur
,左指针l
不动,左指针移动循环跳出分界线,跳出后,右指针r
就能继续移动了,这时还得同步更新res = min(res,ncur - k)
指针移动过程中,如何获取这段范围内的and
结果cur
?假设范围长度为L
。
只需要在移动过程中对二进制的每一位1
的个数进行统计维护即可,若第i
位的1
个数等于L
,则cur |= 1 << i
。
快速获取一个数的二进制1
的位值:
# 获取所有1\\ns = 123\\nres = []\\nwhile s:\\n res.append(s&-s)\\n s &= s-1\\n
\\n三分搜索(Ternary Search)是一种用于求解单峰或单谷函数极值问题的数值方法。在固定子数组右端点r
的情况下,假设子数组长度为x
,f(x)
为and
运算结果,
\\nabs(f(x)-k)
,存在单谷,所以也可以用三分搜索来获取最小值。
三分搜索时,需要获取某一段子数组的and
结果,可以使用前缀和来获取
n = len(nums)\\n level = 32\\n # 前缀和\\n pre = [[0] * level for _ in range(n+1)]\\n\\n for i,num in enumerate(nums):\\n pre[i+1] = pre[i].copy()\\n for j in range(level):\\n if (1<<j)&num:\\n pre[i+1][j] += 1\\n
\\n不过我用Python3
写三分搜索超时了,用ai
改成c++
后过了。
更多题目模板总结,请参考2023年度总结与题目分享
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n # 按为与,越长越小,与k的差值,大 -> 小 -> 大\\n # 双指针 + 统计 + 位运算\\n l,r = 0,0\\n # 每一位的1的个数\\n cnt = Counter()\\n n = len(nums)\\n level = 32\\n\\n cur = -1\\n res = inf\\n while r < n:\\n # 获取当前值含有1的位置,并统计\\n s = nums[r]\\n cur &= s\\n while s:\\n cnt[s&-s] += 1\\n s &= s-1\\n\\n # 左指针移动\\n while l < r and cur <= k:\\n s = nums[l]\\n # 移动 l 后的and值\\n ncur = 0\\n for i in range(level):\\n t = cnt[1 << i]\\n if ((1 << i) & s):\\n t -= 1\\n if t == r - l:\\n ncur |= 1 << i\\n \\n # 左指针右移\\n if ncur <= k:\\n while s:\\n cnt[s&-s] -= 1\\n s &= s-1\\n l += 1\\n cur = ncur\\n # 循环跳出分界线\\n else:\\n res = min(res,ncur - k)\\n break\\n \\n res = min(res,abs(cur - k))\\n # 右指针右移\\n r += 1\\n\\n return res\\n
\\nclass Solution:\\n def minimumDifference(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n level = 32\\n # 前缀和\\n pre = [[0] * level for _ in range(n+1)]\\n\\n for i,num in enumerate(nums):\\n pre[i+1] = pre[i].copy()\\n for j in range(level):\\n if (1<<j)&num:\\n pre[i+1][j] += 1\\n \\n def getAnd(l,r):\\n res = 0\\n L = r - l + 1\\n\\n for i in range(level):\\n if pre[r+1][i] - pre[l][i] == L:\\n res |= 1 << i\\n return res\\n\\n res = inf\\n # 枚举右端点\\n for r,num in enumerate(nums):\\n if num <= k:\\n # if k - num:\\n res = min(res,k-num)\\n continue\\n \\n start = 0\\n end = r\\n\\n while start <= end:\\n # 计算三等分点\\n mid1 = start + int((end - start) / 3)\\n mid2 = end - int((end - start) / 3)\\n \\n # 计算函数在这两个点上的取值\\n val1 = getAnd(mid1,r)\\n val2 = getAnd(mid2,r)\\n # print(r,start,end)\\n # 根据函数值的比较结果缩小搜索区间\\n if abs(val1 - k) < abs(val2 - k):\\n end = mid2 - 1\\n else:\\n start = mid1 + 1\\n \\n # 边界特殊处理\\n if start <= r:\\n tmp = getAnd(start,r)\\n res = min(res,abs(k-tmp))\\n if start:\\n tmp &= nums[start-1]\\n res = min(res,abs(k-tmp))\\n else:\\n start -= 1\\n res = min(res,abs(k-getAnd(start,r)))\\n\\n return res\\n
\\nclass Solution {\\npublic:\\n int minimumDifference(vector<int>& nums, int k) {\\n int n = nums.size();\\n int level = 32;\\n std::vector<std::vector<int>> pre(n + 1, std::vector<int>(level, 0));\\n\\n for (int i = 0; i < n; i++) {\\n pre[i + 1] = pre[i];\\n int s = nums[i];\\n for (int j = 0; j < level; j++) {\\n if ((1 << j)&s) {\\n pre[i+1][j]++;\\n }\\n }\\n }\\n\\n auto getAnd = [&](int l, int r) {\\n int res = 0;\\n int L = r - l + 1;\\n\\n for (int i = 0; i < level; i++) {\\n if (pre[r + 1][i] - pre[l][i] == L) {\\n res |= 1 << i;\\n }\\n }\\n return res;\\n };\\n\\n int res = INT_MAX;\\n\\n for (int r = 0; r < n; r++) {\\n if (nums[r] <= k) {\\n res = min(res, k - nums[r]);\\n continue;\\n }\\n\\n int start = 0;\\n int end = r;\\n\\n while (start <= end) {\\n int mid1 = start + (end - start) / 3;\\n int mid2 = end - (end - start) / 3;\\n\\n int val1 = getAnd(mid1, r);\\n int val2 = getAnd(mid2, r);\\n\\n if (abs(val1 - k) < abs(val2 - k)) {\\n end = mid2 - 1;\\n } else {\\n start = mid1 + 1;\\n }\\n }\\n\\n // 边界特殊处理\\n if (start <= r) {\\n int tmp = getAnd(start, r);\\n res = min(res, abs(k - tmp));\\n if (start > 0) {\\n tmp &= nums[start - 1];\\n res = min(res, abs(k - tmp));\\n }\\n } else {\\n start--;\\n res = min(res, abs(k - getAnd(start, r)));\\n }\\n }\\n\\n return res;\\n }\\n};\\n
\\n","description":"Problem: 100315. 找到按位与最接近 K 的子数组 [TOC]\\n\\n双指针\\n\\nand运算操作,有个经典特征,and的数越多,结果越小,即固定子数组右端点r时,子数组长度越长,and结果越小。\\n\\n因此,在固定子数组右端点r时,假设左端点为l:\\n\\n# [l:r] 范围内做 and 操作\\ncur = AND(nums[l:r+1]) \\n# [l+1:r] 范围内做 and 操作\\nncur = AND(nums[l+1:r+1])\\n\\n\\n很明显,cur <= ncur\\n 由于要使abs(cur-k)最小,即越接近k越好,所以cur,ncur与k的关系根据以下…","guid":"https://leetcode.cn/problems/find-subarray-with-bitwise-or-closest-to-k//solution/shuang-zhi-zhen-wei-yun-suan-ji-shu-by-m-er7a","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-06-02T04:12:17.694Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【类质数筛】偷跑成功","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/lei-zhi-shu-shai-tou-pao-cheng-gong-by-l-b1qi","content":"\\n\\n\\n
[TOC]
\\n我觉得这题并不是一个好题目,因为类似灵神这种大神的解题方案如果严格计算复杂度也超过一般 $10^7$ 就会爆炸的程度,我直接筛掉几个看似爆炸的方案,理论上给的数据范围挺无解的,只是用例没有进行总量上的卡位,在难度上如果卡,这题会很恐怖;
\\n比赛时踩了 3 颗雷最后一气之下写了个投机取巧,赛后发现我这方法竟然是最快的??虽然第一次我偷家成功!
\\n虽然过了,但是还是觉得这个数据范围是不是给大了,或者这个题目标中等就不应该疑神疑鬼?
\\n这个方法是把 nums1
当质数筛,然后筛 nums2 * k
的结果,如果 nums2
中全是质数或者大量重复,这个方法就是经典质数筛非常快,但很明显如果补充大量 nums1 = [1,2,3,...,10^6], nums2 = [1,2,3,...,10^5]
的用例 这方法必爆炸,但估计官方就盯着质数、错乱排列和重复数卡时间,忽略了老6的存在;
###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n maxNum = max(nums1) + 1\\n cnt = [0] * maxNum\\n for num in nums1:\\n cnt[num] += 1\\n dic = {}\\n ans = 0\\n for num in nums2:\\n p = num * k\\n if p in dic:\\n ans += dic[p]\\n continue\\n c = 0\\n for pp in range(p, maxNum, p):\\n c += cnt[pp]\\n ans += c\\n dic[p] = c\\n return ans\\n
\\n这个方法就是把 nums1
的数字全部可能的因子都做哈希存放,然后 nums2 * k
进行碰撞,但这个方案但分解 nums1
的复杂度就去到 $O(n * \\\\sqrt{U})$ 给的范围 $U = 10^6, n = 10^5$ 这怎么看都是爆表的方案;
###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = Counter(nums1)\\n dic = {}\\n for num in cnt.keys():\\n for p in range(1, int(num ** 0.5) + 1):\\n if num % p: continue\\n if not p in dic: dic[p] = 0\\n dic[p] += cnt[num]\\n d = num // p\\n if d == p: continue\\n if not d in dic: dic[d] = 0\\n dic[d] += cnt[num]\\n ans = 0\\n for num in nums2:\\n p = num * k\\n if p in dic: ans += dic[p]\\n return ans\\n
\\n这个是参考灵神的思路,但其实跟 “类质数筛” 思路很相似,灵神没有把整个坐标存起来,而且直接for 里面倍数枚举,节约内存,但速度上慢一点
\\n###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = Counter(num // k for num in nums1 if num % k == 0)\\n if len(cnt) == 0: return 0\\n maxNum = max(cnt.keys())\\n ans = 0\\n for num, c in Counter(nums2).items():\\n for p in range(num, maxNum + 1, num):\\n if p in cnt: ans += cnt[p] * c\\n return ans\\n
\\n灵神的这个 nums1 // k
很妙,借鉴(抄)一下😁
###python
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n maxNum = max(nums1) // k + 1\\n cnt = [0] * maxNum\\n for num in nums1:\\n if num % k == 0: cnt[num // k] += 1\\n dic = {}\\n ans = 0\\n for num in nums2:\\n if not num in dic:\\n c = 0\\n for pp in range(num, maxNum, num): c += cnt[pp]\\n ans += c\\n dic[num] = c\\n else: ans += dic[num]\\n return ans\\n
\\n由于每个查询都需要返回数组 $\\\\textit{nums}$ 中的整数 $x$ 的特定次出现的下标,因此可以遍历数组 $\\\\textit{nums}$ 并使用哈希表记录整数 $x$ 的每次出现的编号对应的下标。遍历数组 $\\\\textit{nums}$ 之后,遍历查询数组 $\\\\textit{queries}$,对于每个查询从哈希表中得到对应下标作为查询的答案,如果哈希表中不存在对应下标则查询的答案是 $-1$。
\\n###Java
\\nclass Solution {\\n public int[] occurrencesOfElement(int[] nums, int[] queries, int x) {\\n Map<Integer, Integer> occurrencesIndices = new HashMap<Integer, Integer>();\\n int occurrences = 0;\\n int length = nums.length;\\n for (int i = 0; i < length; i++) {\\n if (nums[i] == x) {\\n occurrences++;\\n occurrencesIndices.put(occurrences, i);\\n }\\n }\\n int queriesCount = queries.length;\\n int[] answer = new int[queriesCount];\\n for (int i = 0; i < queriesCount; i++) {\\n answer[i] = occurrencesIndices.getOrDefault(queries[i], -1);\\n }\\n return answer;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] OccurrencesOfElement(int[] nums, int[] queries, int x) {\\n IDictionary<int, int> occurrencesIndices = new Dictionary<int, int>();\\n int occurrences = 0;\\n int length = nums.Length;\\n for (int i = 0; i < length; i++) {\\n if (nums[i] == x) {\\n occurrences++;\\n occurrencesIndices.Add(occurrences, i);\\n }\\n }\\n int queriesCount = queries.Length;\\n int[] answer = new int[queriesCount];\\n for (int i = 0; i < queriesCount; i++) {\\n answer[i] = occurrencesIndices.ContainsKey(queries[i]) ? occurrencesIndices[queries[i]] : -1;\\n }\\n return answer;\\n }\\n}\\n
\\n时间复杂度:$O(n + q)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$q$ 是数组 $\\\\textit{queries}$ 的长度。需要遍历数组 $\\\\textit{nums}$ 和 $\\\\textit{queries}$ 各一次,对于每个元素的操作时间都是 $O(1)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。哈希表的空间是 $O(n)$。注意返回值不计入空间复杂度。
\\n为了判断数组 $\\\\textit{nums}$ 中的每个数字是否出现两次,可以使用哈希集合存储数组 $\\\\textit{nums}$ 中的数字,并计算异或结果值。
\\n遍历数组 $\\\\textit{nums}$,对于遍历到的每个数字 $\\\\textit{num}$,判断其是否在哈希集合中,执行如下操作。
\\n如果 $\\\\textit{num}$ 不在哈希集合中,则将其加入哈希集合。
\\n如果 $\\\\textit{num}$ 在哈希集合中,则 $\\\\textit{num}$ 出现两次,使用 $\\\\textit{num}$ 更新异或结果值。
\\n遍历结束之后,即可得到异或结果值。
\\n###Java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n int xorValue = 0;\\n Set<Integer> set = new HashSet<Integer>();\\n for (int num : nums) {\\n if (!set.add(num)) {\\n xorValue ^= num;\\n }\\n }\\n return xorValue;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int DuplicateNumbersXOR(int[] nums) {\\n int xorValue = 0;\\n ISet<int> set = new HashSet<int>();\\n foreach (int num in nums) {\\n if (!set.Add(num)) {\\n xorValue ^= num;\\n }\\n }\\n return xorValue;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。哈希集合的空间是 $O(n)$。
\\n由于数组 $\\\\textit{nums}$ 中的所有数字都是不超过 $50$ 的正整数,因此可以使用二进制数代替哈希集合,将空间复杂度降低到 $O(1)$。
\\n使用二进制数 $\\\\textit{mask}$ 表示数组 $\\\\textit{nums}$ 中出现的所有数字,$\\\\textit{mask}$ 的从低到高第 $i$ 位是 $1$ 表示 $i$ 在数组 $\\\\textit{nums}$ 中,$0$ 表示 $i$ 不在数组 $\\\\textit{nums}$ 中,初始时 $\\\\textit{mask} = 0$。
\\n遍历数组 $\\\\textit{nums}$,对于遍历到的每个数字 $\\\\textit{num}$,计算 $\\\\textit{mask} ~&~ 2^{\\\\textit{num}}$ 的值判断 $\\\\textit{num}$ 是否已经出现过,执行如下操作。
\\n如果 $\\\\textit{mask} ~&~ 2^{\\\\textit{num}} = 0$,则 $\\\\textit{num}$ 尚未出现过,将 $\\\\textit{mask}$ 的值更新为 $\\\\textit{mask} ~|~ 2^{\\\\textit{num}} = 0$。
\\n如果 $\\\\textit{mask} ~&~ 2^{\\\\textit{num}} \\\\ne 0$,则 $\\\\textit{num}$ 已经出现过,因此 $\\\\textit{num}$ 出现两次,使用 $\\\\textit{num}$ 更新异或结果值。
\\n遍历结束之后,即可得到异或结果值。
\\n###Java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n int xorValue = 0;\\n long mask = 0;\\n for (int num : nums) {\\n if ((mask & (1L << num)) == 0) {\\n mask |= 1L << num;\\n } else {\\n xorValue ^= num;\\n }\\n }\\n return xorValue;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int DuplicateNumbersXOR(int[] nums) {\\n int xorValue = 0;\\n long mask = 0;\\n foreach (int num in nums) {\\n if ((mask & (1L << num)) == 0) {\\n mask |= 1L << num;\\n } else {\\n xorValue ^= num;\\n }\\n }\\n return xorValue;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(1)$。
\\n思路
\\n枚举第一个小朋友分得 $x$ 颗糖果,那么还剩下 $n-x$ 颗糖果,此时有两种情况:
\\n$n-x > \\\\textit{limit} \\\\times 2$,至少有一个小朋友会分得大于 $\\\\textit{limit}$ 颗糖果,此时不存在合法方案。
\\n$n-x \\\\le \\\\textit{limit} \\\\times 2$,对于第二个小朋友来说,至少得分得 $\\\\max(0, n-x-\\\\textit{limit})$ 颗糖果,才能保证第三个小朋友分得的糖果不超过 $\\\\textit{limit}$ 颗。同时至多能拿到 $\\\\min(\\\\textit{limit}, n-x)$ 颗糖果。
\\n对于第二种情况计算出所有的合法方案即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long distributeCandies(int n, int limit) {\\n long long ans = 0;\\n for (int i = 0; i <= min(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += min(n - i, limit) - max(0, n - i - limit) + 1;\\n }\\n return ans;\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long distributeCandies(int n, int limit) {\\n long ans = 0;\\n for (int i = 0; i <= Math.min(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += Math.min(n - i, limit) - Math.max(0, n - i - limit) + 1;\\n }\\n return ans;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long DistributeCandies(int n, int limit) {\\n long ans = 0;\\n for (int i = 0; i <= Math.Min(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += Math.Min(n - i, limit) - Math.Max(0, n - i - limit) + 1;\\n }\\n return ans;\\n }\\n}\\n
\\n###Go
\\nfunc distributeCandies(n int, limit int) int64 {\\n ans := int64(0)\\n for i := 0; i <= min(limit, n); i++ {\\n if n - i > 2 * limit {\\n continue\\n }\\n ans += int64(min(n - i, limit) - max(0, n - i - limit) + 1)\\n }\\n return ans\\n}\\n
\\n###C
\\nlong long distributeCandies(int n, int limit) {\\n long long ans = 0;\\n for (int i = 0; i <= fmin(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += fmin(n - i, limit) - fmax(0, n - i - limit) + 1;\\n }\\n return ans;\\n}\\n
\\n###Python
\\nclass Solution:\\n def distributeCandies(self, n: int, limit: int) -> int:\\n ans = 0\\n for i in range(min(limit, n) + 1):\\n if n - i > 2 * limit:\\n continue\\n ans += min(n - i, limit) - max(0, n - i - limit) + 1\\n return ans\\n
\\n###JavaScript
\\nvar distributeCandies = function(n, limit) {\\n let ans = 0;\\n for (let i = 0; i <= Math.min(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += Math.min(n - i, limit) - Math.max(0, n - i - limit) + 1;\\n }\\n return ans;\\n};\\n
\\n###TypeScript
\\nfunction distributeCandies(n: number, limit: number): number {\\n let ans = 0;\\n for (let i = 0; i <= Math.min(limit, n); i++) {\\n if (n - i > 2 * limit) {\\n continue;\\n }\\n ans += Math.min(n - i, limit) - Math.max(0, n - i - limit) + 1;\\n }\\n return ans;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn distribute_candies(n: i32, limit: i32) -> i64 {\\n let mut ans: i64 = 0;\\n for i in 0..=std::cmp::min(limit, n) {\\n if n - i > 2 * limit {\\n continue;\\n }\\n ans += std::cmp::min(n - i, limit) as i64 - std::cmp::max(0, n - i - limit) as i64 + 1;\\n }\\n\\n return ans;\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(\\\\min(\\\\textit{limit}, n))$。
\\n空间复杂度:$O(1)$。
\\n也可以采用常规计数的思想,用所有方案减去不合法的方案。使用组合数学的容斥原理,用所有的方案数减去至少有一个小朋友分得超过 $\\\\textit{limit}$ 颗糖果。但会重复计算至少有两个小朋友分得超过 $\\\\textit{limit}$ 颗糖果,因此把这部分加回来。计算这部分的时候又会重复计算三个小朋友都分得超过 $\\\\textit{limit}$ 颗糖果的方案,因此再减去这部分方案数。
\\n对于所有的方案数,因为允许小朋友分得 $0$ 颗糖果,问题可转化为在 $n+3$ 颗糖果中插两块板,使得每位小朋友至少分得一颗糖果。在 $n+3$ 颗糖果中有 $n+2$ 个空位,故方案数为 $C_{n+2}^2$,这里使用 $C$ 来表示组合数。
\\n至少有一个小朋友分得超过 $\\\\textit{limit}$ 颗糖果的方案数,可以先给任意一个小朋友分得 $\\\\textit{limit}+1$ 颗糖果,此时问题转化为将 $n-\\\\textit{limit}-1$ 颗糖果分给三个小朋友,故方案数为 $C_3^1 \\\\times C_{n-(\\\\textit{limit}+1)+2}^2$。
\\n至少有两个小朋友分得超过 $\\\\textit{limit}$ 颗糖果的方案数,可以先给任意两个小朋友分得 $\\\\textit{limit}+1$ 颗糖果,此时问题转化为将 $n-(\\\\textit{limit}+1) \\\\times 2$ 颗糖果分给三个小朋友,故方案数为 $C_3^2 \\\\times C_{n-(\\\\textit{limit}+1) \\\\times 2+2}^2$。
\\n至少有三个小朋友分得超过 $\\\\textit{limit}$ 颗糖果的方案数,可以先给三个小朋友分得 $\\\\textit{limit}+1$ 颗糖果,此时问题转化为将 $n-(\\\\textit{limit}+1) \\\\times 3$ 颗糖果分给三个小朋友,故方案数为 $C_{n-(\\\\textit{limit}+1) \\\\times 3+2}^{2}$。
\\n最后整理方案数为 $C_{n+2}^2 - C_3^1 \\\\times C_{n-(\\\\textit{limit}+1)+2}^2 + C_3^2 \\\\times C_{n-(\\\\textit{limit}+1) \\\\times 2+2}^2 - C_{n-(\\\\textit{limit}+1) \\\\times 3+2}^2$。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n long long cal(int x) {\\n if (x < 0) {\\n return 0;\\n }\\n return (long) x * (x - 1) / 2;\\n }\\n\\n long long distributeCandies(int n, int limit) {\\n return cal(n + 2) - 3 * cal(n - limit + 1) + 3 * cal(n - (limit + 1) * 2 + 2) - cal(n - 3 * (limit + 1) + 2);\\n }\\n};\\n
\\n###Java
\\nclass Solution {\\n public long distributeCandies(int n, int limit) {\\n return cal(n + 2) - 3 * cal(n - limit + 1) + 3 * cal(n - (limit + 1) * 2 + 2) - cal(n - 3 * (limit + 1) + 2);\\n }\\n\\n public long cal(int x) {\\n if (x < 0) {\\n return 0;\\n }\\n return (long) x * (x - 1) / 2;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long DistributeCandies(int n, int limit) {\\n return Cal(n + 2) - 3 * Cal(n - limit + 1) + 3 * Cal(n - (limit + 1) * 2 + 2) - Cal(n - 3 * (limit + 1) + 2);\\n }\\n\\n public long Cal(int x) {\\n if (x < 0) {\\n return 0;\\n }\\n return (long) x * (x - 1) / 2;\\n }\\n}\\n
\\n###Go
\\nfunc cal(x int) int64 {\\n if x < 0 {\\n return 0\\n }\\n return int64(x) * int64(x - 1) / 2\\n}\\n\\nfunc distributeCandies(n int, limit int) int64 {\\n return cal(n + 2) - 3 * cal(n - limit + 1) + 3 * cal(n - (limit + 1) * 2 + 2) - cal(n - 3 * (limit + 1) + 2)\\n}\\n
\\n###C
\\nlong long cal(int x) {\\n if (x < 0) {\\n return 0;\\n }\\n return (long long) x * (x - 1) / 2;\\n}\\n\\nlong long distributeCandies(int n, int limit) {\\n return cal(n + 2) - 3 * cal(n - limit + 1) + 3 * cal(n - (limit + 1) * 2 + 2) - cal(n - 3 * (limit + 1) + 2);\\n}\\n
\\n###Python
\\ndef cal(x):\\n if x < 0:\\n return 0\\n return x * (x - 1) // 2 \\n \\nclass Solution:\\n def distributeCandies(self, n: int, limit: int) -> int:\\n return cal(n + 2) - 3 * cal(n - limit + 1) + 3 * cal(n - (limit + 1) * 2 + 2) - cal(n - 3 * (limit + 1) + 2)\\n
\\n###Rust
\\nimpl Solution {\\n pub fn distribute_candies(n: i32, limit: i32) -> i64 {\\n Self::cal(n + 2) - 3 * Self::cal(n - limit + 1) + 3 * Self::cal(n - (limit + 1) * 2 + 2) - Self::cal(n - 3 * (limit + 1) + 2)\\n }\\n\\n fn cal(x: i32) -> i64 {\\n if x < 0 {\\n 0\\n } else {\\n x as i64 * (x - 1) as i64 / 2\\n }\\n }\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(1)$。
\\n空间复杂度:$O(1)$。
\\n最直观的思路是遍历数组 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的所有数对,计算优质数对的总数。
\\n数组 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度分别是 $n$ 和 $m$。对于所有 $0 \\\\le i < n$ 和 $0 \\\\le j < m$,如果 $\\\\textit{nums}_1[i] \\\\bmod (\\\\textit{nums}_2[j] \\\\times k) = 0$,则数对 $(i, j)$ 是优质数对。遍历结束之后即可得到优质数对的总数。
\\n###Java
\\nclass Solution {\\n public int numberOfPairs(int[] nums1, int[] nums2, int k) {\\n int pairs = 0;\\n int n = nums1.length, m = nums2.length;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < m; j++) {\\n if (nums1[i] % (nums2[j] * k) == 0) {\\n pairs++;\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfPairs(int[] nums1, int[] nums2, int k) {\\n int pairs = 0;\\n int n = nums1.Length, m = nums2.Length;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < m; j++) {\\n if (nums1[i] % (nums2[j] * k) == 0) {\\n pairs++;\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n时间复杂度:$O(nm)$,其中 $n$ 是数组 $\\\\textit{nums}_1$ 的长度,$m$ 是数组 $\\\\textit{nums}_2$ 的长度。需要遍历的数对数目是 $O(nm)$。
\\n空间复杂度:$O(1)$。
\\n为了计算优质数对的总数,可以计算每个正整数的因数个数。
\\n首先遍历数组 $\\\\textit{nums}_2$ 并使用哈希表记录每个正整数在数组 $\\\\textit{nums}_2$ 中的出现次数,然后遍历数组 $\\\\textit{nums}_1$ 计算优质数对的总数。由于当 $\\\\textit{nums}_1[i]$ 可以被 $\\\\textit{nums}_2[j] \\\\times k$ 整除时,数对 $(i, j)$ 为优质数对,因此 $\\\\textit{nums}_1[i]$ 必须是 $k$ 的倍数。数组 $\\\\textit{nums}_1$ 计算优质数对的总数时,对于遍历到的每个正整数 $\\\\textit{num}$,如果 $\\\\textit{num} \\\\bmod k \\\\ne 0$ 则跳过 $\\\\textit{num}$,如果 $\\\\textit{num} \\\\bmod k \\\\ne 0$ 则执行如下操作。
\\n计算 $x = \\\\dfrac{\\\\textit{num}}{k}$,则需要计算数组 $\\\\textit{nums}_2$ 中的可以整除 $x$ 的正整数个数。
\\n遍历不超过 $\\\\sqrt{x}$ 的所有正整数。对于正整数 $i$,如果 $x \\\\bmod i = 0$,则计算 $j = \\\\dfrac{x}{i}$,更新优质数对的总数的做法如下。
\\n从哈希表中得到正整数 $i$ 的出现次数,加到优质数对的总数。
\\n如果 $j > i$,则从哈希表中得到正整数 $j$ 的出现次数,加到优质数对的总数。
\\n遍历结束之后,即可得到优质数对的总数。
\\n###Java
\\nclass Solution {\\n public int numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> counts = new HashMap<Integer, Integer>();\\n for (int num : nums2) {\\n counts.put(num, counts.getOrDefault(num, 0) + 1);\\n }\\n int pairs = 0;\\n for (int num : nums1) {\\n if (num % k != 0) {\\n continue;\\n }\\n int x = num / k;\\n for (int i = 1; i * i <= x; i++) {\\n if (x % i != 0) {\\n continue;\\n }\\n int j = x / i;\\n pairs += counts.getOrDefault(i, 0);\\n if (j > i) {\\n pairs += counts.getOrDefault(j, 0);\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int NumberOfPairs(int[] nums1, int[] nums2, int k) {\\n IDictionary<int, int> counts = new Dictionary<int, int>();\\n foreach (int num in nums2) {\\n counts.TryAdd(num, 0);\\n counts[num]++;\\n }\\n int pairs = 0;\\n foreach (int num in nums1) {\\n if (num % k != 0) {\\n continue;\\n }\\n int x = num / k;\\n for (int i = 1; i * i <= x; i++) {\\n if (x % i != 0) {\\n continue;\\n }\\n int j = x / i;\\n pairs += counts.ContainsKey(i) ? counts[i] : 0;\\n if (j > i) {\\n pairs += counts.ContainsKey(j) ? counts[j] : 0;\\n }\\n }\\n }\\n return pairs;\\n }\\n}\\n
\\n时间复杂度:$O\\\\Big(n\\\\sqrt{\\\\dfrac{h}{k}} + m\\\\Big)$,其中 $n$ 是数组 $\\\\textit{nums}_1$ 的长度,$m$ 是数组 $\\\\textit{nums}_2$ 的长度,$k$ 是给定的正整数,$h$ 是数组 $\\\\textit{nums}_1$ 中的最大正整数。遍历数组 $\\\\textit{nums}_2$ 并使用哈希表记录的时间是 $O(m)$,遍历数组 $\\\\textit{nums}_1$ 时对于每个正整数的计算时间都是 $O\\\\Big(\\\\sqrt{\\\\dfrac{h}{k}}\\\\Big)$,因此时间复杂度是 $O\\\\Big(n\\\\sqrt{\\\\dfrac{h}{k}} + m\\\\Big)$。
\\n空间复杂度:$O\\\\Big(\\\\dfrac{h}{k}\\\\Big)$,其中 $k$ 是给定的正整数,$h$ 是数组 $\\\\textit{nums}_1$ 中的最大正整数。哈希表的空间是 $O\\\\Big(\\\\dfrac{h}{k}\\\\Big)$。
\\n\\n\\nProblem: 100323. 优质数对的总数 I
\\n
[TOC]
\\n\\n\\n讲述看到这一题的思路
\\n
模拟遍历
\\n\\n\\n描述你的解题方法
\\n
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Elixir
\\ndefmodule Solution do\\n @spec number_of_pairs(nums1 :: [integer], nums2 :: [integer], k :: integer) :: integer\\n def number_of_pairs(nums1, nums2, k) do\\n for x<- nums1, y<- nums2, rem(x, y*k) == 0 do\\n :ok\\n end\\n |> Enum.count\\n end\\nend\\n
\\n","description":"Problem: 100323. 优质数对的总数 I [TOC]\\n\\n讲述看到这一题的思路\\n\\n模拟遍历\\n\\n描述你的解题方法\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n空间复杂度:\\n\\n添加空间复杂度, 示例: $O(n)$\\n\\n###Elixir\\n\\ndefmodule Solution do\\n @spec number_of_pairs(nums1 :: [integer], nums2 :: [integer], k :: integer) :: integer\\n def number_of_pairs(nums1…","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i//solution/elixir-mo-ni-by-lambda2void-pbku","author":"lambda2void","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T05:13:50.095Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"非暴力做法(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i//solution/fei-bao-li-zuo-fa-pythonjavacgo-by-endle-tovv","content":"本题和周赛第三题是一样的,请看 我的题解。
\\n","description":"本题和周赛第三题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-i//solution/fei-bao-li-zuo-fa-pythonjavacgo-by-endle-tovv","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T04:14:10.211Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举因子/枚举倍数(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/tong-ji-yin-zi-ge-shu-pythonjavacgo-by-e-bl3o","content":"为方便描述,把 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 记作 $a$ 和 $b$。
\\n$a[i]$ 能被 $b[j]\\\\cdot k$ 整除,等价于 $a[i]$ 是 $k$ 的倍数且 $\\\\dfrac{a[i]}{k}$ 能被 $b[j]$ 整除。
\\n也就是说,$\\\\dfrac{a[i]}{k}$ 有一个因子 $d$ 等于 $b[j]$。
\\n枚举因子的技巧请看 视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = defaultdict(int)\\n for x in nums1:\\n if x % k:\\n continue\\n x //= k\\n for d in range(1, isqrt(x) + 1): # 枚举因子\\n if x % d:\\n continue\\n cnt[d] += 1 # 统计因子\\n if d * d < x:\\n cnt[x // d] += 1 # 因子总是成对出现\\n return sum(cnt[x] for x in nums2)\\n
\\n###java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int x : nums1) {\\n if (x % k != 0) {\\n continue;\\n }\\n x /= k;\\n for (int d = 1; d * d <= x; d++) { // 枚举因子\\n if (x % d > 0) {\\n continue;\\n }\\n cnt.merge(d, 1, Integer::sum); // cnt[d]++\\n if (d * d < x) {\\n cnt.merge(x / d, 1, Integer::sum); // cnt[x/d]++\\n }\\n }\\n }\\n\\n long ans = 0;\\n for (int x : nums2) {\\n ans += cnt.getOrDefault(x, 0);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> cnt;\\n for (int x : nums1) {\\n if (x % k) {\\n continue;\\n }\\n x /= k;\\n for (int d = 1; d * d <= x; d++) { // 枚举因子\\n if (x % d) {\\n continue;\\n }\\n cnt[d]++; // 统计因子\\n if (d * d < x) {\\n cnt[x / d]++; // 因子总是成对出现\\n }\\n }\\n }\\n\\n long long ans = 0;\\n for (int x : nums2) {\\n ans += cnt.contains(x) ? cnt[x] : 0;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfPairs(nums1, nums2 []int, k int) (ans int64) {\\n cnt := map[int]int{}\\n for _, x := range nums1 {\\n if x%k > 0 {\\n continue\\n }\\n x /= k\\n for d := 1; d*d <= x; d++ { // 枚举因子\\n if x%d == 0 {\\n cnt[d]++ // 统计因子\\n if d*d < x {\\n cnt[x/d]++ // 因子总是成对出现\\n }\\n }\\n }\\n }\\n\\n for _, x := range nums2 {\\n ans += int64(cnt[x])\\n }\\n return\\n}\\n
\\n###js
\\nvar numberOfPairs = function(nums1, nums2, k) {\\n const cnt = new Map();\\n for (let x of nums1) {\\n if (x % k) {\\n continue;\\n }\\n x /= k;\\n for (let d = 1; d * d <= x; d++) { // 枚举因子\\n if (x % d) {\\n continue;\\n }\\n cnt.set(d, (cnt.get(d) || 0) + 1); // 统计因子\\n if (d * d < x) {\\n cnt.set(x / d, (cnt.get(x / d) ?? 0) + 1); // 因子总是成对出现\\n }\\n }\\n }\\n\\n let ans = 0;\\n for (const x of nums2) {\\n ans += cnt.get(x) ?? 0;\\n }\\n return ans;\\n};\\n
\\n###rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn number_of_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> i64 {\\n let mut cnt = HashMap::new();\\n for mut x in nums1 {\\n if x % k != 0 {\\n continue;\\n }\\n x /= k;\\n let mut d = 1;\\n while d * d <= x {\\n if x % d == 0 {\\n *cnt.entry(d).or_insert(0) += 1;\\n if d * d < x {\\n *cnt.entry(x / d).or_insert(0) += 1;\\n }\\n }\\n d += 1;\\n }\\n }\\n\\n nums2.iter().map(|x| *cnt.get(x).unwrap_or(&0) as i64).sum()\\n }\\n}\\n
\\n横看成岭侧成峰,我们还可以枚举 $b[j]$ 的倍数。
\\n例如 $b[j]=3$,枚举 $3,6,9,12,\\\\cdots$,统计 $a$ 中有多少个 $\\\\dfrac{a[i]}{k}$ 等于 $3,6,9,12,\\\\cdots$
\\n###py
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt1 = Counter(x // k for x in nums1 if x % k == 0)\\n if not cnt1:\\n return 0\\n\\n ans = 0\\n u = max(cnt1)\\n for x, cnt in Counter(nums2).items():\\n s = sum(cnt1[y] for y in range(x, u + 1, x)) # 枚举 x 的倍数\\n ans += s * cnt\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n Map<Integer, Integer> cnt1 = new HashMap<>();\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1.merge(x / k, 1, Integer::sum); // cnt1[x/k]++\\n }\\n }\\n if (cnt1.isEmpty()) {\\n return 0;\\n }\\n\\n Map<Integer, Integer> cnt2 = new HashMap<>();\\n for (int x : nums2) {\\n cnt2.merge(x, 1, Integer::sum); // cnt2[x]++\\n }\\n\\n long ans = 0;\\n int u = Collections.max(cnt1.keySet());\\n for (Map.Entry<Integer, Integer> e : cnt2.entrySet()) {\\n int x = e.getKey();\\n int cnt = e.getValue();\\n int s = 0;\\n for (int y = x; y <= u; y += x) { // 枚举 x 的倍数\\n if (cnt1.containsKey(y)) {\\n s += cnt1.get(y);\\n }\\n }\\n ans += (long) s * cnt;\\n }\\n return ans;\\n }\\n}\\n
\\n###java
\\nclass Solution {\\n public long numberOfPairs(int[] nums1, int[] nums2, int k) {\\n int mx1 = 0;\\n for (int x : nums1) {\\n if (x % k == 0) {\\n mx1 = Math.max(mx1, x / k);\\n }\\n }\\n if (mx1 == 0) {\\n return 0;\\n }\\n\\n int[] cnt1 = new int[mx1 + 1];\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1[x / k]++;\\n }\\n }\\n\\n int mx2 = 0;\\n for (int x : nums2) {\\n mx2 = Math.max(mx2, x);\\n }\\n int[] cnt2 = new int[mx2 + 1];\\n for (int x : nums2) {\\n cnt2[x]++;\\n }\\n\\n long ans = 0;\\n for (int x = 1; x <= mx2; x++) {\\n if (cnt2[x] == 0) {\\n continue;\\n }\\n int s = 0;\\n for (int y = x; y <= mx1; y += x) { // 枚举 x 的倍数\\n s += cnt1[y];\\n }\\n ans += (long) s * cnt2[x];\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long numberOfPairs(vector<int>& nums1, vector<int>& nums2, int k) {\\n unordered_map<int, int> cnt1;\\n for (int x : nums1) {\\n if (x % k == 0) {\\n cnt1[x / k]++;\\n }\\n }\\n if (cnt1.empty()) {\\n return 0;\\n }\\n\\n unordered_map<int, int> cnt2;\\n for (int x : nums2) {\\n cnt2[x]++;\\n }\\n\\n long long ans = 0;\\n int u = ranges::max_element(cnt1)->first;\\n for (auto& [x, cnt] : cnt2) {\\n int s = 0;\\n for (int y = x; y <= u; y += x) { // 枚举 x 的倍数\\n s += cnt1.contains(y) ? cnt1[y] : 0;\\n }\\n ans += (long long) s * cnt;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc numberOfPairs(nums1, nums2 []int, k int) (ans int64) {\\n cnt1 := map[int]int{}\\n u := 0\\n for _, x := range nums1 {\\n if x%k == 0 {\\n u = max(u, x/k)\\n cnt1[x/k]++\\n }\\n }\\n if u == 0 {\\n return\\n }\\n\\n cnt2 := map[int]int{}\\n for _, x := range nums2 {\\n cnt2[x]++\\n }\\n\\n for x, cnt := range cnt2 {\\n s := 0\\n for y := x; y <= u; y += x { // 枚举 x 的倍数\\n s += cnt1[y]\\n }\\n ans += int64(s * cnt)\\n }\\n return\\n}\\n
\\n###js
\\nconst numberOfPairs = function(nums1, nums2, k) {\\n const cnt1 = new Map();\\n let u = 0;\\n for (const x of nums1) {\\n if (x % k === 0) {\\n u = Math.max(u, x / k);\\n cnt1.set(x / k, (cnt1.get(x / k) ?? 0) + 1);\\n }\\n }\\n if (u === 0) {\\n return 0;\\n }\\n\\n const cnt2 = new Map();\\n for (const x of nums2) {\\n cnt2.set(x, (cnt2.get(x) ?? 0) + 1);\\n }\\n\\n let ans = 0;\\n for (const [x, cnt] of cnt2.entries()) {\\n let s = 0;\\n for (let y = x; y <= u; y += x) { // 枚举 x 的倍数\\n s += cnt1.get(y) ?? 0;\\n }\\n ans += s * cnt;\\n }\\n return ans;\\n}\\n
\\n###rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn number_of_pairs(nums1: Vec<i32>, nums2: Vec<i32>, k: i32) -> i64 {\\n let mut cnt1 = HashMap::new();\\n for x in nums1 {\\n if x % k == 0 {\\n *cnt1.entry(x / k).or_insert(0) += 1;\\n }\\n }\\n if cnt1.is_empty() {\\n return 0;\\n }\\n\\n let mut cnt2 = HashMap::new();\\n for x in nums2 {\\n *cnt2.entry(x).or_insert(0) += 1;\\n }\\n\\n let mut ans = 0i64;\\n let u = *cnt1.keys().max().unwrap();\\n for (x, cnt) in cnt2 {\\n let mut s = 0;\\n for y in (x..=u).step_by(x as usize) { // 枚举 x 的倍数\\n if let Some(&c) = cnt1.get(&y) {\\n s += c;\\n }\\n }\\n ans += s as i64 * cnt as i64;\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举因子 分析\\n\\n为方便描述,把 $\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 记作 $a$ 和 $b$。\\n\\n$a[i]$ 能被 $b[j]\\\\cdot k$ 整除,等价于 $a[i]$ 是 $k$ 的倍数且 $\\\\dfrac{a[i]}{k}$ 能被 $b[j]$ 整除。\\n\\n也就是说,$\\\\dfrac{a[i]}{k}$ 有一个因子 $d$ 等于 $b[j]$。\\n\\n算法\\n遍历 $a$,枚举 $\\\\dfrac{a[i]}{k}$ 的所有因子,统计到哈希表 $\\\\textit{cnt}$ 中。比如遍历完后 $\\\\textit{cnt…","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/tong-ji-yin-zi-ge-shu-pythonjavacgo-by-e-bl3o","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T04:13:04.008Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"计数 + 记忆化搜索优化","url":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/ji-shu-ji-yi-hua-sou-suo-you-hua-by-miph-9mz9","content":"\\n\\nProblem: 100321. 优质数对的总数 II
\\n
[TOC]
\\n通过获取一个数的所有因子,并进行计数。因子分解函数如下:
\\n# 全局记忆化搜索优化\\n@cache\\ndef getFactor(num):\\n res = []\\n d = 1\\n while d * d <= num:\\n if num % d == 0:\\n res.append(d)\\n if d * d != num:\\n res.append(num//d)\\n d += 1\\n return res\\n
\\n两种计数查询方式:
\\nnums1
中的数进行因子分解然后计数,然后对nums2
中的数乘以k
进行查询: cnt = Counter()\\n for num in nums1:\\n for tmp in getFactor(num):\\n cnt[tmp] += 1\\n\\n res = 0\\n for num in nums2:\\n res += cnt[num*k]\\n return res\\n
\\nnums2
中的数进行计数统计,若nums1
中的数能被k
整除,则对nums1[i]//k
进行因子分解然后查询: cnt = Counter(nums2)\\n res = 0\\n for num in nums1:\\n if num % k == 0:\\n for tmp in getFactor(num//k):\\n res += cnt[tmp]\\n return res\\n
\\n若用计数查询方式1会TLE
,需要对因子分解函数进行全局记忆化搜索,减少重复数的因子分解。
\\n如果是计数查询方式2,不用这个优化也能过。。。
更多题目模板总结,请参考2023年度总结与题目分享
\\n@cache\\ndef getFactor(num):\\n res = []\\n d = 1\\n while d * d <= num:\\n if num % d == 0:\\n res.append(d)\\n if d * d != num:\\n res.append(num//d)\\n d += 1\\n return res\\n\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = Counter()\\n for num in nums1:\\n for tmp in getFactor(num):\\n cnt[tmp] += 1\\n\\n res = 0\\n for num in nums2:\\n res += cnt[num*k]\\n \\n return res\\n
\\n@cache\\ndef getFactor(num):\\n res = []\\n d = 1\\n while d * d <= num:\\n if num % d == 0:\\n res.append(d)\\n if d * d != num:\\n res.append(num//d)\\n d += 1\\n return res\\n\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = Counter(nums2)\\n res = 0\\n for num in nums1:\\n if num % k == 0:\\n for tmp in getFactor(num//k):\\n res += cnt[tmp]\\n \\n return res\\n
\\nclass Solution:\\n def numberOfPairs(self, nums1: List[int], nums2: List[int], k: int) -> int:\\n cnt = Counter()\\n for num in nums1:\\n d = 1\\n while d * d <= num:\\n if num % d == 0:\\n cnt[d] += 1\\n if d * d != num:\\n cnt[num//d] += 1\\n d += 1\\n\\n res = 0\\n for num in nums2:\\n res += cnt[num*k]\\n \\n return res\\n
\\n","description":"Problem: 100321. 优质数对的总数 II [TOC]\\n\\n计数\\n\\n通过获取一个数的所有因子,并进行计数。因子分解函数如下:\\n\\n# 全局记忆化搜索优化\\n@cache\\ndef getFactor(num):\\n res = []\\n d = 1\\n while d * d <= num:\\n if num % d == 0:\\n res.append(d)\\n if d * d != num:\\n res.append(num//d)\\n d…","guid":"https://leetcode.cn/problems/find-the-number-of-good-pairs-ii//solution/ji-shu-ji-yi-hua-sou-suo-you-hua-by-miph-9mz9","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T04:08:32.190Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"还是线段树,维护四种情况","url":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements//solution/huan-shi-xian-duan-shu-wei-hu-si-chong-q-p5q7","content":"\\n\\nProblem: 100306. 不包含相邻元素的子序列的最大和
\\n
[TOC]
\\n这周两场周赛都考到线段树了。如何判断某一题是否用到线段树,围绕以下四个点:
\\n根据题意:
\\nnums
中 不包含相邻元素 的子序列的最大 和:区间查询这个明显是线段树了,线段树中维护四个值:
\\n(l,r)
:不包含区间左右边界的子序列最大和[l,r)
:只包含区间左边界的子序列最大和(l,r]
:只包含区间右边界的子序列最大和[l,r]
:包含区间左右边界的子序列最大和(注意,子序列可为空)
\\n做过打家劫舍系列的,应该对不包含相邻元素 的 子序列比较熟悉。
\\nleftPart
是区间[l,mid]
的四个维护值[l_lr,l_Lr,l_lR,l_LR]
rightPart
是区间[mid+1,r]
的四个维护值[r_lr,r_Lr,r_lR,r_LR]
max
合并即可,区间合并函数如下: def pushup(self,leftPart,rightPart):\\n res = [0,0,0,0]\\n # 父节点的信息为左右孩子汇总\\n l_lr,l_Lr,l_lR,l_LR = leftPart\\n r_lr,r_Lr,r_lR,r_LR = rightPart\\n\\n res[0] = max(l_lr + r_lr, l_lr + r_Lr, l_lR + r_lr)\\n res[1] = max(l_Lr + r_lr, l_Lr + r_Lr, l_LR + r_lr)\\n res[2] = max(l_lr + r_lR, l_lr + r_LR, l_lR + r_lR)\\n res[3] = max(l_Lr + r_lR, l_Lr + r_LR, l_LR + r_lR)\\n \\n return res\\n
\\n对于区间[l,r]
中l == r
,即线段树叶子节点,只有一个值,因此不存在(l,r]
和 [l,r)
,这两种情况,可以赋值一个极小值或者0
。
self.f[k] = [0,-inf,-inf,self.nums[l]]\\n
\\n线段树入坑题:
\\n307. 区域和检索 - 数组可修改
更多题目模板总结,请参考2023年度总结与题目分享
\\n###Python3
\\n# 线段树的有效索引从1开始\\n# 对于序号为k的线段树,左孩子是2*k,右孩子是2*k+1\\n# 线段树推荐使用左闭右闭区间来处理问题\\n\\nclass SegmentTree:\\n def __init__(self,nums):\\n n = len(nums)\\n # # f记录的是特定区间,f[k],序号为k的点:该节点掌管的索引为l,r,值区间l~r的数字总和\\n self.nums = [0] + nums # 加一个哨兵节点,使得数组的有效索引为1~n\\n # 四个值,区间内\\n \'\'\'\\n (l,r)\\n [l,r)\\n (l,r]\\n [l,r]\\n 对应的子序列最大和,子序列可为空\\n \'\'\'\\n self.f = [[0,0,0,0] for i in range(4*n)]\\n self.buildTree(1,1,n)\\n \\n def pushup(self,leftPart,rightPart):\\n res = [0,0,0,0]\\n # 父节点的信息为左右孩子汇总\\n l_lr,l_Lr,l_lR,l_LR = leftPart\\n r_lr,r_Lr,r_lR,r_LR = rightPart\\n\\n res[0] = max(l_lr + r_lr, l_lr + r_Lr, l_lR + r_lr)\\n res[1] = max(l_Lr + r_lr, l_Lr + r_Lr, l_LR + r_lr)\\n res[2] = max(l_lr + r_lR, l_lr + r_LR, l_lR + r_lR)\\n res[3] = max(l_Lr + r_lR, l_Lr + r_LR, l_LR + r_lR)\\n \\n return res\\n\\n def buildTree(self,k,l,r):\\n # 序号为k的索引,掌管的范围是l~r\\n # 这里要注意,对于一棵数组长度确定的线段树,k是可以唯一确定l,r的\\n # 例如根节点1 一定对应 1~n\\n # 即同一个k对应唯一的l,r\\n if l == r: \\n # 叶子节点\\n # 不存在(l,r] 和 [l,r)\\n self.f[k] = [0,0,0,self.nums[l]]\\n return \\n mid = (l+r)//2\\n # 分治 + 后序遍历的思想\\n self.buildTree(2*k,l,mid) # 处理左孩子\\n self.buildTree(2*k+1,mid+1,r) # 处理右孩子\\n\\n self.f[k] = self.pushup(self.f[2*k],self.f[2*k+1])\\n \\n\\n \\n # 更新单点,设立障碍物\\n def update(self,k,l,r,i,x):\\n # 序号为k的索引,掌管的范围是l~r\\n if l == r:\\n self.f[k] = [0,0,0,self.nums[l]]\\n # 叶子节点\\n return \\n mid = (l+r)//2\\n # 看索引i在左右子树的哪一边。递归更新\\n if i <= mid: # 在左子树\\n self.update(2*k,l,mid,i,x)\\n elif i > mid: # 在右子树\\n self.update(2*k+1,mid+1,r,i,x)\\n \\n self.f[k] = self.pushup(self.f[2*k],self.f[2*k+1])\\n \\n def query(self,k,l,r,start,end):\\n # start~end始终是l~r的子区间\\n # 序号为k的索引,掌管的范围是l~r\\n # 在整棵树上进行搜寻 start~end 索引所汇总的范围和\\n if l == start and r == end:\\n return self.f[k]\\n mid = (l+r)//2\\n if end <= mid: # 如果start~end完全在左半边,则只需要算左子树\\n return self.query(2*k,l,mid,start,end)\\n if mid < start: # 如果start~end完全在右半边,则只需要算右子树\\n return self.query(2*k+1,mid+1,r,start,end)\\n # 否则,需要同时考虑左右孩子\\n leftPart = self.query(2*k,l,mid,start,mid) # 注意:在这里最后一个参数是mid而不是end\\n rightPart = self.query(2*k+1,mid+1,r,mid+1,end) # 注意:在这里倒数第二个参数是mid+1而不是start\\n\\n # 父节点的信息为左右孩子汇总 \\n return self.pushup(leftPart,rightPart)\\n\\nclass Solution:\\n def maximumSumSubsequence(self, nums: List[int], queries: List[List[int]]) -> int:\\n tree = SegmentTree(nums)\\n n = len(nums)\\n mod = int(1e9+7)\\n res = 0\\n for pos,x in queries:\\n pos += 1\\n tree.nums[pos] = x\\n tree.update(1,1,n,pos,x)\\n res += max(tree.query(1,1,n,1,n))\\n res %= mod\\n\\n return res\\n
\\n","description":"Problem: 100306. 不包含相邻元素的子序列的最大和 [TOC]\\n\\n线段树\\n\\n这周两场周赛都考到线段树了。如何判断某一题是否用到线段树,围绕以下四个点:\\n\\n单点修改\\n区间修改\\n单点查询\\n区间查询\\n 涉及到上面功能的,就可以考虑是否可以使用线段树来做了。\\n\\n根据题意:\\n\\n将 $nums[pos_i]$ 设置为 $x_i$:单点修改\\n查询 nums 中 不包含相邻元素 的子序列的最大 和:区间查询\\n\\n这个明显是线段树了,线段树中维护四个值:\\n\\n(l,r):不包含区间左右边界的子序列最大和\\n[l,r):只包含区间左边界的子序列最大和\\n(l,r]:只…","guid":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements//solution/huan-shi-xian-duan-shu-wei-hu-si-chong-q-p5q7","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T04:07:31.514Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分治思想+线段树(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements//solution/fen-zhi-si-xiang-xian-duan-shu-pythonjav-xnhz","content":"本题是可以修改数组元素值的 198. 打家劫舍。
\\n为了解决本题,首先来换一个角度,用分治的思想解决打家劫舍。
\\n设 $f(A)$ 为数组 $A$ 的打家劫舍的答案。把 $\\\\textit{nums}$ 从中间切开,分成左右两个子数组,分别记作 $a$ 和 $b$。要计算 $f(\\\\textit{nums})$,看上去,我们只需要分别计算 $f(a)$ 和 $f(b)$。但这是不对的,万一同时选了 $a$ 的最后一个数和 $b$ 的第一个数,就不满足题目要求了。
\\n怎么办?要么不选 $a$ 的最后一个数,要么不选 $b$ 的第一个数。所以加个约束,先来(非正式地)讨论一下:
\\n两种情况取最大值,即
\\n$$
\\nf(\\\\textit{nums}) = \\\\max(f(a\') + f(b), f(a) + f(\'b))
\\n$$
继续计算下去,就需要进一步地把 $a\',b,a,\\\\ \'b$ 切开,继续分类讨论。
\\n为方便描述,定义:
\\n前文的分类讨论可以(正式地)表述为
\\n$$
\\nf_{11}(\\\\textit{nums}) = \\\\max(f_{10}(a) + f_{11}(b),\\\\ f_{11}(a) + f_{01}(b))
\\n$$
$f_{10}$ 和 $f_{01}$ 又该如何计算?以 $f_{10}(a)$ 为例,把 $a$ 分成左右两个数组 $p$ 和 $q$,分类讨论:
\\n两种情况取最大值,得
\\n$$
\\nf_{10}(a) = \\\\max(f_{10}(p) + f_{10}(q),\\\\ f_{11}(p) + f_{00}(q))
\\n$$
同理可以得到 $f_{00}$ 和 $f_{01}$。
\\n综上所述:
\\n$$
\\n\\\\begin{aligned}
\\nf_{00}(a) &= \\\\max(f_{00}(p) + f_{10}(q),\\\\ f_{01}(p) + f_{00}(q)) \\\\
\\nf_{01}(a) &= \\\\max(f_{00}(p) + f_{11}(q),\\\\ f_{01}(p) + f_{01}(q)) \\\\
\\nf_{10}(a) &= \\\\max(f_{10}(p) + f_{10}(q),\\\\ f_{11}(p) + f_{00}(q)) \\\\
\\nf_{11}(a) &= \\\\max(f_{10}(p) + f_{11}(q),\\\\ f_{11}(p) + f_{01}(q)) \\\\
\\n\\\\end{aligned}
\\n$$
这样就可以分治计算 $\\\\textit{nums}$ 的打家劫舍了。
\\n递归边界:如果 $a$ 的长度等于 $1$,那么按照定义,$f_{11}(a) = \\\\max(a[0], 0)$,其余 $f_{00},f_{01},f_{10}$ 均为 $0$。
\\n回到本题:
\\n具体请看 视频讲解 第四题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def maximumSumSubsequence(self, nums: List[int], queries: List[List[int]]) -> int:\\n n = len(nums)\\n # 4 个数分别保存 f00, f01, f10, f11\\n t = [[0] * 4 for _ in range(2 << n.bit_length())]\\n\\n # 手写 max,效率更高\\n def max(a: int, b: int) -> int:\\n return b if b > a else a\\n\\n # 合并左右儿子\\n def maintain(o: int):\\n a, b = t[o * 2], t[o * 2 + 1]\\n t[o][0] = max(a[0] + b[2], a[1] + b[0])\\n t[o][1] = max(a[0] + b[3], a[1] + b[1])\\n t[o][2] = max(a[2] + b[2], a[3] + b[0])\\n t[o][3] = max(a[2] + b[3], a[3] + b[1])\\n\\n # 用 nums 初始化线段树\\n def build(o: int, l: int, r: int) -> None:\\n if l == r:\\n t[o][3] = max(nums[l], 0)\\n return\\n m = (l + r) // 2\\n build(o * 2, l, m)\\n build(o * 2 + 1, m + 1, r)\\n maintain(o)\\n\\n # 把 nums[i] 改成 val\\n def update(o: int, l: int, r: int, i: int, val: int) -> None:\\n if l == r:\\n t[o][3] = max(val, 0)\\n return\\n m = (l + r) // 2\\n if i <= m:\\n update(o * 2, l, m, i, val)\\n else:\\n update(o * 2 + 1, m + 1, r, i, val)\\n maintain(o)\\n\\n build(1, 0, n - 1)\\n\\n ans = 0\\n for i, x in queries:\\n update(1, 0, n - 1, i, x)\\n ans += t[1][3] # 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n return ans % 1_000_000_007\\n
\\n###java
\\nclass Solution {\\n public int maximumSumSubsequence(int[] nums, int[][] queries) {\\n int n = nums.length;\\n // 4 个数分别保存 f00, f01, f10, f11\\n long[][] t = new long[2 << (32 - Integer.numberOfLeadingZeros(n))][4];\\n build(t, nums, 1, 0, n - 1);\\n\\n long ans = 0;\\n for (int[] q : queries) {\\n update(t, 1, 0, n - 1, q[0], q[1]);\\n ans += t[1][3]; // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n return (int) (ans % 1_000_000_007);\\n }\\n\\n // 合并左右儿子\\n private void maintain(long[][] t, int o) {\\n long[] a = t[o * 2];\\n long[] b = t[o * 2 + 1];\\n t[o][0] = Math.max(a[0] + b[2], a[1] + b[0]);\\n t[o][1] = Math.max(a[0] + b[3], a[1] + b[1]);\\n t[o][2] = Math.max(a[2] + b[2], a[3] + b[0]);\\n t[o][3] = Math.max(a[2] + b[3], a[3] + b[1]);\\n }\\n\\n // 用 nums 初始化线段树\\n private void build(long[][] t, int[] nums, int o, int l, int r) {\\n if (l == r) {\\n t[o][3] = Math.max(nums[l], 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n build(t, nums, o * 2, l, m);\\n build(t, nums, o * 2 + 1, m + 1, r);\\n maintain(t, o);\\n }\\n\\n // 把 nums[i] 改成 val\\n private void update(long[][] t, int o, int l, int r, int i, int val) {\\n if (l == r) {\\n t[o][3] = Math.max(val, 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n if (i <= m) {\\n update(t, o * 2, l, m, i, val);\\n } else {\\n update(t, o * 2 + 1, m + 1, r, i, val);\\n }\\n maintain(t, o);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n // 根据本题的数据范围,unsigned int 足矣,无需 long long\\n // 4 个数分别保存 f00, f01, f10, f11\\n vector<array<unsigned int, 4>> t;\\n\\n // 合并左右儿子\\n void maintain(int o) {\\n auto& a = t[o * 2];\\n auto& b = t[o * 2 + 1];\\n t[o] = {\\n max(a[0] + b[2], a[1] + b[0]),\\n max(a[0] + b[3], a[1] + b[1]),\\n max(a[2] + b[2], a[3] + b[0]),\\n max(a[2] + b[3], a[3] + b[1]),\\n };\\n }\\n\\n // 用 nums 初始化线段树\\n void build(vector<int>& nums, int o, int l, int r) {\\n if (l == r) {\\n t[o][3] = max(nums[l], 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n build(nums, o * 2, l, m);\\n build(nums, o * 2 + 1, m + 1, r);\\n maintain(o);\\n };\\n\\n // 把 nums[i] 改成 val\\n void update(int o, int l, int r, int i, int val) {\\n if (l == r) {\\n t[o][3] = max(val, 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n if (i <= m) {\\n update(o * 2, l, m, i, val);\\n } else {\\n update(o * 2 + 1, m + 1, r, i, val);\\n }\\n maintain(o);\\n };\\n\\npublic:\\n int maximumSumSubsequence(vector<int>& nums, vector<vector<int>>& queries) {\\n int n = nums.size();\\n t.resize(2 << (32 - __builtin_clz(n)));\\n build(nums, 1, 0, n - 1);\\n\\n long long ans = 0;\\n for (auto& q : queries) {\\n update(1, 0, n - 1, q[0], q[1]);\\n ans += t[1][3]; // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n return ans % 1\'000\'000\'007;\\n }\\n};\\n
\\n###c
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\n// 根据本题的数据范围,unsigned int 足矣,无需 long long\\ntypedef struct {\\n unsigned int f00; // 第一个数一定不选,最后一个数一定不选\\n unsigned int f01; // 第一个数一定不选,最后一个数可选可不选\\n unsigned int f10; // 第一个数可选可不选,最后一个数一定不选\\n unsigned int f11; // 第一个数可选可不选,最后一个数可选可不选,也就是没有任何限制\\n} Data;\\n\\ntypedef Data* SegmentTree;\\n\\n// 合并左右儿子\\nvoid maintain(SegmentTree t, int o) {\\n Data a = t[o * 2], b = t[o * 2 + 1];\\n t[o].f00 = MAX(a.f00 + b.f10, a.f01 + b.f00);\\n t[o].f01 = MAX(a.f00 + b.f11, a.f01 + b.f01);\\n t[o].f10 = MAX(a.f10 + b.f10, a.f11 + b.f00);\\n t[o].f11 = MAX(a.f10 + b.f11, a.f11 + b.f01);\\n}\\n\\n// 用 nums 初始化线段树\\nvoid build(SegmentTree t, int* nums, int o, int l, int r) {\\n if (l == r) {\\n t[o].f11 = MAX(nums[l], 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n build(t, nums, o * 2, l, m);\\n build(t, nums, o * 2 + 1, m + 1, r);\\n maintain(t, o);\\n}\\n\\n// 把 nums[i] 改成 val\\nvoid update(SegmentTree t, int o, int l, int r, int i, int val) {\\n if (l == r) {\\n t[o].f11 = MAX(val, 0);\\n return;\\n }\\n int m = (l + r) / 2;\\n if (i <= m) {\\n update(t, o * 2, l, m, i, val);\\n } else {\\n update(t, o * 2 + 1, m + 1, r, i, val);\\n }\\n maintain(t, o);\\n}\\n\\nint maximumSumSubsequence(int* nums, int numsSize, int** queries, int queriesSize, int* queriesColSize) {\\n SegmentTree t = calloc(2 << (32 - __builtin_clz(numsSize)), sizeof(Data));\\n build(t, nums, 1, 0, numsSize - 1);\\n\\n long long ans = 0;\\n for (int i = 0; i < queriesSize; i++) {\\n update(t, 1, 0, numsSize - 1, queries[i][0], queries[i][1]);\\n ans += t[1].f11; // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n\\n free(t);\\n return ans % 1000000007;\\n}\\n
\\n###go
\\ntype data struct {\\n f00 int // 第一个数一定不选,最后一个数一定不选\\n f01 int // 第一个数一定不选,最后一个数可选可不选\\n f10 int // 第一个数可选可不选,最后一个数一定不选\\n f11 int // 第一个数可选可不选,最后一个数可选可不选,也就是没有任何限制\\n}\\n\\ntype seg []data\\n\\n// 合并左右儿子\\nfunc (t seg) maintain(o int) {\\n a, b := t[o<<1], t[o<<1|1]\\n t[o] = data{\\n max(a.f00+b.f10, a.f01+b.f00),\\n max(a.f00+b.f11, a.f01+b.f01),\\n max(a.f10+b.f10, a.f11+b.f00),\\n max(a.f10+b.f11, a.f11+b.f01),\\n }\\n}\\n\\n// 用 a 初始化线段树\\nfunc (t seg) build(a []int, o, l, r int) {\\n if l == r {\\n t[o].f11 = max(a[l], 0)\\n return\\n }\\n m := (l + r) >> 1\\n t.build(a, o<<1, l, m)\\n t.build(a, o<<1|1, m+1, r)\\n t.maintain(o)\\n}\\n\\n// 把 a[i] 改成 val\\nfunc (t seg) update(o, l, r, i, val int) {\\n if l == r {\\n t[o].f11 = max(val, 0)\\n return\\n }\\n m := (l + r) >> 1\\n if i <= m {\\n t.update(o<<1, l, m, i, val)\\n } else {\\n t.update(o<<1|1, m+1, r, i, val)\\n }\\n t.maintain(o)\\n}\\n\\nfunc maximumSumSubsequence(nums []int, queries [][]int) (ans int) {\\n n := len(nums)\\n t := make(seg, 2<<bits.Len(uint(n-1)))\\n t.build(nums, 1, 0, n-1)\\n\\n for _, q := range queries {\\n t.update(1, 0, n-1, q[0], q[1])\\n ans += t[1].f11 // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n return ans % 1_000_000_007\\n}\\n
\\n###js
\\nvar maximumSumSubsequence = function(nums, queries) {\\n const n = nums.length;\\n // 4 个数分别保存 f00, f01, f10, f11\\n const t = Array.from({length: 2 << (32 - Math.clz32(n))}, () => [0, 0, 0, 0]);\\n\\n // 合并左右儿子\\n function maintain(o) {\\n const a = t[o * 2], b = t[o * 2 + 1];\\n t[o][0] = Math.max(a[0] + b[2], a[1] + b[0]);\\n t[o][1] = Math.max(a[0] + b[3], a[1] + b[1]);\\n t[o][2] = Math.max(a[2] + b[2], a[3] + b[0]);\\n t[o][3] = Math.max(a[2] + b[3], a[3] + b[1]);\\n }\\n\\n // 用 nums 初始化线段树\\n function build(o, l, r) {\\n if (l === r) {\\n t[o][3] = Math.max(nums[l], 0);\\n return;\\n }\\n const m = Math.floor((l + r) / 2);\\n build(o * 2, l, m);\\n build(o * 2 + 1, m + 1, r);\\n maintain(o);\\n }\\n\\n // 把 nums[i] 改成 val\\n function update(o, l, r, i, val) {\\n if (l === r) {\\n t[o][3] = Math.max(val, 0);\\n return;\\n }\\n const m = Math.floor((l + r) / 2);\\n if (i <= m) {\\n update(o * 2, l, m, i, val);\\n } else {\\n update(o * 2 + 1, m + 1, r, i, val);\\n }\\n maintain(o);\\n }\\n\\n build(1, 0, n - 1);\\n\\n let ans = 0;\\n for (const [i, x] of queries) {\\n update(1, 0, n - 1, i, x);\\n ans += t[1][3]; // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n return ans % 1_000_000_007;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn maximum_sum_subsequence(nums: Vec<i32>, queries: Vec<Vec<i32>>) -> i32 {\\n let n = nums.len();\\n // 4 个数分别保存 f00, f01, f10, f11\\n let mut t = vec![vec![0u32; 4]; 2 << (64 - n.leading_zeros())];\\n Self::build(&mut t, &nums, 1, 0, n - 1);\\n\\n let mut ans = 0u64;\\n for q in queries {\\n Self::update(&mut t, 1, 0, n - 1, q[0] as usize, q[1]);\\n ans += t[1][3] as u64; // 注意 f11 没有任何限制,也就是整个数组的打家劫舍\\n }\\n (ans % 1_000_000_007) as _\\n }\\n\\n // 合并左右儿子\\n fn maintain(t: &mut Vec<Vec<u32>>, o: usize) {\\n let l = o * 2;\\n let r = o * 2 + 1;\\n t[o][0] = (t[l][0] + t[r][2]).max(t[l][1] + t[r][0]);\\n t[o][1] = (t[l][0] + t[r][3]).max(t[l][1] + t[r][1]);\\n t[o][2] = (t[l][2] + t[r][2]).max(t[l][3] + t[r][0]);\\n t[o][3] = (t[l][2] + t[r][3]).max(t[l][3] + t[r][1]);\\n }\\n\\n // 用 nums 初始化线段树\\n fn build(t: &mut Vec<Vec<u32>>, nums: &[i32], o: usize, l: usize, r: usize) {\\n if l == r {\\n t[o][3] = nums[l].max(0) as u32;\\n return;\\n }\\n let m = (l + r) / 2;\\n Self::build(t, nums, o * 2, l, m);\\n Self::build(t, nums, o * 2 + 1, m + 1, r);\\n Self::maintain(t, o);\\n }\\n\\n // 把 nums[i] 改成 val\\n fn update(t: &mut Vec<Vec<u32>>, o: usize, l: usize, r: usize, i: usize, val: i32) {\\n if l == r {\\n t[o][3] = val.max(0) as u32;\\n return;\\n }\\n let m = (l + r) / 2;\\n if i <= m {\\n Self::update(t, o * 2, l, m, i, val);\\n } else {\\n Self::update(t, o * 2 + 1, m + 1, r, i, val);\\n }\\n Self::maintain(t, o);\\n }\\n}\\n
\\n如果一个题目可以用分治解决,那么这个题目的带修改版本可以用线段树解决。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本题是可以修改数组元素值的 198. 打家劫舍。 为了解决本题,首先来换一个角度,用分治的思想解决打家劫舍。\\n\\n设 $f(A)$ 为数组 $A$ 的打家劫舍的答案。把 $\\\\textit{nums}$ 从中间切开,分成左右两个子数组,分别记作 $a$ 和 $b$。要计算 $f(\\\\textit{nums})$,看上去,我们只需要分别计算 $f(a)$ 和 $f(b)$。但这是不对的,万一同时选了 $a$ 的最后一个数和 $b$ 的第一个数,就不满足题目要求了。\\n\\n怎么办?要么不选 $a$ 的最后一个数,要么不选 $b$ 的第一个数。所以加个约束,先来(非正式地…","guid":"https://leetcode.cn/problems/maximum-sum-of-subsequence-with-non-adjacent-elements//solution/fen-zhi-si-xiang-xian-duan-shu-pythonjav-xnhz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T04:07:07.493Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"哈希记录下标","url":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array//solution/ha-xi-ji-lu-xia-biao-by-ecstatic-noether-37la","content":"\\n\\nProblem: 100303. 查询数组中元素的出现位置
\\n
###Python3
\\nclass Solution:\\n def occurrencesOfElement(self, nums: List[int], queries: List[int], x: int) -> List[int]:\\n cnt = []\\n for i, num in enumerate(nums):\\n if num == x:\\n cnt.append(i)\\n ans = []\\n for i in queries:\\n if i <= len(cnt):\\n ans.append(cnt[i - 1])\\n else:\\n ans.append(-1)\\n return ans\\n
\\n","description":"Problem: 100303. 查询数组中元素的出现位置 ###Python3\\n\\nclass Solution:\\n def occurrencesOfElement(self, nums: List[int], queries: List[int], x: int) -> List[int]:\\n cnt = []\\n for i, num in enumerate(nums):\\n if num == x:\\n cnt.append(i)\\n ans = […","guid":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array//solution/ha-xi-ji-lu-xia-biao-by-ecstatic-noether-37la","author":"ecstatic-noetheryvx","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T03:26:23.523Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"记录所有等于 x 的元素下标(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array//solution/ji-lu-suo-you-deng-yu-x-de-yuan-su-xia-b-ku53","content":"用 $\\\\textit{pos}$ 数组记录所有等于 $x$ 的 $\\\\textit{nums}[i]$ 的下标 $i$。
\\n对于每个询问 $q=\\\\textit{queries}[i]$,如果 $q$ 大于 $\\\\textit{pos}$ 的长度,则答案为 $-1$,否则答案为 $\\\\textit{pos}[q-1]$。
\\n###py
\\nclass Solution:\\n def occurrencesOfElement(self, nums: List[int], queries: List[int], x: int) -> List[int]:\\n pos = [i for i, num in enumerate(nums) if num == x]\\n return [-1 if q > len(pos) else pos[q - 1] for q in queries]\\n
\\n###java
\\nclass Solution {\\n public int[] occurrencesOfElement(int[] nums, int[] queries, int x) {\\n List<Integer> pos = new ArrayList<>();\\n for (int i = 0; i < nums.length; i++) {\\n if (nums[i] == x) {\\n pos.add(i);\\n }\\n }\\n for (int i = 0; i < queries.length; i++) {\\n queries[i] = queries[i] > pos.size() ? -1 : pos.get(queries[i] - 1);\\n }\\n return queries;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> occurrencesOfElement(vector<int>& nums, vector<int>& queries, int x) {\\n vector<int> pos;\\n for (int i = 0; i < nums.size(); i++) {\\n if (nums[i] == x) {\\n pos.push_back(i);\\n }\\n }\\n for (int& q : queries) {\\n q = q > pos.size() ? -1 : pos[q - 1];\\n }\\n return queries;\\n }\\n};\\n
\\n###c
\\nint* occurrencesOfElement(int* nums, int numsSize, int* queries, int queriesSize, int x, int* returnSize) {\\n int* pos = malloc(numsSize * sizeof(int));\\n int k = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == x) {\\n pos[k++] = i;\\n }\\n }\\n\\n for (int i = 0; i < queriesSize; i++) {\\n queries[i] = queries[i] > k ? -1 : pos[queries[i] - 1];\\n }\\n\\n free(pos);\\n *returnSize = queriesSize;\\n return queries;\\n}\\n
\\n###go
\\nfunc occurrencesOfElement(nums, queries []int, x int) []int {\\npos := []int{}\\nfor i, num := range nums {\\nif num == x {\\npos = append(pos, i)\\n}\\n}\\nfor i, q := range queries {\\nif q > len(pos) {\\nqueries[i] = -1\\n} else {\\nqueries[i] = pos[q-1]\\n}\\n}\\nreturn queries\\n}\\n
\\n###js
\\nvar occurrencesOfElement = function(nums, queries, x) {\\n const pos = [];\\n for (let i = 0; i < nums.length; i++) {\\n if (nums[i] === x) {\\n pos.push(i);\\n }\\n }\\n return queries.map(q => q > pos.length ? -1 : pos[q - 1]);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn occurrences_of_element(nums: Vec<i32>, queries: Vec<i32>, x: i32) -> Vec<i32> {\\n let pos = nums.iter()\\n .enumerate()\\n .filter(|(_, &num)| num == x)\\n .map(|(i, _)| i)\\n .collect::<Vec<_>>();\\n queries.iter()\\n .map(|&q| if q as usize > pos.len() { -1 } else { pos[q as usize - 1] as i32 })\\n .collect()\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用 $\\\\textit{pos}$ 数组记录所有等于 $x$ 的 $\\\\textit{nums}[i]$ 的下标 $i$。 对于每个询问 $q=\\\\textit{queries}[i]$,如果 $q$ 大于 $\\\\textit{pos}$ 的长度,则答案为 $-1$,否则答案为 $\\\\textit{pos}[q-1]$。\\n\\n###py\\n\\nclass Solution:\\n def occurrencesOfElement(self, nums: List[int], queries: List[int], x: int) -> List[int]:…","guid":"https://leetcode.cn/problems/find-occurrences-of-an-element-in-an-array//solution/ji-lu-suo-you-deng-yu-x-de-yuan-su-xia-b-ku53","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T01:32:20.172Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历+O(1) 空间(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-xor-of-numbers-which-appear-twice//solution/yi-ci-bian-li-wei-yun-suan-pythonjavacgo-7su0","content":"遍历 $\\\\textit{nums}$,同时用一个 $\\\\textit{vis}$ 集合记录遇到的数字。
\\n代码实现时,由于 $\\\\textit{nums}[i]\\\\le 50$,可以用二进制数表示集合,具体见 从集合论到位运算,常见位运算技巧分类总结!
\\n本题视频讲解,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def duplicateNumbersXOR(self, nums: List[int]) -> int:\\n ans = vis = 0\\n for x in nums:\\n if vis >> x & 1:\\n ans ^= x\\n else:\\n vis |= 1 << x\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int duplicateNumbersXOR(int[] nums) {\\n int ans = 0;\\n long vis = 0;\\n for (int x : nums) {\\n if ((vis >> x & 1) > 0) {\\n ans ^= x;\\n } else {\\n vis |= 1L << x;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int duplicateNumbersXOR(vector<int>& nums) {\\n int ans = 0;\\n long long vis = 0;\\n for (int x : nums) {\\n if (vis >> x & 1) {\\n ans ^= x;\\n } else {\\n vis |= 1LL << x;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint duplicateNumbersXOR(int* nums, int numsSize) {\\n int ans = 0;\\n long long vis = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n if (vis >> x & 1) {\\n ans ^= x;\\n } else {\\n vis |= 1LL << x;\\n }\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc duplicateNumbersXOR(nums []int) (ans int) {\\nvis := 0\\nfor _, x := range nums {\\nif vis>>x&1 > 0 {\\nans ^= x\\n} else {\\nvis |= 1 << x\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar duplicateNumbersXOR = function(nums) {\\n let ans = 0;\\n // JS 的位运算会强转成 32 位整数,需要用 BigInt 处理\\n let vis = 0n;\\n for (const x of nums) {\\n if (vis >> BigInt(x) & 1n) {\\n ans ^= x;\\n } else {\\n vis |= 1n << BigInt(x);\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn duplicate_numbers_xor(nums: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut vis = 0i64;\\n for x in nums {\\n if (vis >> x & 1) > 0 {\\n ans ^= x;\\n } else {\\n vis |= 1 << x;\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历 $\\\\textit{nums}$,同时用一个 $\\\\textit{vis}$ 集合记录遇到的数字。 设 $x=\\\\textit{nums}[i]$。\\n如果 $x$ 不在 $\\\\textit{vis}$ 中,说明是第一次遇到,加入 $\\\\textit{vis}$。\\n如果 $x$ 在 $\\\\textit{vis}$ 中,说明是第二次遇到(注意每个数至多出现两次),加入答案(异或)。\\n\\n代码实现时,由于 $\\\\textit{nums}[i]\\\\le 50$,可以用二进制数表示集合,具体见 从集合论到位运算,常见位运算技巧分类总结!\\n\\n本题视频讲解,欢迎点赞关注~\\n\\n###…","guid":"https://leetcode.cn/problems/find-the-xor-of-numbers-which-appear-twice//solution/yi-ci-bian-li-wei-yun-suan-pythonjavacgo-7su0","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-26T00:05:03.284Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"三种写法:记忆化搜索 / 递推 / bitset 优化(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/0-1-bei-bao-cong-ji-yi-hua-sou-suo-dao-d-ev76","content":"设 $\\\\textit{nums}$ 的元素和为 $s$。
\\n两个子集的元素和相等,意味着:
\\n如果 $s$ 是奇数,$\\\\dfrac{s}{2}$ 不是整数,直接返回 $\\\\texttt{false}$。
\\n如果 $s$ 是偶数,问题相当于:
\\n这可以用「恰好装满」型 0-1 背包解决,请看视频讲解:0-1 背包和完全背包【基础算法精讲 18】。制作不易,欢迎点赞关注~
\\n定义 $\\\\textit{dfs}(i,j)$ 表示能否从 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[i]$ 中选出一个和恰好等于 $j$ 的子序列。
\\n考虑 $\\\\textit{nums}[i]$ 选或不选:
\\n这两个只要有一个成立,$\\\\textit{dfs}(i,j)$ 就是 $\\\\texttt{true}$。所以有
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\textit{dfs}(i-1,j-\\\\textit{nums}[i]) \\\\vee \\\\textit{dfs}(i-1,j)
\\n$$
其中 $\\\\vee$ 即编程语言中的 ||
。代码实现时,可以只在 $j\\\\ge \\\\textit{nums}[i]$ 时才调用 $\\\\textit{dfs}(i-1,j-\\\\textit{nums}[i])$,因为任何子序列的和都不会是负的。
递归边界:$\\\\textit{dfs}(-1,0) = \\\\texttt{true},\\\\ \\\\textit{dfs}(-1,>0) = \\\\texttt{false}$。如果所有数都考虑完了,且 $j=0$,表示找到了一个和为 $s/2$ 的子序列,返回 $\\\\texttt{true}$,否则返回 $\\\\texttt{false}$。
\\n递归入口:$\\\\textit{dfs}(n-1,s/2)$,即答案。
\\n###py
\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int) -> bool:\\n if i < 0:\\n return j == 0\\n return j >= nums[i] and dfs(i - 1, j - nums[i]) or dfs(i - 1, j)\\n\\n s = sum(nums)\\n return s % 2 == 0 and dfs(len(nums) - 1, s // 2)\\n
\\n###java
\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int s = 0;\\n for (int num : nums) {\\n s += num;\\n }\\n if (s % 2 != 0) {\\n return false;\\n }\\n int n = nums.length;\\n int[][] memo = new int[n][s / 2 + 1];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(n - 1, s / 2, nums, memo);\\n }\\n\\n private boolean dfs(int i, int j, int[] nums, int[][] memo) {\\n if (i < 0) {\\n return j == 0;\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j] == 1;\\n }\\n boolean res = j >= nums[i] && dfs(i - 1, j - nums[i], nums, memo) || dfs(i - 1, j, nums, memo);\\n memo[i][j] = res ? 1 : 0; // 记忆化\\n return res;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int s = reduce(nums.begin(), nums.end());\\n if (s % 2) {\\n return false;\\n }\\n int n = nums.size();\\n vector memo(n, vector<int>(s / 2 + 1, -1)); // -1 表示没有计算过\\n auto dfs = [&](this auto&& dfs, int i, int j) -> bool {\\n if (i < 0) {\\n return j == 0;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n return res = j >= nums[i] && dfs(i - 1, j - nums[i]) || dfs(i - 1, j);\\n };\\n return dfs(n - 1, s / 2);\\n }\\n};\\n
\\n###c
\\nbool dfs(int i, int j, int* nums, int** memo) {\\n if (i < 0) {\\n return j == 0;\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j] == 1;\\n }\\n return memo[i][j] = j >= nums[i] && dfs(i - 1, j - nums[i], nums, memo) || dfs(i - 1, j, nums, memo);\\n}\\n\\nbool canPartition(int* nums, int numsSize) {\\n int s = 0;\\n for (int i = 0; i < numsSize; i++) {\\n s += nums[i];\\n }\\n if (s % 2) {\\n return false;\\n }\\n int** memo = malloc(numsSize * sizeof(int*));\\n for (int i = 0; i < numsSize; i++) {\\n memo[i] = malloc((s / 2 + 1) * sizeof(int));\\n memset(memo[i], -1, (s / 2 + 1) * sizeof(int)); // -1 表示没有计算过\\n }\\n int ans = dfs(numsSize - 1, s / 2, nums, memo);\\n for (int i = 0; i < numsSize; i++) {\\n free(memo[i]);\\n }\\n free(memo);\\n return ans;\\n}\\n
\\n###go
\\nfunc canPartition(nums []int) bool {\\n s := 0\\n for _, x := range nums {\\n s += x\\n }\\n if s%2 != 0 {\\n return false\\n }\\n n := len(nums)\\n memo := make([][]int8, n)\\n for i := range memo {\\n memo[i] = make([]int8, s/2+1)\\n for j := range memo[i] {\\n memo[i][j] = -1 // -1 表示没有计算过\\n }\\n }\\n var dfs func(int, int) bool\\n dfs = func(i, j int) bool {\\n if i < 0 {\\n return j == 0\\n }\\n p := &memo[i][j]\\n if *p != -1 { // 之前计算过\\n return *p == 1\\n }\\n res := j >= nums[i] && dfs(i-1, j-nums[i]) || dfs(i-1, j)\\n if res {\\n *p = 1 // 记忆化\\n } else {\\n *p = 0\\n }\\n return res\\n }\\n return dfs(n-1, s/2)\\n}\\n
\\n###js
\\nconst canPartition = function(nums) {\\n const s = _.sum(nums);\\n if (s % 2) {\\n return false;\\n }\\n const n = nums.length;\\n const memo = Array.from({length: n}, () => Array(s / 2 + 1).fill(-1)); // -1 表示没有计算过\\n function dfs(i, j) {\\n if (i < 0) {\\n return j === 0;\\n }\\n if (memo[i][j] !== -1) { // 之前计算过\\n return memo[i][j] === 1;\\n }\\n const res = j >= nums[i] && dfs(i - 1, j - nums[i]) || dfs(i - 1, j);\\n memo[i][j] = res ? 1 : 0; // 记忆化\\n return res;\\n }\\n return dfs(n - 1, s / 2);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_partition(nums: Vec<i32>) -> bool {\\n let s = nums.iter().sum::<i32>() as usize;\\n if s % 2 != 0 {\\n return false;\\n }\\n fn dfs(i: usize, j: usize, nums: &[i32], memo: &mut [Vec<i32>]) -> bool {\\n if i == nums.len() {\\n return j == 0;\\n }\\n if memo[i][j] != -1 { // 之前计算过\\n return memo[i][j] == 1;\\n }\\n let x = nums[i] as usize;\\n let res = j >= x && dfs(i + 1, j - x, nums, memo) || dfs(i + 1, j, nums, memo);\\n memo[i][j] = if res { 1 } else { 0 }; // 记忆化\\n res\\n }\\n let n = nums.len();\\n let mut memo = vec![vec![-1; s / 2 + 1]; n]; // -1 表示没有计算过\\n // 为方便起见,改成 i 从 0 开始\\n dfs(0, s / 2, &nums, &mut memo)\\n }\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示能否从 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[i]$ 中选出一个和恰好等于 $j$ 的子序列。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i][j] = f[i-1][j-\\\\textit{nums}[i]] \\\\vee f[i-1][j]
\\n$$
但是,这种定义方式没有状态能表示递归边界,即 $i=-1$ 的情况。
\\n解决办法:在二维数组 $f$ 的最上边插入一排状态,那么其余状态全部向下偏移一位,把 $f[i]$ 改为 $f[i+1]$,把 $f[i-1]$ 改为 $f[i]$。
\\n修改后 $f[i+1][j]$ 表示能否从 $\\\\textit{nums}[0]$ 到 $\\\\textit{nums}[i]$ 中选出一个和为 $j$ 的子序列。$f[0]$ 对应递归边界。
\\n修改后的递推式为
\\n$$
\\nf[i+1][j] = f[i][j-\\\\textit{nums}[i]] \\\\vee f[i][j]
\\n$$
\\n\\n问:为什么 $\\\\textit{nums}$ 的下标不用变?
\\n答:既然是在 $f$ 的最上边插入一排状态,那么就只需要修改和 $f$ 有关的下标,其余任何逻辑都无需修改。或者说,如果把 $\\\\textit{nums}[i]$ 也改成 $\\\\textit{nums}[i+1]$,那么 $\\\\textit{nums}[0]$ 就被我们给忽略掉了。
\\n
初始值 $f[0][0]=\\\\texttt{true}$,翻译自递归边界 $\\\\textit{dfs}(-1,0)=\\\\texttt{true}$。其余值初始化成 $\\\\texttt{false}$。
\\n答案为 $f[n][s/2]$,翻译自递归入口 $\\\\textit{dfs}(n-1,s/2)$。
\\n###py
\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n s = sum(nums)\\n if s % 2:\\n return False\\n s //= 2 # 注意这里把 s 减半了\\n n = len(nums)\\n f = [[False] * (s + 1) for _ in range(n + 1)]\\n f[0][0] = True\\n for i, x in enumerate(nums):\\n for j in range(s + 1):\\n f[i + 1][j] = j >= x and f[i][j - x] or f[i][j]\\n return f[n][s]\\n
\\n###java
\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int s = 0;\\n for (int num : nums) {\\n s += num;\\n }\\n if (s % 2 != 0) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n int n = nums.length;\\n boolean[][] f = new boolean[n + 1][s + 1];\\n f[0][0] = true;\\n for (int i = 0; i < n; i++) {\\n int x = nums[i];\\n for (int j = 0; j <= s; j++) {\\n f[i + 1][j] = j >= x && f[i][j - x] || f[i][j];\\n }\\n }\\n return f[n][s];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int s = reduce(nums.begin(), nums.end());\\n if (s % 2) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n int n = nums.size();\\n vector f(n + 1, vector<int>(s + 1));\\n f[0][0] = true;\\n for (int i = 0; i < n; i++) {\\n int x = nums[i];\\n for (int j = 0; j <= s; j++) {\\n f[i + 1][j] = j >= x && f[i][j - x] || f[i][j];\\n }\\n }\\n return f[n][s];\\n }\\n};\\n
\\n###c
\\nbool canPartition(int* nums, int numsSize) {\\n int s = 0;\\n for (int i = 0; i < numsSize; i++) {\\n s += nums[i];\\n }\\n if (s % 2) {\\n return false;\\n }\\n s = s / 2 + 1;\\n bool* f = calloc((numsSize + 1) * s, sizeof(bool));\\n f[0] = true;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n for (int j = 0; j < s; j++) {\\n f[(i + 1) * s + j] = j >= x && f[i * s + j - x] || f[i * s + j];\\n }\\n }\\n bool ans = f[(numsSize + 1) * s - 1];\\n free(f);\\n return ans;\\n}\\n
\\n###go
\\nfunc canPartition(nums []int) bool {\\n s := 0\\n for _, num := range nums {\\n s += num\\n }\\n if s%2 != 0 {\\n return false\\n }\\n s /= 2 // 注意这里把 s 减半了\\n n := len(nums)\\n f := make([][]bool, n+1)\\n for i := range f {\\n f[i] = make([]bool, s+1)\\n }\\n f[0][0] = true\\n for i, x := range nums {\\n for j := 0; j <= s; j++ {\\n f[i+1][j] = j >= x && f[i][j-x] || f[i][j]\\n }\\n }\\n return f[n][s]\\n}\\n
\\n###js
\\nvar canPartition = function(nums) {\\n let s = _.sum(nums);\\n if (s % 2) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n const n = nums.length;\\n const f = Array.from({length: n + 1}, () => Array(s + 1).fill(false));\\n f[0][0] = true;\\n for (let i = 0; i < n; i++) {\\n const x = nums[i];\\n for (let j = 0; j <= s; j++) {\\n f[i + 1][j] = j >= x && f[i][j - x] || f[i][j];\\n }\\n }\\n return f[n][s];\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_partition(nums: Vec<i32>) -> bool {\\n let s = nums.iter().sum::<i32>();\\n if s % 2 != 0 {\\n return false;\\n }\\n let s = s as usize / 2; // 注意这里把 s 减半了\\n let n = nums.len();\\n let mut f = vec![vec![false; s + 1]; n + 1];\\n f[0][0] = true;\\n for (i, &x) in nums.iter().enumerate() {\\n let x = x as usize;\\n for j in 0..=s {\\n f[i + 1][j] = j >= x && f[i][j - x] || f[i][j];\\n }\\n }\\n f[n][s]\\n }\\n}\\n
\\n观察上面的状态转移方程,在计算 $f[i+1]$ 时,只会用到 $f[i]$,不会用到比 $i$ 更早的状态。
\\n因此可以去掉第一个维度,反复利用同一个一维数组。
\\n状态转移方程改为
\\n$$
\\nf[j] = f[j] \\\\vee f[j-\\\\textit{nums}[i]]
\\n$$
初始值 $f[0]= \\\\texttt{true}$。
\\n答案为 $f[s/2]$。
\\n具体例子,以及为什么要倒序遍历 $j$,请看 0-1 背包视频讲解。
\\n此外,设前 $i$ 个数的和为 $s\'$,由于子序列的元素和不可能比 $s\'$ 还大,$j$ 可以从 $\\\\min(s\',s/2)$ 开始倒着枚举。比如 $\\\\textit{nums}$ 前两个数的和等于 $5$,那么我们无法在前两个数中,选出一个元素和大于 $5$ 的子序列,所以对于 $j>5$ 的 $f$ 值,一定是 $\\\\texttt{false}$,无需计算。
\\n此外,可以在循环中提前判断 $f[s/2]$ 是否为 $\\\\texttt{true}$,是就直接返回 $\\\\texttt{true}$。
\\n###py
\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n s = sum(nums)\\n if s % 2:\\n return False\\n s //= 2 # 注意这里把 s 减半了\\n f = [True] + [False] * s\\n s2 = 0\\n for i, x in enumerate(nums):\\n s2 = min(s2 + x, s)\\n for j in range(s2, x - 1, -1):\\n f[j] = f[j] or f[j - x]\\n if f[s]:\\n return True\\n return False\\n
\\n###java
\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int s = 0;\\n for (int x : nums) {\\n s += x;\\n }\\n if (s % 2 != 0) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n boolean[] f = new boolean[s + 1];\\n f[0] = true;\\n int s2 = 0;\\n for (int x : nums) {\\n s2 = Math.min(s2 + x, s);\\n for (int j = s2; j >= x; j--) {\\n f[j] = f[j] || f[j - x];\\n }\\n if (f[s]) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int s = reduce(nums.begin(), nums.end());\\n if (s % 2) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n vector<int> f(s + 1);\\n f[0] = true;\\n int s2 = 0;\\n for (int x : nums) {\\n s2 = min(s2 + x, s);\\n for (int j = s2; j >= x; j--) {\\n f[j] |= f[j - x];\\n }\\n if (f[s]) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nbool canPartition(int* nums, int numsSize) {\\n int s = 0;\\n for (int i = 0; i < numsSize; i++) {\\n s += nums[i];\\n }\\n if (s % 2) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n bool* f = calloc(s + 1, sizeof(bool));\\n f[0] = true;\\n int s2 = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n s2 = MIN(s2 + x, s);\\n for (int j = s2; j >= x; j--) {\\n f[j] |= f[j - x];\\n }\\n if (f[s]) {\\n free(f);\\n return true;\\n }\\n }\\n free(f);\\n return false;\\n}\\n
\\n###go
\\nfunc canPartition(nums []int) bool {\\n s := 0\\n for _, x := range nums {\\n s += x\\n }\\n if s%2 != 0 {\\n return false\\n }\\n s /= 2 // 注意这里把 s 减半了\\n f := make([]bool, s+1)\\n f[0] = true\\n s2 := 0\\n for _, x := range nums {\\n s2 = min(s2+x, s)\\n for j := s2; j >= x; j-- {\\n f[j] = f[j] || f[j-x]\\n }\\n if f[s] {\\n return true\\n }\\n }\\n return false\\n}\\n
\\n###js
\\nvar canPartition = function(nums) {\\n let s = _.sum(nums);\\n if (s % 2) {\\n return false;\\n }\\n s /= 2; // 注意这里把 s 减半了\\n const f = Array(s + 1).fill(false);\\n f[0] = true;\\n let s2 = 0;\\n for (const x of nums) {\\n s2 = Math.min(s2 + x, s);\\n for (let j = s2; j >= x; j--) {\\n f[j] = f[j] || f[j - x];\\n }\\n if (f[s]) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn can_partition(nums: Vec<i32>) -> bool {\\n let s = nums.iter().sum::<i32>();\\n if s % 2 != 0 {\\n return false;\\n }\\n let s = s as usize / 2; // 注意这里把 s 减半了\\n let mut f = vec![false; s + 1];\\n f[0] = true;\\n let mut s2 = 0;\\n for x in nums {\\n let x = x as usize;\\n s2 = (s2 + x).min(s);\\n for j in (x..=s2).rev() {\\n f[j] = f[j] || f[j - x];\\n }\\n if f[s] {\\n return true;\\n }\\n }\\n false\\n }\\n}\\n
\\n把布尔数组压缩成一个二进制数,二进制数从低到高第 $i$ 位是 $0$,表示布尔数组的第 $i$ 个元素是 $\\\\texttt{false}$;从低到高第 $i$ 位是 $1$,表示布尔数组的第 $i$ 个元素是 $\\\\texttt{true}$。
\\n转移方程等价于,把 $f$ 中的每个比特位增加 $x=\\\\textit{nums}[i]$,即左移 $x$ 位,然后跟原来 $f$ 计算 OR。前者对应选 $x$,后者对应不选 $x$。
\\n判断 $f[s]$ 是否为 $\\\\texttt{true}$,等价于判断 $f$ 的第 $s$ 位是否为 $1$,即 (f >> s & 1) == 1
。
###py
\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n s = sum(nums)\\n if s % 2:\\n return False\\n s //= 2\\n f = 1\\n for x in nums:\\n f |= f << x\\n return (f >> s & 1) == 1\\n
\\n###java
\\nimport java.math.BigInteger;\\n\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int s = 0;\\n for (int x : nums) {\\n s += x;\\n }\\n if (s % 2 != 0) {\\n return false;\\n }\\n s /= 2;\\n BigInteger f = BigInteger.ONE;\\n for (int x : nums) {\\n f = f.or(f.shiftLeft(x)); // f |= f << x;\\n }\\n return f.testBit(s); // 判断 f 中第 s 位是否为 1\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int s = reduce(nums.begin(), nums.end());\\n if (s % 2) {\\n return false;\\n }\\n s /= 2;\\n bitset<10001> f; // sum(nums[i]) / 2 <= 10000\\n f[0] = 1;\\n for (int x : nums) {\\n f |= f << x;\\n }\\n return f[s]; // 判断 f 中第 s 位是否为 1\\n }\\n};\\n
\\n###go
\\nfunc canPartition(nums []int) bool {\\n s := 0\\n for _, x := range nums {\\n s += x\\n }\\n if s%2 != 0 {\\n return false\\n }\\n s /= 2\\n f := big.NewInt(1)\\n p := new(big.Int)\\n for _, x := range nums {\\n f.Or(f, p.Lsh(f, uint(x)))\\n }\\n return f.Bit(s) == 1\\n}\\n
\\n###js
\\nvar canPartition = function(nums) {\\n let s = _.sum(nums);\\n if (s % 2) {\\n return false;\\n }\\n s /= 2;\\n let f = 1n;\\n for (const x of nums) {\\n f |= f << BigInt(x);\\n }\\n return (f >> BigInt(s) & 1n) === 1n;\\n};\\n
\\n改成计算分割的方案数,要怎么做?
\\n欢迎在评论区分享你的思路/代码。
\\n更多相似题目,见下面动态规划题单中的「§3.1 0-1 背包」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、分析 设 $\\\\textit{nums}$ 的元素和为 $s$。\\n\\n两个子集的元素和相等,意味着:\\n\\n把 $\\\\textit{nums}$ 分成两个子集,每个子集的元素和恰好等于 $\\\\dfrac{s}{2}$。\\n$s$ 必须是偶数。\\n\\n如果 $s$ 是奇数,$\\\\dfrac{s}{2}$ 不是整数,直接返回 $\\\\texttt{false}$。\\n\\n如果 $s$ 是偶数,问题相当于:\\n\\n能否从 $\\\\textit{nums}$ 中选出一个子序列,其元素和恰好等于 $\\\\dfrac{s}{2}$?\\n\\n这可以用「恰好装满」型 0-1 背包解决,请看视频讲解:0-1…","guid":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/0-1-bei-bao-cong-ji-yi-hua-sou-suo-dao-d-ev76","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-21T07:21:16.085Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 n 的因子,至多 128 个(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/mei-ju-n-de-yin-zi-zhi-duo-128-ge-python-u36n","content":"设 $s$ 的长度为 $n$,$t$ 的长度为 $k$。
\\n由于 $s$ 是由若干长度为 $k$ 的字符串拼接而成,所以 $k$ 一定是 $n$ 的因子。
\\n由于 $10^5$ 以内的数,因子个数至多为 $128$($83160$ 的因子个数),所以我们可以暴力枚举 $n$ 的因子 $k$。
\\n然后比较所有首字母下标为 $0,k,2k,3k,\\\\cdots,n-k$ 的长为 $k$ 的子串,所包含的字母及其个数是否一样(同位字符串)。
\\n注意只需枚举小于 $n$ 的因子,如果这些因子都不满足要求,答案一定是 $n$(如示例 2)。
\\n请看 视频讲解 第三题,欢迎点赞关注~
\\n###py
\\nclass Solution:\\n def minAnagramLength(self, s: str) -> int:\\n n = len(s)\\n for k in range(1, n // 2 + 1):\\n if n % k:\\n continue\\n cnt0 = Counter(s[:k])\\n for i in range(k * 2, n + 1, k):\\n if Counter(s[i - k: i]) != cnt0:\\n break\\n else:\\n return k\\n return n\\n
\\n###py
\\nclass Solution:\\n def minAnagramLength(self, s: str) -> int:\\n n = len(s)\\n for k in range(1, n // 2 + 1):\\n if n % k:\\n continue\\n cnt0 = Counter(s[:k])\\n if all(Counter(s[i - k: i]) == cnt0 for i in range(k * 2, n + 1, k)):\\n return k\\n return n\\n
\\n###py
\\nclass Solution:\\n def minAnagramLength(self, s: str) -> int:\\n n = len(s)\\n for k in range(1, n // 2 + 1):\\n if n % k:\\n continue\\n t = sorted(s[:k])\\n if all(sorted(s[i - k: i]) == t for i in range(k * 2, n + 1, k)):\\n return k\\n return n\\n
\\n###java
\\nclass Solution {\\n public int minAnagramLength(String S) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n next:\\n for (int k = 1; k <= n / 2; k++) {\\n if (n % k > 0) {\\n continue;\\n }\\n int[] cnt0 = new int[26];\\n for (int j = 0; j < k; j++) {\\n cnt0[s[j] - \'a\']++;\\n }\\n for (int i = k * 2; i <= n; i += k) {\\n int[] cnt = new int[26];\\n for (int j = i - k; j < i; j++) {\\n cnt[s[j] - \'a\']++;\\n }\\n if (!Arrays.equals(cnt, cnt0)) {\\n continue next;\\n }\\n }\\n return k;\\n }\\n return n;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minAnagramLength(string s) {\\n int n = s.length();\\n for (int k = 1; k <= n / 2; k++) {\\n if (n % k) {\\n continue;\\n }\\n array<int, 26> cnt0{};\\n for (int j = 0; j < k; j++) {\\n cnt0[s[j] - \'a\']++;\\n }\\n bool ok = true;\\n for (int i = k * 2; i <= n; i += k) {\\n array<int, 26> cnt{};\\n for (int j = i - k; j < i; j++) {\\n cnt[s[j] - \'a\']++;\\n }\\n if (cnt != cnt0) {\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n return k;\\n }\\n }\\n return n;\\n }\\n};\\n
\\n###go
\\nfunc minAnagramLength(s string) int {\\nn := len(s)\\nnext:\\nfor k := 1; k <= n/2; k++ {\\nif n%k > 0 {\\ncontinue\\n}\\ncnt0 := [26]int{}\\nfor _, b := range s[:k] {\\ncnt0[b-\'a\']++\\n}\\nfor i := k * 2; i <= len(s); i += k {\\ncnt := [26]int{}\\nfor _, b := range s[i-k : i] {\\ncnt[b-\'a\']++\\n}\\nif cnt != cnt0 {\\ncontinue next\\n}\\n}\\nreturn k\\n}\\nreturn n\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"设 $s$ 的长度为 $n$,$t$ 的长度为 $k$。 由于 $s$ 是由若干长度为 $k$ 的字符串拼接而成,所以 $k$ 一定是 $n$ 的因子。\\n\\n由于 $10^5$ 以内的数,因子个数至多为 $128$($83160$ 的因子个数),所以我们可以暴力枚举 $n$ 的因子 $k$。\\n\\n然后比较所有首字母下标为 $0,k,2k,3k,\\\\cdots,n-k$ 的长为 $k$ 的子串,所包含的字母及其个数是否一样(同位字符串)。\\n\\n注意只需枚举小于 $n$ 的因子,如果这些因子都不满足要求,答案一定是 $n$(如示例 2)。\\n\\n请看 视频讲解 第三题…","guid":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/mei-ju-n-de-yin-zi-zhi-duo-128-ge-python-u36n","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-05T09:33:28.015Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n+cloglogc) 的解法","url":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/onloglogn-de-jie-fa-by-vclip-lx3a","content":"这道题思路很简单,但要分析复杂度就会难很多。
\\n显然的想法是计算每种字符的出现次数 $c_i$,分割出的 $t$ 的个数必须是每个出现次数的因数,因此可以计算每种字符出现次数的最大公因数 $c$,然后枚举 $c$ 的因数验证,这个解法的复杂度是 $O(nd(c))$,$d$ 是因子个数函数,$d(c)$ 的值大约为 $O(2^{\\\\log c/\\\\log\\\\log c})$,是个介于对数函数和幂函数之间的函数,数值不大。
\\n注意到在验证字符串能否分割 $c$ 段时,只需要每 $\\\\dfrac nc$ 个字符检验一次就行,这些点处的检验可以一次计算完而不是每次都去计算。枚举到因数 $d$ 时,可以直接找到对应的 $d$ 个检验点,这样只需要 $O(d)$ 的时间就可以完成检验,这样复杂度为 $O(\\\\sum_{d|c}d)=O(\\\\sigma(c))=O(c\\\\log\\\\log c)$,其中 $\\\\sigma$ 是因数和函数,大约为 $O(c\\\\log\\\\log c)$ 量级。
\\n前面的预处理部分复杂度为 $O(c|\\\\Sigma|)$,由于 $c=\\\\gcd_{i=0}^{|\\\\Sigma|-1}c_i \\\\le \\\\min_{i=0}^{|\\\\Sigma|-1}c_i \\\\le \\\\dfrac n{|\\\\Sigma|}$,因此这一部分是 $O(n)$ 的。总复杂度为 $O(n+c\\\\log\\\\log c)$。
\\n###C++
\\nclass Solution {\\npublic:\\n int minAnagramLength(string s) {\\n const int n = s.size();\\n int sum[26] = {};\\n int mask = 0;\\n for (int i = 0;i < n;++i) {\\n mask |= 1 << (s[i] - \'a\');\\n ++sum[s[i] - \'a\'];\\n }\\n int c = 0;\\n for (int e : sum)\\n c = gcd(c, e);\\n const int n0 = n / c;\\n int cnt[26] = {};\\n vector<bool> invaild(c);\\n for (int i = 0;i < c;++i) {\\n for (int j = 0;j < n0;++j)\\n ++cnt[s[n0 * i + j] - \'a\'];\\n // 排除不在 s 中的字符\\n for (int t = mask;t != 0;) {\\n const int j = __builtin_ctz(t);\\n if (1ll * (i + 1) * sum[j] != 1ll * c * cnt[j]) {\\n invaild[i] = true;\\n break;\\n }\\n t -= 1 << j;\\n }\\n }\\n const auto check = [&] (int d) {\\n for (int i = 1;i <= d;++i)\\n if (invaild[c / d * i - 1])\\n return false;\\n return true;\\n };\\n int ans = n;\\n for (int i = 1;i * i <= c;++i) {\\n if (c % i != 0) continue;\\n if (check(c / i)) return i * n0;\\n if (i != c / i && check(i)) ans = n / i;\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"这道题思路很简单,但要分析复杂度就会难很多。 显然的想法是计算每种字符的出现次数 $c_i$,分割出的 $t$ 的个数必须是每个出现次数的因数,因此可以计算每种字符出现次数的最大公因数 $c$,然后枚举 $c$ 的因数验证,这个解法的复杂度是 $O(nd(c))$,$d$ 是因子个数函数,$d(c)$ 的值大约为 $O(2^{\\\\log c/\\\\log\\\\log c})$,是个介于对数函数和幂函数之间的函数,数值不大。\\n\\n注意到在验证字符串能否分割 $c$ 段时,只需要每 $\\\\dfrac nc$ 个字符检验一次就行,这些点处的检验可以一次计算完而不是每次都去计算…","guid":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/onloglogn-de-jie-fa-by-vclip-lx3a","author":"vclip","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-05T05:05:56.427Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/mei-ju-by-tsreaper-f6ew","content":"中文题意很不清晰,我们重新描述一下题目:给定一个字符串 $s$,它是由某个字符串 $t$ 以及它的若干个同位字符串连接而成。问字符串 $t$ 的最小可能长度。
\\n显然 $t$ 的长度必须是 $s$ 的因数。$10^5$ 以内因数最多的数是 $83160$,有 $128$ 个因数。因数个数不多,可以考虑直接枚举因数 $p$。
\\n问题变为如何检查字符串 $s$ 是不是由若干个长度为 $p$ 的同位字符串连接而成。那么将 $s$ 分成长度为 $p$ 的子串,并检查每个子串每种字母出现次数是否相同即可。
\\n复杂度 $\\\\mathcal{O}(nf + |\\\\Sigma|n\\\\log n)$,其中 $f = 128$ 是因数数量,$|\\\\Sigma| = 26$ 是字符集大小。
\\n###c++
\\nclass Solution {\\npublic:\\n int minAnagramLength(string s) {\\n int n = s.size();\\n // 计算整个字符串里每种字母的出现次数\\n int cnt[26] = {0};\\n for (char c : s) cnt[c - \'a\']++;\\n\\n // 检查子串的长度是否可能为 len\\n auto check = [&](int len) {\\n for (int i = 0; i < n; i += len) {\\n // 统计当前子串每种字母的出现次数\\n int tmp[26] = {0};\\n for (int j = 0; j < len; j++) tmp[s[i + j] - \'a\']++;\\n for (int j = 0; j < 26; j++) if (tmp[j] * (n / len) != cnt[j]) return false;\\n }\\n return true;\\n };\\n\\n // 枚举子串的长度\\n for (int i = 1; i <= n; i++) if (n % i == 0 && check(i)) return i;\\n return -1;\\n }\\n};\\n
\\n","description":"解法:枚举 中文题意很不清晰,我们重新描述一下题目:给定一个字符串 $s$,它是由某个字符串 $t$ 以及它的若干个同位字符串连接而成。问字符串 $t$ 的最小可能长度。\\n\\n显然 $t$ 的长度必须是 $s$ 的因数。$10^5$ 以内因数最多的数是 $83160$,有 $128$ 个因数。因数个数不多,可以考虑直接枚举因数 $p$。\\n\\n问题变为如何检查字符串 $s$ 是不是由若干个长度为 $p$ 的同位字符串连接而成。那么将 $s$ 分成长度为 $p$ 的子串,并检查每个子串每种字母出现次数是否相同即可。\\n\\n复杂度 $\\\\mathcal{O}(nf…","guid":"https://leetcode.cn/problems/minimum-length-of-anagram-concatenation//solution/mei-ju-by-tsreaper-f6ew","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-05-05T04:08:48.109Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"字符计算,C 0ms","url":"https://leetcode.cn/problems/score-of-a-string//solution/zi-fu-ji-suan-by-admiring-meninskyuli-pc0r","content":"\\n\\nProblem: 100270. 字符串的分数
\\n
[TOC]
\\n按题意,字符值相减求绝对值即可。
\\n执行用时分布0ms击败100.00%;消耗内存分布5.37MB击败100.00%
\\n###C
\\nint scoreOfString(char* s) {\\n int ans = 0;\\n for (char * c1 = s, * c2 = c1 + 1; * c2;) \\n ans += abs(* c1 ++ - * c2 ++); \\n return ans;\\n}\\n
\\n###Python3
\\nclass Solution:\\n def scoreOfString(self, s: str) -> int:\\n return sum(abs(ord(x) - ord(y)) for x, y in pairwise(s))\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100270. 字符串的分数 [TOC]\\n\\n按题意,字符值相减求绝对值即可。\\n\\n执行用时分布0ms击败100.00%;消耗内存分布5.37MB击败100.00%\\n\\n###C\\n\\nint scoreOfString(char* s) {\\n int ans = 0;\\n for (char * c1 = s, * c2 = c1 + 1; * c2;) \\n ans += abs(* c1 ++ - * c2 ++); \\n return ans;\\n}\\n\\n\\n###Python3\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/score-of-a-string//solution/zi-fu-ji-suan-by-admiring-meninskyuli-pc0r","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-04-14T00:56:26.634Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/score-of-a-string//solution/bian-li-pythonjavacgo-by-endlesscheng-x63p","content":"遍历字符串 $s$,累加相邻字母 ASCII 值的绝对差,即为答案。
\\n###py
\\nclass Solution:\\n def scoreOfString(self, s: str) -> int:\\n return sum(abs(x - y) for x, y in pairwise(map(ord, s)))\\n
\\n###java
\\nclass Solution {\\n public int scoreOfString(String S) {\\n char[] s = S.toCharArray();\\n int ans = 0;\\n for (int i = 1; i < s.length; i++) {\\n ans += Math.abs(s[i] - s[i - 1]);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int scoreOfString(string s) {\\n int ans = 0;\\n for (int i = 1; i < s.length(); i++) {\\n ans += abs(s[i] - s[i - 1]);\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint scoreOfString(char* s) {\\n int ans = 0;\\n for (int i = 1; s[i]; i++) {\\n ans += abs(s[i] - s[i - 1]);\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc scoreOfString(s string) (ans int) {\\nfor i := 1; i < len(s); i++ {\\nans += abs(int(s[i-1]) - int(s[i]))\\n}\\nreturn\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\n###js
\\nvar scoreOfString = function(s) {\\n let ans = 0;\\n for (let i = 1; i < s.length; i++) {\\n ans += Math.abs(s.charCodeAt(i) - s.charCodeAt(i - 1));\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn score_of_string(s: String) -> i32 {\\n s.as_bytes()\\n .windows(2)\\n .map(|w| (w[1] as i32 - w[0] as i32).abs())\\n .sum()\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历字符串 $s$,累加相邻字母 ASCII 值的绝对差,即为答案。 ###py\\n\\nclass Solution:\\n def scoreOfString(self, s: str) -> int:\\n return sum(abs(x - y) for x, y in pairwise(map(ord, s)))\\n\\n\\n###java\\n\\nclass Solution {\\n public int scoreOfString(String S) {\\n char[] s = S.toCharArray();\\n int…","guid":"https://leetcode.cn/problems/score-of-a-string//solution/bian-li-pythonjavacgo-by-endlesscheng-x63p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-04-14T00:31:38.921Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"或值至少k的最短子数组","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i//solution/huo-zhi-zhi-shao-kde-zui-duan-zi-shu-zu-qih0b","content":"\\n\\nProblem: 3095. 或值至少 K 的最短子数组 I
\\n
[TOC]
\\n\\n\\n由于给定的数据量很小,优先考虑暴力解法。解决了再考虑优化问题。
\\n
for(int i=0;i<n;i++)
第一层循环枚举每一个可能的起始下标for(int j=i+1;j<n;j++)
第二层循环枚举每一个可能的结束下标for(int index=i;index<=j;index++)
第三层循环遍历开始到结尾的子数组if(minLen == Integer.MAX_VALUE) return -1;
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $$$O(n^3)$$$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int minLen = Integer.MAX_VALUE;\\n\\n\\n for(int i=0;i<n;i++){\\n if(nums[i] >= k) return 1;//自己与自己或,简写\\n }\\n\\n for(int i=0;i<n;i++){//枚举起始下标\\n for(int j=i+1;j<n;j++)//枚举结束下标\\n {\\n int or = 0;\\n for(int index=i;index<=j;index++){//遍历开始到结尾的数\\n or |= nums[index];\\n if(or >= k) minLen = Math.min(minLen,j-i+1);\\n }\\n }\\n }\\n if(minLen == Integer.MAX_VALUE) return -1;\\n return minLen;\\n }\\n}\\n
\\n对于数据量较少的情况,暴力解法是没有问题的,但数据量太大时,这种方法就行不通了。这就是为什么这道题是Easy,而同样的另一题3097是Medium
\\n所有就有了第二种优化解法:滑动窗口(可以参考灵神的解法,思路也是相当牛逼 子数组 OR/AND/GCD 通用模板)
\\n另外,如果对位运算不够了解的,可以看看这篇文章 位运算有什么奇技淫巧
\\n时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int n = nums.length;\\n int minLen = Integer.MAX_VALUE;\\n List<int[]> ors = new ArrayList<>();//存放数组\\n for(int i=0;i<n;i++){\\n ors.add(new int[]{0,i});//初始化,或值为0和对应下标\\n int j = 0;\\n for(int[] or:ors){//遍历每一个列表中的数组\\n or[0] |= nums[i];\\n if(or[0] >= k) minLen = Math.min(minLen,i-or[1]+1);\\n if(ors.get(j)[0] == or[0]){\\n ors.get(j)[1] = or[1];//更新下标最大值\\n }else {\\n j++;\\n ors.set(j,or);//把当前数组替换为计算后的or数组\\n }\\n }\\n ors.subList(j+1,ors.size()).clear();//清空j+1到结尾的一些数组\\n }\\n return minLen == Integer.MAX_VALUE?-1:minLen;\\n }\\n}\\n
\\n","description":"Problem: 3095. 或值至少 K 的最短子数组 I [TOC]\\n\\n由于给定的数据量很小,优先考虑暴力解法。解决了再考虑优化问题。\\n\\n首先遍历一遍数组,判断有没有单个数就已经>=k的,如果有直接返回1,因为自己和自己求或运算还是等于自身(A or A = A)\\n接着就是三层循环遍历每一个可能的子数组\\nfor(int i=0;iProblem: 3095. 或值至少 K 的最短子数组 I
\\n\\n[TOC]
\\n暴力模拟
\\n执行用时分布50ms击败47.20%;消耗内存分布16.50MB击败20.98%
\\n###Python3
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n for l in range(1, n + 1):\\n for i in range(n - l + 1):\\n if reduce(or_, nums[i: i + l]) >= k: return l\\n return -1\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 3095. 或值至少 K 的最短子数组 I [TOC]\\n\\n暴力模拟\\n\\n执行用时分布50ms击败47.20%;消耗内存分布16.50MB击败20.98%\\n\\n###Python3\\n\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n for l in range(1, n + 1):\\n for i in range(n - l + 1):…","guid":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i//solution/bao-li-mo-ni-by-admiring-meninskyuli-93jk","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-04-02T02:08:42.040Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"非暴力做法(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i//solution/fei-bao-li-zuo-fa-pythonjavacgo-by-endle-aw2z","content":"本题和周赛第三题是一样的,请看 我的题解。
\\n","description":"本题和周赛第三题是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-i//solution/fei-bao-li-zuo-fa-pythonjavacgo-by-endle-aw2z","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-31T00:44:16.186Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:LogTrick/滑动窗口+栈(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-ii//solution/zi-shu-zu-orandgcd-tong-yong-mo-ban-pyth-n8xj","content":"去年十月的每日一题,出过一道类似的 3171. 找到按位或最接近 K 的子数组。
\\n由于思路是一样的,本文只留代码,具体原理请看 我的题解。
\\n###py
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n ans = inf\\n for i, x in enumerate(nums):\\n if x >= k:\\n return 1\\n j = i - 1\\n while j >= 0 and nums[j] | x != nums[j]:\\n nums[j] |= x\\n if nums[j] >= k:\\n ans = min(ans, i - j + 1)\\n j -= 1\\n return ans if ans < inf else -1\\n
\\n###java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int ans = Integer.MAX_VALUE;\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n if (x >= k) {\\n return 1;\\n }\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n if (nums[j] >= k) {\\n ans = Math.min(ans, i - j + 1);\\n }\\n }\\n }\\n return ans == Integer.MAX_VALUE ? -1 : ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n if (x >= k) {\\n return 1;\\n }\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n if (nums[j] >= k) {\\n ans = min(ans, i - j + 1);\\n }\\n }\\n }\\n return ans == INT_MAX ? -1 : ans;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minimumSubarrayLength(int* nums, int numsSize, int k) {\\n int ans = INT_MAX;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n if (x >= k) {\\n return 1;\\n }\\n for (int j = i - 1; j >= 0 && (nums[j] | x) != nums[j]; j--) {\\n nums[j] |= x;\\n if (nums[j] >= k) {\\n ans = MIN(ans, i - j + 1);\\n }\\n }\\n }\\n return ans == INT_MAX ? -1 : ans;\\n}\\n
\\n###go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\nans := math.MaxInt\\nfor i, x := range nums {\\nif x >= k {\\nreturn 1\\n}\\nfor j := i - 1; j >= 0 && nums[j]|x != nums[j]; j-- {\\nnums[j] |= x\\nif nums[j] >= k {\\nans = min(ans, i-j+1)\\n}\\n}\\n}\\nif ans == math.MaxInt {\\nreturn -1\\n}\\nreturn ans\\n}\\n
\\n###js
\\nvar minimumSubarrayLength = function(nums, k) {\\n let ans = Infinity;\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n if (x >= k) {\\n return 1;\\n }\\n for (let j = i - 1; j >= 0 && (nums[j] | x) !== nums[j]; j--) {\\n nums[j] |= x;\\n if (nums[j] >= k) {\\n ans = Math.min(ans, i - j + 1);\\n }\\n }\\n }\\n return ans === Infinity ? -1 : ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(mut nums: Vec<i32>, k: i32) -> i32 {\\n let mut ans = usize::MAX;\\n for i in 0..nums.len() {\\n let x = nums[i];\\n if x >= k {\\n return 1;\\n }\\n let mut j = i - 1;\\n while j < nums.len() && (nums[j] | x) != nums[j] {\\n nums[j] |= x;\\n if nums[j] >= k {\\n ans = ans.min(i - j + 1);\\n }\\n j -= 1;\\n }\\n }\\n if ans == usize::MAX { -1 } else { ans as _ }\\n }\\n}\\n
\\n###py
\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n ans = inf\\n left = bottom = right_or = 0\\n for right, x in enumerate(nums):\\n right_or |= x\\n while left <= right and nums[left] | right_or >= k:\\n ans = min(ans, right - left + 1)\\n left += 1\\n if bottom < left:\\n # 重新构建一个栈\\n for i in range(right - 1, left - 1, -1):\\n nums[i] |= nums[i + 1]\\n bottom = right\\n right_or = 0\\n return ans if ans < inf else -1\\n
\\n###java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n int ans = Integer.MAX_VALUE;\\n int left = 0;\\n int bottom = 0;\\n int rightOr = 0;\\n for (int right = 0; right < nums.length; right++) {\\n rightOr |= nums[right];\\n while (left <= right && (nums[left] | rightOr) >= k) {\\n ans = Math.min(ans, right - left + 1);\\n left++;\\n if (bottom < left) {\\n // 重新构建一个栈\\n for (int i = right - 1; i >= left; i--) {\\n nums[i] |= nums[i + 1];\\n }\\n bottom = right;\\n rightOr = 0;\\n }\\n }\\n }\\n return ans == Integer.MAX_VALUE ? -1 : ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumSubarrayLength(vector<int>& nums, int k) {\\n int ans = INT_MAX, left = 0, bottom = 0, right_or = 0;\\n for (int right = 0; right < nums.size(); right++) {\\n right_or |= nums[right];\\n while (left <= right && (nums[left] | right_or) >= k) {\\n ans = min(ans, right - left + 1);\\n left++;\\n if (bottom < left) {\\n // 重新构建一个栈\\n for (int i = right - 1; i >= left; i--) {\\n nums[i] |= nums[i + 1];\\n }\\n bottom = right;\\n right_or = 0;\\n }\\n }\\n }\\n return ans == INT_MAX ? -1 : ans;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minimumSubarrayLength(int* nums, int numsSize, int k) {\\n int ans = INT_MAX, left = 0, bottom = 0, right_or = 0;\\n for (int right = 0; right < numsSize; right++) {\\n right_or |= nums[right];\\n while (left <= right && (nums[left] | right_or) >= k) {\\n ans = MIN(ans, right - left + 1);\\n left++;\\n if (bottom < left) {\\n // 重新构建一个栈\\n for (int i = right - 1; i >= left; i--) {\\n nums[i] |= nums[i + 1];\\n }\\n bottom = right;\\n right_or = 0;\\n }\\n }\\n }\\n return ans == INT_MAX ? -1 : ans;\\n}\\n
\\n###go
\\nfunc minimumSubarrayLength(nums []int, k int) int {\\nans := math.MaxInt\\nvar left, bottom, rightOr int\\nfor right, x := range nums {\\nrightOr |= x\\nfor left <= right && nums[left]|rightOr >= k {\\nans = min(ans, right-left+1)\\nleft++\\nif bottom < left {\\n// 重新构建一个栈\\nfor i := right - 1; i >= left; i-- {\\nnums[i] |= nums[i+1]\\n}\\nbottom = right\\nrightOr = 0\\n}\\n}\\n}\\nif ans == math.MaxInt {\\nreturn -1\\n}\\nreturn ans\\n}\\n
\\n###js
\\nvar minimumSubarrayLength = function(nums, k) {\\n let ans = Infinity, left = 0, bottom = 0, rightOr = 0;\\n for (let right = 0; right < nums.length; right++) {\\n rightOr |= nums[right];\\n while (left <= right && (nums[left] | rightOr) >= k) {\\n ans = Math.min(ans, right - left + 1);\\n left++;\\n if (bottom < left) {\\n // 重新构建一个栈\\n for (let i = right - 1; i >= left; i--) {\\n nums[i] |= nums[i + 1];\\n }\\n bottom = right;\\n rightOr = 0;\\n }\\n }\\n }\\n return ans === Infinity ? -1 : ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn minimum_subarray_length(mut nums: Vec<i32>, k: i32) -> i32 {\\n let mut ans = usize::MAX;\\n let mut left = 0;\\n let mut bottom = 0;\\n let mut right_or = 0;\\n for right in 0..nums.len() {\\n right_or |= nums[right];\\n while left <= right && (nums[left] | right_or) >= k {\\n ans = ans.min(right - left + 1);\\n left += 1;\\n if bottom < left {\\n // 重新构建一个栈\\n for i in (left..right).rev() {\\n nums[i] |= nums[i + 1];\\n }\\n bottom = right;\\n right_or = 0;\\n }\\n }\\n }\\n if ans == usize::MAX { -1 } else { ans as _ }\\n }\\n}\\n
\\n更多相似题目,见位运算题单中的「LogTrick」。
\\n如果把 OR 改成 XOR,要怎么做?
\\n提示:前缀异或和 + 1803. 统计异或值在范围内的数对有多少。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"去年十月的每日一题,出过一道类似的 3171. 找到按位或最接近 K 的子数组。 由于思路是一样的,本文只留代码,具体原理请看 我的题解。\\n\\n方法一:LogTrick\\n\\n###py\\n\\nclass Solution:\\n def minimumSubarrayLength(self, nums: List[int], k: int) -> int:\\n ans = inf\\n for i, x in enumerate(nums):\\n if x >= k:\\n return 1…","guid":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-ii//solution/zi-shu-zu-orandgcd-tong-yong-mo-ban-pyth-n8xj","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-31T00:39:40.204Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"滑动窗口+数组模拟位运算","url":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-ii//solution/hua-dong-chuang-kou-shu-zu-mo-ni-wei-yun-oxwf","content":"\\n\\nProblem: 3097. 或值至少为 K 的最短子数组 II
\\n
[TOC]
\\n\\n\\n本题就是 209. 长度最小的子数组 的升级版,关于滑动窗口的使用可以看我的另一篇题解:滑动窗口零基础入门
\\n
\\n\\n本题的难点在于滑动窗口缩小左端点时元素由于是或运算,不能像加减一样直接减掉就能抛弃,或运算相当于不可逆运算,因为你不知道其他元素的第i个二进制位是否为1
\\n
\\n我们可以创建一个大小为32的int型数组arr来模拟2进制位:
\\n当第i个元素的第j个2进制位为1时:arr[j]++;否则不动
这样我们就可以知道当前n个元素进行或运算后每个2进制位上的1总共有多少个,当要删掉某个元素x时我们只需把x二进制位上的1从arr中减掉就行,然后遍历arr算出新的sum,并判断这个新的sum值是否满足条件,不断循环缩小left直至找出满足条件长度最小的子数组即可。
\\n时间复杂度:
\\n\\n\\narr[]长度为32,每次进行遍历依旧是常数故复杂度依旧是: $O(n)$
\\n
空间复杂度:
\\n\\n\\n$O(n)$
\\n
###Java
\\nclass Solution {\\n public int minimumSubarrayLength(int[] nums, int k) {\\n /*\\n * sum:临时和,用来判断当前子数组是否满组条件\\n * mask:掩码,从最低位开始\\n * arr[]:用来累计当前sum每个二进制位上1的个数\\n */\\n int sum = 0, left = 0;\\n int ans = Integer.MAX_VALUE;\\n int mask;\\n int[] arr = new int[32];\\n\\n if (k == 0)\\n return 1; // 把目标值k为0的案例单独判断\\n // 开始滑动窗口:外层循环right负责移动右端点,当右端点满足条件了开始缩小左端点left\\n for (int right = 0; right < nums.length; right++) {\\n sum |= nums[right]; // 异或累加sum\\n // 每加上一个元素都要更新arr\\n mask = 1;\\n for (int i = 31; i >= 0; i--) {\\n if ((nums[right] & mask) != 0) {\\n arr[i]++;\\n }\\n mask <<= 1; // 将掩码左移一位,以检查下一位\\n }\\n // 判断当前sum是否满足条件\\n if (sum >= k) {\\n while (left <= right) { // 满足条件开始缩小左端点\\n int x = nums[left];\\n // 从arr上删除当前左端点元素x的二进制位\\n mask = 1;\\n for (int i = 31; i >= 0; i--) {\\n if ((x & mask) != 0) {\\n arr[i]--;\\n }\\n mask <<= 1;\\n }\\n // 将更新后的arr转成新的sum\\n StringBuilder s = new StringBuilder();\\n for (int m : arr) {\\n if (m > 0) {\\n s.append(1);\\n } else {\\n s.append(0);\\n }\\n }\\n sum = Integer.parseInt(s.toString(), 2);\\n\\n if (sum >= k) { // 如果当前sum依旧满足条件则继续缩小左端点\\n left++;\\n } else { // 否则返还左端点元素x,继续外循环right增加右端点\\n sum |= nums[left];\\n mask = 1;\\n for (int i = 31; i >= 0; i--) {\\n if ((nums[left] & mask) != 0) {\\n arr[i]++;\\n }\\n mask <<= 1;\\n }\\n /*\\n * 这里对right - left + 1进行证明:\\n * 当左端点与右端点在统一下标时\\n * 即:left==right\\n * 此时子数组内只有一个元素,长度自然为1\\n * 故相减后需要+1\\n */\\n ans = Math.min(ans, right - left + 1);\\n break;\\n }\\n }\\n }\\n }\\n return ans == Integer.MAX_VALUE ? -1 : ans;\\n }\\n}\\n
\\n","description":"Problem: 3097. 或值至少为 K 的最短子数组 II [TOC]\\n\\n本题就是 209. 长度最小的子数组 的升级版,关于滑动窗口的使用可以看我的另一篇题解:滑动窗口零基础入门\\n\\n本题的难点在于滑动窗口缩小左端点时元素由于是或运算,不能像加减一样直接减掉就能抛弃,或运算相当于不可逆运算,因为你不知道其他元素的第i个二进制位是否为1\\n 我们可以创建一个大小为32的int型数组arr来模拟2进制位:\\n 当第i个元素的第j个2进制位为1时:arr[j]++;否则不动\\n\\n这样我们就可以知道当前n个元素进行或运算后每个2进制位上的1总共有多少个…","guid":"https://leetcode.cn/problems/shortest-subarray-with-or-at-least-k-ii//solution/hua-dong-chuang-kou-shu-zu-mo-ni-wei-yun-oxwf","author":"dreamy-hert2ucl","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-30T17:03:33.675Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"字符串及其反转中是否存在同一子字符串 389周赛复盘 Python3","url":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/zi-fu-chuan-ji-qi-fan-zhuan-zhong-shi-fo-wpjn","content":"\\n\\nProblem: 3083. 字符串及其反转中是否存在同一子字符串
\\n
[TOC]
\\n\\n\\n遍历
\\n
\\n\\n遍历字符串s,取出长度为2的子串,判断这个子串是否在反转后的字符串中出现
\\n
时间复杂度:
\\n\\n\\n遍历了一次给定的字符串 $O(n)$
\\n
空间复杂度:
\\n\\n\\n只使用常量 $O(1)$
\\n
###Python3
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n for i in range(len(s) - 1):\\n if s[i:i+2] in s[::-1]:\\n return True\\n return False\\n
\\n","description":"Problem: 3083. 字符串及其反转中是否存在同一子字符串 [TOC]\\n\\n遍历\\n\\n遍历字符串s,取出长度为2的子串,判断这个子串是否在反转后的字符串中出现\\n\\n时间复杂度:\\n\\n遍历了一次给定的字符串 $O(n)$\\n\\n空间复杂度:\\n\\n只使用常量 $O(1)$\\n\\n###Python3\\n\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n for i in range(len(s) - 1):\\n if s[i:i+2] in…","guid":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/zi-fu-chuan-ji-qi-fan-zhuan-zhong-shi-fo-wpjn","author":"elastic-poincarewho","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-24T06:48:38.106Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python 一行","url":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/python-yi-xing-by-nrib8zib57-jvi0","content":"\\n\\nProblem: 100248. 字符串及其反转中是否存在同一子字符串
\\n
[TOC]
\\n\\n\\npython 一行
\\n
\\n\\npython 一行
\\n
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Python3
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n return any(s[i:i+2] in s[::-1] for i in range(len(s)-1))\\n
\\n","description":"Problem: 100248. 字符串及其反转中是否存在同一子字符串 [TOC]\\n\\npython 一行\\n\\npython 一行\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n空间复杂度:\\n\\n添加空间复杂度, 示例: $O(n)$\\n\\n###Python3\\n\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n return any(s[i:i+2] in s[::-1] for i in range(len(s)-1))","guid":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/python-yi-xing-by-nrib8zib57-jvi0","author":"nriB8ZIB57","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-17T07:19:00.095Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历+位运算优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/yi-ci-bian-li-wei-yun-suan-you-hua-pytho-ijj7","content":"用一个 $26\\\\times 26$ 的布尔数组(或哈希表)$\\\\textit{vis}$ 记录是否遇到了 $\\\\textit{vis}[x][y]$,其中 $x$ 和 $y$ 是一对相邻字母 $(s[i-1],s[i])$。
\\n如果 $\\\\textit{vis}[y][x]$ 为真,则说明 $x+y$ 在反转后的字符串中。
\\n视频讲解。
\\n###py
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n st = set()\\n for x, y in pairwise(s):\\n st.add((x, y))\\n if (y, x) in st:\\n return True\\n return False\\n
\\n###java
\\nclass Solution {\\n public boolean isSubstringPresent(String S) {\\n char[] s = S.toCharArray();\\n boolean[][] vis = new boolean[26][26];\\n for (int i = 1; i < s.length; i++) {\\n int x = s[i - 1] - \'a\';\\n int y = s[i] - \'a\';\\n vis[x][y] = true;\\n if (vis[y][x]) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isSubstringPresent(string s) {\\n bool vis[26][26]{};\\n for (int i = 1; i < s.length(); i++) {\\n int x = s[i - 1] - \'a\', y = s[i] - \'a\';\\n vis[x][y] = true;\\n if (vis[y][x]) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###go
\\nfunc isSubstringPresent(s string) bool {\\nvis := [26][26]bool{}\\nfor i := 1; i < len(s); i++ {\\nx, y := s[i-1]-\'a\', s[i]-\'a\'\\nvis[x][y] = true\\nif vis[y][x] {\\nreturn true\\n}\\n}\\nreturn false\\n}\\n
\\n也可以用位运算优化,把 $\\\\textit{vis}$ 数组的第二维压缩成二进制数。原理见 从集合论到位运算,常见位运算技巧分类总结!
\\n###py
\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n vis = [0] * 26\\n for x, y in pairwise(map(ord, s)):\\n x -= ord(\'a\')\\n y -= ord(\'a\')\\n vis[x] |= 1 << y\\n if vis[y] >> x & 1:\\n return True\\n return False\\n
\\n###java
\\nclass Solution {\\n public boolean isSubstringPresent(String S) {\\n char[] s = S.toCharArray();\\n int[] vis = new int[26];\\n for (int i = 1; i < s.length; i++) {\\n int x = s[i - 1] - \'a\';\\n int y = s[i] - \'a\';\\n vis[x] |= 1 << y;\\n if ((vis[y] >> x & 1) > 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isSubstringPresent(string s) {\\n int vis[26]{};\\n for (int i = 1; i < s.length(); i++) {\\n int x = s[i - 1] - \'a\', y = s[i] - \'a\';\\n vis[x] |= 1 << y;\\n if (vis[y] >> x & 1) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\n###go
\\nfunc isSubstringPresent(s string) bool {\\nvis := [26]int{}\\nfor i := 1; i < len(s); i++ {\\nx, y := s[i-1]-\'a\', s[i]-\'a\'\\nvis[x] |= 1 << y\\nif vis[y]>>x&1 > 0 {\\nreturn true\\n}\\n}\\nreturn false\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用一个 $26\\\\times 26$ 的布尔数组(或哈希表)$\\\\textit{vis}$ 记录是否遇到了 $\\\\textit{vis}[x][y]$,其中 $x$ 和 $y$ 是一对相邻字母 $(s[i-1],s[i])$。 如果 $\\\\textit{vis}[y][x]$ 为真,则说明 $x+y$ 在反转后的字符串中。\\n\\n视频讲解。\\n\\n###py\\n\\nclass Solution:\\n def isSubstringPresent(self, s: str) -> bool:\\n st = set()\\n for x, y in…","guid":"https://leetcode.cn/problems/existence-of-a-substring-in-a-string-and-its-reverse//solution/yi-ci-bian-li-wei-yun-suan-you-hua-pytho-ijj7","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-17T05:20:44.475Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"各路大佬们放过新手吧,不要用花哨的语法劝退初学者","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i//solution/ge-lu-da-lao-men-fang-guo-xin-shou-ba-bu-pqyd","content":"\\n\\nProblem: 3065. 超过阈值的最少操作数 I
\\n
[TOC]
\\n每次都说让去找最简单的题,通过率从高到低排序,但是最近太多人说\\"那个最简单的题\\"完全看不懂题解.
\\n还望各路大佬收了神通,炫技可以,但是还是让新手了解下最基本的操作最重要
\\n\\n\\n讲述看到这一题的思路
\\n
其实就是判断一下nums里面有多少个元素小于n而已
\\n\\n\\n描述你的解题方法
\\n
各种语言有无数种花哨的语法来写,这里只用最简单的思路
\\n时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
从头到尾遍历一次
\\nO(n)
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
只需要一个返回值
\\nO(1)
###C
\\nint minOperations(int* nums, int numsSize, int k) {\\n int ret = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] < k) {\\n ret++;\\n }\\n }\\n return ret;\\n}\\n
\\nPython有两种写法,推荐直接用第一种,毕竟nums[i]就浪费了Python的特性了
\\n###Python3
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n ret = 0\\n for i in nums:\\n if i < k:\\n ret += 1\\n return ret\\n\\n
\\n###Python3
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n ret = 0\\n for i in range(len(nums)):\\n if nums[i] < k:\\n ret += 1\\n return ret\\n
\\n题目本身不重要,重点是要掌握基本的操作:
\\n定义 $g[i][j]$ 表示节点 $i$ 到节点 $j$ 这条边的边权。如果没有 $i$ 到 $j$ 的边,则 $g[i][j]=\\\\infty$。
\\n定义 $\\\\textit{dis}[i]$ 表示起点 $k$ 到节点 $i$ 的最短路长度,一开始 $\\\\textit{dis}[k]=0$,其余 $\\\\textit{dis}[i]=\\\\infty$ 表示尚未计算出。
\\n我们的目标是计算出最终的 $\\\\textit{dis}$ 数组。
\\n对于本题,在计算最短路时,如果发现当前找到的最小最短路等于 $\\\\infty$,说明有节点无法到达,可以提前结束算法,返回 $-1$。
\\n如果所有节点都可以到达,返回 $\\\\max(\\\\textit{dis})$。
\\n代码实现时,节点编号改成从 $0$ 开始。
\\n###py
\\nclass Solution:\\n def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:\\n g = [[inf for _ in range(n)] for _ in range(n)] # 邻接矩阵\\n for x, y, d in times:\\n g[x - 1][y - 1] = d\\n\\n dis = [inf] * n\\n ans = dis[k - 1] = 0\\n done = [False] * n\\n while True:\\n x = -1\\n for i, ok in enumerate(done):\\n if not ok and (x < 0 or dis[i] < dis[x]):\\n x = i\\n if x < 0:\\n return ans # 最后一次算出的最短路就是最大的\\n if dis[x] == inf: # 有节点无法到达\\n return -1\\n ans = dis[x] # 求出的最短路会越来越大\\n done[x] = True # 最短路长度已确定(无法变得更小)\\n for y, d in enumerate(g[x]):\\n # 更新 x 的邻居的最短路\\n dis[y] = min(dis[y], dis[x] + d)\\n
\\n###java
\\nclass Solution {\\n public int networkDelayTime(int[][] times, int n, int k) {\\n final int INF = Integer.MAX_VALUE / 2; // 防止加法溢出\\n int[][] g = new int[n][n]; // 邻接矩阵\\n for (int[] row : g) {\\n Arrays.fill(row, INF);\\n }\\n for (int[] t : times) {\\n g[t[0] - 1][t[1] - 1] = t[2];\\n }\\n\\n int maxDis = 0;\\n int[] dis = new int[n];\\n Arrays.fill(dis, INF);\\n dis[k - 1] = 0;\\n boolean[] done = new boolean[n];\\n while (true) {\\n int x = -1;\\n for (int i = 0; i < n; i++) {\\n if (!done[i] && (x < 0 || dis[i] < dis[x])) {\\n x = i;\\n }\\n }\\n if (x < 0) {\\n return maxDis; // 最后一次算出的最短路就是最大的\\n }\\n if (dis[x] == INF) { // 有节点无法到达\\n return -1;\\n }\\n maxDis = dis[x]; // 求出的最短路会越来越大\\n done[x] = true; // 最短路长度已确定(无法变得更小)\\n for (int y = 0; y < n; y++) {\\n // 更新 x 的邻居的最短路\\n dis[y] = Math.min(dis[y], dis[x] + g[x][y]);\\n }\\n }\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int networkDelayTime(vector<vector<int>>& times, int n, int k) {\\n vector<vector<int>> g(n, vector<int>(n, INT_MAX / 2)); // 邻接矩阵\\n for (auto& t : times) {\\n g[t[0] - 1][t[1] - 1] = t[2];\\n }\\n\\n vector<int> dis(n, INT_MAX / 2), done(n);\\n dis[k - 1] = 0;\\n while (true) {\\n int x = -1;\\n for (int i = 0; i < n; i++) {\\n if (!done[i] && (x < 0 || dis[i] < dis[x])) {\\n x = i;\\n }\\n }\\n if (x < 0) {\\n return ranges::max(dis);\\n }\\n if (dis[x] == INT_MAX / 2) { // 有节点无法到达\\n return -1;\\n }\\n done[x] = true; // 最短路长度已确定(无法变得更小)\\n for (int y = 0; y < n; y++) {\\n // 更新 x 的邻居的最短路\\n dis[y] = min(dis[y], dis[x] + g[x][y]);\\n }\\n }\\n }\\n};\\n
\\n###go
\\nfunc networkDelayTime(times [][]int, n, k int) int {\\n const inf = math.MaxInt / 2 // 防止加法溢出\\n g := make([][]int, n) // 邻接矩阵\\n for i := range g {\\n g[i] = make([]int, n)\\n for j := range g[i] {\\n g[i][j] = inf\\n }\\n }\\n for _, t := range times {\\n g[t[0]-1][t[1]-1] = t[2]\\n }\\n\\n dis := make([]int, n)\\n for i := range dis {\\n dis[i] = inf\\n }\\n dis[k-1] = 0\\n done := make([]bool, n)\\n for {\\n x := -1\\n for i, ok := range done {\\n if !ok && (x < 0 || dis[i] < dis[x]) {\\n x = i\\n }\\n }\\n if x < 0 {\\n return slices.Max(dis)\\n }\\n if dis[x] == inf { // 有节点无法到达\\n return -1\\n }\\n done[x] = true // 最短路长度已确定(无法变得更小)\\n for y, d := range g[x] {\\n // 更新 x 的邻居的最短路\\n dis[y] = min(dis[y], dis[x]+d)\\n }\\n }\\n}\\n
\\n###js
\\nvar networkDelayTime = function(times, n, k) {\\n const g = Array.from({length: n}, () => Array(n).fill(Infinity)); // 邻接矩阵\\n for (const [x, y, d] of times) {\\n g[x - 1][y - 1] = d;\\n }\\n\\n const dis = Array(n).fill(Infinity);\\n dis[k - 1] = 0;\\n const done = Array(n).fill(false);\\n while (true) {\\n let x = -1;\\n for (let i = 0; i < n; i++) {\\n if (!done[i] && (x < 0 || dis[i] < dis[x])) {\\n x = i;\\n }\\n }\\n if (x < 0) {\\n return Math.max(...dis);\\n }\\n if (dis[x] === Infinity) { // 有节点无法到达\\n return -1;\\n }\\n done[x] = true; // 最短路长度已确定(无法变得更小)\\n for (let y = 0; y < n; y++) {\\n // 更新 x 的邻居的最短路\\n dis[y] = Math.min(dis[y], dis[x] + g[x][y]);\\n }\\n }\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn network_delay_time(times: Vec<Vec<i32>>, n: i32, k: i32) -> i32 {\\n const INF: i32 = i32::MAX / 2; // 防止加法溢出\\n let n = n as usize;\\n let mut g = vec![vec![INF; n]; n]; // 邻接矩阵\\n for t in × {\\n g[t[0] as usize - 1][t[1] as usize - 1] = t[2];\\n }\\n\\n let mut dis = vec![INF; n];\\n dis[k as usize - 1] = 0;\\n let mut done = vec![false; n];\\n loop {\\n let mut x = n;\\n for (i, &ok) in done.iter().enumerate() {\\n if !ok && (x == n || dis[i] < dis[x]) {\\n x = i;\\n }\\n }\\n if x == n {\\n return *dis.iter().max().unwrap();\\n }\\n if dis[x] == INF { // 有节点无法到达\\n return -1;\\n }\\n done[x] = true; // 最短路长度已确定(无法变得更小)\\n for (y, &d) in g[x].iter().enumerate() {\\n // 更新 x 的邻居的最短路\\n dis[y] = dis[y].min(dis[x] + d);\\n }\\n }\\n }\\n}\\n
\\n寻找最小值的过程可以用一个最小堆来快速完成:
\\n注意,如果一个节点 $x$ 在出堆前,其最短路长度 $\\\\textit{dis}[x]$ 被多次更新,那么堆中会有多个重复的 $x$,并且包含 $x$ 的二元组中的 $\\\\textit{dis}[x]$ 是互不相同的(因为我们只在找到更小的最短路时才会把二元组入堆)。
\\n所以写法一中的 $\\\\textit{done}$ 数组可以省去,取而代之的是用出堆的最短路值(记作 $\\\\textit{dx}$)与当前的 $\\\\textit{dis}[x]$ 比较,如果 $\\\\textit{dx} > \\\\textit{dis}[x]$ 说明 $x$ 之前出堆过,我们已经更新了 $x$ 的邻居的最短路,所以这次就不用更新了,继续外层循环。
\\n问:为什么代码要判断 dx > dis[x]
?
答:对于同一个 $x$,例如先入堆一个比较大的 $\\\\textit{dis}[x]=10$,后面又把 $\\\\textit{dis}[x]$ 更新成 $5$,之后这个 $5$ 会先出堆,然后再把 $10$ 出堆。$10$ 出堆时候是没有必要去更新周围邻居的最短路的,因为 $5$ 出堆之后,就已经把邻居的最短路更新过了,用 $10$ 是无法把邻居的最短路变得更短的,所以直接 continue
。
###py
\\nclass Solution:\\n def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:\\n g = [[] for _ in range(n)] # 邻接表\\n for x, y, d in times:\\n g[x - 1].append((y - 1, d))\\n\\n dis = [inf] * n\\n dis[k - 1] = 0\\n h = [(0, k - 1)]\\n while h:\\n dx, x = heappop(h)\\n if dx > dis[x]: # x 之前出堆过\\n continue\\n for y, d in g[x]:\\n new_dis = dx + d\\n if new_dis < dis[y]:\\n dis[y] = new_dis # 更新 x 的邻居的最短路\\n heappush(h, (new_dis, y))\\n mx = max(dis)\\n return mx if mx < inf else -1\\n
\\n###java
\\nclass Solution {\\n public int networkDelayTime(int[][] times, int n, int k) {\\n List<int[]>[] g = new ArrayList[n]; // 邻接表\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] t : times) {\\n g[t[0] - 1].add(new int[]{t[1] - 1, t[2]});\\n }\\n\\n int maxDis = 0;\\n int left = n; // 未确定最短路的节点个数\\n int[] dis = new int[n];\\n Arrays.fill(dis, Integer.MAX_VALUE);\\n dis[k - 1] = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (a[0] - b[0]));\\n pq.offer(new int[]{0, k - 1});\\n while (!pq.isEmpty()) {\\n int[] p = pq.poll();\\n int dx = p[0];\\n int x = p[1];\\n if (dx > dis[x]) { // x 之前出堆过\\n continue;\\n }\\n maxDis = dx; // 求出的最短路会越来越大\\n left--;\\n for (int[] e : g[x]) {\\n int y = e[0];\\n int newDis = dx + e[1];\\n if (newDis < dis[y]) {\\n dis[y] = newDis; // 更新 x 的邻居的最短路\\n pq.offer(new int[]{newDis, y});\\n }\\n }\\n }\\n return left == 0 ? maxDis : -1;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int networkDelayTime(vector<vector<int>>& times, int n, int k) {\\n vector<vector<pair<int, int>>> g(n); // 邻接表\\n for (auto& t : times) {\\n g[t[0] - 1].emplace_back(t[1] - 1, t[2]);\\n }\\n\\n vector<int> dis(n, INT_MAX);\\n dis[k - 1] = 0;\\n priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;\\n pq.emplace(0, k - 1);\\n while (!pq.empty()) {\\n auto [dx, x] = pq.top();\\n pq.pop();\\n if (dx > dis[x]) { // x 之前出堆过\\n continue;\\n }\\n for (auto &[y, d] : g[x]) {\\n int new_dis = dx + d;\\n if (new_dis < dis[y]) {\\n dis[y] = new_dis; // 更新 x 的邻居的最短路\\n pq.emplace(new_dis, y);\\n }\\n }\\n }\\n int mx = ranges::max(dis);\\n return mx < INT_MAX ? mx : -1;\\n }\\n};\\n
\\n###go
\\nfunc networkDelayTime(times [][]int, n, k int) int {\\n type edge struct{ to, wt int }\\n g := make([][]edge, n) // 邻接表\\n for _, t := range times {\\n g[t[0]-1] = append(g[t[0]-1], edge{t[1] - 1, t[2]})\\n }\\n\\n dis := make([]int, n)\\n for i := range dis {\\n dis[i] = math.MaxInt\\n }\\n dis[k-1] = 0\\n h := hp{{0, k - 1}}\\n for len(h) > 0 {\\n p := heap.Pop(&h).(pair)\\n dx := p.dis\\n x := p.x\\n if dx > dis[x] { // x 之前出堆过\\n continue\\n }\\n for _, e := range g[x] {\\n y := e.to\\n newDis := dx + e.wt\\n if newDis < dis[y] {\\n dis[y] = newDis // 更新 x 的邻居的最短路\\n heap.Push(&h, pair{newDis, y})\\n }\\n }\\n }\\n mx := slices.Max(dis)\\n if mx < math.MaxInt {\\n return mx\\n }\\n return -1\\n}\\n\\ntype pair struct{ dis, x int }\\ntype hp []pair\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].dis < h[j].dis }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(pair)) }\\nfunc (h *hp) Pop() (v any) { a := *h; *h, v = a[:len(a)-1], a[len(a)-1]; return }\\n
\\n###js
\\nvar networkDelayTime = function(times, n, k) {\\n const g = Array.from({length: n}, () => []); // 邻接表\\n for (const [x, y, d] of times) {\\n g[x - 1].push([y - 1, d]);\\n }\\n\\n const dis = Array(n).fill(Infinity);\\n dis[k - 1] = 0;\\n const pq = new MinPriorityQueue({priority: (p) => p[0]});\\n pq.enqueue([0, k - 1]);\\n while (!pq.isEmpty()) {\\n const [dx, x] = pq.dequeue().element;\\n if (dx > dis[x]) { // x 之前出堆过\\n continue;\\n }\\n for (const [y, d] of g[x]) {\\n const newDis = dx + d;\\n if (newDis < dis[y]) {\\n dis[y] = newDis; // 更新 x 的邻居的最短路\\n pq.enqueue([newDis, y]);\\n }\\n }\\n }\\n const mx = Math.max(...dis);\\n return mx < Infinity ? mx : -1;\\n};\\n
\\n###rust
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn network_delay_time(times: Vec<Vec<i32>>, n: i32, k: i32) -> i32 {\\n let n = n as usize;\\n let k = k as usize - 1;\\n let mut g = vec![vec![]; n]; // 邻接表\\n for t in × {\\n g[t[0] as usize - 1].push((t[1] as usize - 1, t[2]));\\n }\\n\\n let mut dis = vec![i32::MAX; n];\\n dis[k] = 0;\\n let mut h = BinaryHeap::new();\\n h.push((0, k));\\n while let Some((dx, x)) = h.pop() {\\n if -dx > dis[x] { // x 之前出堆过\\n continue;\\n }\\n for &(y, d) in &g[x] {\\n let new_dis = -dx + d;\\n if new_dis < dis[y] {\\n dis[y] = new_dis; // 更新 x 的邻居的最短路\\n h.push((-new_dis, y));\\n }\\n }\\n }\\n let mx = *dis.iter().max().unwrap();\\n if mx < i32::MAX { mx } else { -1 }\\n }\\n}\\n
\\n更多相似题目,见下面图论题单中的「单源最短路:Dijkstra」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"Dijkstra 算法介绍 定义 $g[i][j]$ 表示节点 $i$ 到节点 $j$ 这条边的边权。如果没有 $i$ 到 $j$ 的边,则 $g[i][j]=\\\\infty$。\\n\\n定义 $\\\\textit{dis}[i]$ 表示起点 $k$ 到节点 $i$ 的最短路长度,一开始 $\\\\textit{dis}[k]=0$,其余 $\\\\textit{dis}[i]=\\\\infty$ 表示尚未计算出。\\n\\n我们的目标是计算出最终的 $\\\\textit{dis}$ 数组。\\n\\n首先更新起点 $k$ 到其邻居 $y$ 的最短路,即更新 $\\\\textit{dis}[y]$ 为 $g…","guid":"https://leetcode.cn/problems/network-delay-time//solution/liang-chong-dijkstra-xie-fa-fu-ti-dan-py-ooe8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-05T02:23:26.116Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序双指针+仅操作小于k/3的数","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/pai-xu-shuang-zhi-zhen-jin-cao-zuo-xiao-4tx6c","content":"\\n\\nProblem: 3066. 超过阈值的最少操作数 II
\\n
\\n\\n先对原数组arr1排序
\\n
\\n依次取最小数x,y,产生的min(x, y) * 2 + max(x, y)是有序的,放入新数组arr2
\\n使用双指针从两数组取最小数
\\n\\n可根据 k/3,k 按大小将数划分三类
\\n
\\n大于等于k/3小于k的数(k3k)必可以两两配对产生超阈值数
\\n只需要操作和保存小于k/3的数,将其全部转化
时间复杂度:$O(nlogn)$
\\n空间复杂度:$O(n)$
###Java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n double k3=k/3.0;\\n int ans=0,index1=0,index2=0;\\n int x=0,y=0;\\n int n=nums.length,size1=0,size2=0;\\n int k3kCnt=0,mink3k=k;\\n int[] arr1=nums;\\n for(int num:nums){\\n if(num<k3){\\n arr1[size1++]=num;\\n }else if(num<k){\\n mink3k=Math.min(mink3k,num);\\n k3kCnt++;\\n } \\n }\\n int[] arr2=new int[size1];\\n Arrays.sort(arr1,0,size1);\\n while(size1+size2-index1-index2>1){\\n if(index1<size1&&(index2==size2||arr1[index1]<arr2[index2])){\\n x=arr1[index1++];\\n }else{\\n x=arr2[index2++];\\n }\\n ans++;\\n if(index1<size1&&(index2==size2||arr1[index1]<arr2[index2])){\\n y=arr1[index1++];\\n }else{\\n y=arr2[index2++];\\n }\\n int newnum=x*2+y;\\n if(newnum<k3){\\n arr2[size2++]=newnum;\\n }else if(newnum<k){\\n mink3k=Math.min(mink3k,newnum);\\n k3kCnt++;\\n }\\n }\\n if(size1+size2-index1-index2==1){\\n ans++;\\n if(mink3k!=k){\\n int newnum=(index1!=size1?arr1[index1++]:arr2[index2++])*2+mink3k;\\n if(newnum>=k){\\n k3kCnt--;\\n }\\n } \\n }\\n ans+=Math.ceil(k3kCnt/2.0);\\n return ans;\\n }\\n}\\n
\\n","description":"Problem: 3066. 超过阈值的最少操作数 II 先对原数组arr1排序\\n 依次取最小数x,y,产生的min(x, y) * 2 + max(x, y)是有序的,放入新数组arr2\\n 使用双指针从两数组取最小数\\n\\n可根据 k/3,k 按大小将数划分三类\\n 大于等于k/3小于k的数(k3k)必可以两两配对产生超阈值数\\n 只需要操作和保存小于k/3的数,将其全部转化\\n\\n时间复杂度:$O(nlogn)$\\n 空间复杂度:$O(n)$\\n\\n###Java\\n\\nclass Solution {\\n public int minOperations(int[]…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/pai-xu-shuang-zhi-zhen-jin-cao-zuo-xiao-4tx6c","author":"lMFcHre2sC","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-04T15:19:03.438Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序 + 双指针","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/pai-xu-shuang-zhi-zhen-java-zui-kuai-yun-lq2l","content":"\\n\\nProblem: 3066. 超过阈值的最少操作数 II
\\n
我们首先对数组 $nums$ 进行排序,然后从数组 $nums$ 中取两个最小值 $min$ 和 $max$,计算 $min * 2 + max$的值,把计算得到的值添加到队列尾。
\\n在后面的操作中我们比较数组的下一个数据和队列头的数据,取其中的两个最小值 $min$ 和 $max$,计算 $min * 2 + max$的值,然后把它添加到队列尾。
\\n排序后,在数组$nums$ 中的数据是有序的。有序的数据进行$min * 2 + max$计算后,得到的数据仍然是有序的。所以我们不需要对队列中的数据进行排序。
\\n我们每次取两个最小值,然后删除这两个最小值,同时添加一个值为 $min * 2 + max$ 的数。这相当于每次操作会减少一个数。如果数组$nums$中的数据总共有 $n$ 个, 那么操作 $n -1$ 次后,数组中只有一个数据。当数组中的数据数量少于两个时,我们将无法继续取两个最小值。根据题目描述,输入保证答案一定存在,也就是说一定存在一个操作序列使数组中所有元素都大于等于 $k$ 。所以我们最多操作 $n -1$ 次一定能够找到答案。
\\n###Java
\\n\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n \\n Arrays.sort(nums);\\n int n = nums.length;\\n \\n int[] que = new int[n];\\n int write = 0, read = 0, index = 0;\\n int min = 0, max = 0, count = 0;\\n \\n while(index < n || read < write){\\n \\n min = (index < n && ( read == write ||nums[index] < que[read]))? nums[index++]:que[read++];\\n if(min >= k)\\n break;\\n \\n ++count;\\n if(index == n && read == write)\\n break;\\n \\n max = (index < n && ( read == write ||nums[index] < que[read]))? nums[index++]:que[read++];\\n\\n long x = min * 2L + max;\\n if(x < k)\\n que[write++] = (int)x;\\n }\\n \\n return count;\\n }\\n}\\n\\n\\n
\\n我们还可以进一步优化,对原始数据进行预处理。遍历数组 $nums$,如果当前值 $x$ 的三倍小于 $k$,我们把它添加到数组 $arr$; 如果 $x$ 小于 $k$,但其三倍大于或等于 $k$,我们累计这些数的数量,并维护最小值; 如果 $x$ 已经大于或等于 $k$, 则不需要进行任何处理。然后只对 $arr$ 中的数据进行排序。计算 $x = min * 2 + max$,如果 $x$ 的三倍小于 $k$, 我们把它添加到队列 $que$;如果 $x$ 小于 $k$,但其三倍大于或等于 $k$,我们累计这些数的数量,并维护最小值;如果 $x$ 已经大于或等于 $k$, 则不需要进行任何处理。
\\n\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int n = nums.length;\\n int[] arr = new int[n];\\n int size = 0, count = 0;\\n int d = k / 3, r = k % 3, minNum = k;\\n for(int x: nums){\\n if(x - r < d)\\n arr[size++] = x;\\n else if(x < k){\\n ++count;\\n minNum = Math.min(minNum, x);\\n }\\n }\\n \\n Arrays.sort(arr, 0, size);\\n \\n int[] que = new int[size];\\n int write = 0, read = 0, index = 0;\\n int min = 0, max = 0, steps = 0;\\n \\n while(index < size || read < write){\\n \\n min = (index < size && ( read == write ||arr[index] < que[read]))? arr[index++]:que[read++];\\n \\n ++steps; \\n if(index == size && read == write){\\n int x = min * 2 + minNum;\\n if(x >= k) --count;\\n break;\\n }\\n \\n max = (index < size && ( read == write ||arr[index] < que[read]))? arr[index++]:que[read++];\\n \\n int x = min * 2 + max;\\n if(x - r < d)\\n que[write++] = x;\\n else if(x < k){\\n ++count;\\n minNum = Math.min(minNum, x); \\n }\\n }\\n \\n return steps + (count + 1)/2;\\n }\\n}\\n\\n\\n
\\n","description":"Problem: 3066. 超过阈值的最少操作数 II 解题思路\\n\\n我们首先对数组 $nums$ 进行排序,然后从数组 $nums$ 中取两个最小值 $min$ 和 $max$,计算 $min * 2 + max$的值,把计算得到的值添加到队列尾。\\n\\n在后面的操作中我们比较数组的下一个数据和队列头的数据,取其中的两个最小值 $min$ 和 $max$,计算 $min * 2 + max$的值,然后把它添加到队列尾。\\n\\n排序后,在数组$nums$ 中的数据是有序的。有序的数据进行$min * 2 + max$计算后,得到的数据仍然是有序的…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/pai-xu-shuang-zhi-zhen-java-zui-kuai-yun-lq2l","author":"peaceful-explorer","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-04T04:35:12.577Z","media":[{"url":"https://pic.leetcode.cn/1709783921-EGadzf-image.png","type":"photo","width":919,"height":177,"blurhash":"LPS?4C.7x@$m_LVvR7tiR7kBtPV]"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3066. 超过阈值的最少操作数 II","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/3066-chao-guo-yu-zhi-de-zui-shao-cao-zuo-tt9u","content":"可以模拟操作过程,计算最少操作次数。
\\n为了快速得到数组 $\\\\textit{nums}$ 中的最小元素,需要使用优先队列存储数组 $\\\\textit{nums}$ 中的所有元素,优先队列的队首元素是最小元素。
\\n初始时,将数组 $\\\\textit{nums}$ 中的所有元素加入优先队列。当优先队列的队首元素小于 $k$ 时,执行如下操作。
\\n从优先队列中取出两个元素,将两个元素依次记为 $x$ 和 $y$,则 $x \\\\le y$。
\\n此时 $\\\\min(x, y) = x$,$\\\\max(x, y) = y$,因此 $\\\\min(x, y) \\\\times 2 + \\\\max(x, y) = 2x + y$,将 $2x + y$ 加入优先队列。
\\n操作过程中维护操作次数,当优先队列的队首元素大于等于 $k$ 时,操作结束,即可得到最少操作次数。
\\n###Java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int operations = 0;\\n PriorityQueue<Long> pq = new PriorityQueue<Long>();\\n for (int num : nums) {\\n pq.offer((long) num);\\n }\\n while (pq.size() > 1 && pq.peek() < k) {\\n long x = pq.poll();\\n long y = pq.poll();\\n pq.offer(x * 2 + y);\\n operations++;\\n }\\n return operations;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MinOperations(int[] nums, int k) {\\n int operations = 0;\\n PriorityQueue<long, long> pq = new PriorityQueue<long, long>();\\n foreach (int num in nums) {\\n pq.Enqueue(num, num);\\n }\\n while (pq.Count > 1 && pq.Peek() < k) {\\n long x = pq.Dequeue();\\n long y = pq.Dequeue();\\n pq.Enqueue(x * 2 + y, x * 2 + y);\\n operations++;\\n }\\n return operations;\\n }\\n}\\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。优先队列的操作次数是 $O(n)$,每次优先队列操作的时间是 $O(\\\\log n)$,因此时间复杂度是 $O(n \\\\log n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。优先队列中的元素个数不超过 $n$。
\\n由于所有小于 $k$ 的数都要被删除,所以统计 $\\\\textit{nums}$ 中的小于 $k$ 的元素个数即可。
\\n###py
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return sum(x < k for x in nums)\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int ans = 0;\\n for (int x : nums) {\\n if (x < k) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n int ans = 0;\\n for (int x : nums) {\\n ans += x < k;\\n }\\n return ans;\\n }\\n};\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n return ranges::count_if(nums, [&](int x) {\\n return x < k;\\n });\\n }\\n};\\n
\\n###c
\\nint minOperations(int* nums, int numsSize, int k) {\\n int ans = 0;\\n for (int i = 0; i < numsSize; i++) {\\n ans += nums[i] < k;\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc minOperations(nums []int, k int) (ans int) {\\nfor _, x := range nums {\\nif x < k {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar minOperations = function(nums, k) {\\n let ans = 0;\\n for (const x of nums) {\\n if (x < k) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\n###js
\\nvar minOperations = function(nums, k) {\\n return nums.filter(x => x < k).length;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {\\n nums.iter().filter(|&&x| x < k).count() as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"由于所有小于 $k$ 的数都要被删除,所以统计 $\\\\textit{nums}$ 中的小于 $k$ 的元素个数即可。 ###py\\n\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return sum(x < k for x in nums)\\n\\n\\n###java\\n\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int ans = 0;…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i//solution/tong-ji-xiao-yu-k-de-yuan-su-ge-shu-pyth-joar","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-03T01:17:43.996Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最小堆模拟(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/zui-xiao-dui-mo-ni-pythonjavacgo-by-endl-07jk","content":"每次选择最小的两个数操作,这可以用最小堆模拟。
\\n\\n\\n部分语言可以直接修改堆顶:弹出 $x$ 后,把堆顶增加 $2x$。
\\n
如果最小值 $\\\\ge k$,那么所有数都 $\\\\ge k$。所以循环直到堆顶 $\\\\ge k$ 为止。
\\n\\n\\n注意题目保证答案一定存在。也就是说,堆中只有一个数且这个数小于 $k$ 的情况是不存在的。
\\n
###py
\\nclass Solution:\\n def minOperations(self, h: List[int], k: int) -> int:\\n heapify(h)\\n ans = 0\\n while h[0] < k:\\n x = heappop(h)\\n heapreplace(h, h[0] + x * 2)\\n ans += 1\\n return ans\\n
\\n###java
\\nclass Solution {\\n public int minOperations(int[] nums, int k) {\\n int ans = 0;\\n PriorityQueue<Long> pq = new PriorityQueue<>();\\n for (int x : nums) {\\n pq.offer((long) x);\\n }\\n\\n while (pq.peek() < k) {\\n long x = pq.poll();\\n long y = pq.poll();\\n pq.offer(x * 2 + y);\\n ans++;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minOperations(vector<int>& nums, int k) {\\n priority_queue<long long, vector<long long>, greater<>> pq;\\n for (int x : nums) {\\n pq.push((long long) x);\\n }\\n\\n int ans = 0;\\n while (pq.top() < k) {\\n long long x = pq.top(); pq.pop();\\n long long y = pq.top(); pq.pop();\\n pq.push(x * 2 + y);\\n ans++;\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc minOperations(nums []int, k int) (ans int) {\\nh := &hp{nums}\\nheap.Init(h)\\nfor h.IntSlice[0] < k {\\nx := heap.Pop(h).(int)\\nh.IntSlice[0] += x * 2\\nheap.Fix(h, 0)\\nans++\\n}\\nreturn\\n}\\n\\ntype hp struct{ sort.IntSlice }\\nfunc (hp) Push(any) {}\\nfunc (h *hp) Pop() any { a := h.IntSlice; v := a[len(a)-1]; h.IntSlice = a[:len(a)-1]; return v }\\n
\\n###js
\\nvar minOperations = function(nums, k) {\\n const pq = new MinPriorityQueue(); // datastructures-js/priority-queue@5.4.0\\n for (const x of nums) {\\n pq.enqueue(x);\\n }\\n\\n let ans = 0;\\n while (pq.front().element < k) {\\n const x = pq.dequeue().element;\\n const y = pq.dequeue().element;\\n pq.enqueue(x * 2 + y);\\n ans++;\\n }\\n return ans;\\n};\\n
\\n###rust
\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn min_operations(nums: Vec<i32>, k: i32) -> i32 {\\n let mut h = BinaryHeap::new();\\n for x in nums {\\n h.push(-x as i64); // 取负号变成最小堆\\n }\\n\\n let mut ans = 0;\\n while -h.peek().unwrap() < k as i64 {\\n let x = h.pop().unwrap();\\n let y = h.pop().unwrap();\\n h.push(x * 2 + y);\\n ans += 1;\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"每次选择最小的两个数操作,这可以用最小堆模拟。 部分语言可以直接修改堆顶:弹出 $x$ 后,把堆顶增加 $2x$。\\n\\n如果最小值 $\\\\ge k$,那么所有数都 $\\\\ge k$。所以循环直到堆顶 $\\\\ge k$ 为止。\\n\\n注意题目保证答案一定存在。也就是说,堆中只有一个数且这个数小于 $k$ 的情况是不存在的。\\n\\n###py\\n\\nclass Solution:\\n def minOperations(self, h: List[int], k: int) -> int:\\n heapify(h)\\n ans = 0…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-ii//solution/zui-xiao-dui-mo-ni-pythonjavacgo-by-endl-07jk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-03T00:54:02.023Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:树形 DP / 状态机 DP(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-maximum-sum-of-node-values//solution/liang-chong-fang-fa-shu-xing-dp-xian-xin-lh6b","content":"视频讲解 第四题。
\\n前置知识:树形 DP【基础算法精讲 24】
\\n用「选或不选」思考。
\\n对于以 $x$ 为根的子树,考虑 $x$ 和它的儿子 $y$ 之间的边是否操作。
\\n初始化 $f[x][0]=0,\\\\ f[x][1] = -\\\\infty$。遍历并递归计算 $x$ 的所有儿子,设当前遍历到的儿子为 $y$,
\\n两种情况取最大值,有
\\n$$
\\n\\\\begin{aligned}
\\n&f[x][0] = \\\\max(f[x][0] + r_0, f[x][1] + r_1)\\\\
\\n&f[x][1] = \\\\max(f[x][1] + r_0, f[x][0] + r_1)
\\n\\\\end{aligned}
\\n$$
注意这两个转移是同时发生的。
\\n最后答案为根节点对应的 $r_0$。
\\n###py
\\nclass Solution:\\n def maximumValueSum(self, nums: List[int], k: int, edges: List[List[int]]) -> int:\\n g = [[] for _ in nums]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n def dfs(x: int, fa: int) -> Tuple[int, int]:\\n f0, f1 = 0, -inf # f[x][0] 和 f[x][1]\\n for y in g[x]:\\n if y != fa:\\n r0, r1 = dfs(y, x)\\n f0, f1 = max(f0 + r0, f1 + r1), max(f1 + r0, f0 + r1)\\n return max(f0 + nums[x], f1 + (nums[x] ^ k)), max(f1 + nums[x], f0 + (nums[x] ^ k))\\n return dfs(0, -1)[0]\\n
\\n###java
\\nclass Solution {\\n public long maximumValueSum(int[] nums, int k, int[][] edges) {\\n int n = nums.length;\\n List<Integer>[] g = new ArrayList[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n return dfs(0, -1, g, nums, k)[0];\\n }\\n\\n private long[] dfs(int x, int fa, List<Integer>[] g, int[] nums, int k) {\\n long f0 = 0;\\n long f1 = Long.MIN_VALUE; // f[x][0] 和 f[x][1]\\n for (int y : g[x]) {\\n if (y != fa) {\\n long[] r = dfs(y, x, g, nums, k);\\n long t = Math.max(f1 + r[0], f0 + r[1]);\\n f0 = Math.max(f0 + r[0], f1 + r[1]);\\n f1 = t;\\n }\\n }\\n return new long[]{Math.max(f0 + nums[x], f1 + (nums[x] ^ k)),\\n Math.max(f1 + nums[x], f0 + (nums[x] ^ k))};\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maximumValueSum(vector<int> &nums, int k, vector<vector<int>> &edges) {\\n int n = nums.size();\\n vector<vector<int>> g(n);\\n for (auto &e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n\\n function<pair<long long, long long>(int, int)> dfs = [&](int x, int fa) -> pair<long long, long long> {\\n long long f0 = 0, f1 = LLONG_MIN; // f[x][0] 和 f[x][1]\\n for (auto &y : g[x]) {\\n if (y != fa) {\\n auto [r0, r1] = dfs(y, x);\\n long long t = max(f1 + r0, f0 + r1);\\n f0 = max(f0 + r0, f1 + r1);\\n f1 = t;\\n }\\n }\\n return {max(f0 + nums[x], f1 + (nums[x] ^ k)), max(f1 + nums[x], f0 + (nums[x] ^ k))};\\n };\\n return dfs(0, -1).first;\\n }\\n};\\n
\\n###go
\\nfunc maximumValueSum(nums []int, k int, edges [][]int) int64 {\\nn := len(nums)\\ng := make([][]int, n)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\ng[x] = append(g[x], y)\\ng[y] = append(g[y], x)\\n}\\n\\nvar dfs func(int, int) (int, int)\\ndfs = func(x, fa int) (int, int) {\\nf0, f1 := 0, math.MinInt // f[x][0] 和 f[x][1]\\nfor _, y := range g[x] {\\nif y != fa {\\nr0, r1 := dfs(y, x)\\nf0, f1 = max(f0+r0, f1+r1), max(f1+r0, f0+r1)\\n}\\n}\\nreturn max(f0+nums[x], f1+(nums[x]^k)), max(f1+nums[x], f0+(nums[x]^k))\\n}\\nans, _ := dfs(0, -1)\\nreturn int64(ans)\\n}\\n
\\n由于一个数异或两次 $k$ 后保持不变,所以对于一条从 $x$ 到 $y$ 的简单路径,我们把路径上的所有边操作后,路径上除了 $x$ 和 $y$ 的其它节点都恰好操作两次,所以只有 $\\\\textit{nums}[x]$ 和 $\\\\textit{nums}[y]$ 都异或了 $k$,其余元素不变。
\\n所以题目中的操作可以作用在任意两个数上。我们不需要建树,$\\\\textit{edges}$ 是多余的!
\\n注意到,无论操作多少次,总是有偶数个元素异或了 $k$。理由如下:
\\n结合这两个性质,问题变成:
\\n这可以用状态机 DP 解决。
\\n设 $x=\\\\textit{nums}[i]$。
\\n两种情况取最大值,有
\\n$$
\\n\\\\begin{aligned}
\\n&f[i+1][0] = \\\\max(f[i][0] + x, f[i][1] + (x\\\\oplus k))\\\\
\\n&f[i+1][1] = \\\\max(f[i][1] + x, f[i][0] + (x\\\\oplus k))
\\n\\\\end{aligned}
\\n$$
初始值 $f[0][0] = 0,\\\\ f[0][1] = -\\\\infty$。
\\n答案为 $f[n][0]$。
\\n代码实现时,$f$ 数组的第一个维度可以压缩掉。
\\n###py
\\nclass Solution:\\n def maximumValueSum(self, nums: List[int], k: int, _: List[List[int]]) -> int:\\n f0, f1 = 0, -inf\\n for x in nums:\\n f0, f1 = max(f0 + x, f1 + (x ^ k)), max(f1 + x, f0 + (x ^ k))\\n return f0\\n
\\n###java
\\nclass Solution {\\n public long maximumValueSum(int[] nums, int k, int[][] edges) {\\n long f0 = 0;\\n long f1 = Long.MIN_VALUE;\\n for (int x : nums) {\\n long t = Math.max(f1 + x, f0 + (x ^ k));\\n f0 = Math.max(f0 + x, f1 + (x ^ k));\\n f1 = t;\\n }\\n return f0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maximumValueSum(vector<int> &nums, int k, vector<vector<int>> &edges) {\\n long long f0 = 0, f1 = LLONG_MIN;\\n for (int x : nums) {\\n long long t = max(f1 + x, f0 + (x ^ k));\\n f0 = max(f0 + x, f1 + (x ^ k));\\n f1 = t;\\n }\\n return f0;\\n }\\n};\\n
\\n###go
\\nfunc maximumValueSum(nums []int, k int, _ [][]int) int64 {\\nf0, f1 := 0, math.MinInt\\nfor _, x := range nums {\\nf0, f1 = max(f0+x, f1+(x^k)), max(f1+x, f0+(x^k))\\n}\\nreturn int64(f0)\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"视频讲解 第四题。 方法一:树形 DP\\n\\n前置知识:树形 DP【基础算法精讲 24】\\n\\n用「选或不选」思考。\\n\\n对于以 $x$ 为根的子树,考虑 $x$ 和它的儿子 $y$ 之间的边是否操作。\\n\\n定义 $f[x][0]$ 表示 $x$ 操作偶数次时,子树 $x$ 的除去 $x$ 的最大价值和。\\n定义 $f[x][1]$ 表示 $x$ 操作奇数次时,子树 $x$ 的除去 $x$ 的最大价值和。\\n\\n初始化 $f[x][0]=0,\\\\ f[x][1] = -\\\\infty$。遍历并递归计算 $x$ 的所有儿子,设当前遍历到的儿子为 $y$,\\n\\n情况一,不操作 $x$ 和…","guid":"https://leetcode.cn/problems/find-the-maximum-sum-of-node-values//solution/liang-chong-fang-fa-shu-xing-dp-xian-xin-lh6b","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-03T00:41:47.997Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++贪心","url":"https://leetcode.cn/problems/find-the-maximum-sum-of-node-values//solution/ctan-xin-by-thdlrt-fk6i","content":"\\n\\nProblem: 100210. 最大节点价值之和
\\n
[TOC]
\\n###C++
\\nclass Solution {\\npublic:\\n long long maximumValueSum(vector<int>& nums, int k, vector<vector<int>>& edges) {\\n long long res=accumulate(nums.begin(),nums.end(),(long long)0);\\n//计算异或并排序\\n vector<int>arr;\\n for(auto&a:nums){\\n arr.push_back((a^k)-a);\\n }\\n sort(arr.begin(),arr.end());\\n//从大到小遍历,计算操作带来的附加值\\n for(int i=arr.size()-1;i>0;i-=2){\\n int x=arr[i];\\n int y=arr[i-1];\\n//如果操作后值变小,还不如不操作\\n res+=max(0,x+y);\\n }\\n return res;\\n }\\n};\\n
\\n","description":"Problem: 100210. 最大节点价值之和 [TOC]\\n\\n对与树上任意两点,存在一条路径,对路径上每一条边进行一次操作,不难发现实际上就是对两点进行异或(中间节点都进行了两次异或,值不变),也就是说可以一次对任两个节点做一次对k异或\\n计算$(a^k)-a$并排序,按照从大到小的顺序与k异或(注意,如果异或后 总值还变小了,那自然就不进行操作了)\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n long long maximumValueSum(vector提示 1: 操作能否拓展为任意点对 $(u, v)$ 的操作?
\\n提示 2: 整体操作的不变量是什么?在不变量满足的情况下是否任意状态都可行?
\\n脑筋急转弯(虽然不转也能做)。
\\n我们的操作是,每次对连着边的两个点同时进行 XOR k
运算。考虑每个节点,其一定要么不变,要么变成其 XOR k
的数值,这取决于其执行了几次操作。
那么我们考虑变为 XOR k
的元素的个数,发现其保持 奇偶性不变,只需要讨论目前两个数是否经过 XOR k
操作即可。
那是否只要 XOR k
的点是偶数个,就一定可行呢?
是这样的,我们只需要证明任意 $2$ 个节点可以同时进行 XOR k
操作。
考虑 $u$ 到 $v$ 的路径是 $u\\\\to x_1\\\\to x_2\\\\to\\\\dots\\\\to x_k\\\\to v$,则对其中经过的每一条边进行一次操作即可。
\\n因此,树的形态不重要,只需要保证操作的元素个数是偶数,再使得数组和最大。
\\n这件事情很容易通过 DP 实现,我们用 $dp_0[i], dp_1[i]$ 分别记录到 $i$ 位置为止,有偶数个 / 奇数个点进行过操作时,这些元素的和的最大值即可。转移只需要枚举新元素是否做操作。
\\n时间复杂度为 $\\\\mathcal{O}(n)$.
\\n###Python
\\nclass Solution:\\n def maximumValueSum(self, nums: List[int], k: int, edges: List[List[int]]) -> int:\\n dp0, dp1 = 0, -inf\\n for num in nums:\\n dp0, dp1 = max(dp0 + num, dp1 + (num ^ k)), max(dp0 + (num ^ k), dp1 + num)\\n return dp0\\n
\\nPython 可以进一步把 DP 过程用 reduce
实现——
###Python
\\nclass Solution:\\n def maximumValueSum(self, nums: List[int], k: int, edges: List[List[int]]) -> int:\\n return reduce(lambda x, y: (max(x[0] + y, x[1] + (y ^ k)), max(x[1] + y, x[0] + (y ^ k))), nums, (0, -inf))[0]\\n
\\n","description":"提示 1: 操作能否拓展为任意点对 $(u, v)$ 的操作? 提示 2: 整体操作的不变量是什么?在不变量满足的情况下是否任意状态都可行?\\n\\n脑筋急转弯(虽然不转也能做)。\\n\\n我们的操作是,每次对连着边的两个点同时进行 XOR k 运算。考虑每个节点,其一定要么不变,要么变成其 XOR k 的数值,这取决于其执行了几次操作。\\n\\n那么我们考虑变为 XOR k 的元素的个数,发现其保持 奇偶性不变,只需要讨论目前两个数是否经过 XOR k 操作即可。\\n\\n那是否只要 XOR k 的点是偶数个,就一定可行呢?\\n\\n是这样的,我们只需要证明任意 $2$ 个节点可以同时进行…","guid":"https://leetcode.cn/problems/find-the-maximum-sum-of-node-values//solution/xiao-yang-xiao-en-jia-shu-zhen-dp-on-de-ddxag","author":"Yawn_Sean","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-02T16:44:19.496Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二分查找","url":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i//solution/er-fen-cha-zhao-by-admiring-meninskyuli-1w1z","content":"\\n\\nProblem: 100231. 超过阈值的最少操作数 I
\\n
[TOC]
\\n二分查找即可
\\n执行用时分布37ms击败100.00%;消耗内存分布16.43MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return bisect_left(sorted(nums), k)\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100231. 超过阈值的最少操作数 I [TOC]\\n\\n二分查找即可\\n\\n执行用时分布37ms击败100.00%;消耗内存分布16.43MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def minOperations(self, nums: List[int], k: int) -> int:\\n return bisect_left(sorted(nums), k)\\n\\n\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^\\n\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^…","guid":"https://leetcode.cn/problems/minimum-operations-to-exceed-threshold-value-i//solution/er-fen-cha-zhao-by-admiring-meninskyuli-1w1z","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-03-02T16:40:18.888Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/split-the-array//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-8k36","content":"根据题意,如果 $\\\\textit{nums}[i]$ 的出现次数超过 $2$,则无法分割,否则可以分割。
\\n###py
\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return max(Counter(nums).values()) <= 2\\n
\\n###py
\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return all(c <= 2 for c in Counter(nums).values())\\n
\\n###java
\\nclass Solution {\\n public boolean isPossibleToSplit(int[] nums) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int x : nums) {\\n if (cnt.merge(x, 1, Integer::sum) > 2) { // ++cnt[x] > 2\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n bool isPossibleToSplit(vector<int>& nums) {\\n unordered_map<int, int> cnt;\\n for (int x : nums) {\\n if (++cnt[x] > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n
\\n###c
\\nbool isPossibleToSplit(int* nums, int numsSize) {\\n int cnt[101] = {};\\n for (int i = 0; i < numsSize; i++) {\\n if (++cnt[nums[i]] > 2) {\\n return false;\\n }\\n }\\n return true;\\n}\\n
\\n###go
\\nfunc isPossibleToSplit(nums []int) bool {\\n cnt := map[int]int{}\\n for _, x := range nums {\\n cnt[x]++\\n if cnt[x] > 2 {\\n return false\\n }\\n }\\n return true\\n}\\n
\\n###js
\\nvar isPossibleToSplit = function(nums) {\\n const cnt = new Map();\\n for (const x of nums) {\\n const c = (cnt.get(x) ?? 0) + 1;\\n if (c > 2) {\\n return false;\\n }\\n cnt.set(x, c);\\n }\\n return true;\\n};\\n
\\n###rust
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn is_possible_to_split(nums: Vec<i32>) -> bool {\\n let mut cnt = HashMap::new();\\n for x in nums {\\n *cnt.entry(x).or_insert(0) += 1;\\n if cnt[&x] > 2 {\\n return false;\\n }\\n }\\n true\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据题意,如果 $\\\\textit{nums}[i]$ 的出现次数超过 $2$,则无法分割,否则可以分割。 ###py\\n\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return max(Counter(nums).values()) <= 2\\n\\n\\n###py\\n\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return all…","guid":"https://leetcode.cn/problems/split-the-array//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-8k36","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-25T11:22:48.268Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"发个C语言版的~~","url":"https://leetcode.cn/problems/split-the-array//solution/fa-ge-cyu-yan-ban-de-by-kagari-a-pxga","content":"\\n\\nProblem: 100224. 分割数组
\\n
[TOC]
\\n\\n\\n哈希表
\\n
\\n\\n哈希表记忆所有值出现次数,之后遍历哈希表,其中出现次数超过两次就不符合条件,即return false,否则就return true。
\\n
###C
\\nbool isPossibleToSplit(int* nums, int numsSize) {\\n int* hash = (int *)malloc(sizeof(int) * 101);\\n memset(hash,0,sizeof(int) * 101);\\n for(int i = 0;i < numsSize;i++){\\n hash[ nums[i] ]++;\\n }\\n for(int i = 0;i < 101;i++){\\n if(hash[i] > 2){\\n return false;\\n }\\n }\\n free(hash);\\n return true;\\n}\\n
\\n","description":"Problem: 100224. 分割数组 [TOC]\\n\\n哈希表\\n\\n哈希表记忆所有值出现次数,之后遍历哈希表,其中出现次数超过两次就不符合条件,即return false,否则就return true。\\n\\n###C\\n\\nbool isPossibleToSplit(int* nums, int numsSize) {\\n int* hash = (int *)malloc(sizeof(int) * 101);\\n memset(hash,0,sizeof(int) * 101);\\n for(int i = 0;i…","guid":"https://leetcode.cn/problems/split-the-array//solution/fa-ge-cyu-yan-ban-de-by-kagari-a-pxga","author":"kagari-a","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-25T10:41:30.585Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python 一行 40ms","url":"https://leetcode.cn/problems/split-the-array//solution/python-yi-xing-40ms-by-nrib8zib57-43t1","content":"\\n\\nProblem: 100224. 分割数组
\\n
[TOC]
\\n\\n\\npython 一行 40ms
\\n
\\n\\npython 一行 40ms
\\n
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Python3
\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return all(y<=2 for x,y in Counter(nums).items())\\n
\\n","description":"Problem: 100224. 分割数组 [TOC]\\n\\npython 一行 40ms\\n\\npython 一行 40ms\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n空间复杂度:\\n\\n添加空间复杂度, 示例: $O(n)$\\n\\n###Python3\\n\\nclass Solution:\\n def isPossibleToSplit(self, nums: List[int]) -> bool:\\n return all(y<=2 for x,y in Counter(nums).items())","guid":"https://leetcode.cn/problems/split-the-array//solution/python-yi-xing-40ms-by-nrib8zib57-43t1","author":"nriB8ZIB57","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-25T07:50:10.733Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简单易懂","url":"https://leetcode.cn/problems/split-the-array//solution/jian-dan-yi-dong-by-awesome-diraczqg-4c2p","content":"\\n\\nProblem: 100224. 分割数组
\\n
[TOC]
\\n\\n\\n判断原数组中是否有一个数出现了2次以上,如果超过2次肯定没办法分割保证两个数组中的元素分别互不相同.
\\n
\\n\\n哈希表存储数据,key为元素,value为元素出现的个数,遍历原数组,如果表中已经有该元素,get方法取到value以后加1,并判断此时value是否大于2,如果是则返回false,如果表中没有,set方法存储,最后遍历完返回true.
\\n
时间复杂度:
\\n\\n\\nO(n)
\\n
空间复杂度:
\\n\\n\\nO(n)
\\n
###JavaScript
\\n/**\\n * @param {number[]} nums\\n * @return {boolean}\\n */\\nvar isPossibleToSplit = function(nums) {\\n let map =new Map()\\n for(let i of nums){\\n if(map.has(i)){\\n let time=map.get(i)\\n map.set(i,time+1)\\n if(time+1>2){\\n return false\\n }\\n }else{\\n map.set(i,1)\\n }\\n }\\n return true\\n};\\n
\\n","description":"Problem: 100224. 分割数组 [TOC]\\n\\n判断原数组中是否有一个数出现了2次以上,如果超过2次肯定没办法分割保证两个数组中的元素分别互不相同.\\n\\n哈希表存储数据,key为元素,value为元素出现的个数,遍历原数组,如果表中已经有该元素,get方法取到value以后加1,并判断此时value是否大于2,如果是则返回false,如果表中没有,set方法存储,最后遍历完返回true.\\n\\n时间复杂度:\\n\\nO(n)\\n\\n空间复杂度:\\n\\nO(n)\\n\\n###JavaScript\\n\\n/**\\n * @param {number[]} nums…","guid":"https://leetcode.cn/problems/split-the-array//solution/jian-dan-yi-dong-by-awesome-diraczqg-4c2p","author":"awesome-diraczqg","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-25T07:16:16.468Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"3046. 分割数组","url":"https://leetcode.cn/problems/split-the-array//solution/3046-fen-ge-shu-zu-by-stormsunshine-5yo5","content":"这道题给定一个长度为偶数的数组 $\\\\textit{nums}$,要求将该数组分割成两部分,满足两部分的元素个数都等于原始数组的元素个数的一半,且两部分的元素都各不相同。用 $n$ 表示数组 $\\\\textit{nums}$ 的长度,则两部分的元素个数都等于 $\\\\dfrac{n}{2}$。
\\n可以将数组 $\\\\textit{nums}$ 按照要求分割成两部分,等价于数组 $\\\\textit{nums}$ 中的每个元素最多出现两次。理由如下。
\\n当数组 $\\\\textit{nums}$ 中存在一个元素 $x$ 出现超过两次时,对于任何一种分配方案,都会至少有一个部分出现两次或以上的 $x$,此时不能分割数组。
\\n当数组 $\\\\textit{nums}$ 中的每个元素的出现次数都不超过两次时,出现两次的元素个数一定不超过 $\\\\dfrac{n}{2}$,因此一定可以将出现两次的元素分配到两部分。其余元素只出现一次,因此其余元素的分配不会导致同一个部分出现重复元素,可以在满足两个部分都有 $\\\\dfrac{n}{2}$ 个元素的前提下将其余元素任意分配。
\\n根据上述分析,判断是否可以分割数组的做法如下:遍历数组 $\\\\textit{nums}$ 统计每个元素的出现次数,如果至少有一个元素的出现次数超过两次则返回 $\\\\text{false}$,如果所有元素的出现次数都不超过两次则返回 $\\\\text{true}$。
\\n###Java
\\nclass Solution {\\n public boolean isPossibleToSplit(int[] nums) {\\n Map<Integer, Integer> counts = new HashMap<Integer, Integer>();\\n for (int num : nums) {\\n counts.put(num, counts.getOrDefault(num, 0) + 1);\\n if (counts.get(num) > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public bool IsPossibleToSplit(int[] nums) {\\n IDictionary<int, int> counts = new Dictionary<int, int>();\\n foreach (int num in nums) {\\n counts.TryAdd(num, 0);\\n counts[num]++;\\n if (counts[num] > 2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次统计每个元素的出现次数,并判断是否有元素的出现次数超过两次。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。哈希表的空间是 $O(n)$。
\\n\\n\\nProblem: 3024. 三角形类型 II
\\n
[TOC]
\\n直接模拟处理。
\\n执行用时分布38ms击败53.04%;消耗内存分布16.18MB击败99.58%
\\n###Python3
\\nclass Solution:\\n def triangleType(self, nums: List[int]) -> str:\\n if nums[0] == nums[1] == nums[2]: return \\"equilateral\\"\\n nums.sort()\\n if nums[0] + nums[1] <= nums[2]: return \\"none\\"\\n if len(set(nums)) == 2: return \\"isosceles\\"\\n return \\"scalene\\"\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 3024. 三角形类型 II [TOC]\\n\\n直接模拟处理。\\n\\n执行用时分布38ms击败53.04%;消耗内存分布16.18MB击败99.58%\\n\\n###Python3\\n\\nclass Solution:\\n def triangleType(self, nums: List[int]) -> str:\\n if nums[0] == nums[1] == nums[2]: return \\"equilateral\\"\\n nums.sort()\\n if nums[0] + nums[1…","guid":"https://leetcode.cn/problems/type-of-triangle//solution/mo-ni-chu-li-by-admiring-meninskyuli-4teh","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-07T07:55:34.717Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"用排序简化代码逻辑(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/type-of-triangle//solution/an-ti-yi-mo-ni-pythonjavacgo-by-endlessc-zq6e","content":"把 $\\\\textit{nums}$ 从小到大排序,可以简化判断逻辑。
\\n设排序后 $\\\\textit{nums}=[a,b,c]$,那么有 $1\\\\le a\\\\le b\\\\le c$。
\\n###py
\\nclass Solution:\\n def triangleType(self, nums: List[int]) -> str:\\n nums.sort()\\n a, b, c = nums\\n if a + b <= c:\\n return \\"none\\"\\n if a == c:\\n return \\"equilateral\\"\\n if a == b or b == c:\\n return \\"isosceles\\"\\n return \\"scalene\\"\\n
\\n###java
\\nclass Solution {\\n public String triangleType(int[] nums) {\\n Arrays.sort(nums);\\n int a = nums[0];\\n int b = nums[1];\\n int c = nums[2];\\n if (a + b <= c) {\\n return \\"none\\";\\n }\\n if (a == c) {\\n return \\"equilateral\\";\\n }\\n if (a == b || b == c) {\\n return \\"isosceles\\";\\n }\\n return \\"scalene\\";\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n string triangleType(vector<int>& nums) {\\n ranges::sort(nums);\\n int a = nums[0], b = nums[1], c = nums[2];\\n if (a + b <= c) {\\n return \\"none\\";\\n }\\n if (a == c) {\\n return \\"equilateral\\";\\n }\\n if (a == b || b == c) {\\n return \\"isosceles\\";\\n }\\n return \\"scalene\\";\\n }\\n};\\n
\\n###c
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nchar* triangleType(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int a = nums[0], b = nums[1], c = nums[2];\\n if (a + b <= c) {\\n return \\"none\\";\\n }\\n if (a == c) {\\n return \\"equilateral\\";\\n }\\n if (a == b || b == c) {\\n return \\"isosceles\\";\\n }\\n return \\"scalene\\";\\n}\\n
\\n###go
\\nfunc triangleType(nums []int) string {\\nslices.Sort(nums)\\na, b, c := nums[0], nums[1], nums[2]\\nif a+b <= c {\\nreturn \\"none\\"\\n}\\nif a == c {\\nreturn \\"equilateral\\"\\n}\\nif a == b || b == c {\\nreturn \\"isosceles\\"\\n}\\nreturn \\"scalene\\"\\n}\\n
\\n###js
\\nvar triangleType = function(nums) {\\n nums.sort((a, b) => a - b);\\n const [a, b, c] = nums;\\n if (a + b <= c) {\\n return \\"none\\";\\n }\\n if (a === c) {\\n return \\"equilateral\\";\\n }\\n if (a === b || b === c) {\\n return \\"isosceles\\";\\n }\\n return \\"scalene\\";\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn triangle_type(mut nums: Vec<i32>) -> String {\\n nums.sort_unstable();\\n let (a, b, c) = (nums[0], nums[1], nums[2]);\\n if a + b <= c {\\n return \\"none\\".to_string();\\n }\\n if a == c {\\n return \\"equilateral\\".to_string();\\n }\\n if a == b || b == c {\\n return \\"isosceles\\".to_string();\\n }\\n \\"scalene\\".to_string()\\n }\\n}\\n
\\n在三边长能构成三角形的情况下,用哈希表计算 $\\\\textit{nums}$ 中有 $c$ 个不同元素,然后:
\\n###py
\\nclass Solution:\\n def triangleType(self, nums: List[int]) -> str:\\n nums.sort()\\n if nums[0] + nums[1] <= nums[2]:\\n return \\"none\\"\\n return (\\"equilateral\\", \\"isosceles\\", \\"scalene\\")[len(set(nums)) - 1]\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"把 $\\\\textit{nums}$ 从小到大排序,可以简化判断逻辑。 设排序后 $\\\\textit{nums}=[a,b,c]$,那么有 $1\\\\le a\\\\le b\\\\le c$。\\n\\n先判是否合法,即三角形任意两边之和必须大于第三边。由于排序后 $a+c > b$ 和 $b+c>a$ 自动成立(注意数组元素都是正数),所以只需判断 $a+b > c$ 是否成立。如果 $a+b\\\\le c$,那么无法构成三角形。\\n然后判等边:只需判断 $a=c$。注意已经排序了,如果 $a=c$,那么必然有 $a=b=c$。\\n然后判等腰:判断 $a=b$ 或者 $b=c$。\\n其他…","guid":"https://leetcode.cn/problems/type-of-triangle//solution/an-ti-yi-mo-ni-pythonjavacgo-by-endlessc-zq6e","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-02-04T01:31:54.901Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2270. 分割数组的方案数","url":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/2270-fen-ge-shu-zu-de-fang-an-shu-by-sto-tj6z","content":"数组 $\\\\textit{nums}$ 的长度是 $n$。朴素的思想是遍历 $0 \\\\le i < n - 1$ 的每个下标 $i$,计算数组 $\\\\textit{nums}$ 的下标范围 $[0, i]$ 的元素和与下标范围 $[i + 1, n - 1]$ 的元素和并判断是否为合法分割。该做法对于每个分割方案的判断时间是 $O(n)$,时间复杂度是 $O(n^2)$,该时间复杂度过高,需要优化。
\\n用 $\\\\textit{leftSum}$ 和 $\\\\textit{rightSum}$ 分别表示数组 $\\\\textit{nums}$ 的下标范围 $[0, i]$ 的元素和与下标范围 $[i + 1, n - 1]$ 的元素和,将此时的下标 $i$ 称为分割下标。初始时 $\\\\textit{leftSum}$ 等于 $0$,$\\\\textit{rightSum}$ 等于数组 $\\\\textit{nums}$ 的所有元素之和,此时分割下标是 $-1$。对于 $0 \\\\le i < n - 1$,当分割下标从 $i - 1$ 移动到 $i$ 时,元素 $\\\\textit{nums}[i]$ 从分割下标右侧移动到分割下标左侧,因此 $\\\\textit{leftSum}$ 的值增加 $\\\\textit{nums}[i]$,$\\\\textit{rightSum}$ 的值减少 $\\\\textit{nums}[i]$,可以使用 $O(1)$ 的时间更新 $\\\\textit{leftSum}$ 和 $\\\\textit{rightSum}$,对于每个分割下标的判断时间降低到 $O(1)$,时间复杂度降低到 $O(n)$。
\\n对于每个分割下标计算 $\\\\textit{leftSum}$ 和 $\\\\textit{rightSum}$ 之后,如果 $\\\\textit{leftSum} \\\\ge \\\\textit{rightSum}$,则将合法分割的方案数增加 $1$。遍历结束之后即可得到合法分割的方案数。
\\n###Java
\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n int ways = 0;\\n long leftSum = 0, rightSum = 0;\\n for (int num : nums) {\\n rightSum += num;\\n }\\n int n = nums.length;\\n for (int i = 0; i < n - 1; i++) {\\n leftSum += nums[i];\\n rightSum -= nums[i];\\n if (leftSum >= rightSum) {\\n ways++;\\n }\\n }\\n return ways;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int WaysToSplitArray(int[] nums) {\\n int ways = 0;\\n long leftSum = 0, rightSum = 0;\\n foreach (int num in nums) {\\n rightSum += num;\\n }\\n int n = nums.Length;\\n for (int i = 0; i < n - 1; i++) {\\n leftSum += nums[i];\\n rightSum -= nums[i];\\n if (leftSum >= rightSum) {\\n ways++;\\n }\\n }\\n return ways;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组两次。
\\n空间复杂度:$O(1)$。
\\n用 $\\\\textit{maxRow}$ 表示 $1$ 最多的行的行下标,用 $\\\\textit{maxOnes}$ 表示 $1$ 最多的行的 $1$ 的数量,初始时 $\\\\textit{maxRow} = \\\\textit{maxOnes} = -1$。
\\n按照行下标递增的顺序依次遍历矩阵 $\\\\textit{mat}$ 的每一行寻找 $1$ 最多的行,遍历过程中维护 $\\\\textit{maxRow}$ 和 $\\\\textit{maxOnes}$ 的值。当遍历到第 $i$ 行时,计算该行的 $1$ 的数量 $\\\\textit{ones}$,如果 $\\\\textit{ones} > \\\\textit{maxOnes}$,则将 $\\\\textit{maxRow}$ 的值更新为 $i$,将 $\\\\textit{maxOnes}$ 的值更新为 $\\\\textit{ones}$。遍历结束之后返回 $[\\\\textit{maxRow}, \\\\textit{maxOnes}]$。
\\n由于遍历矩阵行的顺序是行下标递增的顺序,只有当遍历到的行的 $1$ 的数量严格大于 $\\\\textit{maxOnes}$ 时才会更新 $\\\\textit{maxRow}$ 和 $\\\\textit{maxOnes}$,因此可以确保答案中的行下标为 $1$ 最多的行的最小行下标。
\\n###Java
\\nclass Solution {\\n public int[] rowAndMaximumOnes(int[][] mat) {\\n int m = mat.length, n = mat[0].length;\\n int maxRow = -1, maxOnes = -1;\\n for (int i = 0; i < m; i++) {\\n int ones = 0;\\n for (int j = 0; j < n; j++) {\\n ones += mat[i][j];\\n }\\n if (ones > maxOnes) {\\n maxRow = i;\\n maxOnes = ones;\\n }\\n }\\n return new int[]{maxRow, maxOnes};\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] RowAndMaximumOnes(int[][] mat) {\\n int m = mat.Length, n = mat[0].Length;\\n int maxRow = -1, maxOnes = -1;\\n for (int i = 0; i < m; i++) {\\n int ones = 0;\\n for (int j = 0; j < n; j++) {\\n ones += mat[i][j];\\n }\\n if (ones > maxOnes) {\\n maxRow = i;\\n maxOnes = ones;\\n }\\n }\\n return new int[]{maxRow, maxOnes};\\n }\\n}\\n
\\n时间复杂度:$O(mn)$,其中 $m$ 和 $n$ 分别是矩阵 $\\\\textit{mat}$ 的行数和列数。需要遍历矩阵一次。
\\n空间复杂度:$O(1)$。
\\n由于计算按键变更的次数时不区分大小写,因此可以将字符串 $s$ 中的每个字母转换成小写字母之后比较每一对相邻字母是否相等,如果相邻字母不相等则需要一次按键变更。以下描述中,所有字母都按小写字母处理。
\\n用 $\\\\textit{prev}$ 表示上一个字母,初始时 $\\\\textit{prev} = s[0]$。从下标 $1$ 开始从左到右遍历字符串 $s$,对于遍历到的每个下标 $i$,记当前字母为 $\\\\textit{curr} = s[i]$,如果 $\\\\textit{curr} \\\\ne \\\\textit{prev}$,则将按键变更的次数加 $1$,然后将 $\\\\textit{prev}$ 的值更新为 $\\\\textit{curr}$。遍历结束之后,即可得到按键变更的次数。
\\n###Java
\\nclass Solution {\\n public int countKeyChanges(String s) {\\n int changes = 0;\\n int length = s.length();\\n char prev = Character.toLowerCase(s.charAt(0));\\n for (int i = 1; i < length; i++) {\\n char curr = Character.toLowerCase(s.charAt(i));\\n if (curr != prev) {\\n changes++;\\n prev = curr;\\n }\\n }\\n return changes;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int CountKeyChanges(string s) {\\n int changes = 0;\\n int length = s.Length;\\n char prev = char.ToLower(s[0]);\\n for (int i = 1; i < length; i++) {\\n char curr = char.ToLower(s[i]);\\n if (curr != prev) {\\n changes++;\\n prev = curr;\\n }\\n }\\n return changes;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。需要遍历字符串一次。
\\n空间复杂度:$O(1)$。
\\n\\n\\nProblem: 100215. 按键变更的次数
\\n
[TOC]
\\n全转大写,然后遍历比较计数。
\\n执行用时分布41ms击败100.00%;消耗内存分布16.37MB击败100.00%
\\n###Python3
\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(x != y for x, y in pairwise(s.upper()))\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100215. 按键变更的次数 [TOC]\\n\\n全转大写,然后遍历比较计数。\\n\\n执行用时分布41ms击败100.00%;消耗内存分布16.37MB击败100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(x != y for x, y in pairwise(s.upper()))\\n\\n\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^\\n\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者…","guid":"https://leetcode.cn/problems/number-of-changing-keys//solution/bian-li-bi-jiao-ji-shu-pythonyi-xing-shu-dqph","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-28T04:43:20.796Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"位运算简洁写法(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/number-of-changing-keys//solution/wei-yun-suan-jian-ji-xie-fa-pythonjavacg-h7rz","content":"本题可以用 $\\\\texttt{lower}$ 这样的库函数解决,也可以用如下的位运算方法。
\\n对于同一字母的大写和小写,ASCII 值的二进制的低 $5$ 位是相同的,所以只需统计
\\n$$
\\ns[i-1] & 31 \\\\ne s[i]& 31
\\n$$
的个数。
\\n其中 $& 31$ 表示取二进制的低 $5$ 位。
\\n\\n###py
\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(x != y for x, y in pairwise(s.lower()))\\n
\\n###java
\\nclass Solution {\\n public int countKeyChanges(String s) {\\n int ans = 0;\\n for (int i = 1; i < s.length(); i++) {\\n if ((s.charAt(i - 1) & 31) != (s.charAt(i) & 31)) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int countKeyChanges(string s) {\\n int ans = 0;\\n for (int i = 1; i < s.length(); i++) {\\n ans += (s[i - 1] & 31) != (s[i] & 31);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc countKeyChanges(s string) (ans int) {\\nfor i := 1; i < len(s); i++ {\\nif s[i-1]&31 != s[i]&31 {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本题可以用 $\\\\texttt{lower}$ 这样的库函数解决,也可以用如下的位运算方法。 对于同一字母的大写和小写,ASCII 值的二进制的低 $5$ 位是相同的,所以只需统计\\n\\n$$\\n s[i-1] & 31 \\\\ne s[i]& 31\\n $$\\n\\n的个数。\\n\\n其中 $& 31$ 表示取二进制的低 $5$ 位。\\n\\n扩展阅读:从集合论到位运算,常见位运算技巧分类总结!\\n\\n###py\\n\\nclass Solution:\\n def countKeyChanges(self, s: str) -> int:\\n return sum(x != y for x…","guid":"https://leetcode.cn/problems/number-of-changing-keys//solution/wei-yun-suan-jian-ji-xie-fa-pythonjavacg-h7rz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-28T04:29:23.484Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最强解法:O(1)空间,O(n)时间,代码简洁","url":"https://leetcode.cn/problems/maximum-or//solution/zui-qiang-jie-fa-o1kong-jian-onshi-jian-9c835","content":"首先,k次操作全部放到一个数上才能让结果最大,这个贪心还是比较好想的。
\\n那么问题就转化为了:如何快速求除了一个数以外所有数的或值?
\\n这里我们得从或运算的本质下手:当某一位上为1的数的数量大于等于1时,这一位就为1。因此我们可以统计每一位上为1的数的数量,在去除某个数时,将该数为1的每一位减去1,再还原出总或值,这样就可以快速获得答案了。
\\n###Python3
\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int:\\n # 每一位上为1的数的数量\\n cnt = [0] * 30\\n for x in nums:\\n i = 0\\n while x:\\n if x & 1:\\n cnt[i] += 1\\n i += 1\\n x >>= 1\\n\\n p = 1 << max(nums).bit_length() - 1\\n ans = 0\\n for x in nums:\\n if x >= p:\\n # 减去该数\\n cnt2 = cnt.copy()\\n mask = x << k\\n i = 0\\n while x:\\n if x & 1:\\n cnt2[i] -= 1\\n i += 1\\n x >>= 1\\n # 还原总或值\\n y = 0\\n for bit in reversed(cnt2):\\n y <<= 1\\n if bit:\\n y += 1\\n ans = max(ans, y | mask)\\n \\n return ans\\n
\\n上面这种做法还有很大的进步空间。注意某一位只要大于等于1,那一位就为1,而我们最多去除一个数,这意味着什么?只要某一位上为1的数的数量大于1,那么不管去除哪个数,这一位总是为1。
\\n因此实际上只有2种情况,某一位计数为1,或者大于1。只有我们去除的数正好改变了计数为1的位,才会将其变为0。
\\n具体实现方法有很多种,这里我使用异或移除所有1,再将恒定为1的部分补上,没被补上的自然就是被真正去除的1。
\\n###Python3
\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int:\\n # v2是某一位有多个数都是1的情况\\n v = v2 = 0\\n for x in nums:\\n v2 |= v & x\\n v |= x\\n return max(x<<k | v^x | v2 for x in nums)\\n
\\nPS: Python的运算顺序为:加减、位移、位与、异或、位或,因此有些地方不用写括号
\\n最后还有一步优化,可以让代码稳定击败100%。这是一个特殊情况的判断:假如总或值最高位以后的每一位都是1,并且每一位都有1个以上的数为1,那就说明不管你移动哪个数,都不会将某一位变为0,此时直接移动最大值即可。
\\n###Python3
\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int:\\n # v2是某一位有多个数都是1的情况\\n v = v2 = 0\\n for x in nums:\\n v2 |= v & x\\n v |= x\\n\\n # 所有位都有多个数为1,并且每一位都是1,那么位移最大数即可\\n if v2 == v and (v+1 & v) == 0:\\n return max(nums)<<k | v\\n\\n return max(x<<k | x^v | v2 for x in nums)\\n\\n
\\n","description":"首先,k次操作全部放到一个数上才能让结果最大,这个贪心还是比较好想的。 那么问题就转化为了:如何快速求除了一个数以外所有数的或值?\\n\\n这里我们得从或运算的本质下手:当某一位上为1的数的数量大于等于1时,这一位就为1。因此我们可以统计每一位上为1的数的数量,在去除某个数时,将该数为1的每一位减去1,再还原出总或值,这样就可以快速获得答案了。\\n\\n初版做法,特别拉跨\\n\\n###Python3\\n\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int…","guid":"https://leetcode.cn/problems/maximum-or//solution/zui-qiang-jie-fa-o1kong-jian-onshi-jian-9c835","author":"i2everent-moser9x1","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-25T04:27:28.179Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2595. 奇偶位数","url":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/2595-qi-ou-wei-shu-by-stormsunshine-3c0c","content":"最直观的思路是遍历正整数 $n$ 的二进制表示的从低到高的每一位,对于每个 $1$ 所在位,根据其所在下标的奇偶性计算位数,遍历结束之后即可得到答案。
\\n###Java
\\nclass Solution {\\n public int[] evenOddBit(int n) {\\n int[] answer = new int[2];\\n int index = 0;\\n while (n != 0) {\\n answer[index % 2] += n % 2;\\n n /= 2;\\n index++;\\n }\\n return answer;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int[] EvenOddBit(int n) {\\n int[] answer = new int[2];\\n int index = 0;\\n while (n != 0) {\\n answer[index % 2] += n % 2;\\n n /= 2;\\n index++;\\n }\\n return answer;\\n }\\n}\\n
\\n时间复杂度:$O(\\\\log n)$,其中 $n$ 是给定的正整数。正整数 $n$ 的二进制表示的位数是 $O(\\\\log n)$,需要遍历二进制表示的每一位。
\\n空间复杂度:$O(1)$。
\\n为了得到正整数 $n$ 的二进制表示的值为 $1$ 的偶数下标个数和奇数下标个数,可以根据正整数 $n$ 生成两个正整数,第一个正整数保留正整数 $n$ 的偶数下标不变并将奇数下标全部置为 $0$,第二个正整数保留正整数 $n$ 的奇数下标不变并将偶数下标全部置为 $0$,然后分别计算两个正整数的二进制表示的 $1$ 的个数即可得到答案。计算两个正整数的方法如下:第一个正整数等于 $n ~&~ 55555555_{(16)}$,第二个正整数等于 $n ~&~ \\\\text{aaaaaaaa}_{(16)}$。
\\n计算一个整数 $m$ 的二进制表示的 $1$ 的个数的方法有多种,此处介绍一种基于分治的解法。
\\n将整数 $m$ 看成二进制位 $1$ 的个数的表示形式,初始时 $m$ 分成 $32$ 组,每组有 $1$ 位,表示该组二进制位 $1$ 的个数。当 $m$ 的分组个数大于 $1$ 时,每次将 $m$ 的相邻两组合并,合并后每组中的数表示该组二进制位 $1$ 的个数,直到 $m$ 的分组个数等于 $1$ 时结束。分组情况具体变化如下。
\\n$16$ 组,每组有 $2$ 位。
\\n$8$ 组,每组有 $4$ 位。
\\n$4$ 组,每组有 $8$ 位。
\\n$2$ 组,每组有 $16$ 位。
\\n$1$ 组,每组有 $32$ 位。
\\n由于 $k$ 位二进制数可以表示的最大值是 $2^k - 1$,对于任意正整数 $k$ 都有 $k \\\\le 2^k - 1$,因此可以使用 $k$ 位的分组表示 $k$ 位中位 $1$ 的个数。
\\n将 $1$ 位分组合并成 $2$ 位分组时,由于 $2$ 位二进制数的可能取值有 $00$、$01$、$10$、$11$,对应的位 $1$ 个数的二进制表示分别是 $00$、$01$、$01$、$10$,因此 $2$ 位二进制数 $x$ 的位 $1$ 个数是 $x - (x >> 1)$。对于整数 $m$,在计算 $m - (m >> 1)$ 时,为了避免跨组运算对结果的影响,每组 $2$ 位二进制数只保留低位,因此需要将 $m >> 1$ 和 $55555555_{(16)}$(十六进制数)做按位与运算。合并之后,$m$ 的值变成 $m - ((m >> 1) ~&~ 55555555_{(16)})$。
\\n将 $2$ 位分组合并成 $4$ 位分组时,需要将每对相邻的 $2$ 位分组的值相加得到 $4$ 位分组的值。对于需要合并的相邻两对 $2$ 位分组,将 $4$ 位二进制数记为 $x$,则合并后的 $4$ 位分组是 $x$ 的最低 $2$ 位与 $x$ 的最高 $2$ 位之和,因此合并后的 $4$ 位分组是 $(x ~&~ 3) + ((x >> 2) ~&~ 3)$。合并之后,$m$ 的值变成 $(m ~&~ 33333333_{(16)}) + ((m >> 2) ~&~ 33333333_{(16)})$。
\\n后续合并过程相似。
\\n将 $4$ 位分组合并成 $8$ 位分组之后,$m$ 的值变成 $(m + (m >> 4)) ~&~ \\\\text{0f0f0f0f}_{(16)}$。
\\n将 $8$ 位分组合并成 $16$ 位分组之后,$m$ 的值变成 $(m + (m >> 8)) ~&~ \\\\text{00ff00ff}_{(16)}$。
\\n将 $16$ 位分组合并成 $32$ 位分组之后,$m$ 的值变成 $(m + (m >> 16)) ~&~ \\\\text{0000ffff}_{(16)}$。
\\n此时 $m$ 的分组个数等于 $1$,$m$ 即为答案。
\\nJava 的 $\\\\texttt{Imteger.bitCount}$ 的实现原理即为上述计算方式,只是细节有所不同。Java 的 $\\\\texttt{Imteger.bitCount}$ 的实现中,将 $8$ 位分组合并成 $16$ 位分组以及将 $16$ 位分组合并成 $32$ 位分组的过程中没有按位与运算的操作,而是在最后计算 $m ~&~ \\\\text{3f}_{(16)}$,该结果和上述计算方式是等价的。由于原始的 $m$ 的位 $1$ 的个数最多是 $32$,即不会超过 $6$ 位二进制数,因此这两步合并过程的按位与运算可以用最后一步按位与运算代替。
\\n上述计算方式对于 $m$ 是正整数、$0$ 和负整数的情况都适用。当 $m$ 可能是负整数时,由于 Java 没有无符号整数类型,因此在实现方面,上述右移运算应使用逻辑右移。
\\n对于 $k$ 位二进制数,$k = O(\\\\log m)$,分治解法的分组合并操作共需要 $O(\\\\log k)$ 次,每次分组合并操作的时间都是 $O(1)$,因此分治解法的时间复杂度是 $O(\\\\log k) = O(\\\\log \\\\log m)$。
\\n###Java
\\nclass Solution {\\n static final int EVEN_MASK = 0x55555555, ODD_MASK = 0xaaaaaaaa;\\n\\n public int[] evenOddBit(int n) {\\n return new int[]{bitCount(n & EVEN_MASK), bitCount(n & ODD_MASK)};\\n }\\n\\n public int bitCount(int m) {\\n m = m - ((m >>> 1) & 0x55555555);\\n m = (m & 0x33333333) + ((m >>> 2) & 0x33333333);\\n m = (m + (m >>> 4)) & 0x0f0f0f0f;\\n m = (m + (m >>> 8)) & 0x00ff00ff;\\n m = (m + (m >>> 16)) & 0x0000ffff;\\n return m;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n const uint EVEN_MASK = 0x55555555, ODD_MASK = 0xaaaaaaaa;\\n\\n public int[] EvenOddBit(int n) {\\n return new int[]{BitCount((uint) n & EVEN_MASK), BitCount((uint) n & ODD_MASK)};\\n }\\n\\n public int BitCount(uint m) {\\n m = m - ((m >> 1) & 0x55555555);\\n m = (m & 0x33333333) + ((m >> 2) & 0x33333333);\\n m = (m + (m >> 4)) & 0x0f0f0f0f;\\n m = (m + (m >> 8)) & 0x00ff00ff;\\n m = (m + (m >> 16)) & 0x0000ffff;\\n return (int) m;\\n }\\n}\\n
\\n时间复杂度:$O(\\\\log \\\\log n)$,其中 $n$ 是给定的正整数。对于 $k$ 位二进制数,$k = O(\\\\log n)$,分治解法的分组合并操作次数是 $O(\\\\log k)$,每次分组合并操作的时间都是 $O(1)$,因此分治解法的时间复杂度是 $O(\\\\log k) = O(\\\\log \\\\log n)$。
\\n空间复杂度:$O(1)$。
\\n\\n\\nProblem: 2999. 统计强大整数的数目
\\n
[toc]
\\n可以将题目理解为:把 $s$ 看成一个数的后缀,在它的前面加上一个前缀,使得新的数在 $[start,finish]$ 之中,并且数的每一位数都要小于等于 $limit$。显然,加的这个前缀应当有个范围:$[low,upper]$。
\\n每一位数都要小于等于 $limit$,可以想到这是一个 $limit+1$进制数(后面就叫它 $li$ 进制数)。
\\n那所要求的答案便是,将 $low$ 和 $upper$ 看成是 $li$进制数之后,二者的差加 $1$(因为是闭区间)。
那接下来就是怎么求 $low$ 和 $upper$ 了。
\\n先求 $low$ 。
\\n注意:虽然都叫 $low$,但现在开始讨论的 $low$ 是带后缀的,结束之后去掉后缀得到前面说的那个 “前缀$low$”。后面 $upper$ 也是。
\\n注意2:题目给的 $s$ 是字符串,但有的时候我就直接当整型啦~
下界 $low$ 要符合一些要求:
\\n由 $1$ 和 $2$ 可以将 $low$ 初始值设置为 $max(start,s)$。
\\n再看每一位的值。
\\n假设现在的 $low$ 为 $\\\\underline{a} \\\\space \\\\underline{b} \\\\space \\\\underline{c} \\\\space \\\\underline{{\\\\color{Red} r} } \\\\space \\\\underline{x} \\\\space \\\\underline{y} \\\\space \\\\underline{z} $,某一位上的值是 $r$ 且 $r \\\\gt limit$,那么比这 $low$ 大的(要找下界肯定不能往小的方向去考虑吧,往小的方向考虑说不定就比 $start$ 还小了呢。同样地,上界就应当往小的方向考虑)又要保证每一位数都 $\\\\le limit$ 的最小的数是多少呢?那应该是 $ \\\\underline{a} \\\\space \\\\underline{b} \\\\space \\\\underline{c+1} \\\\space \\\\underline{{\\\\color{Red} 0} } \\\\space \\\\underline{0} \\\\space \\\\underline{0} \\\\space \\\\underline{0} $ 吧,也就是向前进位一位,同时将低位的值都变为 $0$。
因为要往前进位一位,而进位之后可能也会超过 $limit$,所以要从低位开始遍历。
\\n还有一件事情,要提前做。
\\n现在这个 $low$ 的后缀,如果比 $s$ 要大,那它的前缀部分也应当要加 $1$ 后缀部分全部变为 $0$,也就是和前面那样的操作。
\\n假设现在 $low$ 为 $ \\\\underline{low \\\\underline{\\\\space}pre} \\\\space \\\\underline{low\\\\underline{\\\\space}suf} $,其中 $ low\\\\underline{\\\\space}suf \\\\gt s$,那么,为了找到后缀是 $s$ 的数,又不能往小了考虑,就要变大 $low$,让 $low$ 变成 $ \\\\underline{low \\\\underline{\\\\space}pre+1} \\\\space \\\\underline{s} $。为了方便以及和前统一,也就将后缀每一位变为 $0$ ,这不影响。
现在得到了带后缀的下界 $low$,将后缀“去掉”,就得到了前面所说的“前缀范围”的那个 $low$了。
\\n再说 $upper$ .大思路和求 $low$ 差不多。
\\n上界 $upper$ 要符合一些要求:
\\n由 $1$ 可以将 $upper$ 初始值设置为 $finish$。同时,可以得到两个特判:当 $s \\\\gt finish$ 的时候,可以直接 return 0
了,因为不会有在 $[start,finish]$ 的数;当 $s == finish$ 的时候,可以直接 return 1
。
再看每一位的值。
\\n假设现在的 $upper$ 为 $\\\\underline{a} \\\\space \\\\underline{b} \\\\space \\\\underline{c} \\\\space \\\\underline{{\\\\color{Red} r} } \\\\space \\\\underline{x} \\\\space \\\\underline{y} \\\\space \\\\underline{z} $,某一位上的值是 $r$ 且 $r \\\\gt limit$,那么比这 $upper$ 小的(为什么是小前面有说哦)又要保证每一位数都 $\\\\lt limit$ 的最大的数是多少呢?那应该是 $ \\\\underline{a} \\\\space \\\\underline{b} \\\\space \\\\underline{c} \\\\space \\\\underline{{\\\\color{Red} limit} } \\\\space \\\\underline{limit} \\\\space \\\\underline{limit} \\\\space \\\\underline{limit} $ 吧,也就是从当前位开始往低位全部变为 $limit$。
因为要修改低位的值,所以选择从高位开始遍历。
\\n提前要做的事情。
\\n假设现在的 $upper$ 是 $ \\\\underline{upper\\\\underline{\\\\space}pre} \\\\space \\\\underline{upper\\\\underline{\\\\space}suf} $,其中 $ upper\\\\underline{\\\\space}suf \\\\lt s$,这样的 $upper$ 是大了的,要变成 $ \\\\underline{upper\\\\underline{\\\\space}pre-1} \\\\space \\\\underline{upper\\\\underline{\\\\space}suf} $ 才行。
现在得到了带后缀的上界 $upper$,将后缀“去掉”,就得到了开始所说的“前缀范围”的那个 $upper$ 了。
\\n###Python3
\\nclass Solution:\\n def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:\\n s_int = int(s)\\n if s_int >= finish: # s 本身就比 finish 大,那在 [start,finish] 里就没有强数了,但如果 s==finish,还能有一个\\n return int(s_int == finish)\\n\\n def int_len(x: int) -> int:\\n # 计算 整数 x 的长度\\n ans = 0\\n while x:\\n ans += 1\\n x //= 10\\n return ans\\n\\n s_len = len(s)\\n sk = 10 ** s_len # 经常用到\\n\\n low = max(s_int, start)\\n if s_int < start % sk:\\n # 向前进位,后面全减少为0\\n low += sk - start % sk\\n # 每一位都要比 limit 小或等于,哪一位大了往前进位,再将这一位及其往后的变为 0\\n for k in range(s_len, int_len(low)):\\n kk = pow(10, k)\\n r = low // kk % 10 # 当前这一位的值\\n if r > limit:\\n low += (10 - r) * kk # 当前位往前进 1 # low += kk * 10\\n low = low // kk * kk # 将后面全部变成 0\\n low //= sk\\n\\n upper = finish - (10 ** s_len if s_int > finish % sk else 0)\\n # 每一位都要比 limit 小或等于,哪一位大了就把这一位及其往后都变成 limit\\n flag = False\\n for k in range(int_len(upper), s_len - 1, -1):\\n kk = pow(10, k)\\n r = upper // kk % 10 # 当前这一位的值\\n if flag or r > limit: # 当前这一位变为 limit\\n upper -= (r - limit) * kk\\n flag = True\\n upper //= sk\\n\\n def f(x: int) -> int:\\n li = limit + 1\\n # 将 li进制 数转为十进制数\\n ans = 0\\n k = 1\\n while x:\\n ans += x % 10 * k\\n k *= li\\n x //= 10\\n return ans\\n\\n return f(upper) - f(low) + 1\\n\\n
\\n时间复杂度:$O( \\\\log_{10}{start} + \\\\log_{10}{finish})$ ,时间复杂度与 $start$ 和 $finish$ 的数位长度有关。
\\n空间复杂度:$O(1)$,只有一些额外变量。
\\n\\nProblem: 2942. 查找包含给定字符的单词
\\n
[TOC]
\\n\\n\\n单层for循环判断每个字符串中是否含有某个字符
\\n
\\n\\n先定义一个正则表达式,然后利用Sting中的matches方法进行判断,存在则add到list中
\\n
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Java
\\nclass Solution {\\n public List<Integer> findWordsContaining(String[] words, char x) {\\n List list = new ArrayList();\\n String pattern = \\".*\\" + x + \\".*\\";\\n for(int i =0;i < words.length;i++){\\n if(words[i].matches(pattern)){\\n list.add(i);\\n }\\n }\\n\\n return list;\\n }\\n}\\n
\\n","description":"Problem: 2942. 查找包含给定字符的单词 [TOC]\\n\\n单层for循环判断每个字符串中是否含有某个字符\\n\\n先定义一个正则表达式,然后利用Sting中的matches方法进行判断,存在则add到list中\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n空间复杂度:\\n\\n添加空间复杂度, 示例: $O(n)$\\n\\n###Java\\n\\nclass Solution {\\n public List定义 $\\\\textit{dfs}(i,\\\\textit{limitLow},\\\\textit{limitHigh})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:
\\n枚举第 $i$ 位填什么数字。
\\n如果 $i< n - |s|$,那么可以填 $[\\\\textit{lo}, \\\\min(\\\\textit{hi}, \\\\textit{limit})]$ 内的数,否则只能填 $s[i-(n-|s|)]$。这里 $|s|$ 表示 $s$ 的长度。
\\n为什么不能把 $\\\\textit{hi}$ 置为 $\\\\min(\\\\textit{hi}, \\\\textit{limit})$?请看 视频 中举的反例。
\\n递归终点:$\\\\textit{dfs}(n,,)=1$,表示成功构造出一个合法数字。
\\n递归入口:$\\\\textit{dfs}(0, \\\\texttt{true}, \\\\texttt{true})$,表示:
\\n问:记忆化三个状态有点麻烦,能不能只记忆化 $i$ 这个状态?
\\n答:是可以的。比如 $\\\\textit{finish}=234$,第一位填 $2$,第二位填 $3$,后面无论怎么递归,都不会再次递归到第一位填 $2$,第二位填 $3$ 的情况,所以不需要记录。(注:想象我们在写一个三重循环,枚举每一位填什么数字。第一位填 $2$,第二位填 $3$ 已经是快要结束循环的情况了,不可能再次枚举到。)对于 $\\\\textit{start}$ 也同理。
\\n根据这个例子,我们可以只记录不受到 $\\\\textit{limitLow}$ 或 $\\\\textit{limitHigh}$ 约束时的状态 $i$。相当于记忆化的是 $(i,\\\\texttt{false},\\\\texttt{false})$ 这个状态,因为其它状态只会递归访问一次。
\\n###py
\\nclass Solution:\\n def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:\\n high = list(map(int, str(finish))) # 避免在 dfs 中频繁调用 int()\\n n = len(high)\\n low = list(map(int, str(start).zfill(n))) # 补前导零,和 high 对齐\\n diff = n - len(s)\\n\\n @cache\\n def dfs(i: int, limit_low: bool, limit_high: bool) -> int:\\n if i == n:\\n return 1\\n\\n # 第 i 个数位可以从 lo 枚举到 hi\\n # 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi\\n lo = low[i] if limit_low else 0\\n hi = high[i] if limit_high else 9\\n\\n res = 0\\n if i < diff: # 枚举这个数位填什么\\n for d in range(lo, min(hi, limit) + 1):\\n res += dfs(i + 1, limit_low and d == lo, limit_high and d == hi)\\n else: # 这个数位只能填 s[i-diff]\\n x = int(s[i - diff])\\n if lo <= x <= hi: # 题目保证 x <= limit,无需判断\\n res = dfs(i + 1, limit_low and x == lo, limit_high and x == hi)\\n return res\\n\\n return dfs(0, True, True)\\n
\\n###java
\\nclass Solution {\\n public long numberOfPowerfulInt(long start, long finish, int limit, String s) {\\n String low = Long.toString(start);\\n String high = Long.toString(finish);\\n int n = high.length();\\n low = \\"0\\".repeat(n - low.length()) + low; // 补前导零,和 high 对齐\\n long[] memo = new long[n];\\n Arrays.fill(memo, -1);\\n return dfs(0, true, true, low.toCharArray(), high.toCharArray(), limit, s.toCharArray(), memo);\\n }\\n\\n private long dfs(int i, boolean limitLow, boolean limitHigh, char[] low, char[] high, int limit, char[] s, long[] memo) {\\n if (i == high.length) {\\n return 1;\\n }\\n\\n if (!limitLow && !limitHigh && memo[i] != -1) {\\n return memo[i]; // 之前计算过\\n }\\n\\n // 第 i 个数位可以从 lo 枚举到 hi\\n // 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi\\n int lo = limitLow ? low[i] - \'0\' : 0;\\n int hi = limitHigh ? high[i] - \'0\' : 9;\\n\\n long res = 0;\\n if (i < high.length - s.length) { // 枚举这个数位填什么\\n for (int d = lo; d <= Math.min(hi, limit); d++) {\\n res += dfs(i + 1, limitLow && d == lo, limitHigh && d == hi, low, high, limit, s, memo);\\n }\\n } else { // 这个数位只能填 s[i-diff]\\n int x = s[i - (high.length - s.length)] - \'0\';\\n if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断\\n res = dfs(i + 1, limitLow && x == lo, limitHigh && x == hi, low, high, limit, s, memo);\\n }\\n }\\n\\n if (!limitLow && !limitHigh) {\\n memo[i] = res; // 记忆化 (i,false,false)\\n }\\n return res;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) {\\n string low = to_string(start);\\n string high = to_string(finish);\\n int n = high.size();\\n low = string(n - low.size(), \'0\') + low; // 补前导零,和 high 对齐\\n int diff = n - s.size();\\n\\n vector<long long> memo(n, -1);\\n auto dfs = [&](this auto&& dfs, int i, bool limit_low, bool limit_high) -> long long {\\n if (i == low.size()) {\\n return 1;\\n }\\n\\n if (!limit_low && !limit_high && memo[i] != -1) {\\n return memo[i]; // 之前计算过\\n }\\n\\n // 第 i 个数位可以从 lo 枚举到 hi\\n // 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi\\n int lo = limit_low ? low[i] - \'0\' : 0;\\n int hi = limit_high ? high[i] - \'0\' : 9;\\n\\n long long res = 0;\\n if (i < diff) { // 枚举这个数位填什么\\n for (int d = lo; d <= min(hi, limit); d++) {\\n res += dfs(i + 1, limit_low && d == lo, limit_high && d == hi);\\n }\\n } else { // 这个数位只能填 s[i-diff]\\n int x = s[i - diff] - \'0\';\\n if (lo <= x && x <= hi) { // 题目保证 x <= limit,无需判断\\n res = dfs(i + 1, limit_low && x == lo, limit_high && x == hi);\\n }\\n }\\n\\n if (!limit_low && !limit_high) {\\n memo[i] = res; // 记忆化 (i,false,false)\\n }\\n return res;\\n };\\n return dfs(0, true, true);\\n }\\n};\\n
\\n###go
\\nfunc numberOfPowerfulInt(start, finish int64, limit int, s string) int64 {\\nlow := strconv.FormatInt(start, 10)\\nhigh := strconv.FormatInt(finish, 10)\\nn := len(high)\\nlow = strings.Repeat(\\"0\\", n-len(low)) + low // 补前导零,和 high 对齐\\ndiff := n - len(s)\\n\\nmemo := make([]int64, n)\\nfor i := range memo {\\nmemo[i] = -1\\n}\\nvar dfs func(int, bool, bool) int64\\ndfs = func(i int, limitLow, limitHigh bool) (res int64) {\\nif i == n {\\nreturn 1\\n}\\n\\nif !limitLow && !limitHigh {\\np := &memo[i]\\nif *p >= 0 {\\nreturn *p\\n}\\ndefer func() { *p = res }()\\n}\\n\\n// 第 i 个数位可以从 lo 枚举到 hi\\n// 如果对数位还有其它约束,应当只在下面的 for 循环做限制,不应修改 lo 或 hi\\nlo := 0\\nif limitLow {\\nlo = int(low[i] - \'0\')\\n}\\nhi := 9\\nif limitHigh {\\nhi = int(high[i] - \'0\')\\n}\\n\\nif i < diff { // 枚举这个数位填什么\\nfor d := lo; d <= min(hi, limit); d++ {\\nres += dfs(i+1, limitLow && d == lo, limitHigh && d == hi)\\n}\\n} else { // 这个数位只能填 s[i-diff]\\nx := int(s[i-diff] - \'0\')\\nif lo <= x && x <= hi { // 题目保证 x <= limit,无需判断\\nres += dfs(i+1, limitLow && x == lo, limitHigh && x == hi)\\n}\\n}\\nreturn\\n}\\nreturn dfs(0, true, true)\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"v1.0 视频讲解 v2.0 视频讲解\\n\\n定义 $\\\\textit{dfs}(i,\\\\textit{limitLow},\\\\textit{limitHigh})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:\\n\\n$\\\\textit{limitHigh}$ 表示当前是否受到了 $\\\\textit{finish}$ 的约束(我们要构造的数字不能超过 $\\\\textit{finish}$)。若为真,则第 $i$ 位填入的数字至多为 $\\\\textit{finish}[i]$,否则至多为 $9$,这个数记作 $\\\\textit{hi…","guid":"https://leetcode.cn/problems/count-the-number-of-powerful-integers//solution/shu-wei-dp-shang-xia-jie-mo-ban-fu-ti-da-h6ci","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-07T13:02:39.062Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/mei-ju-by-tsreaper-z7w7","content":"首先注意到答案至多为 $2$。因为
\\n因此我们只要判断车和象能否一步吃到皇后即可。我们枚举车或象走的方向,再枚举走的步数,看能否在离开棋盘或者撞到其它棋子之前吃到皇后。
\\n复杂度 $\\\\mathcal{O}(n + m)$,其中 $n$ 和 $m$ 是棋盘的长宽。
\\n###c++
\\nclass Solution {\\npublic:\\n int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n short dir[8][2] = {0, 1, 1, 0, -1, 0, 0, -1, 1, 1, -1, -1, 1, -1, -1, 1};\\n\\n // 枚举车的方向和步数\\n for (int k = 0; k < 4; k++) for (int w = 1; w <= 8; w++) {\\n int i = a + dir[k][0] * w, j = b + dir[k][1] * w;\\n // 走出棋盘了,这个方向不行\\n if (i <= 0 || j <= 0 || i > 8 || j > 8) break;\\n // 撞到象,这个方向不行\\n if (i == c && j == d) break;\\n // 吃到皇后\\n if (i == e && j == f) return 1;\\n }\\n\\n // 枚举象的方向和步数\\n for (int k = 4; k < 8; k++) for (int w = 1; w <= 8; w++) {\\n int i = c + dir[k][0] * w, j = d + dir[k][1] * w;\\n // 走出棋盘了,这个方向不行\\n if (i <= 0 || j <= 0 || i > 8 || j > 8) break;\\n // 撞到车,这个方向不行\\n if (i == a && j == b) break;\\n // 吃到皇后\\n if (i == e && j == f) return 1;\\n }\\n\\n return 2;\\n }\\n};\\n
\\n","description":"解法:枚举 首先注意到答案至多为 $2$。因为\\n\\n当车和皇后既不在同一行又不在同一列时,车有至少两种走法吃到皇后(先走行再走列,或者先走列再走行),由于象只有一个,因此肯定有一种走法不会撞到象。\\n当车和皇后在同一行或同一列时,就算它们之间有象,也可以先用一步把象移走,第二步就可以用车吃到皇后。\\n\\n因此我们只要判断车和象能否一步吃到皇后即可。我们枚举车或象走的方向,再枚举走的步数,看能否在离开棋盘或者撞到其它棋子之前吃到皇后。\\n\\n复杂度 $\\\\mathcal{O}(n + m)$,其中 $n$ 和 $m$ 是棋盘的长宽。\\n\\n参考代码(c++)\\n\\n###c++\\n\\ncl…","guid":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/mei-ju-by-tsreaper-z7w7","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-07T06:22:20.257Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分类讨论+简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/fen-lei-tao-lun-jian-ji-xie-fa-pythonjav-aoa8","content":"分类讨论:
\\n判断能否直接攻击到:
\\n###py
\\nclass Solution:\\n def minMovesToCaptureTheQueen(self, a: int, b: int, c: int, d: int, e: int, f: int) -> int:\\n # m 在 l 和 r 之间(写不写等号都可以)\\n def in_between(l: int, m: int, r: int) -> bool:\\n return min(l, r) <= m <= max(l, r)\\n\\n # 车直接攻击到皇后 or 象直接攻击到皇后\\n if a == e and (c != e or not in_between(b, d, f)) or \\\\\\n b == f and (d != f or not in_between(a, c, e)) or \\\\\\n c + d == e + f and (a + b != e + f or not in_between(c, a, e)) or \\\\\\n c - d == e - f and (a - b != e - f or not in_between(c, a, e)):\\n return 1\\n return 2\\n
\\n###java
\\nclass Solution {\\n public int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n if (a == e && (c != e || !inBetween(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b == f && (d != f || !inBetween(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c + d == e + f && (a + b != e + f || !inBetween(c, a, e)) || // 象直接攻击到皇后\\n c - d == e - f && (a - b != e - f || !inBetween(c, a, e))) {\\n return 1;\\n }\\n return 2;\\n }\\n\\n // m 在 l 和 r 之间(写不写等号都可以)\\n private boolean inBetween(int l, int m, int r) {\\n return Math.min(l, r) < m && m < Math.max(l, r);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n // m 在 l 和 r 之间(写不写等号都可以)\\n bool in_between(int l, int m, int r) {\\n return min(l, r) < m && m < max(l, r);\\n }\\n\\npublic:\\n int minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n if (a == e && (c != e || !in_between(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b == f && (d != f || !in_between(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c + d == e + f && (a + b != e + f || !in_between(c, a, e)) || // 象直接攻击到皇后\\n c - d == e - f && (a - b != e - f || !in_between(c, a, e))) {\\n return 1;\\n }\\n return 2;\\n }\\n};\\n
\\n###c
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\n// m 在 l 和 r 之间(写不写等号都可以)\\nint inBetween(int l, int m, int r) {\\n return MIN(l, r) < m && m < MAX(l, r);\\n}\\n\\nint minMovesToCaptureTheQueen(int a, int b, int c, int d, int e, int f) {\\n if (a == e && (c != e || !inBetween(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b == f && (d != f || !inBetween(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c + d == e + f && (a + b != e + f || !inBetween(c, a, e)) || // 象直接攻击到皇后\\n c - d == e - f && (a - b != e - f || !inBetween(c, a, e))) {\\n return 1;\\n }\\n return 2;\\n}\\n
\\n###go
\\n// m 在 l 和 r 之间(写不写等号都可以)\\nfunc inBetween(l, m, r int) bool {\\n return min(l, r) < m && m < max(l, r)\\n}\\n\\nfunc minMovesToCaptureTheQueen(a, b, c, d, e, f int) int {\\n if a == e && (c != e || !inBetween(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b == f && (d != f || !inBetween(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c+d == e+f && (a+b != e+f || !inBetween(c, a, e)) || // 象直接攻击到皇后\\n c-d == e-f && (a-b != e-f || !inBetween(c, a, e)) {\\n return 1\\n }\\n return 2\\n}\\n
\\n###js
\\n// m 在 l 和 r 之间(写不写等号都可以)\\nfunction inBetween(l, m, r) {\\n return Math.min(l, r) < m && m < Math.max(l, r);\\n}\\n\\nvar minMovesToCaptureTheQueen = function(a, b, c, d, e, f) {\\n if (a === e && (c !== e || !inBetween(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b === f && (d !== f || !inBetween(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c + d === e + f && (a + b !== e + f || !inBetween(c, a, e)) || // 象直接攻击到皇后\\n c - d === e - f && (a - b !== e - f || !inBetween(c, a, e))) {\\n return 1;\\n }\\n return 2;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_moves_to_capture_the_queen(a: i32, b: i32, c: i32, d: i32, e: i32, f: i32) -> i32 {\\n // m 在 l 和 r 之间(写不写等号都可以)\\n let in_between = |l: i32, m: i32, r: i32| l.min(r) < m && m < l.max(r); \\n\\n if a == e && (c != e || !in_between(b, d, f)) || // 车直接攻击到皇后(同一行)\\n b == f && (d != f || !in_between(a, c, e)) || // 车直接攻击到皇后(同一列)\\n c + d == e + f && (a + b != e + f || !in_between(c, a, e)) || // 象直接攻击到皇后\\n c - d == e - f && (a - b != e - f || !in_between(c, a, e)) {\\n return 1;\\n }\\n 2\\n }\\n}\\n
\\n如果要输出具体移动方案呢?
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分类讨论: 如果车能直接攻击到皇后,答案是 $1$。\\n如果象能直接攻击到皇后,答案是 $1$。\\n其他情况,答案一定是 $2$:\\n如果车能攻击到皇后,但被象挡住,那么移走象,车就可以攻击到皇后,答案是 $2$。小知识:这在国际象棋中叫做「闪击」。\\n如果象能攻击到皇后,但被车挡住,那么移走车,象就可以攻击到皇后,答案是 $2$。\\n如果车不能攻击到皇后,那么车可以水平移动或者垂直移动,其中一种方式必定不会被象挡住,可以攻击到皇后,答案是 $2$。\\n\\n判断能否直接攻击到:\\n\\n对于车,如果和皇后在同一行或者同一列,且中间没有象,那么就可以直接攻击到皇后。…","guid":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/fen-lei-tao-lun-jian-ji-xie-fa-pythonjav-aoa8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-07T04:38:28.988Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"向量点积法判断三点关系","url":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/xiang-liang-dian-ji-fa-pan-duan-shi-fou-n24l1","content":"\\n\\nProblem: 100187. 捕获黑皇后需要的最少移动次数
\\n
###Python3
\\nclass Solution:\\n def minMovesToCaptureTheQueen(self, a: int, b: int, c: int, d: int, e: int, f: int) -> int:\\n #车和后同行且车没有被阻挡\\n if a==e and not(c==e and (d-b)*(d-f)<0):\\n return 1\\n #车和后同列且车没有被阻挡\\n if b==f and not(d==f and (c-a)*(c-e)<0):\\n return 1\\n ##象和后同右斜线\\n if c+d==e+f and not(a+b==e+f and (a-c)*(a-e)<0):\\n return 1\\n ##象和后同左斜线\\n if c-d==e-f and not(a-b==e-f and (a-c)*(a-e)<0):\\n return 1\\n return 2\\n
\\n","description":"Problem: 100187. 捕获黑皇后需要的最少移动次数 ###Python3\\n\\nclass Solution:\\n def minMovesToCaptureTheQueen(self, a: int, b: int, c: int, d: int, e: int, f: int) -> int:\\n #车和后同行且车没有被阻挡\\n if a==e and not(c==e and (d-b)*(d-f)<0):\\n return 1\\n #车和后同列且车没有被阻挡…","guid":"https://leetcode.cn/problems/minimum-moves-to-capture-the-queen//solution/xiang-liang-dian-ji-fa-pan-duan-shi-fou-n24l1","author":"yun-piao-piao-1","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-07T04:26:38.535Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"不用数位DP的计数方法","url":"https://leetcode.cn/problems/count-the-number-of-powerful-integers//solution/bu-yong-shu-wei-dpde-ji-shu-fang-fa-by-f-vjbr","content":"###Python3
\\nclass Solution:\\n def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:\\n x, m = int(s), len(s)\\n\\n def ceil(y: int):\\n sy = str(y)\\n pre, suf = int(\\"0\\" + sy[:-m]), int(sy[-m:])\\n return pre + int(suf >= x)\\n\\n def count(y: int) -> int:\\n n, res = len(str(y)), 0\\n for i, d in enumerate(map(int, str(y))):\\n if limit < d:\\n return res + pow(limit + 1, n - i)\\n res += d * pow(limit + 1, n - i - 1)\\n return res\\n\\n b, a = ceil(finish), ceil(start - 1)\\n return count(b) - count(a)\\n
\\n","description":"###Python3 class Solution:\\n def numberOfPowerfulInt(self, start: int, finish: int, limit: int, s: str) -> int:\\n x, m = int(s), len(s)\\n\\n def ceil(y: int):\\n sy = str(y)\\n pre, suf = int(\\"0\\" + sy[:-m]), int(sy[-m:])\\n return pre + int…","guid":"https://leetcode.cn/problems/count-the-number-of-powerful-integers//solution/bu-yong-shu-wei-dpde-ji-shu-fang-fa-by-f-vjbr","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2024-01-06T16:59:37.658Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"滑窗解决子数组个数的思路和题型总结(2024.1.17更新)","url":"https://leetcode.cn/problems/count-subarrays-with-score-less-than-k//solution/hua-chuang-jie-jue-zi-shu-zu-ge-shu-de-s-h6kt","content":"[TOC]
\\n\\n\\n套用模板,计算答案即可。
\\n
###Python3
\\n ans = 0 \\n left = 0\\n xxx # 为题目准备好统计的变量\\n for right, x in enumerate(nums):\\n # xxx依据题意统计\\n while left <= right and 条件不合法时:\\n y = nums[left]\\n xxx # xxx依据题意改变\\n left += 1\\n # 合法了\\n ans += right-left+1\\n return ans\\n
\\n###Python3
\\n ans = 0\\n left = 0\\n xxx # 为题目准备好统计的变量\\n for right, x in enumerate(nums):\\n # xxx依据题意统计\\n while 条件合法时:\\n y = nums[left]\\n xxx # xxx依据题意改变\\n left += 1\\n # left:right 不合法\\n ans += left \\n return ans\\n
\\n###Python3
\\n def cal(i):\\n ans = 0 \\n left = 0\\n xxx # 为题目准备好统计的变量\\n for right, x in enumerate(nums):\\n # xxx依据题意统计\\n while left <= right and 条件不合法时:\\n y = nums[left]\\n xxx # xxx依据题意改变\\n left += 1\\n # 合法了\\n ans += right-left+1\\n return ans\\n return cal(i) - cal(i-1)\\n
\\n时间复杂度:
\\n\\n\\n$O(n)$
\\n
空间复杂度:
\\n\\n\\n具体题目具体分析
\\n
\\n\\nProblem: 2302. 统计得分小于 K 的子数组数目
\\n
\\n\\nProblem: 2762. 不间断子数组 【SortedList】
\\n
\\n\\nProblem: 713. 乘积小于 K 的子数组
\\n
\\n\\nProblem: LCP 68. 美观的花束
\\n
\\n\\nProblem: 1358. 包含所有三种字符的子字符串数目
\\n
\\n\\nProblem: 2537. 统计好子数组的数目 【计数时候注意等差增加】
\\n
\\n\\nProblem: 2799. 统计完全子数组的数目
\\n
\\n\\nProblem: 2962. 统计最大元素出现至少 K 次的子数组
\\n
\\n\\nProblem: 930. 和相同的二元子数组
\\n
\\n\\nProblem: 1248. 统计「优美子数组」
\\n
\\n\\nProblem: 992. K 个不同整数的子数组 【建议先看】
\\n
###Python3
\\nclass Solution:\\n def countSubarrays(self, nums: List[int], k: int) -> int:\\n ans = 0 \\n left = 0\\n s = 0\\n for right, x in enumerate(nums):\\n s += x\\n while s *(right-left+1) >= k:\\n y = nums[left]\\n s -= y\\n left += 1\\n ans += right-left+1\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def continuousSubarrays(self, nums: List[int]) -> int:\\n from sortedcontainers import SortedList\\n left = 0\\n ans = 0\\n s = SortedList()\\n for right, x in enumerate(nums):\\n s.add(x)\\n while s[-1] - s[0] > 2:\\n s.remove(nums[left])\\n left += 1\\n ans += right-left+1\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:\\n ans = 0\\n left = 0\\n prod = 1\\n for right, x in enumerate(nums):\\n prod *= x\\n while left <= right and prod >= k:\\n prod //= nums[left]\\n left += 1\\n ans += right-left+1\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def beautifulBouquet(self, flowers: List[int], cnt: int) -> int:\\n MOD = 10 ** 9 + 7\\n left = 0\\n ans = 0\\n cnt1 = Counter()\\n for right, x in enumerate(flowers):\\n cnt1[x] += 1\\n while cnt1[x] > cnt:\\n cnt1[flowers[left]] -= 1\\n left += 1\\n ans += right-left+1\\n return ans % MOD\\n
\\n###Python3
\\nclass Solution:\\n def numberOfSubstrings(self, s: str) -> int:\\n cnt = Counter()\\n ans = 0\\n left = 0\\n for right, x in enumerate(s):\\n cnt[x] += 1\\n while len(cnt) == 3:\\n y = s[left]\\n cnt[y] -= 1\\n if cnt[y] == 0:\\n del cnt[y]\\n left += 1\\n\\n ans += left\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def countGood(self, nums: List[int], k: int) -> int:\\n cnt = Counter()\\n s = 0 # 计数一共有多少对\\n left = 0\\n ans = 0\\n for right, x in enumerate(nums):\\n s += cnt[x]\\n cnt[x] += 1\\n while s >= k:\\n y = nums[left]\\n cnt[y] -= 1\\n s -= cnt[y]\\n left += 1\\n ans += left \\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n m = len(set(nums))\\n ans = 0\\n left = 0\\n cnt = Counter()\\n for right, x in enumerate(nums):\\n cnt[x] += 1\\n while len(cnt) == m:\\n cnt[nums[left]] -= 1\\n if cnt[nums[left]] == 0:\\n del cnt[nums[left]]\\n left += 1\\n ans += left\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def countSubarrays(self, nums: List[int], k: int) -> int:\\n mx = max(nums)\\n n = len(nums)\\n left = 0\\n s = 0\\n ans = 0\\n for right, x in enumerate(nums):\\n s += x == mx\\n while s >= k:\\n s -= nums[left] == mx\\n left += 1\\n ans += left\\n return ans\\n
\\n###Python3
\\nclass Solution:\\n def numSubarraysWithSum(self, nums: List[int], goal: int) -> int:\\n def cal(i):\\n s = 0\\n ans = 0\\n left = 0\\n for right, x in enumerate(nums):\\n s += x\\n while s > i and left<= right:\\n s -= nums[left]\\n left += 1\\n ans += right-left + 1\\n return ans \\n return cal(goal) - cal(goal-1)\\n
\\n###Python3
\\nclass Solution:\\n def numberOfSubarrays(self, nums: List[int], k: int) -> int:\\n def cal(i):\\n left = 0\\n s = 0\\n ans = 0\\n for right, x in enumerate(nums):\\n s += x%2\\n while s > i and left <= right:\\n s -= nums[left] % 2\\n left += 1\\n ans += right-left + 1\\n return ans \\n return cal(k)-cal(k-1)\\n
\\n###Python3
\\nclass Solution:\\n def subarraysWithKDistinct(self, nums: List[int], k: int) -> int:\\n def cal(i):\\n cnt = Counter()\\n left = 0\\n ans = 0\\n for right , x in enumerate(nums):\\n cnt[x] += 1\\n while left <= right and len(cnt) > i:\\n cnt[nums[left]] -= 1\\n if cnt[nums[left]] == 0:\\n del cnt[nums[left]]\\n left += 1\\n ans += right - left + 1\\n return ans \\n return cal(k)- cal(k-1)\\n
\\n","description":"本文主要解决在满足单调性的子数组(数组内都是正数)的情况下,求解满足至多、至少、恰好这三种条件的子数组个数的问题。 [TOC]\\n\\n适用范围:子数组(连续),满足单调性(元素都是正数等)\\n碰到求子数组个数的问题,可以以右端点不断出现的思路来思考,从而找到规律。\\n具体分三种情况:\\n第一种题目是 答案在整个数组中间某段区间内才能是合法的 ,一般都是 求至多、小于 ,这种情况下枚举右端点的同时,去看对应的合法的左端点个数,也就是 $right$ 不断加 $1$ 枚举时,每次保证 $[left:right]$ 区间都是合法的…","guid":"https://leetcode.cn/problems/count-subarrays-with-score-less-than-k//solution/hua-chuang-jie-jue-zi-shu-zu-ge-shu-de-s-h6kt","author":"xiao-jiang-ke","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-12-16T10:15:28.409Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2873. 有序三元组中的最大值 I","url":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-i//solution/2873-you-xu-san-yuan-zu-zhong-de-zui-da-h64jk","content":"直观的思路是遍历每个下标三元组 $(i, j, k)$ 计算 $(\\\\textit{nums}[i] - \\\\textit{nums}[j]) \\\\times \\\\textit{nums}[k]$ 的值,并得到最大值。
\\n用 $\\\\textit{maxValue}$ 表示下标三元组的最大值,初始时 $\\\\textit{maxValue} = 0$,遍历过程中如果遇到一个下标三元组的值大于 $\\\\textit{maxValue}$,则用该下标三元组的值更新 $\\\\textit{maxValue}$。遍历结束之后,$\\\\textit{maxValue}$ 即为下标三元组的最大值。
\\n根据题目要求,如果所有下标三元组的值都是负数则返回 $0$。由于 $\\\\textit{maxValue}$ 的初始值是 $0$,因此如果所有下标三元组的值都是负数,则 $\\\\textit{maxValue}$ 的值不会更新,符合题目要求。
\\n###Java
\\nclass Solution {\\n public long maximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int n = nums.length;\\n for (int i = 0; i < n; i++) {\\n for (int j = i + 1; j < n; j++) {\\n for (int k = j + 1; k < n; k++) {\\n long value = (long) (nums[i] - nums[j]) * nums[k];\\n maxValue = Math.max(maxValue, value);\\n }\\n }\\n }\\n return maxValue;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int n = nums.Length;\\n for (int i = 0; i < n; i++) {\\n for (int j = i + 1; j < n; j++) {\\n for (int k = j + 1; k < n; k++) {\\n long value = (long) (nums[i] - nums[j]) * nums[k];\\n maxValue = Math.Max(maxValue, value);\\n }\\n }\\n }\\n return maxValue;\\n }\\n}\\n
\\n时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要使用三层循环遍历所有的下标三元组并计算值。
\\n空间复杂度:$O(1)$。
\\n下标三元组 $(i, j, k)$ 满足 $i < j < k$。当下标 $j$ 确定时,为了使下标三元组的值最大,应使 $\\\\textit{nums}[i]$ 和 $\\\\textit{nums}[k]$ 最大。用 $n$ 表示数组 $\\\\textit{nums}$ 的长度,则 $1 \\\\le j \\\\le n - 2$,当下标 $j$ 确定时,$\\\\textit{nums}[i]$ 应取数组 $\\\\textit{nums}$ 的下标范围 $[0, j - 1]$ 中的最大值,$\\\\textit{nums}[k]$ 应取数组 $\\\\textit{nums}$ 的下标范围 $[j + 1, n - 1]$ 中的最大值,因此需要维护数组 $\\\\textit{nums}$ 的每个前缀与后缀的最大值。
\\n创建长度为 $n$ 的数组 $\\\\textit{leftMax}$ 和 $\\\\textit{rightMax}$,其中 $\\\\textit{leftMax}[i]$ 表示数组 $\\\\textit{nums}$ 的下标范围 $[0, i]$ 中的最大值,$\\\\textit{rightMax}[i]$ 表示数组 $\\\\textit{nums}$ 的下标范围 $[i, n - 1]$ 中的最大值。数组 $\\\\textit{leftMax}$ 和 $\\\\textit{rightMax}$ 的元素值计算如下。
\\n当 $i = 0$ 时,$\\\\textit{leftMax}[i] = \\\\textit{nums}[i]$;当 $i = n - 1$ 时,$\\\\textit{rightMax}[i] = \\\\textit{nums}[i]$。
\\n当 $i > 0$ 时,$\\\\textit{leftMax}[i] = \\\\max(\\\\textit{leftMax}[i - 1], \\\\textit{nums}[i])$;当 $i < n - 1$ 时,$\\\\textit{rightMax}[i] = \\\\max(\\\\textit{rightMax}[i + 1], \\\\textit{nums}[i])$。
\\n计算得到数组 $\\\\textit{leftMax}$ 和 $\\\\textit{rightMax}$ 的值之后,即可计算下标三元组的最大值。对于每个 $1 \\\\le j \\\\le n - 2$,当下标 $j$ 固定时,下标三元组 $(i, j, k)$ 的最大值是 $(\\\\textit{leftMax}[j - 1] - \\\\textit{nums}[j]) \\\\times \\\\textit{rightMax}[j + 1]$。遍历所有可能的下标 $j$ 之后即可得到下标三元组的最大值。
\\n特别地,当所有下标三元组的值都是负数时,返回 $0$。
\\n###Java
\\nclass Solution {\\n public long maximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int n = nums.length;\\n int[] leftMax = new int[n];\\n leftMax[0] = nums[0];\\n for (int i = 1; i < n; i++) {\\n leftMax[i] = Math.max(leftMax[i - 1], nums[i]);\\n }\\n int[] rightMax = new int[n];\\n rightMax[n - 1] = nums[n - 1];\\n for (int i = n - 2; i >= 0; i--) {\\n rightMax[i] = Math.max(rightMax[i + 1], nums[i]);\\n }\\n for (int j = 1; j < n - 1; j++) {\\n long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);\\n maxValue = Math.max(maxValue, value);\\n }\\n return maxValue;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int n = nums.Length;\\n int[] leftMax = new int[n];\\n leftMax[0] = nums[0];\\n for (int i = 1; i < n; i++) {\\n leftMax[i] = Math.Max(leftMax[i - 1], nums[i]);\\n }\\n int[] rightMax = new int[n];\\n rightMax[n - 1] = nums[n - 1];\\n for (int i = n - 2; i >= 0; i--) {\\n rightMax[i] = Math.Max(rightMax[i + 1], nums[i]);\\n }\\n for (int j = 1; j < n - 1; j++) {\\n long value = (long) (leftMax[j - 1] - nums[j]) * (rightMax[j + 1]);\\n maxValue = Math.Max(maxValue, value);\\n }\\n return maxValue;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。计算前缀最大值数组与后缀最大值数组的时间是 $O(n)$,计算三元组最大值的时间是 $O(n)$。
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要创建两个长度为 $n$ 的数组。
\\n下标三元组 $(i, j, k)$ 满足 $i < j < k$。可以从左到右遍历数组 $\\\\textit{nums}$,对于每个下标 $k$ 计算 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 的最大值,得到下标 $k$ 确定时的下标三元组的最大值。
\\n遍历过程中需要维护已经遍历过的元素的最大差值 $\\\\textit{maxDiff}$ 和最大元素值 $\\\\textit{maxNum}$,初始时 $\\\\textit{maxDiff} = \\\\textit{maxNum} = 0$。当遍历到元素 $\\\\textit{num}$ 时,执行如下操作。
\\n将当前元素之前的最大差值 $\\\\textit{maxDiff}$ 作为 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 的最大值,将当前元素 $\\\\textit{num}$ 作为 $\\\\textit{nums}[k]$,计算下标三元组的值 $\\\\textit{maxDiff} \\\\times \\\\textit{num}$,使用该下标三元组的值更新结果。
\\n计算当前元素之前的最大元素值与当前元素值之差 $\\\\textit{maxNum} - \\\\textit{num}$,并更新 $\\\\textit{maxDiff}$ 的值。
\\n使用当前元素值更新最大元素值 $\\\\textit{maxNum}$。
\\n上述操作中,每次计算下标三元组的值时,$\\\\textit{maxDiff}$ 一定是当前元素之前的两个元素 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[j]$ 之差且一定有 $i < j$,因此可以确保得到正确的结果。
\\n###Java
\\nclass Solution {\\n public long maximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int maxDiff = 0;\\n int maxNum = 0;\\n int n = nums.length;\\n for (int i = 0; i < n; i++) {\\n int num = nums[i];\\n maxValue = Math.max(maxValue, (long) maxDiff * num);\\n maxDiff = Math.max(maxDiff, maxNum - num);\\n maxNum = Math.max(maxNum, num);\\n }\\n return maxValue;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaximumTripletValue(int[] nums) {\\n long maxValue = 0;\\n int maxDiff = 0;\\n int maxNum = 0;\\n int n = nums.Length;\\n for (int i = 0; i < n; i++) {\\n int num = nums[i];\\n maxValue = Math.Max(maxValue, (long) maxDiff * num);\\n maxDiff = Math.Max(maxDiff, maxNum - num);\\n maxNum = Math.Max(maxNum, num);\\n }\\n return maxValue;\\n }\\n}\\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要遍历数组一次。
\\n空间复杂度:$O(1)$。
\\n\\n\\nProblem: 2962. 统计最大元素出现至少 K 次的子数组
\\n
[TOC]
\\n参考灵神题解
\\ncntMax
表示$$[left,right]$$闭区间内的最大值数量。right
直到cntMax=k
右移left
直到cntMax<k
\\n
重复前两步,直到right遍历完数组为最后一轮。可以发现left作为累积量可以复用。
\\n
\\n
时间复杂度: 右指针遍历整个数组;左指针也只前进不后退,最多遍历完数组。因此时间复杂度为$O(n)$
\\n空间复杂度: $O(1)$
\\n###Go
\\nfunc countSubarrays(nums []int, k int) (ans int64) {\\nmx := slices.Max(nums)\\ncntMx, left := 0, 0\\nfor _, x := range nums {\\nif x == mx {\\ncntMx++\\n}\\nfor cntMx == k {\\nif nums[left] == mx {\\ncntMx--\\n}\\nleft++\\n}\\nans += int64(left)\\n}\\nreturn\\n}\\n
\\n","description":"Problem: 2962. 统计最大元素出现至少 K 次的子数组 [TOC]\\n\\n参考灵神题解\\n\\n用cntMax表示$$[left,right]$$闭区间内的最大值数量。\\n\\n右移right直到cntMax=k\\n\\n右移left直到cntMaxProblem: 100137. 统计最大元素出现至少 K 次的子数组
\\n\\n[TOC]
\\n首先获取最大元素出现的所有位置
\\n # 获取最大元素出现的所有位置\\n max_num = max(nums)\\n # 哨兵\\n pos = [-1]\\n for i,num in enumerate(nums):\\n if num == max_num:\\n pos.append(i)\\n
\\n然后枚举子数组的左端点l
,根据左端点就能求出最近的右端点的位置r=l+k-1
。
\\n左端点可以向左扩展至上一个左端点的下一个位置,而右端点可以扩展到末尾,因此根据乘法原理:$(pos[l]-pos[l-1])*(n-pos[r])$
###Python3
\\nclass Solution:\\n def countSubarrays(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n max_num = max(nums)\\n \\n pos = [-1]\\n for i,num in enumerate(nums):\\n if num == max_num:\\n pos.append(i)\\n \\n # 滑动窗口\\n l,r = 1,k\\n \\n res = 0\\n while r < len(pos):\\n # 乘法原理\\n res += (pos[l]-pos[l-1]) * (n-pos[r])\\n l += 1\\n r += 1\\n \\n return res\\n
\\n","description":"Problem: 100137. 统计最大元素出现至少 K 次的子数组 [TOC]\\n\\n首先获取最大元素出现的所有位置\\n\\n # 获取最大元素出现的所有位置\\n max_num = max(nums)\\n # 哨兵\\n pos = [-1]\\n for i,num in enumerate(nums):\\n if num == max_num:\\n pos.append(i)\\n\\n\\n然后枚举子数组的左端点l,根据左端点就能求出最近的右端点的位置r=l…","guid":"https://leetcode.cn/problems/count-subarrays-where-max-element-appears-at-least-k-times//solution/hua-dong-chuang-kou-cheng-fa-yuan-li-by-dj1ll","author":"mipha-2022","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-12-10T04:09:10.098Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「越长越合法」型滑动窗口(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-subarrays-where-max-element-appears-at-least-k-times//solution/hua-dong-chuang-kou-fu-ti-dan-pythonjava-xvwg","content":"⚠注意:题目说的最大元素指整个 $\\\\textit{nums}$ 数组的最大值,不是子数组的最大值。
\\n前置知识:滑动窗口【基础算法精讲 03】
\\n由于子数组越长,包含的元素越多,越能满足题目要求;反之,子数组越短,包含的元素越少,越不能满足题目要求。有这种性质的题目,可以用滑动窗口解决。
\\n例如示例 1,当右端点移到第二个 $3$ 时,左端点移到 $2$,此时 $[1,3,2,3]$ 和 $[3,2,3]$ 是满足要求的。当右端点移到第三个 $3$ 时,左端点也移到第三个 $3$,此时 $[1,3,2,3,3], [3,2,3,3], [2,3,3], [3,3]$ 都是满足要求的。所以答案为 $2+4=6$。
\\n###py
\\nclass Solution:\\n def countSubarrays(self, nums: List[int], k: int) -> int:\\n mx = max(nums)\\n ans = cnt_mx = left = 0\\n for x in nums:\\n if x == mx:\\n cnt_mx += 1\\n while cnt_mx == k:\\n if nums[left] == mx:\\n cnt_mx -= 1\\n left += 1\\n ans += left\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long countSubarrays(int[] nums, int k) {\\n int mx = 0;\\n for (int x : nums) {\\n mx = Math.max(mx, x);\\n }\\n\\n long ans = 0;\\n int cntMx = 0, left = 0;\\n for (int x : nums) {\\n if (x == mx) {\\n cntMx++;\\n }\\n while (cntMx == k) {\\n if (nums[left] == mx) {\\n cntMx--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, int k) {\\n int mx = ranges::max(nums);\\n long long ans = 0;\\n int cnt_mx = 0, left = 0;\\n for (int x : nums) {\\n cnt_mx += x == mx;\\n while (cnt_mx == k) {\\n cnt_mx -= nums[left] == mx;\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long countSubarrays(int* nums, int numsSize, int k) {\\n int mx = nums[0];\\n for (int i = 1; i < numsSize; i++) {\\n mx = MAX(mx, nums[i]);\\n }\\n\\n long long ans = 0;\\n int cnt_mx = 0, left = 0;\\n for (int i = 0; i < numsSize; i++) {\\n cnt_mx += nums[i] == mx;\\n while (cnt_mx == k) {\\n cnt_mx -= nums[left] == mx;\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n}\\n
\\n###go
\\nfunc countSubarrays(nums []int, k int) (ans int64) {\\nmx := slices.Max(nums)\\ncntMx, left := 0, 0\\nfor _, x := range nums {\\nif x == mx {\\ncntMx++\\n}\\nfor cntMx == k {\\nif nums[left] == mx {\\ncntMx--\\n}\\nleft++\\n}\\nans += int64(left)\\n}\\nreturn\\n}\\n
\\n###js
\\nvar countSubarrays = function(nums, k) {\\n const mx = Math.max(...nums);\\n let ans = 0, cntMx = 0, left = 0;\\n for (const x of nums) {\\n if (x === mx) {\\n cntMx++;\\n }\\n while (cntMx === k) {\\n if (nums[left] === mx) {\\n cntMx--;\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn count_subarrays(nums: Vec<i32>, k: i32) -> i64 {\\n let mx = *nums.iter().max().unwrap();\\n let mut ans = 0;\\n let mut cnt_mx = 0;\\n let mut left = 0;\\n for &x in &nums {\\n if x == mx {\\n cnt_mx += 1;\\n }\\n while cnt_mx == k {\\n if nums[left] == mx {\\n cnt_mx -= 1;\\n }\\n left += 1;\\n }\\n ans += left;\\n }\\n ans as _\\n }\\n}\\n
\\n改成子数组的最大值在子数组中至少出现 $k$ 次,要怎么做?(原题是整个数组的最大值,这里是子数组的最大值)
\\n欢迎在评论区分享你的思路/代码。
\\n\\n欢迎关注 B站@灵茶山艾府
\\n","description":"⚠注意:题目说的最大元素指整个 $\\\\textit{nums}$ 数组的最大值,不是子数组的最大值。 前置知识:滑动窗口【基础算法精讲 03】\\n\\n由于子数组越长,包含的元素越多,越能满足题目要求;反之,子数组越短,包含的元素越少,越不能满足题目要求。有这种性质的题目,可以用滑动窗口解决。\\n\\n设 $\\\\textit{mx} = \\\\max(\\\\textit{nums})$。\\n元素 $x=\\\\textit{nums}[\\\\textit{right}]$ 进入窗口时,如果 $x=\\\\textit{mx}$,把计数器 $\\\\textit{cntMx}$ 加一。\\n如果…","guid":"https://leetcode.cn/problems/count-subarrays-where-max-element-appears-at-least-k-times//solution/hua-dong-chuang-kou-fu-ti-dan-pythonjava-xvwg","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-12-10T04:08:15.537Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2931. 购买物品的最大开销","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/2931-gou-mai-wu-pin-de-zui-da-kai-xiao-b-cbn6","content":"根据题目要求,购买一件物品的开销等于物品的价值与购买的天数的乘积。由于每个商店的物品按价值降序排序且每天购买物品时可以选择一个商店中尚未购买的最大下标处的物品,因此每天购买的物品一定是其中一家商店中尚未购买的价值最低的物品,每天都可以在所有尚未购买的物品中选择购买价值最低的物品。
\\n已知有 $m$ 个商店,每个商店有 $n$ 件物品,需要购买所有 $m \\\\times n$ 物品。根据贪心策略,为了使开销最大,应每天在所有尚未购买的物品中选择购买价值最低的物品,即购买所有物品的顺序为价值递增的顺序。
\\n贪心策略的正确性说明如下。
\\n用长度为 $m \\\\times n$ 的升序数组 $\\\\textit{allValues}$ 表示所有 $m \\\\times n$ 件物品的价值。对于 $1 \\\\le d \\\\le m \\\\times n$,第 $d$ 天购买的物品价格是 $\\\\textit{allValues}[d - 1]$,开销是 $\\\\textit{allValues}[d - 1] \\\\times d$。将此时的总开销记为 $\\\\textit{totalCost}$。如果选择满足 $0 \\\\le i < j < m \\\\times n$ 的两个下标 $i$ 和 $j$,将 $\\\\textit{allValues}[i]$ 和 $\\\\textit{allValues}[j]$ 的值交换,将交换之后的总开销记为 $\\\\textit{totalCost}\'$,考虑 $\\\\textit{totalCost}\'$ 与 $\\\\textit{totalCost}$ 的大小关系。
\\n$$
\\n\\\\begin{aligned}
\\n&\\\\quad \\\\ \\\\textit{totalCost}\' - \\\\textit{totalCost} \\\\
\\n&= (\\\\textit{allValues}[j] \\\\times (i + 1) + \\\\textit{allValues}[i] \\\\times (j + 1)) - (\\\\textit{allValues}[i] \\\\times (i + 1) + \\\\textit{allValues}[j] \\\\times (j + 1)) \\\\
\\n&= (\\\\textit{allValues}[i] - \\\\textit{allValues}[j]) \\\\times (j - i)
\\n\\\\end{aligned}
\\n$$
由于 $\\\\textit{allValues}[i] - \\\\textit{allValues}[j] \\\\le 0$ 且 $j - i > 0$,因此 $\\\\textit{totalCost}\' - \\\\textit{totalCost} \\\\le 0$,$\\\\textit{totalCost}$ 是购买所有物品的最大开销。
\\n根据上述分析结果,应按照价值递增的顺序购买所有物品。可以使用排序或优先队列实现计算购买所有物品的最大开销。
\\n使用排序的做法是,创建长度为 $m \\\\times n$ 的数组 $\\\\textit{allValues}$,将矩阵 $\\\\textit{values}$ 中的所有元素依次填入数组 $\\\\textit{allValues}$,然后将数组 $\\\\textit{allValues}$ 按升序排序。排序之后,从左到右遍历数组 $\\\\textit{allValues}$,对于每个 $1 \\\\le d \\\\le m \\\\times n$,第 $d$ 天购买价值为 $\\\\textit{allValues}[d - 1]$ 的物品,因此将 $\\\\textit{allValues}[d - 1] \\\\times d$ 加到总开销。遍历结束之后,即可得到购买所有物品的最大开销。
\\n###Java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n int m = values.length, n = values[0].length;\\n int totalCount = m * n;\\n int[] allValues = new int[totalCount];\\n for (int i = 0; i < m; i++) {\\n System.arraycopy(values[i], 0, allValues, i * n, n);\\n }\\n Arrays.sort(allValues);\\n long totalCost = 0;\\n for (int d = 1; d <= totalCount; d++) {\\n totalCost += (long) allValues[d - 1] * d;\\n }\\n return totalCost;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaxSpending(int[][] values) {\\n int m = values.Length, n = values[0].Length;\\n int totalCount = m * n;\\n int[] allValues = new int[totalCount];\\n for (int i = 0; i < m; i++) {\\n Array.Copy(values[i], 0, allValues, i * n, n);\\n }\\n Array.Sort(allValues);\\n long totalCost = 0;\\n for (int d = 1; d <= totalCount; d++) {\\n totalCost += (long) allValues[d - 1] * d;\\n }\\n return totalCost;\\n }\\n}\\n
\\n时间复杂度:$O(mn \\\\log (mn))$,其中 $m$ 和 $n$ 分别是矩阵 $\\\\textit{values}$ 的行数和列数。将元素填入数组 $\\\\textit{allValues}$ 需要 $O(mn)$ 的时间,排序需要 $O(mn \\\\log (mn))$ 的时间,排序之后遍历数组 $\\\\textit{allValues}$ 计算最大开销需要 $O(mn)$ 的时间,因此时间复杂度是 $O(mn \\\\log (mn))$。
\\n空间复杂度:$O(mn)$,其中 $m$ 和 $n$ 分别是矩阵 $\\\\textit{values}$ 的行数和列数。需要创建长度为 $mn$ 的数组 $\\\\textit{allValues}$,排序需要 $O(\\\\log (mn))$ 的递归调用栈空间,因此空间复杂度是 $O(mn)$。
\\n使用优先队列的做法是,创建优先队列存储每个商店的尚未购买的最大下标处的物品信息,每一项信息为三元组,包括物品价值、行下标和列下标,优先队列的队首元素为价值最低的物品信息。每次选择购买尚未购买的价值最低的物品,并计算购买所有物品的最大开销。
\\n初始时,对于每个 $0 \\\\le i < m$,将三元组 $(\\\\textit{values}[i][n - 1], i, n - 1)$ 加入优先队列。对于 $1 \\\\le d \\\\le m \\\\times n$,第 $d$ 天的购买操作如下。
\\n将优先对垒的队首元素取出,得到物品价值 $\\\\textit{value}$、行下标 $\\\\textit{row}$ 和列下标 $\\\\textit{col}$。
\\n将 $\\\\textit{value} \\\\times d$ 加到总开销。
\\n此时第 $\\\\textit{row}$ 家店的第 $\\\\textit{col}$ 件物品被购买。如果 $\\\\textit{col} > 0$,则第 $\\\\textit{row}$ 家店仍有尚未购买的物品,尚未购买的物品的最大下标是 $\\\\textit{col} - 1$,因此将三元组 $(\\\\textit{values}[\\\\textit{row}][\\\\textit{col} - 1], \\\\textit{row}, \\\\textit{col} - 1)$ 加入优先队列。
\\n经过 $m \\\\times n$ 天之后,所有物品都购买,此时即可得到购买所有物品的最大开销。
\\n###Java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n long totalCost = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);\\n int m = values.length, n = values[0].length;\\n int totalCount = m * n;\\n for (int i = 0; i < m; i++) {\\n pq.offer(new int[]{values[i][n - 1], i, n - 1});\\n }\\n for (int d = 1; d <= totalCount; d++) {\\n int[] arr = pq.poll();\\n int value = arr[0], row = arr[1], col = arr[2];\\n totalCost += (long) value * d;\\n if (col > 0) {\\n pq.offer(new int[]{values[row][col - 1], row, col - 1});\\n }\\n }\\n return totalCost;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public long MaxSpending(int[][] values) {\\n long totalCost = 0;\\n PriorityQueue<Tuple<int, int, int>, int> pq = new PriorityQueue<Tuple<int, int, int>, int>();\\n int m = values.Length, n = values[0].Length;\\n int totalCount = m * n;\\n for (int i = 0; i < m; i++) {\\n pq.Enqueue(new Tuple<int, int, int>(values[i][n - 1], i, n - 1), values[i][n - 1]);\\n }\\n for (int d = 1; d <= totalCount; d++) {\\n Tuple<int, int, int> tuple = pq.Dequeue();\\n int value = tuple.Item1, row = tuple.Item2, col = tuple.Item3;\\n totalCost += (long) value * d;\\n if (col > 0) {\\n pq.Enqueue(new Tuple<int, int, int>(values[row][col - 1], row, col - 1), values[row][col - 1]);\\n }\\n }\\n return totalCost;\\n }\\n}\\n
\\n时间复杂度:$O(mn \\\\log m)$,其中 $m$ 和 $n$ 分别是矩阵 $\\\\textit{values}$ 的行数和列数。需要执行 $O(mn)$ 次优先队列操作,由于优先队列中最多有 $m$ 个元素因此每次优先队列操作的时间是 $O(\\\\log m)$,因此时间复杂度是 $O(mn \\\\log m)$。
\\n空间复杂度:$O(m)$,其中 $m$ 是矩阵 $\\\\textit{values}$ 的行数。优先队列中的元素个数不超过 $m$。
\\n\\n\\nProblem: 2942. 查找包含给定字符的单词
\\n
[TOC]
\\n\\n\\n这题很简单,通过头文件自带的方法判断其返回值就可以了
\\n
\\n\\n暴力
\\n
时间复杂度:O(n)
\\n空间复杂度:O(n)
\\n###C
\\n\\n/**\\n * Note: The returned array must be malloced, assume caller calls free().\\n */\\nint* findWordsContaining(char** words, int wordsSize, char x, int* returnSize) {\\n int *arr =(int*)malloc(sizeof(int)*wordsSize);\\n int index=0;\\n for(int i=0;i<wordsSize;i++){\\n if(strchr(words[i],x)){\\n arr[index++]=i;\\n }\\n }\\n *returnSize = index;\\n return arr;\\n}\\n
\\n","description":"Problem: 2942. 查找包含给定字符的单词 [TOC]\\n\\n这题很简单,通过头文件自带的方法判断其返回值就可以了\\n\\n暴力\\n\\n时间复杂度:O(n)\\n\\n空间复杂度:O(n)\\n\\n###C\\n\\n\\n/**\\n * Note: The returned array must be malloced, assume caller calls free().\\n */\\nint* findWordsContaining(char** words, int wordsSize, char x, int* returnSize) {\\n int *arr…","guid":"https://leetcode.cn/problems/find-words-containing-character//solution/jian-dan-yi-dong-tong-guo-tou-wen-jian-f-a5na","author":"relaxed-7ehmannbgd","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-28T08:11:35.968Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"库函数写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-words-containing-character//solution/6-chong-yu-yan-api-ying-yong-pythonjavac-k3ae","content":"遍历 $\\\\textit{words}$,判断 $x$ 是否在 $\\\\textit{words}[i]$ 中,如果是则把 $i$ 加入答案。
\\n###py
\\nclass Solution:\\n def findWordsContaining(self, words: List[str], x: str) -> List[int]:\\n return [i for i, s in enumerate(words) if x in s]\\n
\\n###java
\\nclass Solution {\\n public List<Integer> findWordsContaining(String[] words, char x) {\\n List<Integer> ans = new ArrayList<>();\\n for (int i = 0; i < words.length; i++) {\\n if (words[i].indexOf(x) >= 0) {\\n ans.add(i);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###java
\\nclass Solution {\\n public List<Integer> findWordsContaining(String[] words, char x) {\\n return IntStream.range(0, words.length)\\n .filter(i -> words[i].indexOf(x) >= 0)\\n .boxed()\\n .toList();\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n vector<int> findWordsContaining(vector<string>& words, char x) {\\n vector<int> ans;\\n for (int i = 0; i < words.size(); i++) {\\n if (words[i].contains(x)) {\\n ans.push_back(i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###c
\\nint* findWordsContaining(char** words, int wordsSize, char x, int* returnSize) {\\n int* ans = malloc(sizeof(int) * wordsSize);\\n int k = 0;\\n for (int i = 0; i < wordsSize; i++) {\\n if (strchr(words[i], x)) {\\n ans[k++] = i;\\n }\\n }\\n *returnSize = k;\\n return ans;\\n}\\n
\\n###go
\\nfunc findWordsContaining(words []string, x byte) (ans []int) {\\nfor i, s := range words {\\nif strings.IndexByte(s, x) >= 0 {\\nans = append(ans, i)\\n}\\n}\\nreturn\\n}\\n
\\n###js
\\nvar findWordsContaining = function(words, x) {\\n const ans = [];\\n for (let i = 0; i < words.length; i++) {\\n if (words[i].includes(x)) {\\n ans.push(i);\\n }\\n }\\n return ans;\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn find_words_containing(words: Vec<String>, x: char) -> Vec<i32> {\\n words.into_iter()\\n .enumerate()\\n .filter_map(|(i, s)| s.contains(x).then_some(i as i32))\\n .collect()\\n }\\n}\\n
\\n###rust
\\nimpl Solution {\\n pub fn find_words_containing(words: Vec<String>, x: char) -> Vec<i32> {\\n let mut ans = vec![];\\n for (i, s) in words.into_iter().enumerate() {\\n if s.contains(x) {\\n ans.push(i as i32);\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历 $\\\\textit{words}$,判断 $x$ 是否在 $\\\\textit{words}[i]$ 中,如果是则把 $i$ 加入答案。 ###py\\n\\nclass Solution:\\n def findWordsContaining(self, words: List[str], x: str) -> List[int]:\\n return [i for i, s in enumerate(words) if x in s]\\n\\n\\n###java\\n\\nclass Solution {\\n public List我们需要解决的问题是:「获得第 $1$ 个及其后面的水果所需要的最少金币数」。
\\n第 $1$ 个水果一定要买,然后呢?
\\n第 $2$ 个水果可以购买,也可以免费获得:
\\n无论哪种情况都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。
\\n\\n\\nDP 做题技巧:见微知著,想清楚第一步(或者最后一步)怎么做,就想清楚了递归过程中的每一步要怎么做。
\\n
从上面的讨论可以知道,只需要一个 $i$ 就能表达子问题,即定义 $\\\\textit{dfs}(i)$ 表示在购买第 $i$ 个水果的前提下,获得第 $i$ 个及其后面的水果所需要的最少金币数。注意 $i$ 从 $1$ 开始。
\\n买第 $i$ 个水果,那么从 $i+1$ 到 $2i$ 的水果都是免费的。枚举下一个购买的水果 $j$,问题变成:在购买第 $j$ 个水果的前提下,获得第 $j$ 个及其后面的水果所需要的最少金币数,即 $\\\\textit{dfs}(j)$。
\\n$j$ 的范围是 $[i+1,2i+1]$。其中 $2i+1$ 表示免费获得从 $i+1$ 到 $2i$ 的所有水果,那么第 $2i+1$ 个水果不能免费,一定要买。
\\n这些 $\\\\textit{dfs}(j)$ 取最小值,再加上购买第 $i$ 个水果的花费 $\\\\textit{prices}[i]$,得
\\n$$
\\n\\\\textit{dfs}(i) = \\\\textit{prices}[i] + \\\\min_{j=i+1}^{2i+1} \\\\textit{dfs}(j)
\\n$$
递归边界:注意到当 $2i\\\\ge n$,即 $i\\\\ge \\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{n+1}{2}\\\\right\\\\rfloor$ 时,后面的水果都可以免费获得了,所以递归边界为
\\n$$
\\n\\\\textit{dfs}(i)=\\\\textit{prices}[i]
\\n$$
其中 $i\\\\ge \\\\left\\\\lfloor\\\\dfrac{n+1}{2}\\\\right\\\\rfloor$。
\\n递归入口:$\\\\textit{dfs}(1)$,这是原问题,也是答案。
\\n由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n原理见 动态规划入门:从记忆化搜索到递推,其中包含如何把记忆化搜索 1:1 翻译成递推的技巧。
\\n本题视频讲解,欢迎点赞关注~
\\n\\n\\n注:由于传入的 $\\\\textit{prices}$ 数组的下标是从 $0$ 开始的,代码实现时下标要减一。
\\n
###py
\\nclass Solution:\\n def minimumCoins(self, prices: List[int]) -> int:\\n n = len(prices)\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int) -> int:\\n if i * 2 >= n:\\n return prices[i - 1] # i 从 1 开始\\n return min(dfs(j) for j in range(i + 1, i * 2 + 2)) + prices[i - 1]\\n return dfs(1)\\n
\\n###java
\\nclass Solution {\\n public int minimumCoins(int[] prices) {\\n int n = prices.length;\\n int[] memo = new int[(n + 1) / 2];\\n return dfs(1, prices, memo);\\n }\\n\\n private int dfs(int i, int[] prices, int[] memo) {\\n if (i * 2 >= prices.length) {\\n return prices[i - 1]; // i 从 1 开始\\n }\\n if (memo[i] != 0) { // 之前算过\\n return memo[i];\\n }\\n int res = Integer.MAX_VALUE;\\n for (int j = i + 1; j <= i * 2 + 1; j++) {\\n res = Math.min(res, dfs(j, prices, memo));\\n }\\n return memo[i] = res + prices[i - 1]; // 记忆化\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n vector<int> memo((n + 1) / 2);\\n auto dfs = [&](this auto&& dfs, int i) -> int {\\n if (i * 2 >= n) {\\n return prices[i - 1]; // i 从 1 开始\\n }\\n int& res = memo[i]; // 注意这里是引用\\n if (res) { // 之前算过\\n return res;\\n }\\n res = INT_MAX;\\n for (int j = i + 1; j <= i * 2 + 1; j++) {\\n res = min(res, dfs(j));\\n }\\n res += prices[i - 1];\\n return res;\\n };\\n return dfs(1);\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(prices []int) int {\\nn := len(prices)\\nmemo := make([]int, (n+1)/2)\\nvar dfs func(int) int\\ndfs = func(i int) (res int) {\\nif i*2 >= n {\\nreturn prices[i-1] // i 从 1 开始\\n}\\np := &memo[i]\\nif *p != 0 { // 之前算过\\nreturn *p\\n}\\ndefer func() { *p = res }() // 记忆化\\nres = math.MaxInt\\nfor j := i + 1; j <= i*2+1; j++ {\\nres = min(res, dfs(j))\\n}\\nreturn res + prices[i-1]\\n}\\nreturn dfs(1)\\n}\\n
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i]$ 的定义和 $\\\\textit{dfs}(i)$ 的定义是一样的,都表示在购买第 $i$ 个水果的前提下,获得第 $i$ 个及其后面的水果所需要的最少金币数。注意 $i$ 从 $1$ 开始。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\nf[i] = \\\\textit{prices}[i] + \\\\min_{j=i+1}^{2i+1} f[j]
\\n$$
\\n\\n注:由于从比 $i$ 更大的 $j$ 转移过来,所以必须倒着计算 $f$。
\\n
初始值:当 $i\\\\ge \\\\left\\\\lfloor\\\\dfrac{n+1}{2}\\\\right\\\\rfloor$ 时,$f[i]=\\\\textit{prices}[i]$,翻译自递归边界 $\\\\textit{dfs}(i)=\\\\textit{prices}[i]$。
\\n答案:$f[1]$,翻译自递归入口 $\\\\textit{dfs}(1)$。
\\n\\n\\n注:由于传入的 $\\\\textit{prices}$ 数组的下标是从 $0$ 开始的,代码实现时下标要减一。
\\n
代码实现时,可以直接把 $\\\\textit{prices}$ 当作 $f$ 数组。
\\n###py
\\nclass Solution:\\n def minimumCoins(self, f: List[int]) -> int:\\n n = len(f)\\n for i in range((n + 1) // 2 - 1, 0, -1):\\n f[i - 1] += min(f[i: i * 2 + 1])\\n return f[0]\\n
\\n###java
\\nclass Solution {\\n public int minimumCoins(int[] f) {\\n int n = f.length;\\n for (int i = (n + 1) / 2 - 1; i > 0; i--) {\\n int mn = Integer.MAX_VALUE;\\n for (int j = i; j <= i * 2; j++) {\\n mn = Math.min(mn, f[j]);\\n }\\n f[i - 1] += mn;\\n }\\n return f[0];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& f) {\\n int n = f.size();\\n for (int i = (n + 1) / 2 - 1; i > 0; i--) {\\n f[i - 1] += *min_element(f.begin() + i, f.begin() + i * 2 + 1);\\n }\\n return f[0];\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(f []int) int {\\nn := len(f)\\nfor i := (n+1)/2 - 1; i > 0; i-- {\\nf[i-1] += slices.Min(f[i : i*2+1])\\n}\\nreturn f[0]\\n}\\n
\\n由于随着 $i$ 的变小,$j$ 的范围 $[i+1,2i+1]$ 的左右边界也在变小,所以 $[i+1,2i+1]$ 是一个向左的滑动窗口。
\\n计算 $\\\\min\\\\limits_{j=i+1}^{2i+1} f[j]$ 的过程本质上是在计算滑动窗口最小值,原理见 单调队列【基础算法精讲 27】。
\\n下面代码中的队首在左边,队尾在右边。
\\n\\n\\n注:也可以把 $f$ 存到 $\\\\textit{prices}$ 中,双端队列中只保存下标,但是这样可读性比较差,我在双端队列中保存了下标和对应的 $f[i]$。
\\n
###py
\\nclass Solution:\\n def minimumCoins(self, prices: List[int]) -> int:\\n n = len(prices)\\n q = deque([(n + 1, 0)]) # 哨兵\\n for i in range(n, 0, -1):\\n while q[-1][0] > i * 2 + 1: # 右边离开窗口\\n q.pop()\\n f = prices[i - 1] + q[-1][1]\\n while f <= q[0][1]:\\n q.popleft()\\n q.appendleft((i, f)) # 左边进入窗口\\n return q[0][1]\\n
\\n###java
\\nclass Solution {\\n public int minimumCoins(int[] prices) {\\n int n = prices.length;\\n Deque<int[]> q = new ArrayDeque<>();\\n q.addLast(new int[]{n + 1, 0}); // 哨兵\\n for (int i = n; i > 0; i--) {\\n while (q.peekLast()[0] > i * 2 + 1) { // 右边离开窗口\\n q.pollLast();\\n }\\n int f = prices[i - 1] + q.peekLast()[1];\\n while (f <= q.peekFirst()[1]) {\\n q.pollFirst();\\n }\\n q.addFirst(new int[]{i, f}); // 左边进入窗口\\n }\\n return q.peekFirst()[1];\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n deque<pair<int, int>> q;\\n q.emplace_front(n + 1, 0); // 哨兵\\n for (int i = n; i > 0; i--) {\\n while (q.back().first > i * 2 + 1) { // 右边离开窗口\\n q.pop_back();\\n }\\n int f = prices[i - 1] + q.back().second;\\n while (f <= q.front().second) {\\n q.pop_front();\\n }\\n q.emplace_front(i, f); // 左边进入窗口\\n }\\n return q.front().second;\\n }\\n};\\n
\\n###go
\\nfunc minimumCoins(prices []int) int {\\nn := len(prices)\\ntype pair struct{ i, f int }\\nq := []pair{{n + 1, 0}} // 哨兵\\nfor i := n; i > 0; i-- {\\nfor q[0].i > i*2+1 { // 右边离开窗口\\nq = q[1:]\\n}\\nf := prices[i-1] + q[0].f\\nfor f <= q[len(q)-1].f {\\nq = q[:len(q)-1]\\n}\\nq = append(q, pair{i, f}) // 左边进入窗口\\n}\\nreturn q[len(q)-1].f\\n}\\n
\\n更多相似题目,见下面动态规划题单中的「§11.3 单调队列优化 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 我们需要解决的问题是:「获得第 $1$ 个及其后面的水果所需要的最少金币数」。\\n\\n第 $1$ 个水果一定要买,然后呢?\\n\\n第 $2$ 个水果可以购买,也可以免费获得:\\n\\n如果购买,那么需要解决的问题为:「在购买第 $2$ 个水果的前提下,获得第 $2$ 个及其后面的水果所需要的最少金币数」。\\n如果免费获得,那么根据题意,第 $3$ 个水果必须购买,需要解决的问题为:「在购买第 $3$ 个水果的前提下,获得第 $3$ 个及其后面的水果所需要的最少金币数」。\\n\\n无论哪种情况都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。…","guid":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits//solution/dpcong-on2-dao-onpythonjavacgo-by-endles-nux5","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-26T00:30:20.126Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++, 线性DP, 买或不买","url":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits//solution/c-xian-xing-dp-mai-huo-bu-mai-by-liu-xia-4n01","content":"\\n\\ndp[i]表示买[0,i]水果所需的最小花费;
\\n
\\n转移方程: 第i个水果可以选择买, 或者如果前面有买过的, 当前可以不买;
class Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n vector<int> f(n + 1, INT_MAX);\\n f[0] = 0;\\n for (int i = 1; i <= n; i++) {\\n /* 选择买当前水果的花费 */\\n int buy = f[i - 1] + prices[i - 1];\\n f[i] = min(f[i], buy);\\n\\n /* 如果买了当前水果, 后面i个水果可以免费获得, 更新后面的最小值 */\\n for (int j = 1; j <= i && i + j <= n; j++) {\\n f[i + j] = min(f[i + j], buy);\\n }\\n }\\n return f[n];\\n }\\n};\\n
\\n","description":"dp[i]表示买[0,i]水果所需的最小花费; 转移方程: 第i个水果可以选择买, 或者如果前面有买过的, 当前可以不买;\\n\\nclass Solution {\\npublic:\\n int minimumCoins(vector\\n\\nProblem: 100121. 查找包含给定字符的单词
\\n
[TOC]
\\n\\n\\n讲述看到这一题的思路
\\n
\\n\\npython 模拟法
\\n
时间复杂度:
\\n\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
空间复杂度:
\\n\\n\\n添加空间复杂度, 示例: $O(n)$
\\n
###Python3
\\nclass Solution:\\n def findWordsContaining(self, words: List[str], x: str) -> List[int]:\\n ans=[]\\n for i,word in enumerate(words):\\n if x in word :\\n ans.append(i)\\n return ans\\n
\\n","description":"Problem: 100121. 查找包含给定字符的单词 [TOC]\\n\\n讲述看到这一题的思路\\n\\npython 模拟法\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n空间复杂度:\\n\\n添加空间复杂度, 示例: $O(n)$\\n\\n###Python3\\n\\nclass Solution:\\n def findWordsContaining(self, words: List[str], x: str) -> List[int]:\\n ans=[]\\n for i,word in enumerate(words):…","guid":"https://leetcode.cn/problems/find-words-containing-character//solution/python-mo-ni-fa-by-nrib8zib57-0x4b","author":"nriB8ZIB57","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-25T17:14:49.014Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DP:选/不选","url":"https://leetcode.cn/problems/minimum-number-of-coins-for-fruits//solution/dpxuan-bu-xuan-by-radixun-wfs7","content":"\\n\\nProblem: 购买水果需要的最少金币数
\\n
\\n\\n从几个数中选几个,想到用DP
\\n
用dp[i][0]表示买前i个水果且不买第i个水果的最少金币数
\\n用dp[i][1]表示买前i个水果且买第i个水果的最少金币数
买第i个水果就是从前i-1个水果的范围内价格最少的加上第i个水果
\\n不买第i个水果就前面可以赠送的范围内选价格最少的
时间复杂度:
\\n\\n\\nO(n^2)
\\n
###C++
\\nclass Solution {\\npublic:\\n int minimumCoins(vector<int>& prices) {\\n int n = prices.size();\\n int dp[1005][2];\\n //初始化\\n for(int i = 0 ; i <= n ; i ++){\\n dp[i][0] = dp[i][1] = 999999;\\n }\\n dp[1][1] = prices[0];\\n //dp\\n for(int i = 1 ; i < n ; i ++){\\n //买第i个\\n dp[i + 1][1] = min(dp[i][0] ,dp[i][1]) + prices[i];\\n //不买第i个\\n for(int j = i; j + j >= i + 1; j --){\\n dp[i + 1][0] = min(dp[i + 1][0] , dp[j][1]);\\n }\\n }\\n return min(dp[n][0],dp[n][1]);\\n }\\n};\\n
\\n","description":"Problem: 购买水果需要的最少金币数 从几个数中选几个,想到用DP\\n\\n用dp[i][0]表示买前i个水果且不买第i个水果的最少金币数\\n 用dp[i][1]表示买前i个水果且买第i个水果的最少金币数\\n\\n买第i个水果就是从前i-1个水果的范围内价格最少的加上第i个水果\\n 不买第i个水果就前面可以赠送的范围内选价格最少的\\n\\n时间复杂度:\\n\\nO(n^2)\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int minimumCoins(vector这道题要求从起点(编号为 1
的格子)到终点(编号为 n^2
的格子)的最短路径。
\\n路径搜索有两种方法:深度优先与广度优先。由于这道题要找到的是到达终点时的最短路径,因此 用广度优先搜索更方便些。【广度优先搜索就是每次把离当前节点最近的节点作为待搜索的节点】
这道题和传统的矩阵路径搜索不一样的是,它的下一个搜索方格不是相邻方格,而是下6
个编号。即如果当前处理的方格编号为 curr
,那么其可以转移到编号属于 [curr + 1, min(curr + 6, n2)]
的方格里。
{:width=200}
其次,还有第二个转移规则,如果这个方格编号 可以传送 (board[r][c] != -1
),那么就会 传送到指定位置。即假设编号为 next
的单元格所在位置为 (r,c)
,那么玩家将不会转移到 next
编号的方格,而是转移到 board[r][c]
编号的方格。
{:width=220}
那么现在出现了一个问题,如何根据编号确定方格的位置,即根据 i
确定其所在的 r
和 c
。
\\n传统的矩阵编号和单元格位置的关系如下图:
而这道题有三个特殊的地方:
\\n矩阵编号从 1
开始,而不是从 0
开始。因此计算行和列要先对编号 -1
,即 i - 1
;
其次,行的排列是倒序的【或者说翻转了】,即原本的 r=0
跑到了 r=n-1
,相当于从 n-1
行倒着往回数,因此计算出来的 r\' = n - 1 - r
;
最后,列的排列是蛇形的:原本我们每一列的排序都是从左到右的,因此计算出来的 c
是哪一列就是哪一列;但是现在我们从最后一行到首行的元素排列顺序是交替的:最后一行从左到右,倒数第二行从右到左,...:
从左到右的排列还是和原来的计算方式一致;而从右到左排列的那么列编号就是从 n-1
往回数,即 c = n-1-c
;
由于是交替的,我们把行倒着编码(最后一行当成第 0
行,倒数第二行为 1
行,即 id / n
的结果, 原本倒回来的又倒回去了),那么偶数行是从左到右,c\' = 0+c
【从首列0往右数c个位置】;奇数行是从右到左 c\' = n-1-c
【从最后一列n-1往左数c个位置】。
\\n
通过数学计算,我们可以得到实际的列 c\'
与 行 r
的关系
偶数行 (n-1-r)& 1 = 0\\n奇数行 (n-1-r) & 1 = 1\\n记 x = (n-1-r)& 1\\n当 x = 0, 偶数行,c\' = c; 当 x = 1, 奇数行,c\' = n - 1 - c;\\n根据两点式直线方程计算方式,设 c\' = kx + b\\nx = 0, 0 * k + b = c\\nx = 1, 1 * k + b = n-1-c\\nc\' = (n-1-2c)x + c = (n-1-2c) * (n-1-r)& 1 + c\\n
\\n通过上两步,明确了下一个转移的编号。剩下的就是根据 广度优先搜索 借助队列对起点到终点的路径进行搜索。
\\n-1
。\\n\\n细节处理
\\n
队列中是同时存储了待搜索的方格编号和到达该方格时的最少移动数。
\\n当然也可以只存储方格编号,那么搜索过程就类似 二叉树的层序遍历。每一次循环之前先获取队列中有多少元素,这些元素就是满足当前统计的距离/移动数的节点。我们只处理这么多个元素,剩下的元素都是新加入,都是下一个距离的元素。
###Java
\\nclass Solution {\\n public int snakesAndLadders(int[][] board) {\\n int n = board.length; // 获取方阵的边长\\n int target = n * n; // 获取方阵尺寸,也是最后要到达目的地\\n Queue<int[]> queue = new LinkedList<>(); // 队列用于BFS,存放待搜索的方格编号和到达该方格时的最少移动数\\n queue.offer(new int[]{1, 0}); // 初始{1,0}入队,表示起点1,0次移动\\n boolean[][] visited = new boolean[n][n]; // 用于BFS过程中标记方格是否搜索过\\n // BFS\\n while(!queue.isEmpty()){\\n int[] node = queue.poll(); // 弹出队首待搜索节点\\n int curr = node[0], cnt = node[1]; // 获取当前搜索的方格宾浩和到达该方格的最少移动数\\n cnt++; // 移动数加1\\n for(int i = curr + 1; i <= Math.min(target, curr + 6); i++){\\n // 枚举所有下一个可搜索且未搜索过的方格编号\\n int r = n-1 - (i-1) / n, c = (i-1) % n; // 根据方格编号获取这个编号的行和列\\n c += (n-1 - 2*c) * ((n-1-r) & 1); // 根据行数修正列数\\n if(visited[r][c])continue; // 跳过搜索过的编号\\n visited[r][c] = true; // 标记该编号已搜索\\n int next = board[r][c] == - 1 ? i : board[r][c]; // 如果这个编号所在的方格可以转移到其他格子,转移到对应编号;否则就是在当前编号\\n if(next == target)return cnt; // 到达终点,直接返回最小移动数 \\n queue.offer(new int[]{next, cnt}); // 加入队列\\n }\\n }\\n return -1; // 退出循环说明没有到达目的地\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def snakesAndLadders(self, board: List[List[int]]) -> int:\\n n = len(board) # 获取方阵的边长\\n target = n * n # 获取方阵尺寸,也是最后要到达目的地\\n queue = [(1, 0)] # 队列用于BFS,存放待搜索的方格编号和到达该方格时的最少移动数; 初始{1,0}入队,表示起点1,0次移动\\n visited = [[False] * n for _ in range(n)] # 用于BFS过程中标记方格是否搜索过\\n # BFS\\n while queue:\\n curr, cnt = queue.pop(0) # 获取队首的方格编号和到达该方格的最少移动数\\n cnt += 1 # 移动数加1\\n for i in range(curr + 1, min(curr + 6, target) + 1):\\n # 枚举所有下一个可搜索且未搜索过的方格编号\\n r, c = n-1 - (i-1) // n, (i-1) % n # 根据方格编号获取这个编号的行和列\\n c += (n-1 - 2*c) * ((n-1-r) & 1) # 根据行数修正列数\\n if visited[r][c]: continue # 跳过搜索过的编号\\n visited[r][c] = True # 标记该编号已搜索\\n next_ = i if board[r][c] == - 1 else board[r][c] # 如果这个编号所在的方格可以转移到其他格子,转移到对应编号;否则就是在当前编号\\n if next_ == target: return cnt # 到达终点,直接返回最小移动数 \\n queue.append((next_, cnt)) # 加入队列\\n return -1 # 退出循环说明没有到达目的地\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int snakesAndLadders(vector<vector<int>>& board) {\\n int n = board.size(); // 获取方阵的边长\\n int target = n * n; // 获取方阵尺寸,也是最后要到达目的地\\n queue<pair<int, int>> queue_; // 队列用于BFS,存放待搜索的方格编号和到达该方格时的最少移动数\\n queue_.emplace(1, 0); // 初始{1,0}入队,表示起点1,0次移动\\n vector<vector<bool>> visited(n, vector<bool>(n)); // 用于BFS过程中标记方格是否搜索过\\n // BFS\\n while(!queue_.empty()){\\n auto node = queue_.front();\\n queue_.pop();\\n int curr = node.first, cnt = node.second; // 获取当前搜索的方格宾浩和到达该方格的最少移动数\\n cnt++; // 移动数加1\\n for(int i = curr + 1; i <= min(target, curr + 6); i++){\\n // 枚举所有下一个可搜索且未搜索过的方格编号\\n int r = n-1 - (i-1) / n, c = (i-1) % n; // 根据方格编号获取这个编号的行和列\\n c += (n - 1 - 2 * c) * ((n-1-r) & 1); // 根据行数修正列数\\n if(visited[r][c])continue; // 跳过搜索过的编号\\n visited[r][c] = true; // 标记该编号已搜索\\n int next = board[r][c] == - 1 ? i : board[r][c]; // 如果这个编号所在的方格可以转移到其他格子,转移到对应编号;否则就是在当前编号\\n if(next == target)return cnt; // 到达终点,直接返回最小移动数 \\n queue_.emplace(next, cnt); // 加入队列\\n }\\n }\\n return -1; // 退出循环说明没有到达目的地\\n }\\n};\\n
\\n","description":"909. 蛇梯棋 这道题要求从起点(编号为 1 的格子)到终点(编号为 n^2 的格子)的最短路径。\\n 路径搜索有两种方法:深度优先与广度优先。由于这道题要找到的是到达终点时的最短路径,因此 用广度优先搜索更方便些。【广度优先搜索就是每次把离当前节点最近的节点作为待搜索的节点】\\n\\n转移方向\\n\\n这道题和传统的矩阵路径搜索不一样的是,它的下一个搜索方格不是相邻方格,而是下6个编号。即如果当前处理的方格编号为 curr,那么其可以转移到编号属于 [curr + 1, min(curr + 6, n2)] 的方格里。\\n\\n{:width=200}\\n\\n其次…","guid":"https://leetcode.cn/problems/snakes-and-ladders//solution/javapython3cyan-du-you-xian-sou-suo-shu-27v0f","author":"lxk1203","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-25T10:18:11.768Z","media":[{"url":"https://pic.leetcode.cn/1700902522-soKAOL-image.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://pic.leetcode.cn/1700902829-LqphmB-image.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://pic.leetcode.cn/1700904528-ZfBZem-image.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://pic.leetcode.cn/1700905252-ujgGZo-image.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://pic.leetcode.cn/1700905455-SROsNq-image.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://pic.leetcode.cn/1700906221-xqCxVf-image.png","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两法:①直接模拟②简化计算, Python一行100%","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/liang-fa-1zhi-jie-mo-ni-2jian-hua-ji-sua-n3lf","content":"\\n\\nProblem: 2931. 购买物品的最大开销
\\n
[TOC]
\\n贪心思路,按题意直接模拟:
\\n循环按各行的末元素进行排列 → 提取最小价值 → 最小价值乘以天数 i 累加至 ans → 删去最小价值继续循环
\\n循环结束后返回 ans
\\n时间488ms击败 23.77%;内存25.26MB击败 99.69%
\\n###Python3
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n ans, mn = 0, len(values) * len(values[0]) + 1\\n for i in range(1, mn):\\n values.sort(key = lambda x : x[-1])\\n ans += values[0][-1] * i\\n if len(values[0]) == 1: values.pop(0)\\n else: values[0].pop()\\n return ans\\n
\\n贪心思路,对所有商品按排序从小到大逐项购买,所以可无视有几家商店、如何对商店去排序,不需要考虑每次选择哪个商店:
\\n无视商店因素,直接将 values 各行所有商品价值合并在一行排序,逐项乘以天数 i, 累加返回结果即可
\\npython一行可实现100%:
\\n时间112ms击败 100.00%;内存22.53MB击败 18.75%
\\n###Python3
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n return sum(i * x for i, x in enumerate(sorted(sum(values, [])), 1))\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 2931. 购买物品的最大开销 [TOC]\\n\\n贪心思路,按题意直接模拟:\\n\\n循环按各行的末元素进行排列 → 提取最小价值 → 最小价值乘以天数 i 累加至 ans → 删去最小价值继续循环\\n\\n循环结束后返回 ans\\n\\n时间488ms击败 23.77%;内存25.26MB击败 99.69%\\n\\n###Python3\\n\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n ans, mn = 0, len(values)…","guid":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/liang-fa-1zhi-jie-mo-ni-2jian-hua-ji-sua-n3lf","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-14T08:30:55.055Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学比较差,用O(N)双指针做的","url":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/shu-xue-bi-jiao-chai-yong-onshuang-zhi-z-ew6p","content":"\\n\\nProblem: 100127. 给小朋友们分糖果 II
\\n
[TOC]
\\n[0, min(limit, range)]
, 此时还剩下left = n - i
个糖果,分给2个小朋友。j
和 left-j
每份的取值范围都需要满足要求。分三种情况:\\nleft > 2*limit
, 此时无法满足条件。left <= limit
, 此时 j取[0, limit]
均可,有limit+1
种方法left > limit
且 left/2 <= limit
, 这个时候因为两个人是对称的,只需考虑第一个人的取值范围,也就是[n-limit, limit]
,共limit-(n-limit) + 1 = 2*limit - n + 1
种所以枚举i, 然后对left分情况讨论,一次遍历拿到结果。
\\n\\n\\n$O(n)$
\\n
\\n\\n添加空间复杂度, 示例: $O(1)$
\\n
###Java
\\n\\nclass Solution {\\n public long distributeCandies(int n, int limit) {\\n long res = 0;\\n for(int i = 0; i <= Math.min(limit,n); i++){\\n //第一个人分了i个,剩下left个糖。\\n int left = n - i;\\n // left分两组,每组都要小于等于limit\\n // 如果left>2*limit, 不合理\\n if(left > 2*limit){\\n continue;\\n }\\n //如果limit>=left, 随便分,left+1种\\n if(limit >= left){\\n res += left + 1;\\n }else{\\n res += 2*limit - left + 1;\\n }\\n }\\n \\n return res;\\n }\\n}\\n
\\n","description":"Problem: 100127. 给小朋友们分糖果 II [TOC]\\n\\nn个糖果分给3个小朋友,考虑分给第一个小朋友i个糖果,那么i的取值范围是[0, min(limit, range)], 此时还剩下left = n - i 个糖果,分给2个小朋友。\\n考虑left分成两份,位 j 和 left-j 每份的取值范围都需要满足要求。分三种情况:\\nleft > 2*limit, 此时无法满足条件。\\nleft <= limit, 此时 j取[0, limit]均可,有limit+1种方法\\nleft > limit 且 left/2 <= limit,…","guid":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/shu-xue-bi-jiao-chai-yong-onshuang-zhi-z-ew6p","author":"liuyh17","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-12T03:55:02.888Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(1) 容斥原理(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/o1-rong-chi-yuan-li-pythonjavacgo-by-end-2woj","content":"要计算合法方案数(每个小朋友分到的糖果都不超过 $\\\\textit{limit}$),可以先计算所有方案数(没有 $\\\\textit{limit}$ 限制),再减去不合法的方案数(至少一个小朋友分到的糖果超过 $\\\\textit{limit}$)。
\\n相当于把 $n$ 个无区别的小球放入 $3$ 个有区别的盒子,允许空盒的方案数。
\\n隔板法:假设 $n$ 个球和 $2$ 个隔板放到 $n+2$ 个位置,第一个隔板前的球放入第一个盒子,第一个隔板和第二个隔板之间的球放入第二个盒子,第二个隔板后的球放入第三个盒子。那么从 $n+2$ 个位置中选 $2$ 个位置放隔板,有 $C(n+2, 2)$ 种放法。
\\n注意隔板可以放在最左边或最右边,也可以连续放,对应着空盒的情况。例如第一个隔板放在最左边,意味着第一个盒子是空的;又例如第一个隔板和第二个隔板相邻,意味着第二个盒子是空的。
\\n另一种证明方法见 图解。
\\n设三个小朋友分别叫 $A,B,C$。
\\n只关注 $A$。如果 $A$ 分到的糖果超过 $\\\\textit{limit}$,那么先分给他 $\\\\textit{limit}+1$ 颗糖果,问题变成剩下 $n-(\\\\textit{limit}+1)$ 颗糖果分给三个小朋友的方案数,即 $C(n-(\\\\textit{limit}+1)+2, 2)$。注意 $B$ 和 $C$ 分到的糖果是否超过 $\\\\textit{limit}$ 我们是不关注的。
\\n⚠注意:分给 $A$ $\\\\textit{limit}+1$ 颗糖果后,还可以继续分给 $A$ 糖果。
\\n只关注 $B$ 的情况和只关注 $C$ 的情况同上,均为 $C(n-(\\\\textit{limit}+1)+2, 2)$。
\\n直接加起来,就是 $3\\\\cdot C(n-(\\\\textit{limit}+1)+2, 2)$,但这样就重复统计了「至少两个小朋友分到的糖果超过 $\\\\textit{limit}$」的情况,要减去。
\\n\\n\\n注:三个小朋友分到的糖果均超过 $\\\\textit{limit}$ 的情况,已经包含在至少两个小朋友分到的糖果超过 $\\\\textit{limit}$ 的情况中了。
\\n
只关注 $A$ 和 $B$。如果他们俩分到的糖果超过 $\\\\textit{limit}$,那么先分给他俩 $2\\\\cdot (\\\\textit{limit}+1)$ 颗糖果,问题变成剩下 $n-2\\\\cdot (\\\\textit{limit}+1)$ 颗糖果分给三个小朋友的方案数,即 $C(n-2\\\\cdot(\\\\textit{limit}+1)+2, 2)$。注意 $C$ 分到的糖果是否超过 $\\\\textit{limit}$ 我们是不关注的。
\\n只关注 $A,C$ 的情况和只关注 $B,C$ 的情况同上,均为 $C(n-2\\\\cdot(\\\\textit{limit}+1)+2, 2)$。
\\n直接加起来,就是 $3\\\\cdot C(n-2\\\\cdot(\\\\textit{limit}+1)+2, 2)$,但这样就重复统计了「三个小朋友分到的糖果均超过 $\\\\textit{limit}$」的情况,要减去。
\\n先分给三人一共 $3\\\\cdot (\\\\textit{limit}+1)$ 颗糖果,问题变成剩下 $n-3\\\\cdot (\\\\textit{limit}+1)$ 颗糖果分给三个小朋友的方案数,即 $C(n-3\\\\cdot(\\\\textit{limit}+1)+2, 2)$。
\\n不合法的方案数为
\\n$$
\\n至少一个 - (至少两个 - 三个) = 至少一个 - 至少两个 + 三个
\\n$$
这就是容斥原理。
\\n最后用所有方案数减去不合法的方案数,整理得到答案:
\\n$$
\\nC(n+2, 2) - 3\\\\cdot C(n-\\\\textit{limit}+1, 2) + 3\\\\cdot C(n-2\\\\cdot\\\\textit{limit}, 2) - C(n-3\\\\cdot \\\\textit{limit}-1, 2)
\\n$$
请看 视频讲解,欢迎点赞关注~
\\n###py
\\ndef c2(n: int) -> int:\\n return n * (n - 1) // 2 if n > 1 else 0\\n\\nclass Solution:\\n def distributeCandies(self, n: int, limit: int) -> int:\\n return c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1)\\n
\\n###java
\\nclass Solution {\\n public long distributeCandies(int n, int limit) {\\n return c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1);\\n }\\n\\n private long c2(int n) {\\n return n > 1 ? (long) n * (n - 1) / 2 : 0;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n long long c2(long long n) {\\n return n > 1 ? n * (n - 1) / 2 : 0;\\n }\\n\\npublic:\\n long long distributeCandies(int n, int limit) {\\n return c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1);\\n }\\n};\\n
\\n###c
\\nlong long c2(long long n) {\\n return n > 1 ? n * (n - 1) / 2 : 0;\\n}\\n\\nlong long distributeCandies(int n, int limit) {\\n return c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1);\\n}\\n
\\n###go
\\nfunc c2(n int) int64 {\\nif n < 2 {\\nreturn 0\\n}\\nreturn int64(n) * int64(n-1) / 2\\n}\\n\\nfunc distributeCandies(n, limit int) int64 {\\nreturn c2(n+2) - 3*c2(n-limit+1) + 3*c2(n-2*limit) - c2(n-3*limit-1)\\n}\\n
\\n###js
\\nfunction c2(n) {\\n return n > 1 ? n * (n - 1) / 2 : 0;\\n}\\n \\nvar distributeCandies = function(n, limit) {\\n return c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn distribute_candies(n: i32, limit: i32) -> i64 {\\n let c2 = |n| if n > 1 { n as i64 * (n as i64 - 1) / 2 } else { 0 };\\n c2(n + 2) - 3 * c2(n - limit + 1) + 3 * c2(n - 2 * limit) - c2(n - 3 * limit - 1)\\n }\\n}\\n
\\n见下面数学题单中的「§2.4 容斥原理」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"要计算合法方案数(每个小朋友分到的糖果都不超过 $\\\\textit{limit}$),可以先计算所有方案数(没有 $\\\\textit{limit}$ 限制),再减去不合法的方案数(至少一个小朋友分到的糖果超过 $\\\\textit{limit}$)。 所有方案数\\n\\n相当于把 $n$ 个无区别的小球放入 $3$ 个有区别的盒子,允许空盒的方案数。\\n\\n隔板法:假设 $n$ 个球和 $2$ 个隔板放到 $n+2$ 个位置,第一个隔板前的球放入第一个盒子,第一个隔板和第二个隔板之间的球放入第二个盒子,第二个隔板后的球放入第三个盒子。那么从 $n+2$ 个位置中选 $2…","guid":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/o1-rong-chi-yuan-li-pythonjavacgo-by-end-2woj","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-12T01:08:31.155Z","media":[{"url":"https://pic.leetcode.cn/1748767879-BFKbsF-%E5%AE%B9%E6%96%A5%E5%8E%9F%E7%90%863%E9%9B%86%E5%90%88.png","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:排序/最小堆(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/liang-chong-fang-fa-pai-xu-zui-xiao-dui-bzedc","content":"视频讲解 第四题。
\\n根据 排序不等式,$\\\\textit{values}[i][j]$ 越小的数,应该越早购买。
\\n最小的数在哪?
\\n$\\\\textit{values}$ 的最后一列一定包含最小的数,因为其余列的数都不会比最后一列的这 $m$ 个数小。
\\n次小的数在哪?
\\n去掉最小的数,然后考虑每行的最后一个数,这 $m$ 个数中一定包含次小的数,因为和上面一样,其余数字都不会比这 $m$ 个数小。
\\n依此类推,所以我们一定可以按照 $\\\\textit{values}[i][j]$ 从小到大的顺序取到所有元素。
\\n那么把所有数合并到一个数组中,然后排序计算。
\\n###py
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n a = sorted(x for row in values for x in row)\\n return sum(x * i for i, x in enumerate(a, 1))\\n
\\n###java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n int m = values.length;\\n int n = values[0].length;\\n int[] a = new int[m * n];\\n for (int i = 0; i < m; i++) {\\n System.arraycopy(values[i], 0, a, i * n, n);\\n }\\n Arrays.sort(a);\\n\\n long ans = 0;\\n for (int i = 0; i < a.length; i++) {\\n ans += (long) a[i] * (i + 1);\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxSpending(vector<vector<int>>& values) {\\n int m = values.size(), n = values[0].size();\\n vector<int> a;\\n a.reserve(m * n); // 预分配空间\\n for (auto& row : values) {\\n a.insert(a.end(), row.begin(), row.end());\\n }\\n ranges::sort(a);\\n\\n long long ans = 0;\\n for (int i = 0; i < a.size(); i++) {\\n ans += (long long) a[i] * (i + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxSpending(values [][]int) (ans int64) {\\n m, n := len(values), len(values[0])\\n a := make([]int, 0, m*n) // 预分配空间\\n for _, row := range values {\\n a = append(a, row...)\\n }\\n slices.Sort(a)\\n\\n for i, x := range a {\\n ans += int64(x) * int64(i+1)\\n }\\n return\\n}\\n
\\n也可以用最小堆模拟取数的流程。
\\n###py
\\nclass Solution:\\n def maxSpending(self, values: List[List[int]]) -> int:\\n h = [(a[-1], i) for i, a in enumerate(values)]\\n heapify(h)\\n ans = 0\\n for d in range(1, len(values) * len(values[0]) + 1):\\n v, i = heappop(h)\\n ans += v * d\\n values[i].pop()\\n if values[i]:\\n heappush(h, (values[i][-1], i))\\n return ans\\n
\\n###java
\\nclass Solution {\\n public long maxSpending(int[][] values) {\\n int m = values.length;\\n int n = values[0].length;\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> values[a[0]][a[1]] - values[b[0]][b[1]]);\\n for (int i = 0; i < m; i++) {\\n pq.offer(new int[]{i, n - 1});\\n }\\n\\n long ans = 0;\\n for (int d = 1; d <= m * n; d++) {\\n int[] p = pq.poll();\\n int i = p[0];\\n int j = p[1];\\n ans += (long) values[i][j] * d;\\n if (j > 0) {\\n pq.offer(new int[]{i, j - 1});\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maxSpending(vector<vector<int>>& values) {\\n int m = values.size(), n = values[0].size();\\n priority_queue<pair<int, int>, vector<pair<int, int>>, greater<>> pq;\\n for (int i = 0; i < m; i++) {\\n pq.emplace(values[i].back(), i);\\n }\\n\\n long long ans = 0;\\n for (int d = 1; d <= m * n; d++) {\\n auto [v, i] = pq.top();\\n pq.pop();\\n ans += (long long) v * d;\\n values[i].pop_back();\\n if (!values[i].empty()) {\\n pq.emplace(values[i].back(), i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n###go
\\nfunc maxSpending(values [][]int) (ans int64) {\\n m, n := len(values), len(values[0])\\n idx := make([]int, m)\\n for i := range idx {\\n idx[i] = i\\n }\\n h := &hp{idx, values}\\n heap.Init(h)\\n\\n for d := 1; d <= m*n; d++ {\\n a := values[idx[0]]\\n ans += int64(a[len(a)-1]) * int64(d)\\n if len(a) > 1 {\\n values[idx[0]] = a[:len(a)-1]\\n heap.Fix(h, 0)\\n } else {\\n heap.Pop(h)\\n }\\n }\\n return\\n}\\n\\ntype hp struct {\\n sort.IntSlice\\n values [][]int\\n}\\n\\nfunc (h hp) Less(i, j int) bool {\\n a, b := h.values[h.IntSlice[i]], h.values[h.IntSlice[j]]\\n return a[len(a)-1] < b[len(b)-1]\\n}\\nfunc (hp) Push(any) {}\\nfunc (h *hp) Pop() (_ any) { a := h.IntSlice; h.IntSlice = a[:len(a)-1]; return }\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"视频讲解 第四题。 思路\\n\\n根据 排序不等式,$\\\\textit{values}[i][j]$ 越小的数,应该越早购买。\\n\\n最小的数在哪?\\n\\n$\\\\textit{values}$ 的最后一列一定包含最小的数,因为其余列的数都不会比最后一列的这 $m$ 个数小。\\n\\n次小的数在哪?\\n\\n去掉最小的数,然后考虑每行的最后一个数,这 $m$ 个数中一定包含次小的数,因为和上面一样,其余数字都不会比这 $m$ 个数小。\\n\\n依此类推,所以我们一定可以按照 $\\\\textit{values}[i][j]$ 从小到大的顺序取到所有元素。\\n\\n那么把所有数合并到一个数组中,然后排序计算。\\n\\n写法一…","guid":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/liang-chong-fang-fa-pai-xu-zui-xiao-dui-bzedc","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-11T23:31:39.931Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学","url":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/shu-xue-by-tsreaper-hx1l","content":"我们先来分析一下这个问题:把 n
个糖果分给 2
个小朋友,确保没有人获得超过 limit
颗糖果的方案数。
容易发现:
\\nn <= limit
时,任何分法都是合理的,因此有 n + 1
种方案。这是一个关于 n
的公差为 1
的等差数列。limit < n <= 2 * limit
时,设分给第一个小朋友 x
颗糖果,有 x <= limit
以及 n - x <= limit
即 x >= n - limit
,因此共有 limit - (n - limit) + 1
= 2 * limit + 1 - n
。这是一个关于 n
的公差为 -1
的等差数列。n > 2 * limit
时,没有合法方案。接下来考虑给第三个小朋友分几颗糖果。由于第三个小朋友分到的糖果数在 0
到 limit
之间,那么剩下两个小朋友分到的糖果数就在 max(0, n - limit)
和 n
之间。因此答案就是把 max(0, n - limit) <= t <= n
颗糖果分给 2
个小朋友的方案数之和。
因此我们写一个函数 f
计算方案数的前缀和即可,主要逻辑就是等差数列求和,那么答案就是 f(n) - f(max(0, n - limit) - 1)
。复杂度 $\\\\mathcal{O}(1)$。
###c++
\\nclass Solution {\\npublic:\\n long long distributeCandies(int n, int limit) {\\n// 计算把至多 x 颗糖果分给 2 个小朋友的方案数之和\\n function<long long(int)> gao = [&](int x) {\\n if (x <= limit) {\\n// 求公差为 1 的等差数列之和\\n return 1LL * (1 + x + 1) * (x + 1) / 2;\\n } else if (x <= limit * 2) {\\n// 求出公差为 -1 的等差数列的首项和尾项\\n long long first = limit, last = limit - (x - limit) + 1;\\n return gao(limit) + (first + last) * (first - last + 1) / 2;\\n } else {\\n return gao(limit * 2);\\n }\\n };\\n\\n long long L = max(0, n - limit), R = n;\\n return gao(R) - gao(L - 1);\\n }\\n};\\n
\\n","description":"解法:数学 我们先来分析一下这个问题:把 n 个糖果分给 2 个小朋友,确保没有人获得超过 limit 颗糖果的方案数。\\n\\n容易发现:\\n\\n当 n <= limit 时,任何分法都是合理的,因此有 n + 1 种方案。这是一个关于 n 的公差为 1 的等差数列。\\n当 limit < n <= 2 * limit 时,设分给第一个小朋友 x 颗糖果,有 x <= limit 以及 n - x <= limit 即 x >= n - limit,因此共有 limit - (n - limit) + 1 = 2 * limit + 1 - n。这是一个关于 n 的…","guid":"https://leetcode.cn/problems/distribute-candies-among-children-ii//solution/shu-xue-by-tsreaper-hx1l","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-11T17:00:00.891Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"购买物品的最大开销(优先队列)","url":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/gou-mai-wu-pin-de-zui-da-kai-xiao-you-xi-0ves","content":"时间复杂度,$$O(mn*log(m))$$。
\\n空间复杂度,$$O(m)$$。
###C
\\n#define FATHER_NODE(i) (0 == (i) ? -1 : ((i) - 1 >> 1))\\n#define LEFT_NODE(i) (((i) << 1) + 1)\\n#define RIGHT_NODE(i) (((i) << 1) + 2)\\n#define ROW_INDEX(i) ((i) >> 16 & 0xFFFF)\\n#define COL_INDEX(i) ((i) & 0xFFFF)\\n#define MERGE_LONG(i,j,k) ((long long)(i) << 32 | (long long)(j) << 16 | (long long)(k))\\n\\n/* 优先队列。 */\\ntypedef struct\\n{\\n long long *arr;\\n int arrSize;\\n}\\nPriorityQueue;\\n\\n/* 优先队列的push、pop操作。详细实现见下。 */\\nstatic void queuePush(PriorityQueue *queue, long long t);\\nstatic void queuePop(PriorityQueue *queue);\\n\\n/* 主函数里的优先队列,总空间只要使用m就够了。\\n 天数d用long long类型存储,是为了避免乘法运算时越界。 */\\nlong long maxSpending(int **values, int valuesSize, int *valuesColSize)\\n{\\n const int m = valuesSize, n = valuesColSize[0];\\n int x = 0, y = 0;\\n long long arr[m];\\n PriorityQueue queue;\\n long long d = 1, result = 0;\\n /* 初始化。 */\\n queue.arr = arr;\\n queue.arrSize = 0;\\n /* 把每一行尾的数值先入队。 */\\n for(x = 0; m > x; x++)\\n {\\n queuePush(&queue, MERGE_LONG(values[x][n - 1], x, n - 1));\\n }\\n /* 逐个出队。 */\\n while(0 < queue.arrSize)\\n {\\n /* 把行号、列号取出后,出队。 */\\n x = ROW_INDEX(queue.arr[0]);\\n y = COL_INDEX(queue.arr[0]);\\n queuePop(&queue);\\n /* 计算当前消耗。 */\\n result += d * values[x][y];\\n d++;\\n /* 查看这一行是否还有剩余。 */\\n if(0 < y)\\n {\\n y--;\\n queuePush(&queue, MERGE_LONG(values[x][y], x, y));\\n }\\n }\\n return result;\\n}\\n\\n/* 优先队列push。 */\\nstatic void queuePush(PriorityQueue *queue, long long t)\\n{\\n int son = queue->arrSize, father = FATHER_NODE(son);\\n /* 新入数值后,数量加一。 */\\n queue->arrSize++;\\n /* 根据父子节点大小关系,进行位置调整。 */\\n while(-1 != father && t < queue->arr[father])\\n {\\n queue->arr[son] = queue->arr[father];\\n son = father;\\n father = FATHER_NODE(son);\\n }\\n /* 新加元素放到实际满足父子节点大小关系的位置。 */\\n queue->arr[son] = t;\\n return;\\n}\\n\\n/* 优先队列pop。 */\\nstatic void queuePop(PriorityQueue *queue)\\n{\\n int father = 0, left = LEFT_NODE(father), right = RIGHT_NODE(father), son = 0;\\n /* 队顶元素pop之后留下空缺,总数量减一。 */\\n queue->arrSize--;\\n /* 队尾元素调整上来,但先进行位置调整。 */\\n while((queue->arrSize > left && queue->arr[queue->arrSize] > queue->arr[left])\\n || (queue->arrSize > right && queue->arr[queue->arrSize] > queue->arr[right]))\\n {\\n son = (queue->arrSize > right && queue->arr[left] > queue->arr[right]) ? right : left;\\n queue->arr[father] = queue->arr[son];\\n father = son;\\n left = LEFT_NODE(father);\\n right = RIGHT_NODE(father);\\n }\\n /* 放到实际满足父子节点大小关系的位置。 */\\n queue->arr[father] = queue->arr[queue->arrSize];\\n return;\\n}\\n
\\n","description":"优先队列。 位运算。\\n题目给出的矩阵的每一行,都是按照非递增顺序排序好的,所有,最右边的商品,一定是所在这一行的最小值。\\n按照题目的意思,越往后的商品,乘以的系数$$d$$越大。那么,我们肯定要将价值较大的商品,在后面的天数购买,才能获得较大的总开销。\\n 以两个商品为例,它们的价格分别为$$pa$$和$$pb$$,且$$pa < pb$$,它们分别购得的天数为$$d$$和$$d+1$$。\\n 那么,如果先购买$$pa$$,总开销等于$$pad + pb(d+1)$$。\\n 如果先购买$$pb$$,总开销等于$$pa*(d+1) + pb*d$$。\\n 由于$…","guid":"https://leetcode.cn/problems/maximum-spending-after-buying-items//solution/gou-mai-wu-pin-de-zui-da-kai-xiao-you-xi-0ves","author":"heng-deng-shi","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-11-11T16:55:07.797Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分类讨论,Python双百","url":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-pythonshuang-bai-by-admi-un5c","content":"\\n\\nProblem: 100102. 数组的最小相等和
\\n
[TOC]
\\n贪心思路,计算 4 个变量,分情况讨论即可,一图秒懂:
\\n时间76ms击败 100.00%;内存32.11MB击败 100.00%
\\n###Python3
\\nclass Solution:\\n def minSum(self, nums1: List[int], nums2: List[int]) -> int:\\n sum1, sum2, cnt1, cnt2 = sum(nums1), sum(nums2), nums1.count(0), nums2.count(0)\\n if cnt1 == 0 and cnt2 == 0: return sum1 if sum1 == sum2 else -1 \\n if cnt1 == 0: return sum1 if sum1 >= sum2 + cnt2 else -1\\n if cnt2 == 0: return sum2 if sum2 >= sum1 + cnt1 else -1\\n return max(sum1 + cnt1, sum2 + cnt2)\\n
\\n您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100102. 数组的最小相等和 [TOC]\\n\\n贪心思路,计算 4 个变量,分情况讨论即可,一图秒懂:\\n\\n时间76ms击败 100.00%;内存32.11MB击败 100.00%\\n\\n###Python3\\n\\nclass Solution:\\n def minSum(self, nums1: List[int], nums2: List[int]) -> int:\\n sum1, sum2, cnt1, cnt2 = sum(nums1), sum(nums2), nums1.count(0), nums2.count(0…","guid":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-pythonshuang-bai-by-admi-un5c","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T06:05:02.292Z","media":[{"url":"https://pic.leetcode.cn/1698559356-LIPAdJ-image.png","type":"photo","width":1008,"height":664,"blurhash":"LCSF^c_3%L~q_3WWs:a}j]WBjsWB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分类讨论","url":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-by-tsreaper-bwpe","content":"如果 nums1
和 nums2
里都没有 $0$,那么两者的和无法改变,因此两者的和必须相等。
如果 nums1
和 nums2
其中一个有 $0$,另一个没有。假设 nums1
有 z
个 $0$,由于每个 $0$ 要变成正数,因此 nums1
的最小和为 sum(nums1) + z
。因此只要 sum(nums1) + z <= sum(nums2)
,就能把两者的和都变成 sum(nums2)
。
如果 nums1
和 nums2
都有 $0$,设 nums1
中有 z1
个 $0$,nums2
中有 z2
个 $0$,则 nums1
的最小和为 sum(nums1) + z1
,nums2
的最小和为 sum(nums2) + z2
。把小的值往大的凑即可,答案就是两个最小和中的更大值。
复杂度 $\\\\mathcal{O}(n + m)$。
\\n###c++
\\nclass Solution {\\npublic:\\n long long minSum(vector<int>& nums1, vector<int>& nums2) {\\n // sm1:sum(nums1),sm2:sum(nums2)\\n long long sm1 = 0, sm2 = 0;\\n // zero1:nums1 中几个 0,zero2:nums2 中几个 0\\n int zero1 = 0, zero2 = 0;\\n\\n for (int x : nums1) {\\n sm1 += max(x, 1);\\n if (x == 0) zero1++;\\n }\\n for (int x : nums2) {\\n sm2 += max(x, 1);\\n if (x == 0) zero2++;\\n }\\n\\n // 分类讨论:两个数组都没有 0\\n if (zero1 == 0 && zero2 == 0) {\\n if (sm1 == sm2) return sm1;\\n else return -1;\\n // 分类讨论:其中一个有 0\\n } else if (zero1 == 0) {\\n if (sm1 >= sm2) return sm1;\\n else return -1;\\n } else if (zero2 == 0) {\\n if (sm1 <= sm2) return sm2;\\n else return -1;\\n // 分类讨论:两者都有 0\\n } else {\\n return max(sm1, sm2);\\n }\\n }\\n};\\n
\\n","description":"解法:分类讨论 如果 nums1 和 nums2 里都没有 $0$,那么两者的和无法改变,因此两者的和必须相等。\\n\\n如果 nums1 和 nums2 其中一个有 $0$,另一个没有。假设 nums1 有 z 个 $0$,由于每个 $0$ 要变成正数,因此 nums1 的最小和为 sum(nums1) + z。因此只要 sum(nums1) + z <= sum(nums2),就能把两者的和都变成 sum(nums2)。\\n\\n如果 nums1 和 nums2 都有 $0$,设 nums1 中有 z1 个 $0$,nums2 中有 z2 个 $0$,则 nums1…","guid":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-by-tsreaper-bwpe","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T04:38:43.028Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"树 DP","url":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/shu-dp-by-tsreaper-5629","content":"一个朴素的想法是维护 f[u][t][0/1]
表示节点 u
的祖先节点(含自己)一共做了 t
次除二操作,且自己做了 0/1
次除二操作的情况下,以 u
为根的子树能收集到的最多金币。设 v
是 u
的子节点,则转移方程为
best = sum(max(f[v][t][0], f[v][t + 1][1]))\\nf[u][t][0] = coins[u] / (2 ** t) - k + best\\nf[u][t][1] = coins[u] / (2 ** t) + best\\n
\\n其中 best
就是枚举子节点是否做了除二操作。答案就是 max(f[0][t])
。
这样的复杂度为 $\\\\mathcal{O}(n^2)$,因为 t
的最大值是树的深度。
然而,除二操作只要进行 log(coins[i])
次,即可把 coins[i]
降为 $0$。因此我们只需要关心 t <= log(coins[i])
的情况即可。复杂度 $\\\\mathcal{O}(n\\\\log A)$。
###c++
\\nclass Solution {\\npublic:\\n int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int K) {\\n int n = coins.size();\\n const int MAXP = 20;\\n\\n // 建图\\n vector<int> e[n];\\n for (auto &edge : edges) {\\n e[edge[0]].push_back(edge[1]);\\n e[edge[1]].push_back(edge[0]);\\n }\\n\\n const long long INF = 1e18;\\n long long f[n][MAXP][2];\\n for (int i = 0; i < n; i++) for (int j = 0; j < MAXP; j++) f[i][j][0] = f[i][j][1] = -INF;\\n // 树 dp\\n function<void(int, int)> dp = [&](int sn, int fa) {\\n long long now = coins[sn];\\n for (int j = 0; j < MAXP; j++) {\\n f[sn][j][0] = now - K;\\n if (j > 0) f[sn][j][1] = now;\\n now >>= 1;\\n }\\n // 枚举子节点的操作\\n for (int fn : e[sn]) if (fn != fa) {\\n dp(fn, sn);\\n for (int j = 0; j < MAXP; j++) {\\n // 这里的 min 是因为我们只考虑 log 次操作\\n long long best = max(f[fn][j][0], f[fn][min(MAXP - 1, j + 1)][1]);\\n f[sn][j][0] += best;\\n f[sn][j][1] += best;\\n }\\n }\\n };\\n dp(0, -1);\\n\\n long long ans = 0;\\n for (int j = 0; j < MAXP; j++) ans = max({ans, f[0][j][0], f[0][j][1]});\\n return ans;\\n }\\n};\\n
\\n","description":"解法:树 DP 一个朴素的想法是维护 f[u][t][0/1] 表示节点 u 的祖先节点(含自己)一共做了 t 次除二操作,且自己做了 0/1 次除二操作的情况下,以 u 为根的子树能收集到的最多金币。设 v 是 u 的子节点,则转移方程为\\n\\nbest = sum(max(f[v][t][0], f[v][t + 1][1]))\\nf[u][t][0] = coins[u] / (2 ** t) - k + best\\nf[u][t][1] = coins[u] / (2 ** t) + best\\n\\n\\n其中 best 就是枚举子节点是否做了除二操作。答案就是 m…","guid":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/shu-dp-by-tsreaper-5629","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T04:30:54.491Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【小羊肖恩】树形 DP:关键——如何缩减状态?除法除很多次会没用!","url":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/xiao-yang-xiao-en-shu-xing-dpguan-jian-r-7ak9","content":"这题看起来是很典型的树形动态规划。
\\n但是,我们定义状态不能只包含我们讨论的是哪个子树,因为这并不能完整定义走到该子树的状态,我们还得看走到子树经过的点中有多少个点进行的是第二种操作,即该子树中元素被折半了多少次。
\\n因此我们考虑以这两个变量作为我们动态规划的状态。但是这样我们状态会有多少个呢?如果不进行任何处理,我们的状态会达到 $\\\\mathcal{O}(n\\\\times n)=\\\\mathcal{O}(n^2)$,因为总共有 $n$ 个节点,深度可能达到 $\\\\mathcal{O}(n)$,因此此前折半数量有可能达到 $\\\\mathcal{O}(n)$。
\\n但是,我们发现,每个节点的 coins[i]
不会超过 $10^4$,因此在 $14$ 次操作后总会变成 $0$,因此我们折半数量可以取与 $14$ 的最小值,这样我们的状态总数便不超过 $14n$ 了。
状态转移的过程中,我们只需要讨论当前节点选择的是何种操作,再往下进行答案的寻找即可,具体可见代码。如果你使用的是递归,要记得加上记忆化搜索,避免重复计算相同状态。每个状态的转移次数是 $\\\\mathcal{O}(1)$ 的。
\\n因此,时间复杂度为 $\\\\mathcal{O}(n \\\\log_2M)$.
\\n###Python
\\nclass Solution:\\n def maximumPoints(self, edges: List[List[int]], coins: List[int], k: int) -> int:\\n n = len(coins)\\n path = [[] for _ in range(n)]\\n for u, v in edges:\\n path[u].append(v)\\n path[v].append(u)\\n @cache\\n def dfs(u, p, times):\\n v = coins[u] >> times\\n res0, res1 = v - k, v // 2\\n new_times = min(times + 1, 14)\\n for v in path[u]:\\n if v != p:\\n res0 += dfs(v, u, times)\\n res1 += dfs(v, u, new_times)\\n return max(res0, res1)\\n ans = dfs(0, -1, 0)\\n # 清空 cache 能让你的 Python 跑得快一点\\n dfs.cache_clear()\\n return ans\\n
\\n","description":"这题看起来是很典型的树形动态规划。 但是,我们定义状态不能只包含我们讨论的是哪个子树,因为这并不能完整定义走到该子树的状态,我们还得看走到子树经过的点中有多少个点进行的是第二种操作,即该子树中元素被折半了多少次。\\n\\n因此我们考虑以这两个变量作为我们动态规划的状态。但是这样我们状态会有多少个呢?如果不进行任何处理,我们的状态会达到 $\\\\mathcal{O}(n\\\\times n)=\\\\mathcal{O}(n^2)$,因为总共有 $n$ 个节点,深度可能达到 $\\\\mathcal{O}(n)$,因此此前折半数量有可能达到 $\\\\mathcal{O}(n)$。\\n\\n但是…","guid":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/xiao-yang-xiao-en-shu-xing-dpguan-jian-r-7ak9","author":"Yawn_Sean","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T04:18:03.593Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分类讨论(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-by-endlesscheng-y57m","content":"下界分析:
\\n分类讨论:
\\n###py
\\nclass Solution:\\n def minSum(self, nums1: List[int], nums2: List[int]) -> int:\\n s1 = sum(max(x, 1) for x in nums1)\\n s2 = sum(max(x, 1) for x in nums2)\\n if s1 < s2 and 0 not in nums1 or \\\\\\n s2 < s1 and 0 not in nums2:\\n return -1\\n return max(s1, s2)\\n
\\n###java
\\nclass Solution {\\n private record Pair(long sum, boolean zero) {}\\n\\n public long minSum(int[] nums1, int[] nums2) {\\n Pair p1 = calc(nums1);\\n Pair p2 = calc(nums2);\\n if (!p1.zero && p1.sum < p2.sum || !p2.zero && p2.sum < p1.sum) {\\n return -1;\\n }\\n return Math.max(p1.sum, p2.sum);\\n }\\n\\n private Pair calc(int[] nums) {\\n long sum = 0;\\n boolean zero = false;\\n for (int x : nums) {\\n if (x == 0) {\\n zero = true;\\n sum++;\\n } else {\\n sum += x;\\n }\\n }\\n return new Pair(sum, zero);\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\n pair<long long, bool> calc(vector<int>& nums) {\\n long long sum = 0;\\n bool zero = false;\\n for (int x : nums) {\\n if (x == 0) {\\n zero = true;\\n sum++;\\n } else {\\n sum += x;\\n }\\n }\\n return {sum, zero};\\n }\\n\\npublic:\\n long long minSum(vector<int>& nums1, vector<int>& nums2) {\\n auto [s1, zero1] = calc(nums1);\\n auto [s2, zero2] = calc(nums2);\\n if (!zero1 && s1 < s2 || !zero2 && s2 < s1) {\\n return -1;\\n }\\n return max(s1, s2);\\n }\\n};\\n
\\n###go
\\nfunc calc(nums []int) (sum int64, zero bool) {\\nfor _, x := range nums {\\nif x == 0 {\\nzero = true\\nsum++\\n} else {\\nsum += int64(x)\\n}\\n}\\nreturn\\n}\\n\\nfunc minSum(nums1, nums2 []int) int64 {\\ns1, zero1 := calc(nums1)\\ns2, zero2 := calc(nums2)\\nif !zero1 && s1 < s2 || !zero2 && s2 < s1 {\\nreturn -1\\n}\\nreturn max(s1, s2)\\n}\\n
\\n###c
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long calc(int* nums, int numsSize, bool* zero) {\\n long long sum = 0;\\n *zero = false;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] == 0) {\\n *zero = true;\\n sum++;\\n } else {\\n sum += nums[i];\\n }\\n }\\n return sum;\\n}\\n\\nlong long minSum(int* nums1, int nums1Size, int* nums2, int nums2Size) {\\n bool zero1, zero2;\\n long long s1 = calc(nums1, nums1Size, &zero1);\\n long long s2 = calc(nums2, nums2Size, &zero2);\\n if (!zero1 && s1 < s2 || !zero2 && s2 < s1) {\\n return -1;\\n }\\n return MAX(s1, s2);\\n}\\n
\\n###js
\\nfunction calc(nums) {\\n let sum = 0;\\n let zero = false;\\n for (const x of nums) {\\n if (x === 0) {\\n zero = true;\\n sum++;\\n } else {\\n sum += x;\\n }\\n }\\n return [sum, zero];\\n}\\n\\nvar minSum = function(nums1, nums2) {\\n const [s1, zero1] = calc(nums1);\\n const [s2, zero2] = calc(nums2);\\n if (!zero1 && s1 < s2 || !zero2 && s2 < s1) {\\n return -1;\\n }\\n return Math.max(s1, s2);\\n};\\n
\\n###rust
\\nimpl Solution {\\n pub fn min_sum(nums1: Vec<i32>, nums2: Vec<i32>) -> i64 {\\n fn calc(nums: Vec<i32>) -> (i64, bool) {\\n let mut sum = 0;\\n let mut zero = false;\\n for x in nums {\\n if x == 0 {\\n zero = true;\\n sum += 1;\\n } else {\\n sum += x as i64;\\n }\\n }\\n (sum, zero)\\n }\\n\\n let (s1, zero1) = calc(nums1);\\n let (s2, zero2) = calc(nums2);\\n if !zero1 && s1 < s2 || !zero2 && s2 < s1 {\\n return -1;\\n }\\n s1.max(s2)\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"下界分析: 先把数组中的 $0$ 替换成最小的正整数 $1$,然后再看下一步要怎么走。\\n替换后,设 $\\\\textit{nums}_1$ 的元素和为 $s_1$,$\\\\textit{nums}_2$ 的元素和为 $s_2$。这是元素和的最小值,只能增大,不能减小。\\n所以下一步是,把较小的元素和增大到等于较大的元素和。\\n\\n分类讨论:\\n\\n如果 $s_1 < s_2$ 且 $\\\\textit{nums}_1$ 中没有 $0$,那么 $s_1$ 无法增大,无法让 $s_1=s_2$,返回 $-1$。\\n如果 $s_2 < s_1$ 且 $\\\\textit{nums}_2…","guid":"https://leetcode.cn/problems/minimum-equal-sum-of-two-arrays-after-replacing-zeros//solution/fen-lei-tao-lun-by-endlesscheng-y57m","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T04:14:57.784Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"树形 DP:记忆化搜索 / 递推(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/shu-xing-dp-ji-yi-hua-sou-suo-by-endless-phzx","content":"floor(coins[i] / 2)
等价于 coins[i] >> 1
。
右移运算是可以叠加的,即 (x >> 1) >> 1
等于 x >> 2
。
我们可以在递归的过程中,额外记录从根节点递归到当前节点的过程中,一共执行了多少次右移,也就是子树中的每个节点值需要右移的次数。
\\n故定义 $\\\\textit{dfs}(i,j)$ 表示递归到以 $i$ 为根的子树,在上面已经执行了 $j$ 次右移的前提下,我们在这棵子树中最多可以得到多少积分。
\\n用「选或不选」来思考,即是否执行右移:
\\n两种情况取最大值,得
\\n$$
\\n\\\\textit{dfs}(i,j) = \\\\max
\\n\\\\begin{cases}
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ j)-k + \\\\sum_{\\\\textit{ch}} \\\\textit{dfs}(\\\\textit{ch},j) \\\\
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ (j+1)) + \\\\sum_{\\\\textit{ch}} \\\\textit{dfs}(\\\\textit{ch},j+1) \\\\
\\n\\\\end{cases}
\\n$$
递归入口:$\\\\textit{dfs}(0,0)$。其中 $i=0$ 表示根节点。一开始没有执行右移,所以 $j=0$。
\\n一个数最多右移多少次,就变成 $0$ 了?
\\n设 $w$ 是 $\\\\textit{coins}[i]$ 的二进制长度,那么 $\\\\textit{coins}[i]$ 右移 $w$ 次后就是 $0$ 了。
\\n在本题的数据范围下,$w\\\\le 14$。
\\n所以如果在递归过程中发现 $j+1 = 14$,就不执行右移,因为此时 $\\\\textit{dfs}(\\\\textit{ch},j+1)$ 子树中的每个节点值都要右移 $14$ 次,算出的结果一定是 $0$。既然都知道递归的结果了,那就不需要递归了。
\\n此外,为避免错把父亲当作儿子,可以额外传入 $\\\\textit{fa}$ 表示父节点,遍历 $i$ 的邻居时,跳过邻居节点是 $\\\\textit{fa}$ 的情况。
\\n关于记忆化搜索的原理,请看视频讲解 动态规划入门:从记忆化搜索到递推。
\\n\\n###py
\\nclass Solution:\\n def maximumPoints(self, edges: List[List[int]], coins: List[int], k: int) -> int:\\n g = [[] for _ in coins]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int, fa: int) -> int:\\n res1 = (coins[i] >> j) - k\\n res2 = coins[i] >> (j + 1)\\n for ch in g[i]:\\n if ch != fa:\\n res1 += dfs(ch, j, i) # 不右移\\n if j < 13: # j+1 >= 14 相当于 res2 += 0,无需递归\\n res2 += dfs(ch, j + 1, i) # 右移\\n return max(res1, res2)\\n\\n return dfs(0, 0, -1)\\n
\\n###java
\\nclass Solution {\\n public int maximumPoints(int[][] edges, int[] coins, int k) {\\n int n = coins.length;\\n List<Integer>[] g = new ArrayList[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n\\n int[][] memo = new int[n][14];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(0, 0, -1, memo, g, coins, k);\\n }\\n\\n private int dfs(int i, int j, int fa, int[][] memo, List<Integer>[] g, int[] coins, int k) {\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j];\\n }\\n int res1 = (coins[i] >> j) - k;\\n int res2 = coins[i] >> (j + 1);\\n for (int ch : g[i]) {\\n if (ch == fa) continue;\\n res1 += dfs(ch, j, i, memo, g, coins, k); // 不右移\\n if (j < 13) { // j+1 >= 14 相当于 res2 += 0,无需递归\\n res2 += dfs(ch, j + 1, i, memo, g, coins, k); // 右移\\n }\\n }\\n return memo[i][j] = Math.max(res1, res2); // 记忆化\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {\\n int n = coins.size();\\n vector<vector<int>> g(n);\\n for (auto& e: edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n\\n array<int, 14> init_val;\\n ranges::fill(init_val, -1); // -1 表示没有计算过\\n vector memo(n, init_val);\\n auto dfs = [&](this auto&& dfs, int i, int j, int fa) {\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n int res1 = (coins[i] >> j) - k;\\n int res2 = coins[i] >> (j + 1);\\n for (int ch : g[i]) {\\n if (ch == fa) continue;\\n res1 += dfs(ch, j, i); // 不右移\\n if (j < 13) { // j+1 >= 14 相当于 res2 += 0,无需递归\\n res2 += dfs(ch, j + 1, i); // 右移\\n }\\n }\\n return res = max(res1, res2); // 记忆化\\n };\\n return dfs(0, 0, -1);\\n }\\n};\\n
\\n###go
\\nfunc maximumPoints(edges [][]int, coins []int, k int) int {\\n n := len(coins)\\n g := make([][]int, n)\\n for _, e := range edges {\\n x, y := e[0], e[1]\\n g[x] = append(g[x], y)\\n g[y] = append(g[y], x)\\n }\\n\\n memo := make([][14]int, n)\\n for i := range memo {\\n for j := range memo[i] {\\n memo[i][j] = -1\\n }\\n }\\n var dfs func(int, int, int) int\\n dfs = func(i, j, fa int) (res int) {\\n p := &memo[i][j]\\n if *p != -1 {\\n return *p\\n }\\n defer func() { *p = res }()\\n res1 := coins[i]>>j - k\\n res2 := coins[i] >> (j + 1)\\n for _, ch := range g[i] {\\n if ch != fa {\\n res1 += dfs(ch, j, i) // 不右移\\n if j < 13 { // j+1 >= 14 相当于 res2 += 0 无需递归\\n res2 += dfs(ch, j+1, i) // 右移\\n }\\n }\\n }\\n return max(res1, res2)\\n }\\n return dfs(0, 0, -1)\\n}\\n
\\n类似把记忆化搜索 1:1 翻译成递推的过程,我们也可以从下往上算。
\\n去掉参数 $j$,改成每个节点 $i$ 返回一个长为 $14$ 的列表 $f_i$,其中 $f_i[j]$ 对应上面 $\\\\textit{dfs}(i,j)$ 的计算结果。
\\n递推式为
\\n$$
\\nf_i[j] = \\\\max
\\n\\\\begin{cases}
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ j)-k + \\\\sum_{\\\\textit{ch}} f_{\\\\textit{ch}}[j] \\\\
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ (j+1)) + \\\\sum_{\\\\textit{ch}} f_{\\\\textit{ch}}[j+1] \\\\
\\n\\\\end{cases}
\\n$$
把 $\\\\sum_{\\\\textit{ch}} f_{\\\\textit{ch}}[j]$ 累加到 $s[j]$ 中,上式为
\\n$$
\\nf_i[j] = \\\\max
\\n\\\\begin{cases}
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ j)-k + s[j] \\\\
\\n(\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ (j+1)) + s[j+1] \\\\
\\n\\\\end{cases}
\\n$$
特判 $j=13$ 的情况,上式为
\\n$$
\\nf_i[13] = (\\\\textit{coins}[i]\\\\ \\\\texttt{>>}\\\\ 13)-k + s[13]
\\n$$
代码实现时,可以直接把算出的结果原地保存到 $s$ 数组中。
\\n###py
\\nclass Solution:\\n def maximumPoints(self, edges: List[List[int]], coins: List[int], k: int) -> int:\\n g = [[] for _ in coins]\\n for x, y in edges:\\n g[x].append(y)\\n g[y].append(x)\\n\\n def dfs(x: int, fa: int) -> List[int]:\\n s = [0] * 14\\n for y in g[x]:\\n if y != fa:\\n fy = dfs(y, x)\\n for j, v in enumerate(fy):\\n s[j] += v\\n for j in range(13):\\n s[j] = max((coins[x] >> j) - k + s[j], (coins[x] >> (j + 1)) + s[j + 1])\\n s[13] += (coins[x] >> 13) - k\\n return s\\n\\n return dfs(0, -1)[0]\\n
\\n###java
\\nclass Solution {\\n public int maximumPoints(int[][] edges, int[] coins, int k) {\\n List<Integer>[] g = new ArrayList[coins.length];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n g[x].add(y);\\n g[y].add(x);\\n }\\n return dfs(0, -1, g, coins, k)[0];\\n }\\n\\n private int[] dfs(int x, int fa, List<Integer>[] g, int[] coins, int k) {\\n int[] s = new int[14];\\n for (int y : g[x]) {\\n if (y == fa) continue;\\n int[] fy = dfs(y, x, g, coins, k);\\n for (int j = 0; j < 14; j++) {\\n s[j] += fy[j];\\n }\\n }\\n for (int j = 0; j < 13; j++) {\\n s[j] = Math.max((coins[x] >> j) - k + s[j], (coins[x] >> (j + 1)) + s[j + 1]);\\n }\\n s[13] += (coins[x] >> 13) - k;\\n return s;\\n }\\n}\\n
\\n###cpp
\\nclass Solution {\\npublic:\\n int maximumPoints(vector<vector<int>>& edges, vector<int>& coins, int k) {\\n vector<vector<int>> g(coins.size());\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n g[x].push_back(y);\\n g[y].push_back(x);\\n }\\n\\n auto dfs = [&](this auto&& dfs, int x, int fa) -> array<int, 14> {\\n array<int, 14> s{};\\n for (int y : g[x]) {\\n if (y == fa) continue;\\n auto fy = dfs(y, x);\\n for (int j = 0; j < 14; j++) {\\n s[j] += fy[j];\\n }\\n }\\n for (int j = 0; j < 13; j++) {\\n s[j] = max((coins[x] >> j) - k + s[j], (coins[x] >> (j + 1)) + s[j + 1]);\\n }\\n s[13] += (coins[x] >> 13) - k;\\n return s;\\n };\\n return dfs(0, -1)[0];\\n }\\n};\\n
\\n###go
\\nfunc maximumPoints(edges [][]int, coins []int, k int) int {\\n n := len(coins)\\n g := make([][]int, n)\\n for _, e := range edges {\\n x, y := e[0], e[1]\\n g[x] = append(g[x], y)\\n g[y] = append(g[y], x)\\n }\\n\\n var dfs func(int, int) [14]int\\n dfs = func(x, fa int) (s [14]int) {\\n for _, y := range g[x] {\\n if y != fa {\\n fy := dfs(y, x)\\n for j, v := range fy {\\n s[j] += v\\n }\\n }\\n }\\n for j := range 13 {\\n s[j] = max((coins[x]>>j)-k+s[j], (coins[x]>>(j+1))+s[j+1])\\n }\\n s[13] += (coins[x] >> 13) - k\\n return\\n }\\n return dfs(0, -1)[0]\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:记忆化搜索 floor(coins[i] / 2) 等价于 coins[i] >> 1。\\n\\n右移运算是可以叠加的,即 (x >> 1) >> 1 等于 x >> 2。\\n\\n我们可以在递归的过程中,额外记录从根节点递归到当前节点的过程中,一共执行了多少次右移,也就是子树中的每个节点值需要右移的次数。\\n\\n故定义 $\\\\textit{dfs}(i,j)$ 表示递归到以 $i$ 为根的子树,在上面已经执行了 $j$ 次右移的前提下,我们在这棵子树中最多可以得到多少积分。\\n\\n用「选或不选」来思考,即是否执行右移:\\n\\n不右移:答案为 $(\\\\textit{coins}[i…","guid":"https://leetcode.cn/problems/maximum-points-after-collecting-coins-from-all-nodes//solution/shu-xing-dp-ji-yi-hua-sou-suo-by-endless-phzx","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-29T04:10:12.087Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Python超短一行,找到离任何数最近的数","url":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/pythonyi-xing-xian-jian-01rang-zheng-shu-rmvt","content":"你知道吗,min其实是有键函数的。
\\n但是怎么在距离相同的数中选择正数呢?
\\n很简单,-0.1,正数就离0更近了
###Python3
\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n return min(nums, key=lambda n:abs(n-0.1))\\n
\\n用这个方法,你其实可以找到离任何数最近的数,只需要调整-0.1的大小
\\n例如你想找到离 x 最近的数,那就 -x :
\\nmin(nums, key=lambda n:abs(n-x))\\n
\\n","description":"你知道吗,min其实是有键函数的。 但是怎么在距离相同的数中选择正数呢?\\n 很简单,-0.1,正数就离0更近了\\n\\n###Python3\\n\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n return min(nums, key=lambda n:abs(n-0.1))\\n\\n\\n用这个方法,你其实可以找到离任何数最近的数,只需要调整-0.1的大小\\n\\n例如你想找到离 x 最近的数,那就 -x :\\n\\nmin(nums, key=lambda n:abs(n-x))…","guid":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/pythonyi-xing-xian-jian-01rang-zheng-shu-rmvt","author":"i2everent-moser9x1","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-20T08:42:33.045Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一题三解 | 朴素动态规划 -> 维护转移来源(Kotlin)","url":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-ii//solution/yi-ti-san-jie-po-su-dong-tai-gui-hua-wei-mfsu","content":"满足条件的子序列中字符串长度一定是相等的,我们先对字符串按照长度分组,再按照类似 300. 最长递增子序列 的解法做动态规划。
\\nclass Solution {\\n fun getWordsInLongestSubsequence(n: Int, words: Array<String>, groups: IntArray): List<String> {\\n val U = 10\\n var ret = Collections.emptyList<String>()\\n // 分桶\\n val buckets = Array(U + 1) { ArrayList<Int>() }\\n for ((i, word) in words.withIndex()) {\\n buckets[word.length].add(i)\\n }\\n\\n fun check(i: Int, j: Int): Boolean {\\n if (groups[i] == groups[j]) return false\\n var cnt = 0\\n for (k in words[i].indices) {\\n if (words[i][k] != words[j][k]) cnt ++\\n if (cnt > 1) return false\\n }\\n return cnt == 1\\n }\\n\\n // 动态规划\\n for (bucket in buckets) {\\n if (bucket.isEmpty()) continue\\n val m = bucket.size\\n // dp[i] 表示以 [i] 为结尾的结果数组\\n val dp = Array(m) { LinkedList<String>() }\\n for (i in 0 until m) {\\n // maxIndex:最优状态\\n var maxIndex = -1\\n // 枚举子状态\\n for (j in i - 1 downTo 0) {\\n if (check(bucket[i], bucket[j]) && (-1 == maxIndex || dp[j].size > dp[maxIndex].size)) {\\n maxIndex = j\\n }\\n }\\n if (-1 != maxIndex) {\\n dp[i].addAll(dp[maxIndex])\\n }\\n dp[i].add(words[bucket[i]])\\n // 更新方案\\n if (ret.isEmpty() || dp[i].size > ret.size) {\\n ret = dp[i]\\n }\\n }\\n }\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n直接做动态规划也可以,只需要在 $check$ 函数中增加判断字符串长度的判断。
\\nclass Solution {\\n fun getWordsInLongestSubsequence(n: Int, words: Array<String>, groups: IntArray): List<String> {\\n var ret = Collections.emptyList<String>()\\n\\n fun check(i: Int, j: Int): Boolean {\\n if (words[i].length != words[j].length) return false\\n if (groups[i] == groups[j]) return false\\n var cnt = 0\\n for (k in words[i].indices) {\\n if (words[i][k] != words[j][k]) cnt ++\\n if (cnt > 1) return false\\n }\\n return cnt == 1\\n }\\n\\n // 动态规划\\n // dp[i] 表示以 [i] 为结尾的结果数组\\n val dp = Array(n) { LinkedList<String>() }\\n for (i in 0 until n) {\\n // maxIndex:最优状态\\n var maxIndex = -1\\n // 枚举子状态\\n for (j in i - 1 downTo 0) {\\n if (check(i, j) && (-1 == maxIndex || dp[j].size > dp[maxIndex].size)) {\\n maxIndex = j\\n }\\n }\\n if (-1 != maxIndex) dp[i].addAll(dp[maxIndex])\\n dp[i].add(words[i])\\n // 更新方案\\n if (ret.isEmpty() || dp[i].size > ret.size) ret = dp[i]\\n }\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n在常规的动态规划中,我们不会关心转移来源,因此在前两种解法中,我们记录了每个子状态的方案。
\\n其实,我们可以使用另一个数组 $from$ 维护转移来源。
\\nclass Solution {\\n fun getWordsInLongestSubsequence(n: Int, words: Array<String>, groups: IntArray): List<String> {\\n\\n fun check(i: Int, j: Int): Boolean {\\n if (words[i].length != words[j].length) return false\\n if (groups[i] == groups[j]) return false\\n var cnt = 0\\n for (k in words[i].indices) {\\n if (words[i][k] != words[j][k]) cnt ++\\n if (cnt > 1) return false\\n }\\n return cnt == 1\\n }\\n\\n // dp[i] 表示以 [i] 为结尾的结果数组的长度\\n val dp = IntArray(n)\\n val from = IntArray(n)\\n var max = 0\\n for (i in 0 until n) {\\n // 枚举子状态\\n for (j in i - 1 downTo 0) {\\n if (check(i, j) && dp[j] > dp[i]) {\\n dp[i] = dp[j]\\n from[i] = j\\n }\\n }\\n dp[i] += 1 // 追加当前元素\\n if (dp[i] > dp[max]) max = i // 维护最优解\\n }\\n // 从最优解还原方案\\n val ret = LinkedList<String>()\\n repeat (dp[max]) {\\n ret.addFirst(words[max])\\n max = from[max]\\n }\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n本题属于「子序列 + 相邻元素相关」,用枚举选哪个解决。
\\n类似最长递增子序列,定义 $f[i]$ 表示在子序列第一个字符串是 $\\\\textit{words}[i]$ 的前提下,从后缀 $[i,n-1]$ 中能选出的最长子序列的长度。为什么定义成后缀?方便后面输出具体方案。
\\n枚举子序列的第二个字符串 $\\\\textit{words}[j]$,如果 $\\\\textit{groups}[j] \\\\ne \\\\textit{groups}[i]$ 且 $\\\\textit{words}[j]$ 和 $\\\\textit{words}[i]$ 满足题目要求(长度相等且汉明距离为 $1$),那么问题变成在子序列第一个字符串是 $\\\\textit{words}[j]$ 的前提下,从后缀 $[j,n-1]$ 中能选出的最长子序列的长度,即 $f[j]$。在 $f[j]$ 子序列的前面加上 $\\\\textit{words}[i]$,得 $f[i] = f[j]+1$。
\\n取最大值,得
\\n$$
\\nf[i] = 1 + \\\\max_{j=i+1}^{n-1} f[j]
\\n$$
上式中的 $j$ 需要满足前文说的要求。
\\n计算 $\\\\max$ 的过程中,记录最优转移来源 $\\\\textit{from}[i] = j$,方便输出具体方案。
\\n初始值:$f[i]=1$,表示 $\\\\textit{words}[i]$ 单独组成一个长为 $1$ 的子序列。
\\n答案:$\\\\max(f)$。
\\n设 $\\\\textit{maxI}$ 是 $\\\\max(f)$ 在 $f$ 中的下标,即 $f[\\\\textit{maxI}]=\\\\max(f)$。如果有多个这样的下标,随便取哪个都行。
\\n从 $i=\\\\textit{maxI}$ 开始循环,每次把 $\\\\textit{words}[i]$ 加入答案,然后更新
\\n$$
\\ni = \\\\textit{from}[i]
\\n$$
表示顺着转移来源往右走,找子序列的下一个字符串。
\\n找到 $\\\\max(f)$ 个字符串时,退出循环。
\\nclass Solution:\\n def getWordsInLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:\\n def check(s: str, t: str) -> bool:\\n return len(s) == len(t) and sum(x != y for x, y in zip(s, t)) == 1\\n\\n n = len(words)\\n f = [0] * n\\n from_ = [0] * n\\n max_i = n - 1\\n for i in range(n - 1, -1, -1):\\n for j in range(i + 1, n):\\n # 提前比较 f[j] 与 f[i] 的大小,如果 f[j] <= f[i],就不用执行更耗时的 check 了\\n if f[j] > f[i] and groups[j] != groups[i] and check(words[i], words[j]):\\n f[i] = f[j]\\n from_[i] = j\\n f[i] += 1 # 加一写在这里\\n if f[i] > f[max_i]:\\n max_i = i\\n\\n i = max_i\\n ans = [\'\'] * f[i]\\n for k in range(f[i]):\\n ans[k] = words[i]\\n i = from_[i]\\n return ans\\n
\\nclass Solution {\\n public List<String> getWordsInLongestSubsequence(String[] words, int[] groups) {\\n int n = words.length;\\n int[] f = new int[n];\\n int[] from = new int[n];\\n int maxI = n - 1;\\n for (int i = n - 1; i >= 0; i--) {\\n for (int j = i + 1; j < n; j++) {\\n // 提前比较 f[j] 与 f[i] 的大小,如果 f[j] <= f[i],就不用执行更耗时的 check 了\\n if (f[j] > f[i] && groups[j] != groups[i] && check(words[i], words[j])) {\\n f[i] = f[j];\\n from[i] = j;\\n }\\n }\\n f[i]++; // 加一写在这里\\n if (f[i] > f[maxI]) {\\n maxI = i;\\n }\\n }\\n\\n int i = maxI;\\n int m = f[i];\\n List<String> ans = new ArrayList<>(m); // 预分配空间\\n for (int k = 0; k < m; k++) {\\n ans.add(words[i]);\\n i = from[i];\\n }\\n return ans;\\n }\\n\\n private boolean check(String s, String t) {\\n if (s.length() != t.length()) {\\n return false;\\n }\\n boolean diff = false;\\n for (int i = 0; i < s.length(); i++) {\\n if (s.charAt(i) != t.charAt(i)) {\\n if (diff) { // 汉明距离大于 1\\n return false;\\n }\\n diff = true;\\n }\\n }\\n return diff;\\n }\\n}\\n
\\nclass Solution {\\n bool ok(string& s, string& t) {\\n if (s.size() != t.size()) {\\n return false;\\n }\\n bool diff = false;\\n for (int i = 0; i < s.size(); i++) {\\n if (s[i] != t[i]) {\\n if (diff) { // 汉明距离大于 1\\n return false;\\n }\\n diff = true;\\n }\\n }\\n return diff;\\n }\\n\\npublic:\\n vector<string> getWordsInLongestSubsequence(vector<string>& words, vector<int>& groups) {\\n int n = words.size();\\n vector<int> f(n), from(n);\\n int max_i = n - 1;\\n for (int i = n - 1; i >= 0; i--) {\\n for (int j = i + 1; j < n; j++) {\\n // 提前比较 f[j] 与 f[i] 的大小,如果 f[j] <= f[i],就不用执行更耗时的 check 了\\n if (f[j] > f[i] && groups[j] != groups[i] && ok(words[i], words[j])) {\\n f[i] = f[j];\\n from[i] = j;\\n }\\n }\\n f[i]++; // 加一写在这里\\n if (f[i] > f[max_i]) {\\n max_i = i;\\n }\\n }\\n\\n int i = max_i;\\n int m = f[i];\\n vector<string> ans(m);\\n for (int k = 0; k < m; k++) {\\n ans[k] = words[i];\\n i = from[i];\\n }\\n return ans;\\n }\\n};\\n
\\nfunc ok(s, t string) (diff bool) {\\nif len(s) != len(t) {\\nreturn\\n}\\nfor i := range s {\\nif s[i] != t[i] {\\nif diff { // 汉明距离大于 1\\nreturn false\\n}\\ndiff = true\\n}\\n}\\nreturn\\n}\\n\\nfunc getWordsInLongestSubsequence(words []string, groups []int) []string {\\nn := len(words)\\nf := make([]int, n)\\nfrom := make([]int, n)\\nmaxI := n - 1\\nfor i := n - 1; i >= 0; i-- {\\nfor j := i + 1; j < n; j++ {\\n// 提前比较 f[j] 与 f[i] 的大小,如果 f[j] <= f[i],就不用执行更耗时的 check 了\\nif f[j] > f[i] && groups[j] != groups[i] && ok(words[i], words[j]) {\\nf[i] = f[j]\\nfrom[i] = j\\n}\\n}\\nf[i]++ // 加一写在这里\\nif f[i] > f[maxI] {\\nmaxI = i\\n}\\n}\\n\\ni := maxI\\nans := make([]string, f[i])\\nfor k := range ans {\\nans[k] = words[i]\\ni = from[i]\\n}\\nreturn ans\\n}\\n
\\n如果 $n=10^5$,方法一就超时了。怎么办?
\\n在方法一中,我们其实没有充分利用这个性质:汉明距离只有 $1$,也就是只有一个字母不同。如何利用这个性质?
\\n方法一是枚举字符串,计算不同字母个数。横看成岭侧成峰,反过来,枚举哪个字母不同。
\\n字符串 $w=\\\\textit{words}[i]$ 的长度至多为 $10$,与 $w$ 只有一个字母不同的字符串,本质只有 $10$ 种:
\\n关键思路:我们只需枚举这 $10$ 种字符串,而不是 $\\\\mathcal{O}(n)$ 个字符串!
\\n首先考虑一个简单情况,所有 $\\\\textit{groups}[i]$ 互不相同。此时只需保证子序列相邻字符串的汉明距离为 $1$。
\\n设子序列中的一对相邻字符串为 $s$ 和 $t$。
\\n比如 $s=\\\\texttt{bab}$,那么 $t$ 需要与 $s$ 恰好有一个字符不同,例如 $t=\\\\texttt{cab}$。
\\n我们可以把 $s$ 视作三种字符串:$\\\\texttt{?ab},\\\\ \\\\texttt{b?b},\\\\ \\\\texttt{ba?}$,其中 $\\\\texttt{?}$ 表示通配符,可以匹配任意单个字符。
\\n同样地,$t=\\\\texttt{cab}$ 也可以视作 $\\\\texttt{?ab}$,这样就与 $s=\\\\texttt{?ab}$ 相等了。
\\n⚠注意:题目保证所有字符串互不相同。用「通配符」转化后,两个相等的字符串的汉明距离一定恰好等于 $1$。
\\n子序列 DP 的另一个套路是,把元素值作为 DP 的状态。(方法一是把下标作为状态)
\\n本题元素是字符串,可以用哈希表,把字符串作为哈希表的 key,子序列长度作为哈希表的 value。
\\n具体地,定义 $\\\\textit{fMap}[s]$ 表示以字符串 $s$ 开头的合法子序列的最长长度,从子序列的第二个字符串转移过来。其中 $s$ 包含一个 $\\\\texttt{?}$ 通配符。
\\n同方法一,倒着遍历 $\\\\textit{words}$,设 $w=\\\\textit{words}[i]$,设把 $w[k]$ 改成 $\\\\texttt{?}$ 后的字符串为 $w\'$。
\\n例如 $\\\\textit{words}=[\\\\texttt{aab},\\\\texttt{aaa},\\\\texttt{baa}]$ 的答案是 $3$,读者可以动手算算这个例子,体会 $\\\\texttt{aaa}$ 是如何作为「桥梁」连接 $\\\\texttt{aab}$ 和 $\\\\texttt{baa}$ 的。
\\n把 $\\\\textit{groups}$ 的约束加进来。
\\n在上述方法的基础上,额外维护次大值,并保证最大值和次大值对应的 $\\\\textit{group}$ 值不同,从而保证最大值和次大值中一定有一个可以转移到当前状态。
\\n具体来说,$\\\\textit{fMap}[s]$ 保存 $4$ 个值:
\\n最终答案就是 $\\\\textit{fMap}[s].\\\\textit{maxF}$ 的最大值。
\\n代码实现时,由于字符串长度 $\\\\le 10$,可以把字符串压缩成一个长度 $\\\\le 50$ 的二进制数(完美哈希),作为哈希表的 key:
\\nclass Solution:\\n def getWordsInLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:\\n n = len(words)\\n f_map = {} # 哈希值 -> (max_f, j, max_f2, j2)\\n from_ = [0] * n\\n global_max_f = max_i = 0\\n for i in range(n - 1, -1, -1):\\n w, g = words[i], groups[i]\\n\\n # 计算 w 的哈希值\\n hash_val = sum((ord(ch) & 31) << (k * 5) for k, ch in enumerate(w))\\n\\n f = 0 # 方法一中的 f[i]\\n for k in range(len(w)):\\n h = hash_val | (31 << (k * 5)) # 用记号笔把 w[k] 涂黑(置为 11111)\\n max_f, j, max_f2, j2 = f_map.get(h, (0, 0, 0, 0))\\n if g != groups[j]: # 可以从最大值转移过来\\n if max_f > f:\\n f = max_f\\n from_[i] = j\\n else: # 只能从次大值转移过来\\n if max_f2 > f:\\n f = max_f2\\n from_[i] = j2\\n\\n f += 1\\n if f > global_max_f:\\n global_max_f, max_i = f, i\\n\\n # 用 f 更新 f_map[h] 的最大次大\\n # 注意要保证最大次大的 group 值不同\\n for k in range(len(w)):\\n h = hash_val | (31 << (k * 5))\\n max_f, j, max_f2, j2 = f_map.get(h, (0, 0, 0, 0))\\n if f > max_f: # 最大值需要更新\\n if g != groups[j]:\\n max_f2, j2 = max_f, j # 旧最大值变成次大值\\n max_f, j = f, i\\n elif f > max_f2 and g != groups[j]: # 次大值需要更新\\n max_f2, j2 = f, i\\n f_map[h] = (max_f, j, max_f2, j2)\\n\\n ans = [\'\'] * global_max_f\\n i = max_i\\n for k in range(global_max_f):\\n ans[k] = words[i]\\n i = from_[i]\\n return ans\\n
\\nclass Solution {\\n private record Info(int maxF, int j, int maxF2, int j2) {\\n }\\n\\n public List<String> getWordsInLongestSubsequence(String[] words, int[] groups) {\\n int n = words.length;\\n Map<Long, Info> fMap = new HashMap<>();\\n int[] from = new int[n];\\n int globalMaxF = 0;\\n int maxI = 0;\\n\\n for (int i = n - 1; i >= 0; i--) {\\n char[] w = words[i].toCharArray();\\n int g = groups[i];\\n\\n // 计算 w 的哈希值\\n long hash = 0;\\n for (char c : w) {\\n hash = (hash << 5) | (c & 31);\\n }\\n\\n int f = 0; // 方法一中的 f[i]\\n for (int k = 0; k < w.length; k++) {\\n long h = hash | (31L << (k * 5)); // 用记号笔把 w[k] 涂黑(置为 11111)\\n Info t = fMap.get(h);\\n if (t == null) {\\n continue;\\n }\\n if (g != groups[t.j]) { // 可以从最大值转移过来\\n if (t.maxF > f) {\\n f = t.maxF;\\n from[i] = t.j;\\n }\\n } else { // 只能从次大值转移过来\\n if (t.maxF2 > f) {\\n f = t.maxF2;\\n from[i] = t.j2;\\n }\\n }\\n }\\n\\n f++;\\n if (f > globalMaxF) {\\n globalMaxF = f;\\n maxI = i;\\n }\\n\\n // 用 f 更新 fMap[h] 的最大次大\\n // 注意要保证最大次大的 group 值不同\\n for (int k = 0; k < w.length; k++) {\\n long h = hash | (31L << (k * 5));\\n Info t = fMap.getOrDefault(h, new Info(0, 0, 0, 0));\\n int maxF = t.maxF, j = t.j, maxF2 = t.maxF2, j2 = t.j2;\\n if (f > maxF) { // 最大值需要更新\\n if (g != groups[j]) {\\n maxF2 = maxF; // 旧最大值变成次大值\\n j2 = j;\\n }\\n maxF = f;\\n j = i;\\n } else if (f > maxF2 && g != groups[j]) { // 次大值需要更新\\n maxF2 = f;\\n j2 = i;\\n }\\n fMap.put(h, new Info(maxF, j, maxF2, j2));\\n }\\n }\\n\\n List<String> ans = new ArrayList<>(globalMaxF); // 预分配空间\\n int i = maxI;\\n for (int k = 0; k < globalMaxF; k++) {\\n ans.add(words[i]);\\n i = from[i];\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<string> getWordsInLongestSubsequence(vector<string>& words, vector<int>& groups) {\\n int n = words.size();\\n unordered_map<long long, tuple<int, int, int, int>> f_map; // 哈希值 -> (max_f, j, max_f2, j2)\\n vector<int> from(n);\\n int global_max_f = 0, max_i = 0;\\n for (int i = n - 1; i >= 0; i--) {\\n string& w = words[i];\\n int g = groups[i];\\n\\n // 计算 w 的哈希值\\n long long hash = 0;\\n for (char ch : w) {\\n hash = (hash << 5) | (ch & 31);\\n }\\n\\n int f = 0; // 方法一中的 f[i]\\n for (int k = 0; k < w.size(); k++) {\\n long long h = hash | (31LL << (k * 5)); // 用记号笔把 w[k] 涂黑(置为 11111)\\n auto& [max_f, j, max_f2, j2] = f_map[h];\\n if (g != groups[j]) { // 可以从最大值转移过来\\n if (max_f > f) {\\n f = max_f;\\n from[i] = j;\\n }\\n } else { // 只能从次大值转移过来\\n if (max_f2 > f) {\\n f = max_f2;\\n from[i] = j2;\\n }\\n }\\n }\\n\\n f++;\\n if (f > global_max_f) {\\n global_max_f = f;\\n max_i = i;\\n }\\n\\n // 用 f 更新 f_map[h] 的最大次大\\n // 注意要保证最大次大的 group 值不同\\n for (int k = 0; k < w.size(); k++) {\\n long long h = hash | (31LL << (k * 5));\\n auto& [max_f, j, max_f2, j2] = f_map[h];\\n if (f > max_f) { // 最大值需要更新\\n if (g != groups[j]) {\\n max_f2 = max_f; // 旧最大值变成次大值\\n j2 = j;\\n }\\n max_f = f;\\n j = i;\\n } else if (f > max_f2 && g != groups[j]) { // 次大值需要更新\\n max_f2 = f;\\n j2 = i;\\n }\\n }\\n }\\n\\n vector<string> ans(global_max_f);\\n int i = max_i;\\n for (int k = 0; k < global_max_f; k++) {\\n ans[k] = words[i];\\n i = from[i];\\n }\\n return ans;\\n }\\n};\\n
\\nfunc getWordsInLongestSubsequence(words []string, groups []int) []string {\\nn := len(words)\\nfMap := map[int]struct{ maxF, j, maxF2, j2 int }{}\\nfrom := make([]int, n)\\nmaxF, maxI := 0, 0\\nfor i := n - 1; i >= 0; i-- {\\nw, g := words[i], groups[i]\\n\\n// 计算 w 的哈希值\\nhash := 0\\nfor _, ch := range w {\\nhash = hash<<5 | int(ch&31)\\n}\\n\\nf := 0 // 方法一中的 f[i]\\nfor j := range w {\\nh := hash | 31<<(j*5) // 用记号笔把 w[k] 涂黑(置为 11111)\\nt := fMap[h]\\nif g != groups[t.j] { // 可以从最大值转移过来\\nif t.maxF > f {\\nf = t.maxF\\nfrom[i] = t.j\\n}\\n} else { // 只能从次大值转移过来\\nif t.maxF2 > f {\\nf = t.maxF2\\nfrom[i] = t.j2\\n}\\n}\\n}\\n\\nf++\\nif f > maxF {\\nmaxF, maxI = f, i\\n}\\n\\n// 用 f 更新 fMap[h] 的最大次大\\n// 注意要保证最大次大的 group 值不同\\nfor j := range w {\\nh := hash | 31<<(j*5)\\nt := fMap[h]\\nif f > t.maxF { // 最大值需要更新\\nif g != groups[t.j] {\\nt.maxF2 = t.maxF // 旧最大值变成次大值\\nt.j2 = t.j\\n}\\nt.maxF = f\\nt.j = i\\n} else if f > t.maxF2 && g != groups[t.j] { // 次大值需要更新\\nt.maxF2 = f\\nt.j2 = i\\n}\\nfMap[h] = t\\n}\\n}\\n\\nans := make([]string, maxF)\\ni := maxI\\nfor k := range ans {\\nans[k] = words[i]\\ni = from[i]\\n}\\nreturn ans\\n}\\n
\\n更多相似题目,见动态规划题单的「§4.2 最长递增子序列」和「§7.4 合法子序列 DP」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"子序列 DP 的常用套路 子序列 + 相邻元素无关:选或不选。代表题目:494. 目标和(0-1 背包)。\\n子序列 + 相邻元素相关:枚举选哪个。代表题目:300. 最长递增子序列。\\n值域 DP。具体在方法二中介绍。\\n方法一:枚举选哪个\\n\\n本题属于「子序列 + 相邻元素相关」,用枚举选哪个解决。\\n\\n类似最长递增子序列,定义 $f[i]$ 表示在子序列第一个字符串是 $\\\\textit{words}[i]$ 的前提下,从后缀 $[i,n-1]$ 中能选出的最长子序列的长度。为什么定义成后缀?方便后面输出具体方案。\\n\\n枚举子序列的第二个字符串 $\\\\textit…","guid":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-ii//solution/zi-xu-lie-dp-de-si-kao-tao-lu-pythonjava-kmaf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-15T00:45:10.315Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"阅读理解,O(n) 一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-i//solution/nao-jin-ji-zhuan-wan-on-yi-ci-bian-li-py-cv1q","content":"题意:选一个 $\\\\textit{words}$ 的子序列,要求相邻字符串对应的 $\\\\textit{groups}[i]$ 不同。
\\n为方便描述,把 $\\\\textit{groups}$ 当作一个 $01$ 字符串。
\\n比如 $\\\\textit{groups}=0001100$,分成三组 $000,11,00$。根据题意,每一组内只能选一个 $\\\\textit{words}[i]$,所以一共选 $3$ 个字符串。如果选超过 $3$ 个字符串,根据鸽巢原理,一定有两个字符串在同一组,这违背了「相邻字符串对应的 $\\\\textit{groups}[i]$ 不同」的要求。
\\n所以遍历 $\\\\textit{groups}$ 的每一个连续相同段,每一段中选一个相应的 $\\\\textit{words}[i]$。
\\nclass Solution:\\n def getLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:\\n n = len(groups)\\n ans = []\\n for i, g in enumerate(groups):\\n if i == n - 1 or g != groups[i + 1]: # i 是连续相同段的末尾\\n ans.append(words[i])\\n return ans\\n
\\nclass Solution:\\n def getLongestSubsequence(self, words: List[str], groups: List[int]) -> List[str]:\\n return [next(g)[0] for _, g in groupby(zip(words, groups), key=lambda z: z[1])]\\n
\\nclass Solution {\\n public List<String> getLongestSubsequence(String[] words, int[] groups) {\\n List<String> ans = new ArrayList<>();\\n int n = groups.length;\\n for (int i = 0; i < n; i++) {\\n if (i == n - 1 || groups[i] != groups[i + 1]) { // i 是连续相同段的末尾\\n ans.add(words[i]);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<string> getLongestSubsequence(vector<string>& words, vector<int>& groups) {\\n vector<string> ans;\\n int n = groups.size();\\n for (int i = 0; i < n; i++) {\\n if (i == n - 1 || groups[i] != groups[i + 1]) { // i 是连续相同段的末尾\\n ans.push_back(words[i]);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nchar** getLongestSubsequence(char** words, int wordsSize, int* groups, int groupsSize, int* returnSize) {\\n char** ans = malloc(sizeof(char*) * groupsSize);\\n int idx = 0;\\n for (int i = 0; i < groupsSize; i++) {\\n if (i == groupsSize - 1 || groups[i] != groups[i + 1]) { // i 是连续相同段的末尾\\n ans[idx++] = words[i];\\n }\\n }\\n *returnSize = idx;\\n return ans;\\n}\\n
\\nfunc getLongestSubsequence(words []string, groups []int) (ans []string) {\\nn := len(groups)\\nfor i, g := range groups {\\nif i == n-1 || g != groups[i+1] { // i 是连续相同段的末尾\\nans = append(ans, words[i])\\n}\\n}\\nreturn\\n}\\n
\\nvar getLongestSubsequence = function(words, groups) {\\n const n = groups.length;\\n const ans = [];\\n for (let i = 0; i < n; i++) {\\n if (i === n - 1 || groups[i] !== groups[i + 1]) { // i 是连续相同段的末尾\\n ans.push(words[i]);\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn get_longest_subsequence(words: Vec<String>, groups: Vec<i32>) -> Vec<String> {\\n let n = groups.len();\\n let mut ans = vec![];\\n for (i, word) in words.into_iter().enumerate() {\\n if i == n - 1 || groups[i] != groups[i + 1] { // i 是连续相同段的末尾\\n ans.push(word);\\n }\\n }\\n ans\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意:选一个 $\\\\textit{words}$ 的子序列,要求相邻字符串对应的 $\\\\textit{groups}[i]$ 不同。 为方便描述,把 $\\\\textit{groups}$ 当作一个 $01$ 字符串。\\n\\n比如 $\\\\textit{groups}=0001100$,分成三组 $000,11,00$。根据题意,每一组内只能选一个 $\\\\textit{words}[i]$,所以一共选 $3$ 个字符串。如果选超过 $3$ 个字符串,根据鸽巢原理,一定有两个字符串在同一组,这违背了「相邻字符串对应的 $\\\\textit{groups}[i]$ 不同」的要求。…","guid":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-i//solution/nao-jin-ji-zhuan-wan-on-yi-ci-bian-li-py-cv1q","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-15T00:01:50.562Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DP","url":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-i//solution/dp-by-tsreaper-40w7","content":"如果题目只需要求最长子序列的长度,那么记 f[i]
表示以 words[i]
为结尾的最长子序列长度,转移方程为
f[i] = max(f[j] + 1) // j < i && groups[i] != groups[j]\\n
\\n复杂度 $\\\\mathcal{O}(n^2)$。由于题目要以字符串的形式输出答案,我们额外记一个 from[i]
表示对于下标 i
来说,最优的 j
是哪一个,这样我们就能沿着 from
数组输出答案。
###c++
\\nclass Solution {\\npublic:\\n vector<string> getWordsInLongestSubsequence(int n, vector<string>& words, vector<int>& groups) {\\n // ans 表示最长子序列的长度\\n int ans = 0;\\n int f[n], from[n];\\n for (int i = 0; i < n; i++) {\\n f[i] = 1; from[i] = -1;\\n for (int j = i - 1; j >= 0; j--)\\n if (groups[i] != groups[j])\\n if (f[j] + 1 > f[i]) {\\n // 从当前的 j 转移更优,记录这个转移\\n f[i] = f[j] + 1;\\n from[i] = j;\\n }\\n ans = max(ans, f[i]);\\n }\\n\\n vector<string> ret;\\n for (int i = 0; i < n; i++) if (f[i] == ans) {\\n // 沿着 from 数组就能倒序取到答案\\n for (int j = i; j >= 0; j = from[j]) ret.push_back(words[j]);\\n reverse(ret.begin(), ret.end());\\n break;\\n }\\n return ret;\\n }\\n};\\n
\\n","description":"解法:DP 如果题目只需要求最长子序列的长度,那么记 f[i] 表示以 words[i] 为结尾的最长子序列长度,转移方程为\\n\\nf[i] = max(f[j] + 1) // j < i && groups[i] != groups[j]\\n\\n\\n复杂度 $\\\\mathcal{O}(n^2)$。由于题目要以字符串的形式输出答案,我们额外记一个 from[i] 表示对于下标 i 来说,最优的 j 是哪一个,这样我们就能沿着 from 数组输出答案。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n vector如果题目只需要求最长子序列的长度,那么记 f[i]
表示以 words[i]
为结尾的最长子序列长度,转移方程为
f[i] = max(f[j] + 1) // j < i && groups[i] != groups[j] &&\\n // words[i].size() == words[j].size() && difference(words[i], words[j]) == 1\\n
\\n复杂度 $\\\\mathcal{O}(n^2)$。由于题目要以字符串的形式输出答案,我们额外记一个 from[i]
表示对于下标 i
来说,最优的 j
是哪一个,这样我们就能沿着 from
数组输出答案。
###c++
\\n// 算出下一位是 0 还是 1\\nclass Solution {\\npublic:\\n vector<string> getWordsInLongestSubsequence(int n, vector<string>& words, vector<int>& groups) {\\n // 求长度相同的字符串 a 和 b 有几个字符不同\\n auto diff = [&](string &a, string &b) {\\n int ret = 0;\\n for (int i = 0; i < a.size(); i++) if (a[i] != b[i]) ret++;\\n return ret;\\n };\\n\\n // ans 表示最长子序列的长度\\n int ans = 0;\\n int f[n], from[n];\\n for (int i = 0; i < n; i++) {\\n f[i] = 1; from[i] = -1;\\n for (int j = i - 1; j >= 0; j--)\\n if (words[i].size() == words[j].size() && groups[i] != groups[j] && diff(words[i], words[j]) == 1)\\n if (f[j] + 1 > f[i]) {\\n // 从当前的 j 转移更优,记录这个转移\\n f[i] = f[j] + 1;\\n from[i] = j;\\n }\\n ans = max(ans, f[i]);\\n }\\n\\n vector<string> ret;\\n for (int i = 0; i < n; i++) if (f[i] == ans) {\\n // 沿着 from 数组就能倒序取到答案\\n for (int j = i; j >= 0; j = from[j]) ret.push_back(words[j]);\\n reverse(ret.begin(), ret.end());\\n break;\\n }\\n return ret;\\n }\\n};\\n
\\n","description":"解法:DP 如果题目只需要求最长子序列的长度,那么记 f[i] 表示以 words[i] 为结尾的最长子序列长度,转移方程为\\n\\nf[i] = max(f[j] + 1) // j < i && groups[i] != groups[j] &&\\n // words[i].size() == words[j].size() && difference(words[i], words[j]) == 1\\n\\n\\n复杂度 $\\\\mathcal{O}(n^2)$。由于题目要以字符串的形式输出答案,我们额外记一个 from[i] 表示对…","guid":"https://leetcode.cn/problems/longest-unequal-adjacent-groups-subsequence-ii//solution/dp-by-tsreaper-8q5o","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-14T16:21:01.947Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"推导公式即可,一行代码,双百","url":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/tui-dao-gong-shi-ji-ke-yi-xing-dai-ma-sh-63qf","content":"\\n\\nProblem: 100103. 分类求和并作差
\\n
[TOC]
\\n推导一下公式即可:
\\n时间0 ms击败100%;内存6.2 MB击败100%
\\n###C
\\n int differenceOfSums(int n, int m) {\\n return (n * (n + 1) >> 1) - n / m * (n / m + 1) * m;\\n }\\n
\\n###Python3
\\n class Solution:\\n def differenceOfSums(self, n: int, m: int) -> int:\\n return (n * (n + 1) >> 1) - n // m * (n // m + 1) * m\\n
\\n若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 100103. 分类求和并作差 [TOC]\\n\\n推导一下公式即可:\\n\\n时间0 ms击败100%;内存6.2 MB击败100%\\n\\n###C\\n\\n int differenceOfSums(int n, int m) {\\n return (n * (n + 1) >> 1) - n / m * (n / m + 1) * m;\\n }\\n\\n\\n###Python3\\n\\n class Solution:\\n def differenceOfSums(self, n: int, m: int) -> int:\\n return…","guid":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/tui-dao-gong-shi-ji-ke-yi-xing-dai-ma-sh-63qf","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-09T01:18:30.674Z","media":[{"url":"https://pic.leetcode.cn/1703396827-KEYmYk-image.png","type":"photo","width":668,"height":372,"blurhash":"LCS$ln_N%M~q.SaesoV@-pRjR*Rj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简单易理解","url":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/jian-dan-yi-li-jie-by-ecstatic-carson8ie-uhrh","content":"\\n\\nProblem: 100103. 分类求和并作差
\\n
[TOC]
\\n\\n\\n遍历一次数组,可以整除加上num2,不可以整除就和num1相加
\\n
\\n\\n遍历一次数组,判断是否可以整除,可以整除加上num2,不可以整除就和num1相加
\\n
\\n\\n$O(n)$
\\n
\\n\\n$O(n)$
\\n
###Python
\\n\\n class Solution(object):\\n def differenceOfSums(self, n, m):\\n \\"\\"\\"\\n :type n: int\\n :type m: int\\n :rtype: int\\n \\"\\"\\"\\n num1=0\\n num2=0\\n for i in range(1,n+1):\\n if i%m==0:\\n num2+=i\\n else:\\n num1+=i\\n return num1-num2\\n
\\n","description":"Problem: 100103. 分类求和并作差 [TOC]\\n\\n遍历一次数组,可以整除加上num2,不可以整除就和num1相加\\n\\n遍历一次数组,判断是否可以整除,可以整除加上num2,不可以整除就和num1相加\\n\\n时间复杂度:\\n\\n$O(n)$\\n\\n空间复杂度:\\n\\n$O(n)$\\n\\n###Python\\n\\n\\n class Solution(object):\\n def differenceOfSums(self, n, m):\\n \\"\\"\\"\\n :type n: int\\n :type m: int…","guid":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/jian-dan-yi-li-jie-by-ecstatic-carson8ie-uhrh","author":"ecstatic-carson8ie","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-08T05:14:00.057Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(1) 数学公式(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/o1-shu-xue-gong-shi-yi-xing-gao-ding-pyt-m5cq","content":"$[1,n]$ 中有 $k = \\\\left\\\\lfloor\\\\dfrac{n}{m}\\\\right\\\\rfloor$ 个 $m$ 的倍数。比如 $[1,20]$ 中有 $\\\\left\\\\lfloor\\\\dfrac{20}{3}\\\\right\\\\rfloor=6$ 个数是 $3$ 的倍数:$3,6,9,12,15,18$。
\\n根据题意,$\\\\textit{num}_2$ 是在 $[1,n]$ 范围中的 $m$ 的倍数之和,即
\\n$$
\\n\\\\begin{aligned}
\\n& m + 2m + \\\\cdots + km \\\\
\\n={} & (1+2+\\\\cdots+k)\\\\cdot m \\\\
\\n={} & \\\\dfrac{k(k+1)}{2}\\\\cdot m
\\n\\\\end{aligned}
\\n$$
$\\\\textit{num}_1$ 相当于 $(1+2+\\\\cdots+n) - \\\\textit{num}_2$,所以有
\\n$$
\\n\\\\begin{aligned}
\\n& \\\\textit{num}_1 - \\\\textit{num}_2 \\\\
\\n={} & (1+2+\\\\cdots+n) - \\\\textit{num}_2 \\\\cdot 2 \\\\
\\n={} & \\\\dfrac{n(n+1)}{2} - k(k+1)m
\\n\\\\end{aligned}
\\n$$
class Solution:\\n def differenceOfSums(self, n: int, m: int) -> int:\\n return n * (n + 1) // 2 - n // m * (n // m + 1) * m\\n
\\nclass Solution {\\n public int differenceOfSums(int n, int m) {\\n return n * (n + 1) / 2 - n / m * (n / m + 1) * m;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int differenceOfSums(int n, int m) {\\n return n * (n + 1) / 2 - n / m * (n / m + 1) * m;\\n }\\n};\\n
\\nint differenceOfSums(int n, int m) {\\n return n * (n + 1) / 2 - n / m * (n / m + 1) * m;\\n}\\n
\\nfunc differenceOfSums(n, m int) int {\\nreturn n*(n+1)/2 - n/m*(n/m+1)*m\\n}\\n
\\nvar differenceOfSums = function(n, m) {\\n const k = Math.floor(n / m);\\n return n * (n + 1) / 2 - k * (k + 1) * m;\\n};\\n
\\nimpl Solution {\\n pub fn difference_of_sums(n: i32, m: i32) -> i32 {\\n n * (n + 1) / 2 - n / m * (n / m + 1) * m\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"$[1,n]$ 中有 $k = \\\\left\\\\lfloor\\\\dfrac{n}{m}\\\\right\\\\rfloor$ 个 $m$ 的倍数。比如 $[1,20]$ 中有 $\\\\left\\\\lfloor\\\\dfrac{20}{3}\\\\right\\\\rfloor=6$ 个数是 $3$ 的倍数:$3,6,9,12,15,18$。 根据题意,$\\\\textit{num}_2$ 是在 $[1,n]$ 范围中的 $m$ 的倍数之和,即\\n\\n$$\\n \\\\begin{aligned}\\n & m + 2m + \\\\cdots + km \\\\\\n ={} & (1+2+\\\\cdots+k)\\\\cdot m \\\\\\n =…","guid":"https://leetcode.cn/problems/divisible-and-non-divisible-sums-difference//solution/o1-shu-xue-gong-shi-yi-xing-gao-ding-pyt-m5cq","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-08T04:17:41.833Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前缀后缀最大","url":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-i//solution/qian-zhui-hou-zhui-zui-da-by-simidagogog-dngw","content":"前缀后缀最大,时间复杂度O(n),空间复杂度O(n)
\\n###cpp
\\nclass Solution {\\npublic:\\n long long maximumTripletValue(vector<int>& nums) {\\n int n = nums.size();\\n \\n // 记录每个元素左边最大和右边最大\\n vector<pair<long long, long long>> vec(n);\\n \\n int left = 0, right = 0;\\n for (int i = n - 1; i >= 0; --i) {\\n vec[i].second = right;\\n right = std::max(nums[i], right);\\n }\\n \\n for (int i = 0; i < n; ++i) {\\n vec[i].first = left;\\n left = std::max(nums[i], left);\\n }\\n \\n long long res = 0;\\n for (int i = 0; i < n; ++i) {\\n long long num = (vec[i].first - nums[i]) * (vec[i].second);\\n if (res < num) res = num;\\n }\\n return res;\\n }\\n};\\n
\\n","description":"前缀后缀最大,时间复杂度O(n),空间复杂度O(n) ###cpp\\n\\nclass Solution {\\npublic:\\n long long maximumTripletValue(vector对于这个问题,数据范围是 $100$ 的情况,我们可以直接暴力枚举所有合法的 $(i, j, k)$ 三元组,看其值取最大即可。 注意要求如果是负值时,返回 $0$.
\\n时间复杂度为 $\\\\mathcal{O}(n^3)$.
\\n###Python
\\nclass Solution:\\n def maximumTripletValue(self, nums: List[int]) -> int:\\n n = len(nums)\\n ans = 0\\n for k in range(n):\\n for j in range(k):\\n for i in range(j):\\n ans = max(ans, (nums[i] - nums[j]) * nums[k])\\n return ans\\n
\\n接下来我们思考如何处理 $n \\\\leq 10^5$ 的情况。
\\n首先,输出的数值最小为 $0$,同时数组中每个元素均为正数,因此,我们要让 (nums[i] - nums[j]) * nums[k]
最大,只需要对于固定的 $k$ 找到其前面 nums[i] - nums[j]
的最大值。
为了使得 nums[i] - nums[j]
尽可能大,我们需要对于固定的 j
找到其前面最大的 nums[i]
再将两者相减。
以上逻辑可见代码注释。
\\n因此我们只需要维护三个东西:到当前位置的最大值,到当前位置为止最大的 nums[i] - nums[j]
,到当前位置为止最大的 (nums[i] - nums[j]) * nums[k]
. 而从上面的分析可以看出,这些都可以遍历过程中实现。
因此时间复杂度是 $\\\\mathcal{O}(n)$ 的。
\\n###Python
\\nclass Solution:\\n def maximumTripletValue(self, nums: List[int]) -> int:\\n # 当前最大值\\n curr_max = 0\\n # 当前最大的 nums[i] - nums[j]\\n curr_v = 0\\n # 当前最大的 (nums[i] - nums[j]) * nums[k]\\n ans = 0\\n n = len(nums)\\n \\n for i in range(n):\\n # 答案的最大值根据最大的 nums[i] - nums[j] 和当前数值的乘积更新\\n ans = max(ans, nums[i] * curr_v)\\n # nums[i] - nums[j] 的最大值根据此前最大值减去当前数值更新\\n curr_v = max(curr_v, curr_max - nums[i])\\n # 更新前缀最大值\\n curr_max = max(curr_max, nums[i])\\n return ans\\n
\\n","description":"对于这个问题,数据范围是 $100$ 的情况,我们可以直接暴力枚举所有合法的 $(i, j, k)$ 三元组,看其值取最大即可。 注意要求如果是负值时,返回 $0$. 时间复杂度为 $\\\\mathcal{O}(n^3)$.\\n\\n具体代码如下——\\n\\n###Python\\n\\nclass Solution:\\n def maximumTripletValue(self, nums: List[int]) -> int:\\n n = len(nums)\\n ans = 0\\n for k in range(n):…","guid":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii//solution/xiao-yang-xiao-en-cong-bao-li-dao-tan-xi-8ndn","author":"Yawn_Sean","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-01T04:23:43.002Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种 O(n) 做法:枚举 j / 枚举 k(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-i//solution/on-zuo-fa-by-endlesscheng-01x5","content":"本题和 2874. 有序三元组中的最大值 II 是一样的,请看 我的题解。
\\n","description":"本题和 2874. 有序三元组中的最大值 II 是一样的,请看 我的题解。","guid":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-i//solution/on-zuo-fa-by-endlesscheng-01x5","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-01T04:19:01.934Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举 j / 枚举 k(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii//solution/mei-ju-jzhao-qian-hou-zui-da-zhi-pythonj-um8q","content":"枚举 $j$,为了让 $(\\\\textit{nums}[i] - \\\\textit{nums}[j]) * \\\\textit{nums}[k]$ 尽量大,我们需要知道 $j$ 左侧元素的最大值(让 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 尽量大),以及 $j$ 右侧元素的最大值(让乘积尽量大)。
\\n也就是计算 $\\\\textit{nums}$ 的前缀最大值 $\\\\textit{preMax}$ 和后缀最大值 $\\\\textit{sufMax}$,这可以用递推预处理:
\\n代码实现时,可以只预处理 $\\\\textit{sufMax}$ 数组,$\\\\textit{preMax}$ 可以在计算答案的同时算出来。
\\n视频讲解 第二题。
\\nclass Solution:\\n def maximumTripletValue(self, nums: List[int]) -> int:\\n n = len(nums)\\n suf_max = [0] * (n + 1)\\n for i in range(n - 1, 1, -1):\\n suf_max[i] = max(suf_max[i + 1], nums[i])\\n\\n ans = pre_max = 0\\n for j, x in enumerate(nums):\\n ans = max(ans, (pre_max - x) * suf_max[j + 1])\\n pre_max = max(pre_max, x)\\n return ans\\n
\\nclass Solution {\\n public long maximumTripletValue(int[] nums) {\\n int n = nums.length;\\n int[] sufMax = new int[n + 1];\\n for (int i = n - 1; i > 1; i--) {\\n sufMax[i] = Math.max(sufMax[i + 1], nums[i]);\\n }\\n\\n long ans = 0;\\n int preMax = nums[0];\\n for (int j = 1; j < n - 1; j++) {\\n ans = Math.max(ans, (long) (preMax - nums[j]) * sufMax[j + 1]);\\n preMax = Math.max(preMax, nums[j]);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumTripletValue(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> suf_max(n + 1);\\n for (int i = n - 1; i > 1; i--) {\\n suf_max[i] = max(suf_max[i + 1], nums[i]);\\n }\\n\\n long long ans = 0;\\n int pre_max = nums[0];\\n for (int j = 1; j < n - 1; j++) {\\n ans = max(ans, 1LL * (pre_max - nums[j]) * suf_max[j + 1]);\\n pre_max = max(pre_max, nums[j]);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long maximumTripletValue(int* nums, int n) {\\n int* suf_max = malloc(n * sizeof(int));\\n suf_max[n - 1] = nums[n - 1];\\n for (int i = n - 2; i > 1; i--) {\\n suf_max[i] = MAX(suf_max[i + 1], nums[i]);\\n }\\n\\n long long ans = 0;\\n int pre_max = nums[0];\\n for (int j = 1; j < n - 1; j++) {\\n ans = MAX(ans, 1LL * (pre_max - nums[j]) * suf_max[j + 1]);\\n pre_max = MAX(pre_max, nums[j]);\\n }\\n\\n free(suf_max);\\n return ans;\\n}\\n
\\nfunc maximumTripletValue(nums []int) int64 {\\n ans := 0\\n n := len(nums)\\n sufMax := make([]int, n+1)\\n for i := n - 1; i > 1; i-- {\\n sufMax[i] = max(sufMax[i+1], nums[i])\\n }\\n\\n preMax := 0\\n for j, x := range nums {\\n ans = max(ans, (preMax-x)*sufMax[j+1])\\n preMax = max(preMax, x)\\n }\\n return int64(ans)\\n}\\n
\\nvar maximumTripletValue = function(nums) {\\n let n = nums.length;\\n let sufMax = Array(n);\\n sufMax[n - 1] = nums[n - 1];\\n for (let i = n - 2; i > 1; i--) {\\n sufMax[i] = Math.max(sufMax[i + 1], nums[i]);\\n }\\n\\n let ans = 0;\\n let preMax = nums[0];\\n for (let j = 1; j < n - 1; j++) {\\n ans = Math.max(ans, (preMax - nums[j]) * sufMax[j + 1]);\\n preMax = Math.max(preMax, nums[j]);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {\\n let n = nums.len();\\n let mut suf_max = vec![0; n + 1];\\n for i in (2..n).rev() {\\n suf_max[i] = suf_max[i + 1].max(nums[i]);\\n }\\n\\n let mut ans = 0;\\n let mut pre_max = nums[0];\\n for j in 1..n - 1 {\\n ans = ans.max((pre_max - nums[j]) as i64 * suf_max[j + 1] as i64);\\n pre_max = pre_max.max(nums[j]);\\n }\\n ans\\n }\\n}\\n
\\n枚举 $k$,我们需要知道 $k$ 左边 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 的最大值。
\\n类似 121. 买卖股票的最佳时机,为了计算 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 的最大值,我们需要知道 $j$ 左边的 $\\\\textit{nums}[i]$ 的最大值。
\\n因此,在遍历的过程中:
\\n代码实现时,要先更新 $\\\\textit{ans}$,再更新 $\\\\textit{maxDiff}$,最后更新 $\\\\textit{preMax}$。为什么?
\\n这个顺序是精心设置的:
\\n能否修改更新顺序?
\\n$\\\\textit{ans}$ 依赖 $\\\\textit{maxDiff}$,$\\\\textit{maxDiff}$ 依赖 $\\\\textit{preMax}$。如果修改更新顺序,那么 $\\\\textit{maxDiff}$ 或者 $\\\\textit{preMax}$ 会包含当前元素,就不是左边元素的计算结果了,这违反了题目 $i<j<k$ 的规定。
\\nclass Solution:\\n def maximumTripletValue(self, nums: List[int]) -> int:\\n ans = max_diff = pre_max = 0\\n for x in nums:\\n ans = max(ans, max_diff * x)\\n max_diff = max(max_diff, pre_max - x)\\n pre_max = max(pre_max, x)\\n return ans\\n
\\nclass Solution {\\n public long maximumTripletValue(int[] nums) {\\n long ans = 0;\\n int maxDiff = 0;\\n int preMax = 0;\\n for (int x : nums) {\\n ans = Math.max(ans, (long) maxDiff * x);\\n maxDiff = Math.max(maxDiff, preMax - x);\\n preMax = Math.max(preMax, x);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumTripletValue(vector<int>& nums) {\\n long long ans = 0;\\n int max_diff = 0, pre_max = 0;\\n for (int x : nums) {\\n ans = max(ans, 1LL * max_diff * x);\\n max_diff = max(max_diff, pre_max - x);\\n pre_max = max(pre_max, x);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long maximumTripletValue(int* nums, int n) {\\n long long ans = 0;\\n int max_diff = 0, pre_max = 0;\\n for (int i = 0; i < n; i++) {\\n ans = MAX(ans, 1LL * max_diff * nums[i]);\\n max_diff = MAX(max_diff, pre_max - nums[i]);\\n pre_max = MAX(pre_max, nums[i]);\\n }\\n return ans;\\n}\\n
\\nfunc maximumTripletValue(nums []int) int64 {\\n var ans, maxDiff, preMax int\\n for _, x := range nums {\\n ans = max(ans, maxDiff*x)\\n maxDiff = max(maxDiff, preMax-x)\\n preMax = max(preMax, x)\\n }\\n return int64(ans)\\n}\\n
\\nvar maximumTripletValue = function(nums) {\\n let ans = 0, maxDiff = 0, preMax = 0;\\n for (const x of nums) {\\n ans = Math.max(ans, maxDiff * x);\\n maxDiff = Math.max(maxDiff, preMax - x);\\n preMax = Math.max(preMax, x);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximum_triplet_value(nums: Vec<i32>) -> i64 {\\n let mut ans = 0;\\n let mut max_diff = 0;\\n let mut pre_max = 0;\\n for x in nums {\\n ans = ans.max(max_diff as i64 * x as i64);\\n max_diff = max_diff.max(pre_max - x);\\n pre_max = pre_max.max(x);\\n }\\n ans\\n }\\n}\\n
\\n如果 $\\\\textit{nums}$ 中有负数,要怎么做?
\\n欢迎在评论区分享你的思路/代码。
\\n更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举 j 枚举 $j$,为了让 $(\\\\textit{nums}[i] - \\\\textit{nums}[j]) * \\\\textit{nums}[k]$ 尽量大,我们需要知道 $j$ 左侧元素的最大值(让 $\\\\textit{nums}[i] - \\\\textit{nums}[j]$ 尽量大),以及 $j$ 右侧元素的最大值(让乘积尽量大)。\\n\\n也就是计算 $\\\\textit{nums}$ 的前缀最大值 $\\\\textit{preMax}$ 和后缀最大值 $\\\\textit{sufMax}$,这可以用递推预处理:\\n\\n$\\\\textit{preMax}[i…","guid":"https://leetcode.cn/problems/maximum-value-of-an-ordered-triplet-ii//solution/mei-ju-jzhao-qian-hou-zui-da-zhi-pythonj-um8q","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-10-01T04:10:41.412Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【2848. 与车相交的点】题解:对齐开始节点+合并结束节点","url":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/2848-yu-che-xiang-jiao-de-dian-ti-jie-du-rl9i","content":"\\n\\nProblem: 2848. 与车相交的点
\\n
[TOC]
\\n\\n\\n对齐开始节点+合并结束节点。
\\n
\\n\\n1、构造哈希表统计区间,key=开始节点,value=结束节点。
\\n
\\n2、按开始节点从小到大排序。
\\n3、对齐开始节点,合并结束节点。由于开始节点已排序,每次只需合并结束节点,并统计没有覆盖的区间范围。
\\n4、最后区间统计:return end - start + 1 - unCoveredCnt
\\n\\n$O(n)$,n为nums数组长度。
\\n
\\n\\n$O(n)$,n为nums数组长度,使用了辅助哈希表区间统计。
\\n
###Java
\\n\\nclass Solution {\\n public int numberOfPoints(List<List<Integer>> nums) {\\n // 1、构造哈希表统计区间,key=开始节点,value=结束节点\\n HashMap<Integer, Integer> numMap = new HashMap<>();\\n for (List<Integer> numList : nums) {\\n int tmpStart = numList.get(0);\\n int tmpEnd = numList.get(1);\\n if (numMap.containsKey(tmpStart)) {\\n // 对于相同的开始节点,只记录最大结束节点\\n numMap.put(tmpStart, Math.max(tmpEnd, numMap.get(tmpStart)));\\n } else {\\n numMap.put(tmpStart, tmpEnd);\\n }\\n }\\n\\n // 2、按开始节点从小到大排序\\n List<Integer> startList = new ArrayList<>(numMap.keySet());\\n startList.sort((a, b) -> {\\n return a.compareTo(b);\\n });\\n\\n // 3、对齐开始节点,合并结束节点。由于开始节点已排序,每次只需合并结束节点,并统计没有覆盖的区间范围\\n int unCoveredCnt = 0;\\n int start = 0;\\n int end = 0;\\n for (int i = 0; i < startList.size(); i++) {\\n int tmpStart = startList.get(i);\\n int tmpEnd = numMap.get(tmpStart);\\n if (i == 0) {\\n start = tmpStart;\\n end = tmpEnd;\\n continue;\\n }\\n\\n if (tmpStart <= end + 1) {\\n if (tmpEnd <= end) {\\n // 包含\\n } else {\\n // 相交或相连\\n end = tmpEnd;\\n }\\n } else {\\n // 不相交,统计没有被覆盖的区间\\n unCoveredCnt += tmpStart - end - 1;\\n end = tmpEnd;\\n }\\n }\\n \\n // 4、区间统计\\n return end - start + 1 - unCoveredCnt;\\n }\\n}\\n
\\n","description":"Problem: 2848. 与车相交的点 [TOC]\\n\\n对齐开始节点+合并结束节点。\\n\\n1、构造哈希表统计区间,key=开始节点,value=结束节点。\\n 2、按开始节点从小到大排序。\\n 3、对齐开始节点,合并结束节点。由于开始节点已排序,每次只需合并结束节点,并统计没有覆盖的区间范围。\\n 4、最后区间统计:return end - start + 1 - unCoveredCnt\\n\\n时间复杂度:\\n\\n$O(n)$,n为nums数组长度。\\n\\n空间复杂度:\\n\\n$O(n)$,n为nums数组长度,使用了辅助哈希表区间统计。\\n\\n###Jav…","guid":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/2848-yu-che-xiang-jiao-de-dian-ti-jie-du-rl9i","author":"fa-xing-bu-luan","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-11T03:03:56.164Z","media":[{"url":"https://pic.leetcode.cn/1694401319-fUQZSY-%E6%8D%95%E8%8E%B7.JPG","type":"photo","width":914,"height":183}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【小彭】一题四解 | 枚举区间、枚举坐标、差分、离散化","url":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/xiao-peng-yi-ti-si-jie-mei-ju-qu-jian-me-4bay","content":"简单模拟题,值域非常小可以暴力做,优化方法也有很多:
\\n枚举每个区间并将区间中的每个点添加到散列表中,最后返回散列表的大小:
\\n###kotlin
\\nclass Solution {\\n fun numberOfPoints(nums: List<List<Int>>): Int {\\n val set = HashSet<Int>()\\n for ((start, end) in nums) {\\n for (e in start..end) set.add(e)\\n }\\n return set.size\\n }\\n}\\n
\\n复杂度分析:
\\n枚举值域坐标,如果被至少一个区间覆盖则记录结果:
\\n###kotlin
\\nclass Solution {\\n fun numberOfPoints(nums: List<List<Int>>): Int {\\n var ret = 0\\n for (i in 1 .. 100) {\\n for ((start, end) in nums) {\\n if (i in start .. end) {\\n ret ++\\n break\\n }\\n }\\n }\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n如果区间非常多,我们可以使用差分数组,计算至少被 1 个区间覆盖的坐标,实现线性时间复杂度:
\\n###kotlin
\\nclass Solution {\\n fun numberOfPoints(nums: List<List<Int>>): Int {\\n var mn = Integer.MAX_VALUE\\n var mm = Integer.MIN_VALUE\\n for ((start, end) in nums) {\\n mn = min(mn, start)\\n mm = max(mm, end)\\n }\\n // 差分数组\\n val diff = IntArray(mm - mn + 2) // mn - mn + 1 + 1\\n for ((start, end) in nums) {\\n diff[start - mn] += 1\\n diff[end - mn + 1] -= 1\\n }\\n // 对差分数组求前缀和\\n var s = 0\\n var ret = 0\\n for (e in diff) {\\n s += e\\n if (s > 0) ret ++\\n }\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n如果值域非常大,差分数组也会超过空间限制,我们可以排序 + 双指针采用计算每段重叠区间的长度,这相当于求解 56. 合并区间。
\\n\\n\\n本质上是离散化优化空间复杂度。
\\n
###kotlin
\\nclass Solution {\\n fun numberOfPoints(nums: MutableList<List<Int>>): Int {\\n // 排序\\n Collections.sort(nums) { i1, i2 ->\\n i1[0] - i2[0]\\n }\\n var ret = 0\\n // 双指针\\n var start = nums[0][0]\\n var end = nums[0][1]\\n for (i in 1 until nums.size) {\\n val (curStart, curEnd) = nums[i]\\n if (curStart <= end) {\\n end = max(end, curEnd)\\n } else{\\n ret += end - start + 1\\n start = curStart\\n end = curEnd\\n }\\n }\\n ret += end - start + 1\\n return ret\\n }\\n}\\n
\\n复杂度分析:
\\n核心思路:计算每个点被覆盖了多少次。统计覆盖次数大于 $0$ 的点,即为答案。
\\n假设一开始有一个全为 $0$ 的数组 $a$,用来保存每个点被覆盖了多少次。
\\n对于示例 1,我们可以把 $a$ 中下标在 $[3,6]$ 的元素都加一,下标在 $[1,5]$ 的元素都加一,下标在 $[4,7]$ 的元素都加一。
\\n然后,统计 $a[i] > 0$ 的个数,即为答案。
\\n如何快速地「把区间内的数都加一」呢?
\\n这可以用差分数组实现。见 原理讲解,推荐和【图解】从一维差分到二维差分 一起看。
\\nclass Solution:\\n def numberOfPoints(self, nums: List[List[int]]) -> int:\\n max_end = max(end for _, end in nums)\\n diff = [0] * (max_end + 2) # 注意下面有 end+1\\n for start, end in nums:\\n diff[start] += 1\\n diff[end + 1] -= 1\\n return sum(s > 0 for s in accumulate(diff))\\n
\\nclass Solution {\\n public int numberOfPoints(List<List<Integer>> nums) {\\n int maxEnd = 0;\\n for (List<Integer> p : nums) {\\n maxEnd = Math.max(maxEnd, p.get(1));\\n }\\n\\n int[] diff = new int[maxEnd + 2]; // 注意下面有 end+1\\n for (List<Integer> interval : nums) {\\n diff[interval.get(0)]++;\\n diff[interval.get(1) + 1]--;\\n }\\n\\n int ans = 0;\\n int s = 0;\\n for (int d : diff) {\\n s += d;\\n if (s > 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int numberOfPoints(vector<vector<int>>& nums) {\\n int max_end = ranges::max(nums, {}, [](const auto& a) { return a[1]; })[1];\\n\\n vector<int> diff(max_end + 2); // 注意下面有 end+1\\n for (auto& interval : nums) {\\n diff[interval[0]]++;\\n diff[interval[1] + 1]--;\\n }\\n\\n int ans = 0, s = 0;\\n for (int d : diff) {\\n s += d;\\n ans += s > 0;\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint numberOfPoints(int** nums, int numsSize, int* numsColSize) {\\n int max_end = 0;\\n for (int i = 0; i < numsSize; i++) {\\n max_end = MAX(max_end, nums[i][1]);\\n }\\n\\n int* diff = calloc(max_end + 2, sizeof(int)); // 注意下面有 end+1\\n for (int i = 0; i < numsSize; i++) {\\n diff[nums[i][0]]++;\\n diff[nums[i][1] + 1]--;\\n }\\n\\n int ans = 0;\\n int s = 0;\\n for (int i = 1; i <= max_end; i++) {\\n s += diff[i];\\n ans += s > 0;\\n }\\n\\n free(diff);\\n return ans;\\n}\\n
\\nfunc numberOfPoints(nums [][]int) (ans int) {\\n maxEnd := 0\\n for _, interval := range nums {\\n maxEnd = max(maxEnd, interval[1])\\n }\\n\\n diff := make([]int, maxEnd+2) // 注意下面有 end+1\\n for _, interval := range nums {\\n diff[interval[0]]++\\n diff[interval[1]+1]--\\n }\\n\\n s := 0\\n for _, d := range diff {\\n s += d\\n if s > 0 {\\n ans++\\n }\\n }\\n return\\n}\\n
\\nvar numberOfPoints = function(nums) {\\n const maxEnd = Math.max(...nums.map(interval => interval[1]));\\n\\n const diff = Array(maxEnd + 2).fill(0); // 注意下面有 end+1\\n for (const [start, end] of nums) {\\n diff[start]++;\\n diff[end + 1]--;\\n }\\n\\n let ans = 0, s = 0;\\n for (const d of diff) {\\n s += d;\\n if (s > 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn number_of_points(nums: Vec<Vec<i32>>) -> i32 {\\n let max_end = nums.iter().map(|interval| interval[1]).max().unwrap() as usize;\\n\\n let mut diff = vec![0; max_end + 2]; // 注意下面有 end+1\\n for interval in nums {\\n diff[interval[0] as usize] += 1;\\n diff[(interval[1] + 1) as usize] -= 1;\\n }\\n\\n let mut ans = 0;\\n let mut s = 0;\\n for d in diff {\\n s += d;\\n if s > 0 {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单中的「差分」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:计算每个点被覆盖了多少次。统计覆盖次数大于 $0$ 的点,即为答案。 假设一开始有一个全为 $0$ 的数组 $a$,用来保存每个点被覆盖了多少次。\\n\\n对于示例 1,我们可以把 $a$ 中下标在 $[3,6]$ 的元素都加一,下标在 $[1,5]$ 的元素都加一,下标在 $[4,7]$ 的元素都加一。\\n\\n然后,统计 $a[i] > 0$ 的个数,即为答案。\\n\\n如何快速地「把区间内的数都加一」呢?\\n\\n这可以用差分数组实现。见 原理讲解,推荐和【图解】从一维差分到二维差分 一起看。\\n\\nclass Solution:\\n def numberOfPoints…","guid":"https://leetcode.cn/problems/points-that-intersect-with-cars//solution/chai-fen-shu-zu-xian-xing-zuo-fa-by-endl-3xpm","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-10T04:26:47.975Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种递归思路(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves//solution/liang-chong-di-gui-si-lu-pythonjavacgojs-xxnk","content":"推荐先把 236. 二叉树的最近公共祖先 做了,对理解本题做法有帮助。
\\n本题最深的叶子可能只有一个,此时这个叶子就是答案。如果最深的叶子不止一个,那么答案为所有最深叶子的最近公共祖先。
\\n回顾 236 题的做法:
\\n对于本题,要找的节点是最深的叶子。
\\n如果左子树的最大深度比右子树的大,那么(子树中的)最深叶子就只在左子树中,所以(子树中的)最深叶子的最近公共祖先也只在左子树中。
\\n如果左右子树的最大深度一样呢?当前节点一定是最近公共祖先吗?
\\n不一定。比如上图节点 $1$ 的左右子树最深叶子 $0,8$ 的深度都是 $2$,但该深度并不是全局最大深度,所以节点 $1$ 并不是答案。
\\n根据以上讨论,正确做法如下:
\\nclass Solution:\\n def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\\n ans = None\\n max_depth = -1 # 全局最大深度\\n def dfs(node: Optional[TreeNode], depth: int) -> int:\\n nonlocal ans, max_depth\\n if node is None:\\n max_depth = max(max_depth, depth) # 维护全局最大深度\\n return depth\\n left_max_depth = dfs(node.left, depth + 1) # 左子树最深空节点的深度\\n right_max_depth = dfs(node.right, depth + 1) # 右子树最深空节点的深度\\n if left_max_depth == right_max_depth == max_depth: # 最深的空节点左右子树都有\\n ans = node\\n return max(left_max_depth, right_max_depth) # 当前子树最深空节点的深度\\n dfs(root, 0)\\n return ans\\n
\\nclass Solution {\\n private TreeNode ans;\\n private int maxDepth = -1; // 全局最大深度\\n\\n public TreeNode lcaDeepestLeaves(TreeNode root) {\\n dfs(root, 0);\\n return ans;\\n }\\n\\n private int dfs(TreeNode node, int depth) {\\n if (node == null) {\\n maxDepth = Math.max(maxDepth, depth); // 维护全局最大深度\\n return depth;\\n }\\n int leftMaxDepth = dfs(node.left, depth + 1); // 左子树最深空节点的深度\\n int rightMaxDepth = dfs(node.right, depth + 1); // 右子树最深空节点的深度\\n if (leftMaxDepth == rightMaxDepth && leftMaxDepth == maxDepth) { // 最深的空节点左右子树都有\\n ans = node;\\n }\\n return Math.max(leftMaxDepth, rightMaxDepth); // 当前子树最深空节点的深度\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n TreeNode* lcaDeepestLeaves(TreeNode* root) {\\n TreeNode* ans = nullptr;\\n int max_depth = -1; // 全局最大深度\\n auto dfs = [&](this auto&& dfs, TreeNode* node, int depth) {\\n if (node == nullptr) {\\n max_depth = max(max_depth, depth); // 维护全局最大深度\\n return depth;\\n }\\n int left_max_depth = dfs(node->left, depth + 1); // 左子树最深空节点的深度\\n int right_max_depth = dfs(node->right, depth + 1); // 右子树最深空节点的深度\\n if (left_max_depth == right_max_depth && left_max_depth == max_depth) { // 最深的空节点左右子树都有\\n ans = node;\\n }\\n return max(left_max_depth, right_max_depth); // 当前子树最深空节点的深度\\n };\\n dfs(root, 0);\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nstruct TreeNode* lcaDeepestLeaves(struct TreeNode* root) {\\n struct TreeNode* ans = NULL;\\n int max_depth = -1; // 全局最大深度\\n\\n int dfs(struct TreeNode* node, int depth) {\\n if (node == NULL) {\\n max_depth = MAX(max_depth, depth); // 维护全局最大深度\\n return depth;\\n }\\n int left_max_depth = dfs(node->left, depth + 1); // 左子树最深空节点的深度\\n int right_max_depth = dfs(node->right, depth + 1); // 右子树最深空节点的深度\\n if (left_max_depth == right_max_depth && left_max_depth == max_depth) { // 最深的空节点左右子树都有\\n ans = node;\\n }\\n return MAX(left_max_depth, right_max_depth); // 当前子树最深空节点的深度\\n }\\n\\n dfs(root, 0);\\n return ans;\\n}\\n
\\nfunc lcaDeepestLeaves(root *TreeNode) (ans *TreeNode) {\\n maxDepth := -1 // 全局最大深度\\n var dfs func(*TreeNode, int) int\\n dfs = func(node *TreeNode, depth int) int {\\n if node == nil {\\n maxDepth = max(maxDepth, depth) // 维护全局最大深度\\n return depth\\n }\\n leftMaxDepth := dfs(node.Left, depth+1) // 左子树最深空节点的深度\\n rightMaxDepth := dfs(node.Right, depth+1) // 右子树最深空节点的深度\\n if leftMaxDepth == rightMaxDepth && leftMaxDepth == maxDepth { // 最深的空节点左右子树都有\\n ans = node\\n }\\n return max(leftMaxDepth, rightMaxDepth) // 当前子树最深空节点的深度\\n }\\n dfs(root, 0)\\n return\\n}\\n
\\nvar lcaDeepestLeaves = function(root) {\\n let ans = null;\\n let maxDepth = -1; // 全局最大深度\\n function dfs(node, depth) {\\n if (node === null) {\\n maxDepth = Math.max(maxDepth, depth); // 维护全局最大深度\\n return depth;\\n }\\n const leftMaxDepth = dfs(node.left, depth + 1); // 左子树最深空节点的深度\\n const rightMaxDepth = dfs(node.right, depth + 1); // 右子树最深空节点的深度\\n if (leftMaxDepth === rightMaxDepth && leftMaxDepth === maxDepth) { // 最深的空节点左右子树都有\\n ans = node;\\n }\\n return Math.max(leftMaxDepth, rightMaxDepth);// 当前子树最深空节点的深度\\n }\\n dfs(root, 0);\\n return ans;\\n};\\n
\\nuse std::rc::Rc;\\nuse std::cell::RefCell;\\n\\nimpl Solution {\\n pub fn lca_deepest_leaves(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {\\n let mut ans = None;\\n let mut max_depth = -1; // 全局最大深度\\n\\n fn dfs(node: &Option<Rc<RefCell<TreeNode>>>, depth: i32, max_depth: &mut i32, ans: &mut Option<Rc<RefCell<TreeNode>>>) -> i32 {\\n if let Some(node) = node {\\n let x = node.borrow();\\n let left_max_depth = dfs(&x.left, depth + 1, max_depth, ans); // 左子树最深空节点的深度\\n let right_max_depth = dfs(&x.right, depth + 1, max_depth, ans); // 右子树最深空节点的深度\\n if left_max_depth == right_max_depth && left_max_depth == *max_depth { // 最深的空节点左右子树都有\\n *ans = Some(node.clone());\\n }\\n left_max_depth.max(right_max_depth) // 当前子树最深空节点的深度\\n } else {\\n *max_depth = (*max_depth).max(depth); // 维护全局最大深度\\n depth\\n }\\n }\\n\\n dfs(&root, 0, &mut max_depth, &mut ans);\\n ans\\n }\\n}\\n
\\n能否不用外部变量 $\\\\textit{ans}$ 和 $\\\\textit{maxDepth}$ 呢?
\\n把每棵子树都看成是一个「子问题」,即对于每棵子树,我们需要知道:
\\n设子树的根节点为 $\\\\textit{node}$,$\\\\textit{node}$ 的左子树的高度为 $\\\\textit{leftHeight}$,$\\\\textit{node}$ 的右子树的高度为 $\\\\textit{rightHeight}$。分类讨论:
\\nclass Solution:\\n def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\\n def dfs(node: Optional[TreeNode]) -> (int, Optional[TreeNode]):\\n if node is None:\\n return 0, None\\n left_height, left_lca = dfs(node.left)\\n right_height, right_lca = dfs(node.right)\\n if left_height > right_height: # 左子树更高\\n return left_height + 1, left_lca\\n if left_height < right_height: # 右子树更高\\n return right_height + 1, right_lca\\n return left_height + 1, node # 一样高\\n return dfs(root)[1]\\n
\\nclass Solution {\\n public TreeNode lcaDeepestLeaves(TreeNode root) {\\n return dfs(root).getValue();\\n }\\n\\n private Pair<Integer, TreeNode> dfs(TreeNode node) {\\n if (node == null) {\\n return new Pair<>(0, null);\\n }\\n Pair<Integer, TreeNode> left = dfs(node.left);\\n Pair<Integer, TreeNode> right = dfs(node.right);\\n if (left.getKey() > right.getKey()) { // 左子树更高\\n return new Pair<>(left.getKey() + 1, left.getValue());\\n }\\n if (left.getKey() < right.getKey()) { // 右子树更高\\n return new Pair<>(right.getKey() + 1, right.getValue());\\n }\\n return new Pair<>(left.getKey() + 1, node); // 一样高\\n }\\n}\\n
\\nclass Solution {\\n pair<int, TreeNode*> dfs(TreeNode* node) {\\n if (node == nullptr) {\\n return {0, nullptr};\\n }\\n auto [left_height, left_lca] = dfs(node->left);\\n auto [right_height, right_lca] = dfs(node->right);\\n if (left_height > right_height) { // 左子树更高\\n return {left_height + 1, left_lca};\\n }\\n if (left_height < right_height) { // 右子树更高\\n return {right_height + 1, right_lca};\\n }\\n return {left_height + 1, node}; // 一样高\\n }\\n\\npublic:\\n TreeNode* lcaDeepestLeaves(TreeNode* root) {\\n return dfs(root).second;\\n }\\n};\\n
\\ntypedef struct {\\n int height;\\n struct TreeNode* lca;\\n} Pair;\\n\\nPair dfs(struct TreeNode* node) {\\n if (node == NULL) {\\n return (Pair) {0, NULL};\\n }\\n Pair left = dfs(node->left);\\n Pair right = dfs(node->right);\\n if (left.height > right.height) { // 左子树更高\\n return (Pair) {left.height + 1, left.lca};\\n }\\n if (left.height < right.height) { // 右子树更高\\n return (Pair) {right.height + 1, right.lca};\\n }\\n return (Pair) {left.height + 1, node}; // 一样高\\n}\\n\\nstruct TreeNode* lcaDeepestLeaves(struct TreeNode* root) {\\n return dfs(root).lca;\\n}\\n
\\nfunc dfs(node *TreeNode) (int, *TreeNode) {\\n if node == nil {\\n return 0, nil\\n }\\n leftHeight, leftLCA := dfs(node.Left)\\n rightHeight, rightLCA := dfs(node.Right)\\n if leftHeight > rightHeight { // 左子树更高\\n return leftHeight + 1, leftLCA\\n }\\n if leftHeight < rightHeight { // 右子树更高\\n return rightHeight + 1, rightLCA\\n }\\n return leftHeight + 1, node // 一样高\\n}\\n\\nfunc lcaDeepestLeaves(root *TreeNode) *TreeNode {\\n _, lca := dfs(root)\\n return lca\\n}\\n
\\nvar dfs = function(node) {\\n if (node === null) {\\n return [0, null];\\n }\\n const [leftHeight, leftLca] = dfs(node.left);\\n const [rightHeight, rightLca] = dfs(node.right);\\n if (leftHeight > rightHeight) { // 左子树更高\\n return [leftHeight + 1, leftLca];\\n }\\n if (leftHeight < rightHeight) { // 右子树更高\\n return [rightHeight + 1, rightLca];\\n }\\n return [leftHeight + 1, node]; // 一样高\\n};\\n\\nvar lcaDeepestLeaves = function(root) {\\n return dfs(root, 0)[1];\\n};\\n
\\nuse std::rc::Rc;\\nuse std::cell::RefCell;\\n\\nimpl Solution {\\n pub fn lca_deepest_leaves(root: Option<Rc<RefCell<TreeNode>>>) -> Option<Rc<RefCell<TreeNode>>> {\\n fn dfs(node: &Option<Rc<RefCell<TreeNode>>>) -> (i32, Option<Rc<RefCell<TreeNode>>>) {\\n if let Some(node) = node {\\n let x = node.borrow();\\n let (left_height, left_lca) = dfs(&x.left);\\n let (right_height, right_lca) = dfs(&x.right);\\n if left_height > right_height {\\n return (left_height + 1, left_lca); // 左子树更高\\n }\\n if left_height < right_height {\\n return (right_height + 1, right_lca); // 右子树更高\\n }\\n (left_height + 1, Some(node.clone())) // 一样高\\n } else {\\n (0, None)\\n }\\n }\\n dfs(&root).1\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前言 推荐先把 236. 二叉树的最近公共祖先 做了,对理解本题做法有帮助。\\n\\n本题最深的叶子可能只有一个,此时这个叶子就是答案。如果最深的叶子不止一个,那么答案为所有最深叶子的最近公共祖先。\\n\\n方法一:递归递归,有递有归\\n\\n回顾 236 题的做法:\\n\\n如果要找的节点只在左子树中,那么最近公共祖先也只在左子树中。\\n如果要找的节点只在右子树中,那么最近公共祖先也只在右子树中。\\n如果要找的节点左右子树都有,那么最近公共祖先就是当前节点。\\n\\n对于本题,要找的节点是最深的叶子。\\n\\n如果左子树的最大深度比右子树的大,那么(子树中的)最深叶子就只在左子树中,所以(子树中的…","guid":"https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves//solution/liang-chong-di-gui-si-lu-pythonjavacgojs-xxnk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-05T23:52:06.793Z","media":[{"url":"https://pic.leetcode.cn/1693882910-FIoXPj-sketch1.png","type":"photo","width":722,"height":614,"blurhash":"LCSigQ_3-;~q~p%2kDM{x^WE%1RP"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"记忆化数位DP","url":"https://leetcode.cn/problems/count-symmetric-integers//solution/ji-yi-hua-shu-wei-dp-by-peaceful-explore-ovp6","content":"\\n\\nProblem: https://leetcode.cn/problems/count-symmetric-integers/description/
\\n
用记忆化的方法保存以数字之和或之差为$sum$开始,在长度为$index + 1$的整数中所有对称整数的数目。以下是代码中用到的其它各个变量的含义。
\\n$index$ 表示目前正在处理数中的第几个十进制位。我们从数的最高位开始处理。$index + 1$ 则表示目前还要处理多少个数字。
\\n$len$ 目前正在构建的非$0$整数的长度
\\n$sum$ 逐位加上前一半部分的各位数字和减去后一半部分的各位数字。
\\n$isLeadingZero$ 表示前面高位的数字是否都为 $0$。
\\n$isMaxDigit$ 表示前面高位的数字是否都为最大值。
\\n###Java
\\n\\nclass Solution {\\n static int[][] memo = new int[5][19];\\n static int[] digits = new int[5];\\n\\n public int countSymmetricIntegers(int low, int high) {\\n return calc(high) - calc(low - 1);\\n }\\n\\n int calc(int num) {\\n if (num == 0) return 0;\\n \\n int n = 0;\\n while (num > 0) {\\n digits[n++] = num % 10;\\n num /= 10;\\n }\\n return dfs(n - 1, 0, 0, true, true);\\n } \\n \\n int dfs(int index, int len, int sum, boolean isLeadingZero, boolean isMaxDigit) {\\n\\n if (index == -1) {\\n if ((len & 1) == 0 && sum == 0 && !isLeadingZero) return 1;\\n return 0;\\n }\\n \\n if(!isLeadingZero && ((len + index ) & 1) == 0)\\n return 0;\\n \\n if(!isLeadingZero && !isMaxDigit && memo[index][sum] != 0)\\n return memo[index][sum];\\n \\n int result = 0;\\n int maxDigit = isMaxDigit? digits[index] : 9;\\n for (int digit = 0; digit <= maxDigit; ++digit) {\\n \\n int newLen = isLeadingZero && digit == 0 ? 0:len + 1;\\n int newSum = len < index? sum + digit:sum - digit;\\n if(newSum < 0) break;\\n \\n result += dfs(index - 1, newLen, newSum, isLeadingZero && digit == 0, isMaxDigit && digit == maxDigit);\\n }\\n\\n if(!isLeadingZero && !isMaxDigit)\\n memo[index][sum] = result; \\n return result;\\n }\\n}\\n\\n\\n
\\n类似的方法可以用来解题1012. 至少有 1 位重复的数字
\\n
###Java
\\n\\nclass Solution {\\n static int[][] memo = new int[10][1 << 10];\\n static int[] digits = new int[10];\\n \\n public int numDupDigitsAtMostN(int n) {\\n return n - calculate(n);\\n }\\n \\n int calculate(int n) {\\n int size = 0;\\n \\n while (n > 0) {\\n digits[size++] = n % 10;\\n n /= 10;\\n }\\n \\n return dfs(size - 1, 0, true, true);\\n }\\n\\n int dfs(int index, int mask, boolean isLeadingZero, boolean isMaxDigit) { \\n \\n if (index == -1) \\n return isLeadingZero? 0 : 1;\\n \\n if (!isLeadingZero && !isMaxDigit && memo[index][mask] != 0) \\n return memo[index][mask];\\n \\n int result = 0;\\n int maxDigit = isMaxDigit ? digits[index] : 9;\\n \\n for (int i = 0; i <= maxDigit; i++) {\\n if ((mask & (1 << i)) != 0) continue;\\n int newMask = isLeadingZero && (i == 0) ? mask : mask| (1 << i); \\n result += dfs(index - 1, newMask, isLeadingZero && i == 0, isMaxDigit && (i == maxDigit));\\n }\\n \\n if (!isLeadingZero && !isMaxDigit) \\n memo[index][mask] = result;\\n \\n return result;\\n }\\n}\\n\\n\\n
\\n","description":"Problem: https://leetcode.cn/problems/count-symmetric-integers/description/ 用记忆化的方法保存以数字之和或之差为$sum$开始,在长度为$index + 1$的整数中所有对称整数的数目。以下是代码中用到的其它各个变量的含义。\\n\\n$index$ 表示目前正在处理数中的第几个十进制位。我们从数的最高位开始处理。$index + 1$ 则表示目前还要处理多少个数字。\\n\\n$len$ 目前正在构建的非$0$整数的长度\\n\\n$sum…","guid":"https://leetcode.cn/problems/count-symmetric-integers//solution/ji-yi-hua-shu-wei-dp-by-peaceful-explore-ovp6","author":"peaceful-explorer","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T13:05:41.685Z","media":[{"url":"https://pic.leetcode.cn/1693875348-uLaAxc-image.png","type":"photo","width":884,"height":203,"blurhash":"LPSimktmxU^*~VxXR.IuM}Rms+t3"},{"url":"https://pic.leetcode.cn/1693997562-EqNpKA-image.png","type":"photo","width":950,"height":197,"blurhash":"LQS?15-;yA%3_LaOMztPRQoxoyVu"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Java/Python3/C++]前缀和+哈希表+模运算:趣味子数组【图解】","url":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/javapython3cqian-zhui-he-ha-xi-biao-mo-y-isrq","content":"给你一个下标从 0
开始的整数数组 nums
,以及整数 modulo
和整数 k
。
请你找出并统计数组中 趣味子数组 的数目。
\\n如果 子数组 nums[l..r]
满足下述条件,则称其为 趣味子数组 :
[l, r]
内,设 cnt
为满足 nums[i] % modulo == k
的索引 i
的数量。并且 cnt % modulo == k
。以整数形式表示并返回趣味子数组的数目。
\\n注意:子数组是数组中的一个连续非空的元素序列。
\\n这道题是一个经典的子数组利用前缀和+哈希表求解的题目。求解子数组的方法通常用两种:前缀和或者滑动窗口。
\\n对于这道题,它要统计子数组中满足某个条件的值的数目(即cnt
值),那么使用前缀和更方便。
我们可以使用preSum[i+1]
表示区间[0, i]
的cnt
值,cnt
表示区间中满足 nums[i] % modulo == k
的元素个数。
\\n首先前缀和的状态转移为:
preSum[i + 1] = preSum[i] + nums[i]是否满足条件\\npreSum[i]是已知的,表示区间[0, i-1]的cnt值,那么只要新加入的元素nums[i]满足他条件,区间[0, i]的cnt值就为前一个区间的值 + 1;\\n否则,跟随前一个区间的cnt值preSum[i]\\n
\\n很明显,i
的取值范围为[0, n-1]
,因此i+1
的取值范围为[1, n]
。且当i+1 = 0
时,所表示的区间[0, -1]
不存在,因此preSum[0] = 0
,从而保证preSum[1]
的值仅取决于nums[0]
本身。
对于任意一个区间[l, r]
,都可以表示为两个区间相见[r, 0] - [l - 1, 0]
。对应的cnt
值preSum[l, r] = preSum[r + 1] - preSum[l]
,因此任意一个区间(子数组区间)的cnt
值都可以通过前缀和表示。
\\n同时当l = 0
时,preSum[0, r] = preSum[r + 1] - preSum[0] = preSum[r + 1] = preSum[r]
,进一步证明了preSum[0] = 0
。
{:width=400}
从上一步我们知道,任意子数组区间的cnt
值可以通过前缀和求解,即preSum[l, r] = preSum[r + 1] - preSum[l]
。这只满足了趣味子数组的第一个条件。
\\n趣味子数组的第二个条件为cnt % modulo == k
,即preSum[l, r] % modulo == k
,进一步的(preSum[r + 1] - preSum[l]) % modulo == k
。
\\n模运算满足分配律:(a + b) mod r = (a mod r + b mod r) mod r
(preSum[r + 1] - preSum[l]) % modulo == k\\n(preSum[r + 1] % modulo - preSum[l] % modulo) % modulo - k == 0\\n(preSum[r + 1] % modulo - preSum[l] % modulo - k % modulo) % modulo == 0\\n【k < modulo,因此 k % modulo = k】\\n【括号内的数值都是小于modulo,我们可以认为其运算结果不会超过modulo,因此括号为的modulo】\\npreSum[r + 1] % modulo - preSum[l] % modulo - k == 0\\n(preSum[r + 1] - k) % modulo == preSum[l] % modulo\\n
\\n【这个模运算的推导不是很严谨,但对于这道题来说是适用的,可以代入数值进行验算】
\\n比如:\\npreSum[r + 1] = 10, preSum[l] = 8, modulo =3, k = 2\\n(10 - 8) mod 3 == 2\\n\\n(preSum[r + 1] - k) % modulo: (10 - 2) mod 3 = 8 mod 3 = 2\\npreSum[l] % modulo: 8 mod 3 = 2\\n\\n即二者相等\\n
\\n因此对于一个右边界r
,我们只要知道在其之前有多少个 l
的前缀和满足(preSum[r + 1] - k) % modulo == preSum[l] % modulo
,就有多少个子数组。
因此,我们可以使用哈希表来存储每个preSum[i] % modulo
,从而实现一次遍历中得到所有满足条件的子数组。
\\n初始将键值对0:1
添加到哈希表中,因为preSum[0] % modulo = 0 % modulo = 0
,保证有一个0
的键。
class Solution {\\n public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {\\n int n = nums.size();\\n long[] preSum = new long[n + 1]; // 前缀和,preSum[i]表示区间[0, i]的cnt值\\n Map<Long, Long> count = new HashMap<>(); // 记录不同cnt%modulo的前缀和个数\\n count.put(0L, 1L); // 初始化cnt%modulo=0有一个,即preSum[0]=0\\n long res = 0; // 结果\\n for(int i = 0; i < n; i++){\\n preSum[i + 1] = preSum[i] + (nums.get(i) % modulo == k ? 1 : 0); // 状态转移\\n res += count.getOrDefault((preSum[i + 1] - k) % modulo, 0L); // 获取r=i的趣味子数组数目\\n count.put(preSum[i + 1] % modulo, count.getOrDefault(preSum[i + 1] % modulo, 0L) + 1); // 添加这个前缀和到哈希表\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution:\\n def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:\\n n = len(nums)\\n preSum = [0] * (n + 1) # 前缀和,preSum[i]表示区间[0, i]的cnt值\\n count = {0 : 1} # 记录不同cnt%modulo的前缀和个数 初始化cnt%modulo=0有一个,即preSum[0]=0\\n res = 0 # 结果\\n for i in range(n):\\n preSum[i + 1] = preSum[i] + (1 if nums[i] % modulo == k else 0) # 状态转移\\n res += count.get((preSum[i + 1] - k) % modulo, 0) # 获取r=i的趣味子数组数目\\n count[preSum[i + 1] % modulo] = count.get(preSum[i + 1] % modulo, 0) + 1 # 添加这个前缀和到哈希表\\n return res\\n
\\nclass Solution {\\npublic:\\n long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {\\n int n = nums.size();\\n vector<long long> preSum(n + 1); // 前缀和,preSum[i]表示区间[0, i]的cnt值\\n unordered_map<long long, long long> count{{0L, 1L}}; // 记录不同cnt%modulo的前缀和个数; 初始化cnt%modulo=0有一个,即preSum[0]=0\\n long long res = 0; // 结果\\n for(int i = 0; i < n; i++){\\n preSum[i + 1] = preSum[i] + (nums[i] % modulo == k ? 1 : 0); // 状态转移\\n res += count[(preSum[i + 1] - k) % modulo]; // 获取r=i的趣味子数组数目\\n count[preSum[i + 1] % modulo] += 1; // 添加这个前缀和到哈希表\\n }\\n return res;\\n }\\n};\\n
\\n","description":"6952. 统计趣味子数组的数目 给你一个下标从 0 开始的整数数组 nums ,以及整数 modulo 和整数 k 。\\n\\n请你找出并统计数组中 趣味子数组 的数目。\\n\\n如果 子数组 nums[l..r] 满足下述条件,则称其为 趣味子数组 :\\n\\n在范围 [l, r] 内,设 cnt 为满足 nums[i] % modulo == k 的索引 i 的数量。并且 cnt % modulo == k 。\\n\\n以整数形式表示并返回趣味子数组的数目。\\n\\n注意:子数组是数组中的一个连续非空的元素序列。\\n\\n这道题是一个经典的子数组利用前缀和+哈希表求解的题目…","guid":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/javapython3cqian-zhui-he-ha-xi-biao-mo-y-isrq","author":"lxk1203","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T09:22:31.319Z","media":[{"url":"https://pic.leetcode.cn/1693729863-kUUqtr-image.png","type":"photo","width":1316,"height":769,"blurhash":"LIB3?58^ae%gtSIUf7t8D*R*juj@"},{"url":"https://pic.leetcode.cn/1693732938-Taekga-image.png","type":"photo","width":1228,"height":1125,"blurhash":"L12rpv~Wae4nRjof%M-pMxM{WBxu"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双指针解决对称整数问题","url":"https://leetcode.cn/problems/count-symmetric-integers//solution/shuang-zhi-zhen-jie-jue-dui-cheng-zheng-dmgii","content":"\\n\\nProblem: 7020. 统计对称整数的数目
\\n
[TOC]
\\n\\n\\n转换成字符串string,然后定义left,right指针,相向而行,依次存储数值,最后对比s左 s右的数值是否相等。
\\n
\\n\\n双指针算法
\\n
\\n\\n添加时间复杂度, 示例: $O(n)$
\\n
###C++
\\n\\nclass Solution \\n{\\n int ans = 0;\\npublic:\\n int countSymmetricIntegers(int low, int high) \\n {\\n for(int i = low;i <= high; i++)\\n {\\n int j = i;\\n if(i < 10) continue;\\n string s = to_string(j);\\n if(s.size()%2 == 1) continue; //101,121这种情况直接跳过\\n int left = 0,right = s.size() - 1;\\n int s1 = 0,s2 = 0;\\n while(left < right)\\n {\\n s1 += s[left++] - \'0\';\\n s2 += s[right--] - \'0\';\\n }\\n if(s1 == s2) ans++;\\n else continue;\\n } \\n return ans;\\n }\\n};\\n
\\n","description":"Problem: 7020. 统计对称整数的数目 [TOC]\\n\\n转换成字符串string,然后定义left,right指针,相向而行,依次存储数值,最后对比s左 s右的数值是否相等。\\n\\n双指针算法\\n\\n时间复杂度:\\n\\n添加时间复杂度, 示例: $O(n)$\\n\\n###C++\\n\\n\\nclass Solution \\n{\\n int ans = 0;\\npublic:\\n int countSymmetricIntegers(int low, int high) \\n {\\n for(int i = low;i <= high;…","guid":"https://leetcode.cn/problems/count-symmetric-integers//solution/shuang-zhi-zhen-jie-jue-dui-cheng-zheng-dmgii","author":"loving-banachqec","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T06:14:43.684Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:枚举 / 数位 DP(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-symmetric-integers//solution/mei-ju-by-endlesscheng-oo2d","content":"枚举 $[\\\\textit{low},\\\\textit{high}]$ 中的整数 $i$。
\\n设 $i$ 的十进制字符串为 $s$。如果 $s$ 的长度为偶数,且 $s$ 左半字符 ASCII 值之和等于 $s$ 右半字符 ASCII 值之和,那么 $i$ 是一个对称整数,答案加一。
\\nclass Solution:\\n def countSymmetricIntegers(self, low: int, high: int) -> int:\\n ans = 0\\n for i in range(low, high + 1):\\n s = str(i)\\n n = len(s)\\n if n % 2 == 0 and sum(map(ord, s[:n // 2])) == sum(map(ord, s[n // 2:])):\\n ans += 1\\n return ans\\n
\\nclass Solution {\\n public int countSymmetricIntegers(int low, int high) {\\n int ans = 0;\\n for (int i = low; i <= high; i++) {\\n char[] s = Integer.toString(i).toCharArray();\\n int n = s.length;\\n if (n % 2 > 0) {\\n continue;\\n }\\n int diff = 0;\\n for (int j = 0; j < n / 2; j++) {\\n diff += s[j];\\n }\\n for (int j = n / 2; j < n; j++) {\\n diff -= s[j];\\n }\\n if (diff == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int countSymmetricIntegers(int low, int high) {\\n int ans = 0;\\n for (int i = low; i <= high; i++) {\\n string s = to_string(i);\\n int n = s.size();\\n if (n % 2 == 0 && reduce(s.begin(), s.begin() + n / 2) == reduce(s.begin() + n / 2, s.end())) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc countSymmetricIntegers(low, high int) (ans int) {\\n for i := low; i <= high; i++ {\\n s := strconv.Itoa(i)\\n n := len(s)\\n if n%2 > 0 {\\n continue\\n }\\n diff := 0\\n for _, c := range s[:n/2] {\\n diff += int(c)\\n }\\n for _, c := range s[n/2:] {\\n diff -= int(c)\\n }\\n if diff == 0 {\\n ans++\\n }\\n }\\n return\\n}\\n
\\n如果 $\\\\textit{high} = 10^{18}$,方法一就超时了,怎么办?
\\n前置知识:
\\n\\n数位 DP v2.0 视频讲解(上下界数位 DP)
\\n状态定义:$\\\\textit{dfs}(i, \\\\textit{start}, \\\\textit{diff},\\\\textit{limitLow},\\\\textit{limitHigh})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:
\\n状态转移:
\\n递归终点:$i=n$ 时,如果 $\\\\textit{diff}=0$,说明我们成功构造出一个对称整数,返回 $1$,否则返回 $0$。
\\n递归入口:$\\\\textit{dfs}(0, -1, 0, \\\\texttt{true}, \\\\texttt{true})$,表示:
\\nclass Solution:\\n def countSymmetricIntegers(self, low: int, high: int) -> int:\\n high = list(map(int, str(high))) # 避免在 dfs 中频繁调用 int()\\n n = len(high)\\n low = list(map(int, str(low).zfill(n))) # 补前导零,和 high 对齐\\n\\n @cache\\n def dfs(i: int, start: int, diff: int, limit_low: bool, limit_high: bool) -> int:\\n if i == n:\\n return 1 if diff == 0 else 0\\n\\n lo = low[i] if limit_low else 0\\n hi = high[i] if limit_high else 9\\n\\n # 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字\\n if start < 0 and (n - i) % 2:\\n # 如果必须填数字(lo > 0),不合法,返回 0\\n return 0 if lo else dfs(i + 1, start, diff, True, False)\\n\\n res = 0\\n is_left = start < 0 or i < (start + n) // 2\\n for d in range(lo, hi + 1):\\n res += dfs(i + 1,\\n i if start < 0 and d else start, # 记录第一个填数字的位置\\n diff + (d if is_left else -d), # 左半 + 右半 -\\n limit_low and d == lo,\\n limit_high and d == hi)\\n return res\\n\\n return dfs(0, -1, 0, True, True)\\n
\\nclass Solution {\\n private char[] lowS, highS;\\n private int n, m, diffLh;\\n private int[][][] memo;\\n\\n public int countSymmetricIntegers(int low, int high) {\\n lowS = String.valueOf(low).toCharArray();\\n highS = String.valueOf(high).toCharArray();\\n n = highS.length;\\n m = n / 2;\\n diffLh = n - lowS.length;\\n\\n // dfs 中的 start <= diffLh,-9m <= diff <= 9m\\n memo = new int[n][diffLh + 1][m * 18 + 1];\\n for (int[][] mat : memo) {\\n for (int[] row : mat) {\\n Arrays.fill(row, -1);\\n }\\n }\\n\\n // 初始化 diff = m * 9,避免出现负数导致 memo 下标越界\\n return dfs(0, -1, m * 9, true, true);\\n }\\n\\n private int dfs(int i, int start, int diff, boolean limitLow, boolean limitHigh) {\\n if (i == n) {\\n return diff == m * 9 ? 1 : 0;\\n }\\n\\n // start 当 isNum 用\\n if (start != -1 && !limitLow && !limitHigh && memo[i][start][diff] != -1) {\\n return memo[i][start][diff];\\n }\\n\\n int lo = limitLow && i >= diffLh ? lowS[i - diffLh] - \'0\' : 0;\\n int hi = limitHigh ? highS[i] - \'0\' : 9;\\n\\n // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字\\n if (start < 0 && (n - i) % 2 > 0) {\\n // 如果必须填数字(lo > 0),不合法,返回 0\\n return lo > 0 ? 0 : dfs(i + 1, start, diff, true, false);\\n }\\n\\n int res = 0;\\n boolean isLeft = start < 0 || i < (start + n) / 2;\\n for (int d = lo; d <= hi; d++) {\\n res += dfs(i + 1,\\n start < 0 && d > 0 ? i : start, // 记录第一个填数字的位置\\n diff + (isLeft ? d : -d), // 左半 +,右半 -\\n limitLow && d == lo,\\n limitHigh && d == hi);\\n }\\n\\n if (start != -1 && !limitLow && !limitHigh) {\\n memo[i][start][diff] = res;\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int countSymmetricIntegers(int low, int high) {\\n string low_s = to_string(low), high_s = to_string(high);\\n int n = high_s.size(), m = n / 2;\\n int diff_lh = n - low_s.size();\\n\\n // dfs 中的 start <= diff_lh,-9m <= diff <= 9m\\n vector memo(n, vector(diff_lh + 1, vector<int>(m * 18 + 1, -1)));\\n auto dfs = [&](this auto&& dfs, int i, int start, int diff, bool limit_low, bool limit_high) -> int {\\n if (i == n) {\\n return diff == m * 9;\\n }\\n\\n // start 当 is_num 用\\n if (start != -1 && !limit_low && !limit_high && memo[i][start][diff] != -1) {\\n return memo[i][start][diff];\\n }\\n\\n int lo = limit_low && i >= diff_lh ? low_s[i - diff_lh] - \'0\' : 0;\\n int hi = limit_high ? high_s[i] - \'0\' : 9;\\n\\n // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字\\n if (start < 0 && (n - i) % 2) {\\n // 如果必须填数字(lo > 0),不合法,返回 0\\n return lo > 0 ? 0 : dfs(i + 1, start, diff, true, false);\\n }\\n\\n int res = 0;\\n bool is_left = start < 0 || i < (start + n) / 2;\\n for (int d = lo; d <= hi; d++) {\\n res += dfs(i + 1,\\n start < 0 && d > 0 ? i : start, // 记录第一个填数字的位置\\n diff + (is_left ? d : -d), // 左半 +,右半 -\\n limit_low && d == lo,\\n limit_high && d == hi);\\n }\\n\\n if (start != -1 && !limit_low && !limit_high) {\\n memo[i][start][diff] = res;\\n }\\n return res;\\n };\\n\\n // 初始化 diff = m * 9,避免出现负数导致 memo 下标越界\\n return dfs(0, -1, m * 9, true, true);\\n }\\n};\\n
\\nfunc countSymmetricIntegers(low, high int) int {\\n lowS := strconv.Itoa(low)\\n highS := strconv.Itoa(high)\\n n := len(highS)\\n m := n / 2\\n diffLH := n - len(lowS)\\n\\n memo := make([][][]int, n)\\n for i := range memo {\\n memo[i] = make([][]int, diffLH+1) // start <= diffLH\\n for j := range memo[i] {\\n memo[i][j] = make([]int, m*18+1) // -9m <= diff <= 9m\\n for k := range memo[i][j] {\\n memo[i][j][k] = -1\\n }\\n }\\n }\\n var dfs func(int, int, int, bool, bool) int\\n dfs = func(i, start, diff int, limitLow, limitHigh bool) (res int) {\\n if i == n {\\n if diff != 0 {\\n return 0\\n }\\n return 1\\n }\\n\\n // start 当 isNum 用\\n if start != -1 && !limitLow && !limitHigh {\\n p := &memo[i][start][diff+m*9]\\n if *p != -1 {\\n return *p\\n }\\n defer func() { *p = res }()\\n }\\n\\n lo := 0\\n if limitLow && i >= diffLH {\\n lo = int(lowS[i-diffLH] - \'0\')\\n }\\n hi := 9\\n if limitHigh {\\n hi = int(highS[i] - \'0\')\\n }\\n\\n // 如果前面没有填数字,且剩余数位个数是奇数,那么当前数位不能填数字\\n if start < 0 && (n-i)%2 > 0 {\\n if lo > 0 {\\n return 0 // 必须填数字但 lo > 0,不合法\\n }\\n return dfs(i+1, start, diff, true, false)\\n }\\n\\n isLeft := start < 0 || i < (start+n)/2\\n for d := lo; d <= hi; d++ {\\n newStart := start\\n if start < 0 && d > 0 {\\n newStart = i // 记录第一个填数字的位置\\n }\\n newDiff := diff\\n if isLeft {\\n newDiff += d\\n } else {\\n newDiff -= d\\n }\\n res += dfs(i+1, newStart, newDiff, limitLow && d == lo, limitHigh && d == hi)\\n }\\n return\\n }\\n return dfs(0, -1, 0, true, true)\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:枚举 枚举 $[\\\\textit{low},\\\\textit{high}]$ 中的整数 $i$。\\n\\n设 $i$ 的十进制字符串为 $s$。如果 $s$ 的长度为偶数,且 $s$ 左半字符 ASCII 值之和等于 $s$ 右半字符 ASCII 值之和,那么 $i$ 是一个对称整数,答案加一。\\n\\nclass Solution:\\n def countSymmetricIntegers(self, low: int, high: int) -> int:\\n ans = 0\\n for i in range(low, high…","guid":"https://leetcode.cn/problems/count-symmetric-integers//solution/mei-ju-by-endlesscheng-oo2d","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T04:21:37.086Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"极简前缀和","url":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/ji-jian-qian-zhui-he-by-freeyourmind-1ux1","content":"class Solution:\\n def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:\\n cnt, res = defaultdict(int), 0\\n for x in accumulate((x % modulo == k for x in nums), initial=0):\\n res += cnt[(x - k) % modulo]\\n cnt[x % modulo] += 1\\n return res\\n
\\n","description":"class Solution: def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:\\n cnt, res = defaultdict(int), 0\\n for x in accumulate((x % modulo == k for x in nums), initial=0):\\n res += cnt[(x - k) % modulo]\\n cnt[x…","guid":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/ji-jian-qian-zhui-he-by-freeyourmind-1ux1","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T04:13:54.556Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前缀和,枚举右维护左(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/qian-zhui-he-ha-xi-biao-fu-ti-dan-by-end-74bb","content":"首先处理 $\\\\textit{cnt}$,把满足 $\\\\textit{nums}[i]\\\\bmod \\\\textit{modulo} = k$ 的 $\\\\textit{nums}[i]$ 视作 $1$,不满足的视作 $0$。
\\n示例 1 的 $\\\\textit{nums}=[3,2,4],\\\\textit{modulo}=2,k=1$,只有 $3\\\\bmod 2 = 1$,所以转化后得到数组 $a=[1,0,0]$。
\\n示例 2 的 $\\\\textit{nums}=[3,1,9,6],\\\\textit{modulo}=3,k=0$,转化后得到数组 $a=[1,0,1,1]$。
\\n现在问题变成统计 $a$ 的子数组中的 $1$ 的个数,这等于子数组的元素和,于是问题转化成:
\\n子数组和问题,通常用 前缀和 处理。
\\n求出 $a$ 的前缀和数组 $s$,问题转化成:
\\n对于初次接触模运算的同学,可以先从简单的情况开始思考:想一想,如果 $k=0$,要怎么做?这是 1512. 好数对的数目 的取模加强版。
\\n本题保证 $0\\\\le k < \\\\textit{modulo}$,所以 $(s[r]-s[l])\\\\bmod \\\\textit{modulo} = k$ 等价于
\\n$$
\\n(s[r]-s[l])\\\\bmod \\\\textit{modulo} = k \\\\bmod \\\\textit{modulo}
\\n$$
所以 $s[r]-s[l]$ 与 $k$ 关于模 $\\\\textit{modulo}$ 同余。由于模运算加减法封闭,可以移项,得
\\n$$
\\n(s[r]-k)\\\\bmod \\\\textit{modulo} = s[l]\\\\bmod \\\\textit{modulo}
\\n$$
枚举右,维护左。根据上式,我们可以一边枚举 $r$,一边统计答案。比如 $(s[r]-k)\\\\bmod \\\\textit{modulo}=6$,我们需要知道在 $r$ 左侧有多少个 $s[l]\\\\bmod \\\\textit{modulo}$ 也等于 $6$,这可以在遍历 $s$ 的过程中,用哈希表(其实只需要数组)统计 $s[l]\\\\bmod \\\\textit{modulo}$ 的出现次数。
\\n由于 $a$ 中只有 $0$ 和 $1$,所以 $s[l]\\\\le n$。
\\n由于 $s[l]\\\\bmod \\\\textit{modulo}\\\\le \\\\min(n, \\\\textit{modulo}-1)$,不需要哈希表,用长为 $\\\\min(n+1, \\\\textit{modulo})$ 的数组代替,效率更高。
\\n\\n\\n此外,如果 $k>n$,那么 $s[l]\\\\bmod \\\\textit{modulo}\\\\le s[l]\\\\le n < k$,所有子数组都不满足要求,直接返回 $0$。不过这个优化不影响运行时间,可以不写。
\\n
class Solution:\\n def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:\\n pre_sum = list(accumulate((x % modulo == k for x in nums), initial=0))\\n cnt = [0] * min(len(nums) + 1, modulo)\\n ans = 0\\n for s in pre_sum:\\n if s >= k:\\n ans += cnt[(s - k) % modulo]\\n cnt[s % modulo] += 1\\n return ans\\n
\\nclass Solution {\\n public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {\\n int n = nums.size();\\n int[] sum = new int[n + 1];\\n for (int i = 0; i < n; i++) {\\n sum[i + 1] = sum[i] + (nums.get(i) % modulo == k ? 1 : 0);\\n }\\n\\n int[] cnt = new int[Math.min(n + 1, modulo)];\\n long ans = 0;\\n for (int s : sum) {\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {\\n int n = nums.size();\\n vector<int> sum(n + 1);\\n for (int i = 0; i < n; i++) {\\n sum[i + 1] = sum[i] + (nums[i] % modulo == k);\\n }\\n\\n vector<int> cnt(min(n + 1, modulo));\\n long long ans = 0;\\n for (int s : sum) {\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nlong long countInterestingSubarrays(int* nums, int numsSize, int modulo, int k) {\\n int* sum = malloc((numsSize + 1) * sizeof(int));\\n sum[0] = 0;\\n for (int i = 0; i < numsSize; i++) {\\n sum[i + 1] = sum[i] + (nums[i] % modulo == k);\\n }\\n\\n int* cnt = calloc(MIN(numsSize + 1, modulo), sizeof(int));\\n long long ans = 0;\\n for (int i = 0; i <= numsSize; i++) {\\n int s = sum[i];\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n\\n free(sum);\\n free(cnt);\\n return ans;\\n}\\n
\\nfunc countInterestingSubarrays(nums []int, modulo, k int) (ans int64) {\\n sum := make([]int, len(nums)+1)\\n for i, x := range nums {\\n sum[i+1] = sum[i]\\n if x%modulo == k {\\n sum[i+1]++\\n }\\n }\\n\\n cnt := make([]int, min(len(nums)+1, modulo))\\n for _, s := range sum {\\n if s >= k {\\n ans += int64(cnt[(s-k)%modulo])\\n }\\n cnt[s%modulo]++\\n }\\n return\\n}\\n
\\nvar countInterestingSubarrays = function(nums, modulo, k) {\\n const n = nums.length;\\n const sum = Array(n + 1);\\n sum[0] = 0;\\n for (let i = 0; i < n; i++) {\\n sum[i + 1] = sum[i] + (nums[i] % modulo === k ? 1 : 0);\\n }\\n\\n const cnt = Array(Math.min(n + 1, modulo)).fill(0);\\n let ans = 0;\\n for (const s of sum) {\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn count_interesting_subarrays(nums: Vec<i32>, modulo: i32, k: i32) -> i64 {\\n let n = nums.len();\\n let mut sum = vec![0; n + 1];\\n for (i, x) in nums.into_iter().enumerate() {\\n sum[i + 1] = sum[i];\\n if x % modulo == k {\\n sum[i + 1] += 1;\\n }\\n }\\n\\n let mut ans = 0;\\n let mut cnt = vec![0; (n + 1).min(modulo as usize)];\\n for s in sum {\\n if s >= k {\\n ans += cnt[((s - k) % modulo) as usize] as i64;\\n }\\n cnt[(s % modulo) as usize] += 1;\\n }\\n ans\\n }\\n}\\n
\\nclass Solution:\\n def countInterestingSubarrays(self, nums: List[int], modulo: int, k: int) -> int:\\n cnt = [0] * min(len(nums) + 1, modulo)\\n cnt[0] = 1 # 单独统计 s[0]=0\\n ans = s = 0\\n for x in nums:\\n if x % modulo == k:\\n s += 1\\n if s >= k:\\n ans += cnt[(s - k) % modulo]\\n cnt[s % modulo] += 1\\n return ans\\n
\\nclass Solution {\\n public long countInterestingSubarrays(List<Integer> nums, int modulo, int k) {\\n int[] cnt = new int[Math.min(nums.size() + 1, modulo)];\\n cnt[0] = 1; // 单独统计 s[0]=0\\n long ans = 0;\\n int s = 0;\\n for (int x : nums) {\\n if (x % modulo == k) {\\n s++;\\n }\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countInterestingSubarrays(vector<int>& nums, int modulo, int k) {\\n vector<int> cnt(min((int) nums.size() + 1, modulo));\\n cnt[0] = 1; // 单独统计 s[0]=0\\n long long ans = 0;\\n int s = 0;\\n for (int x : nums) {\\n s += x % modulo == k;\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nlong long countInterestingSubarrays(int* nums, int numsSize, int modulo, int k) {\\n int* cnt = calloc(MIN(numsSize + 1, modulo), sizeof(int));\\n cnt[0] = 1; // 单独统计 s[0]=0\\n long long ans = 0;\\n int s = 0;\\n for (int i = 0; i < numsSize; i++) {\\n s += nums[i] % modulo == k;\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n free(cnt);\\n return ans;\\n}\\n
\\nfunc countInterestingSubarrays(nums []int, modulo, k int) (ans int64) {\\n cnt := make([]int, min(len(nums)+1, modulo))\\n cnt[0] = 1 // 单独统计 s[0]=0\\n s := 0\\n for _, x := range nums {\\n if x%modulo == k {\\n s++\\n }\\n if s >= k {\\n ans += int64(cnt[(s-k)%modulo])\\n }\\n cnt[s%modulo]++\\n }\\n return\\n}\\n
\\nvar countInterestingSubarrays = function(nums, modulo, k) {\\n const cnt = Array(Math.min(nums.length + 1, modulo)).fill(0);\\n cnt[0] = 1; // 单独统计 s[0]=0\\n let ans = 0, s = 0;\\n for (const x of nums) {\\n if (x % modulo === k) {\\n s++;\\n }\\n if (s >= k) {\\n ans += cnt[(s - k) % modulo];\\n }\\n cnt[s % modulo]++;\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn count_interesting_subarrays(nums: Vec<i32>, modulo: i32, k: i32) -> i64 {\\n let mut cnt = vec![0; (nums.len() + 1).min(modulo as usize)];\\n cnt[0] = 1; // 单独统计 s[0]=0\\n let mut ans = 0;\\n let mut s = 0;\\n for x in nums {\\n if x % modulo == k {\\n s += 1;\\n }\\n if s >= k {\\n ans += cnt[((s - k) % modulo) as usize] as i64;\\n }\\n cnt[(s % modulo) as usize] += 1;\\n }\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面数据结构题单的「§1.2 前缀和与哈希表」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目 303. 区域和检索 - 数组不可变\\n1512. 好数对的数目\\n分析\\n\\n首先处理 $\\\\textit{cnt}$,把满足 $\\\\textit{nums}[i]\\\\bmod \\\\textit{modulo} = k$ 的 $\\\\textit{nums}[i]$ 视作 $1$,不满足的视作 $0$。\\n\\n示例 1 的 $\\\\textit{nums}=[3,2,4],\\\\textit{modulo}=2,k=1$,只有 $3\\\\bmod 2 = 1$,所以转化后得到数组 $a=[1,0,0]$。\\n\\n示例 2 的 $\\\\textit{nums}=[3,1,9,6…","guid":"https://leetcode.cn/problems/count-of-interesting-subarrays//solution/qian-zhui-he-ha-xi-biao-fu-ti-dan-by-end-74bb","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-09-03T04:12:30.708Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最深叶节点的最近公共祖先","url":"https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves//solution/zui-shen-xie-jie-dian-de-zui-jin-gong-go-cjzv","content":"思路与算法
\\n题目给出一个二叉树,要求返回它最深的叶节点的最近公共祖先。其中树的根节点的深度为 $0$,我们注意到所有深度最大的节点,都是树的叶节点。为方便说明,我们把最深的叶节点的最近公共祖先,称之为 $\\\\textit{lca}$ 节点。
\\n我们用递归的方式,进行深度优先搜索,对树中的每个节点进行递归,返回当前子树的最大深度 $d$ 和 $\\\\textit{lca}$ 节点。如果当前节点为空,我们返回深度 $0$ 和空节点。在每次搜索中,我们递归地搜索左子树和右子树,然后比较左右子树的深度:
\\n最后我们返回根节点的 $\\\\textit{lca}$ 节点即可。
\\n代码
\\n###C++
\\nclass Solution {\\npublic:\\n pair<TreeNode*, int> f(TreeNode* root) {\\n if (!root) {\\n return {root, 0};\\n }\\n\\n auto left = f(root->left);\\n auto right = f(root->right);\\n\\n if (left.second > right.second) {\\n return {left.first, left.second + 1};\\n }\\n if (left.second < right.second) {\\n return {right.first, right.second + 1};\\n }\\n return {root, left.second + 1};\\n\\n }\\n\\n TreeNode* lcaDeepestLeaves(TreeNode* root) {\\n return f(root).first;\\n }\\n};\\n\\n
\\n###Java
\\nclass Solution {\\n public TreeNode lcaDeepestLeaves(TreeNode root) {\\n return f(root).getKey();\\n }\\n\\n private Pair<TreeNode, Integer> f(TreeNode root) {\\n if (root == null) {\\n return new Pair<>(root, 0);\\n }\\n\\n Pair<TreeNode, Integer> left = f(root.left);\\n Pair<TreeNode, Integer> right = f(root.right);\\n\\n if (left.getValue() > right.getValue()) {\\n return new Pair<>(left.getKey(), left.getValue() + 1);\\n }\\n if (left.getValue() < right.getValue()) {\\n return new Pair<>(right.getKey(), right.getValue() + 1);\\n }\\n return new Pair<>(root, left.getValue() + 1);\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public TreeNode LcaDeepestLeaves(TreeNode root) {\\n return f(root).Item1;\\n }\\n\\n private Tuple<TreeNode, int> f(TreeNode root) {\\n if (root == null) {\\n return new Tuple<TreeNode, int>(root, 0);\\n }\\n\\n Tuple<TreeNode, int> left = f(root.left);\\n Tuple<TreeNode, int> right = f(root.right);\\n\\n if (left.Item2 > right.Item2) {\\n return new Tuple<TreeNode, int>(left.Item1, left.Item2 + 1);\\n }\\n if (left.Item2 < right.Item2) {\\n return new Tuple<TreeNode, int>(right.Item1, right.Item2 + 1);\\n }\\n return new Tuple<TreeNode, int>(root, left.Item2 + 1);\\n }\\n}\\n
\\n###Python
\\nclass Solution:\\n def lcaDeepestLeaves(self, root: Optional[TreeNode]) -> Optional[TreeNode]:\\n def f(root):\\n if not root:\\n return 0, None\\n\\n d1, lca1 = f(root.left)\\n d2, lca2 = f(root.right)\\n\\n if d1 > d2:\\n return d1 + 1, lca1\\n if d1 < d2:\\n return d2 + 1, lca2\\n return d1 + 1, root\\n\\n return f(root)[1]\\n
\\n###JavaScript
\\nvar lcaDeepestLeaves = function(root) {\\n return f(root)[1];\\n};\\n\\nfunction f(root) {\\n if (!root) {\\n return [0, root];\\n }\\n\\n let [d1, lca1] = f(root.left);\\n let [d2, lca2] = f(root.right);\\n\\n if (d1 > d2) {\\n return [d1 + 1, lca1];\\n }\\n if (d1 < d2) {\\n return [d2 + 1, lca2];\\n }\\n return [d1 + 1, root];\\n}\\n
\\n###Go
\\nfunc lcaDeepestLeaves(root *TreeNode) *TreeNode {\\n _, lca := f(root)\\n return lca\\n}\\n\\nfunc f(root *TreeNode) (int, *TreeNode) {\\n if root == nil {\\n return 0, nil\\n }\\n\\n d1, lca1 := f(root.Left)\\n h2, lca2 := f(root.Right)\\n\\n if d1 > h2 {\\n return d1 + 1, lca1\\n }\\n if d1 < h2 {\\n return h2 + 1, lca2\\n }\\n return d1 + 1, root\\n}\\n
\\n###C
\\nstruct Pair {\\n struct TreeNode *node;\\n int depth;\\n};\\n\\nstruct Pair f(struct TreeNode *root) {\\n if (root == NULL) {\\n return (struct Pair) {NULL, 0};\\n }\\n\\n struct Pair left = f(root->left);\\n struct Pair right = f(root->right);\\n\\n if (left.depth > right.depth) {\\n return (struct Pair) {left.node, left.depth + 1};\\n }\\n if (left.depth < right.depth) {\\n return (struct Pair) {right.node, right.depth + 1};\\n }\\n return (struct Pair) {root, left.depth + 1};\\n}\\n\\nstruct TreeNode *lcaDeepestLeaves(struct TreeNode *root) {\\n return f(root).node;\\n}\\n
\\n复杂度分析
\\n时间复杂度:$O(n)$,其中 $n$ 是树的节点数量。
\\n空间复杂度:$O(d)$,其中 $d$ 是树的深度。空间复杂度主要是递归的空间,最差情况为 $O(n)$,其中 $n$ 是树的节点数量。
\\n题目不好理解...
\\n相加等于 $k$ 的正整数对有 $(1, k - 1), (2, k - 2), \\\\cdots$,我们从每一对数中最多取一个。为了让总和最小,我们取的是 $1, 2, \\\\cdots, \\\\lfloor\\\\frac{k}{2}\\\\rfloor$。
\\n如果这些数还不够 $n$ 个,只能从 $k, k + 1, \\\\cdots$ 接着取。这些数因为都大于等于 $k$,所以不存在和它们相加等于 $k$ 的正整数,可以随便取。取最小的几个即可。
\\n用等差数列求和公式可以在 $\\\\mathcal{O}(1)$ 的复杂度内算出答案。
\\n###c++
\\nclass Solution {\\npublic:\\n int minimumSum(int n, int K) {\\n // 求 L + (L + 1) + ... + (R - 1) + R\\n auto gao = [&](int L, int R) {\\n return (L + R) * (R - L + 1) / 2;\\n };\\n\\n // 先取 1 到 K / 2\\n int t = K / 2;\\n // 如果还不够,从 K 开始接着取\\n if (n > t) return gao(1, t) + gao(K, K + n - t - 1);\\n else return gao(1, n);\\n }\\n};\\n
\\n","description":"解法:数学 题目不好理解...\\n\\n相加等于 $k$ 的正整数对有 $(1, k - 1), (2, k - 2), \\\\cdots$,我们从每一对数中最多取一个。为了让总和最小,我们取的是 $1, 2, \\\\cdots, \\\\lfloor\\\\frac{k}{2}\\\\rfloor$。\\n\\n如果这些数还不够 $n$ 个,只能从 $k, k + 1, \\\\cdots$ 接着取。这些数因为都大于等于 $k$,所以不存在和它们相加等于 $k$ 的正整数,可以随便取。取最小的几个即可。\\n\\n用等差数列求和公式可以在 $\\\\mathcal{O}(1)$ 的复杂度内算出答案。\\n\\n参考代码(c++…","guid":"https://leetcode.cn/problems/determine-the-minimum-sum-of-a-k-avoiding-array//solution/shu-xue-by-tsreaper-1wan","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-08-20T06:43:06.064Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(1) 公式(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/determine-the-minimum-sum-of-a-k-avoiding-array//solution/o1-gong-shi-pythonjavacgo-by-endlesschen-cztk","content":"选 $n$ 个不同的正整数,要求任意两数之和都不等于 $k$。
\\n计算这 $n$ 个数的最小总和。
\\n\\n\\n注意 $k$ 是可以选的,因为题目约束的是两数之和,不是一个数。
\\n
对于 $[1,k-1]$ 内的数字:
\\n设 $m=\\\\min\\\\left(\\\\left\\\\lfloor\\\\dfrac{k}{2}\\\\right\\\\rfloor, n\\\\right)$,那么答案的第一部分是从 $1$ 到 $m$,根据等差数列求和公式,元素和为
\\n$$
\\n\\\\dfrac{m(m+1)}{2}
\\n$$
此时还剩下 $n-m$ 个数,只能在 $\\\\ge k$ 的整数中选。这些数没有其他约束,选最小的 $n-m$ 个数即可。所以答案的第二部分是从 $k$ 到 $k+n-m-1$,根据等差数列求和公式,元素和为
\\n$$
\\n\\\\dfrac{(2k+n-m-1)(n-m)}{2}
\\n$$
综上所述,答案为
\\n$$
\\n\\\\dfrac{m(m+1) + (2k+n-m-1)(n-m)}{2}
\\n$$
视频讲解 第二题。
\\nclass Solution:\\n def minimumSum(self, n: int, k: int) -> int:\\n m = min(k // 2, n)\\n return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) // 2\\n
\\nclass Solution {\\n public int minimumSum(int n, int k) {\\n int m = Math.min(k / 2, n);\\n return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimumSum(int n, int k) {\\n int m = min(k / 2, n);\\n return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint minimumSum(int n, int k) {\\n int m = MIN(k / 2, n);\\n return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2;\\n}\\n
\\nfunc minimumSum(n, k int) int {\\n m := min(k/2, n)\\n return (m*(m+1) + (k*2+n-m-1)*(n-m)) / 2\\n}\\n
\\nvar minimumSum = function(n, k) {\\n const m = Math.min(Math.floor(k / 2), n);\\n return (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2;\\n};\\n
\\nimpl Solution {\\n pub fn minimum_sum(n: i32, k: i32) -> i32 {\\n let m = n.min(k / 2);\\n (m * (m + 1) + (k * 2 + n - m - 1) * (n - m)) / 2\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 选 $n$ 个不同的正整数,要求任意两数之和都不等于 $k$。\\n\\n计算这 $n$ 个数的最小总和。\\n\\n注意 $k$ 是可以选的,因为题目约束的是两数之和,不是一个数。\\n\\n思路\\n第一部分\\n\\n对于 $[1,k-1]$ 内的数字:\\n\\n$1$ 和 $k-1$ 只能选其中一个,不能都选,不然两数之和为 $k$。既然只能选一个,那么选 $1$ 比选 $k-1$ 更好。注意不能都不选,这会导致后面要选更大的数,不是最优的。\\n$2$ 和 $k-2$ 只能选其中一个,选 $2$,理由同上。\\n$3$ 和 $k-3$ 只能选其中一个,选 $3$,理由同上。\\n……\\n一直到 $…","guid":"https://leetcode.cn/problems/determine-the-minimum-sum-of-a-k-avoiding-array//solution/o1-gong-shi-pythonjavacgo-by-endlesschen-cztk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-08-20T04:14:16.991Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数组列表中的最大距离","url":"https://leetcode.cn/problems/maximum-distance-in-arrays//solution/shu-zu-lie-biao-zhong-de-zui-da-ju-chi-b-f9x4","content":"[TOC]
\\n简单的解决办法是从每个数组 $arrays$ 中的每一个元素开始,计算它与除了自身之外的其他所有数组的每一个元素的距离,并找出其中的最大距离。
\\n###Java
\\nclass Solution {\\n public int maxDistance(List<List<Integer>> arrays) {\\n int res = 0;\\n int n = arrays.size();\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = 0; j < arrays.get(i).size(); j++) {\\n for (int k = i + 1; k < n; k++) {\\n for (int l = 0; l < arrays.get(k).size(); l++) {\\n res = Math.max(res, Math.abs(arrays.get(i).get(j) - arrays.get(k).get(l)));\\n }\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxDistance(IList<IList<int>> arrays) {\\n int res = 0;\\n int n = arrays.Count;\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = 0; j < arrays[i].Count; j++) {\\n for (int k = i + 1; k < n; k++) {\\n for (int l = 0; l < arrays[k].Count; l++) {\\n res = Math.Max(res, Math.Abs(arrays[i][j] - arrays[k][l]));\\n }\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxDistance(vector<vector<int>>& arrays) {\\n int res = 0;\\n int n = arrays.size();\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = 0; j < arrays[i].size(); j++) {\\n for (int k = i + 1; k < n; k++) {\\n for (int l = 0; l < arrays[k].size(); l++) {\\n res = max(res, abs(arrays[i][j] - arrays[k][l]));\\n }\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc maxDistance(arrays [][]int) int {\\n res := 0\\n n := len(arrays)\\n for i := 0; i < n - 1; i++ {\\n for j := 0; j < len(arrays[i]); j++ {\\n for k := i + 1; k < n; k++ {\\n for l := 0; l < len(arrays[k]); l++ {\\n res = max(res, abs(arrays[i][j] - arrays[k][l]))\\n }\\n }\\n }\\n }\\n return res\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
\\n###Python
\\nclass Solution:\\n def maxDistance(self, arrays: List[List[int]]) -> int:\\n res = 0\\n n = len(arrays)\\n for i in range(n - 1):\\n for j in range(len(arrays[i])):\\n for k in range(i + 1, n):\\n for l in range(len(arrays[k])):\\n res = max(res, abs(arrays[i][j] - arrays[k][l]))\\n return res\\n
\\n###C
\\nint maxDistance(int** arrays, int arraysSize, int* arraysColSize) {\\n int res = 0;\\n for (int i = 0; i < arraysSize - 1; i++) {\\n for (int j = 0; j < arraysColSize[i]; j++) {\\n for (int k = i + 1; k < arraysSize; k++) {\\n for (int l = 0; l < arraysColSize[k]; l++) {\\n int diff = abs(arrays[i][j] - arrays[k][l]);\\n if (diff > res) {\\n res = diff;\\n }\\n }\\n }\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar maxDistance = function(arrays) {\\n let res = 0;\\n let n = arrays.length;\\n for (let i = 0; i < n - 1; i++) {\\n for (let j = 0; j < arrays[i].length; j++) {\\n for (let k = i + 1; k < n; k++) {\\n for (let l = 0; l < arrays[k].length; l++) {\\n res = Math.max(res, Math.abs(arrays[i][j] - arrays[k][l]));\\n }\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction maxDistance(arrays: number[][]): number {\\n let res = 0;\\n let n = arrays.length;\\n for (let i = 0; i < n - 1; i++) {\\n for (let j = 0; j < arrays[i].length; j++) {\\n for (let k = i + 1; k < n; k++) {\\n for (let l = 0; l < arrays[k].length; l++) {\\n res = Math.max(res, Math.abs(arrays[i][j] - arrays[k][l]));\\n }\\n }\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_distance(arrays: Vec<Vec<i32>>) -> i32 {\\n let mut res = 0;\\n let n = arrays.len();\\n for i in 0..n - 1 {\\n for &x in &arrays[i] {\\n for k in i + 1..n {\\n for &y in &arrays[k] {\\n res = res.max((x - y).abs());\\n }\\n }\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n算法
\\n在上一个方法 中,我们没有利用到每个数组在 $arrays$ 中都被排序过的事实。因此,我们不需要考虑所有数组(除了数组内部的元素)之间所有元素的距离,我们只需要考虑数组中的第一个(最小元素)和其它数组中最后一个(最大元素)之间的距离,然后从这些距离中找出最大距离。
###Java
\\nclass Solution {\\n public int maxDistance(List<List<Integer>> arrays) {\\n List<Integer> array1, array2;\\n int res = 0;\\n int n = arrays.size();\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = i + 1; j < n; j++) {\\n array1 = arrays.get(i);\\n array2 = arrays.get(j);\\n res = Math.max(res, Math.abs(array1.get(0) - array2.get(array2.size() - 1)));\\n res = Math.max(res, Math.abs(array2.get(0) - array1.get(array1.size() - 1)));\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxDistance(IList<IList<int>> arrays) {\\n IList<int> array1, array2;\\n int res = 0;\\n int n = arrays.Count;\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = i + 1; j < n; j++) {\\n array1 = arrays[i];\\n array2 = arrays[j];\\n res = Math.Max(res, Math.Abs(array1[0] - array2[array2.Count - 1]));\\n res = Math.Max(res, Math.Abs(array2[0] - array1[array1.Count - 1]));\\n }\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxDistance(vector<vector<int>>& arrays) {\\n vector<int> array1, array2;\\n int res = 0;\\n int n = arrays.size();\\n for (int i = 0; i < n - 1; i++) {\\n for (int j = i + 1; j < n; j++) {\\n array1 = arrays[i];\\n array2 = arrays[j];\\n res = max(res, abs(array1[0] - array2[array2.size() - 1]));\\n res = max(res, abs(array2[0] - array1[array1.size() - 1]));\\n }\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc maxDistance(arrays [][]int) int {\\n var array1, array2 []int\\n res := 0\\n n := len(arrays)\\n for i := 0; i < n-1; i++ {\\n for j := i + 1; j < n; j++ {\\n array1 = arrays[i]\\n array2 = arrays[j]\\n res = max(res, abs(array1[0] - array2[len(array2) - 1]))\\n res = max(res, abs(array2[0] - array1[len(array1) - 1]))\\n }\\n }\\n return res\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
\\n###Python
\\nclass Solution:\\n def maxDistance(self, arrays: List[List[int]]) -> int:\\n res = 0\\n n = len(arrays)\\n for i in range(n - 1):\\n for j in range(i + 1, n):\\n array1 = arrays[i]\\n array2 = arrays[j]\\n res = max(res, abs(array1[0] - array2[-1]))\\n res = max(res, abs(array2[0] - array1[-1]))\\n return res\\n
\\n###C
\\nint maxDistance(int** arrays, int arraysSize, int* arraysColSize) {\\n int res = 0;\\n for (int i = 0; i < arraysSize - 1; i++) {\\n for (int j = i + 1; j < arraysSize; j++) {\\n int* array1 = arrays[i];\\n int* array2 = arrays[j];\\n res = fmax(res, abs(array1[0] - array2[arraysColSize[j] - 1]));\\n res = fmax(res, abs(array2[0] - array1[arraysColSize[i] - 1]));\\n }\\n }\\n return res;\\n}\\n
\\n###JavaScript
\\nvar maxDistance = function(arrays) {\\n let res = 0;\\n let n = arrays.length;\\n for (let i = 0; i < n - 1; i++) {\\n for (let j = i + 1; j < n; j++) {\\n let array1 = arrays[i];\\n let array2 = arrays[j];\\n res = Math.max(res, Math.abs(array1[0] - array2[array2.length - 1]));\\n res = Math.max(res, Math.abs(array2[0] - array1[array1.length - 1]));\\n }\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction maxDistance(arrays: number[][]): number {\\n let res = 0;\\n let n = arrays.length;\\n for (let i = 0; i < n - 1; i++) {\\n for (let j = i + 1; j < n; j++) {\\n let array1 = arrays[i];\\n let array2 = arrays[j];\\n res = Math.max(res, Math.abs(array1[0] - array2[array2.length - 1]));\\n res = Math.max(res, Math.abs(array2[0] - array1[array1.length - 1]));\\n }\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_distance(arrays: Vec<Vec<i32>>) -> i32 {\\n let mut res = 0;\\n let n = arrays.len();\\n for i in 0..n - 1 {\\n for j in i + 1..n {\\n let array1 = &arrays[i];\\n let array2 = &arrays[j];\\n res = res.max((array1[0] - array2[array2.len() - 1]).abs());\\n res = res.max((array2[0] - array1[array1.len() - 1]).abs());\\n }\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n算法
\\n如已经讨论过的,为了找出任何两个数组之间的最大距离,我们不需要比较数组中的每一个元素,因为数组已经被排序过了。因此,我们只需要考虑数组中的极值来计算距离。
\\n此外,被考虑用于计算距离的两个点不应该都属于同一个数组。因此,对于当前选择的数组 $a$ 和 $b$,我们只需要找出 $a[n-1]-b[0]$ 和 $b[m-1]-a[0]$ 中的较大者来找出较大的距离。这里,$n$ 和 $m$ 分别指的是数组 $a$ 和 $b$ 的长度。
\\n但是,我们不需要比较所有可能的数组对来找出最大距离。相反,我们可以不断地遍历 $arrays$ 中的数组,并跟踪到目前为止发现的最大距离。
\\n为此,我们需要跟踪到目前为止找到的最小值($min_val$)和最大值($max_val$)。因此,现在这些极值可以被视为代表到目前为止考虑过的所有数组的累积数组的极点。
\\n对于每一个新考虑的数组,我们找到 $a[n-1]-min_val$ 和 $max_val - a[0]$ 与到目前为止找到的最大距离进行对比。这里的$n$指的是当前数组 a 的元素数量。此外,我们需要注意,到目前为止找到的最大距离并不总是由距离的端点 $max_val$和$min_val$ 贡献。
\\n但是,这些类型的点可能在未来有助于最大化距离。因此,我们需要跟踪这些最大值和最小值以及到目前为止找到的最大距离进行未来的计算。但是,一般来说,最终找到的最大距离总是由这些极值,$max_val$和$min_val$, 或在某些情况下,由它们两者决定的。
\\n下面的动画示例了这个过程。
<{:width=400},
{:width=400},
{:width=400},
{:width=400},
{:width=400},
{:width=400},
{:width=400},
{:width=400}>
从上述插图中,我们可以清楚地看到,尽管 $max_val$ 或 $min_val$ 可能不能为局部最大距离值做出贡献,但是他们稍后可以做出最大距离的贡献。
\\n###Java
\\nclass Solution {\\n public int maxDistance(List<List<Integer>> arrays) {\\n int res = 0;\\n int n = arrays.get(0).size();\\n int min_val = arrays.get(0).get(0);\\n int max_val = arrays.get(0).get(arrays.get(0).size() - 1);\\n for (int i = 1; i < arrays.size(); i++) {\\n n = arrays.get(i).size();\\n res = Math.max(res, Math.max(Math.abs(arrays.get(i).get(n - 1) - min_val), \\n Math.abs(max_val - arrays.get(i).get(0))));\\n min_val = Math.min(min_val, arrays.get(i).get(0));\\n max_val = Math.max(max_val, arrays.get(i).get(n - 1));\\n }\\n return res;\\n }\\n}\\n
\\n###C#
\\npublic class Solution {\\n public int MaxDistance(IList<IList<int>> arrays) {\\n int res = 0;\\n int n = arrays[0].Count;\\n int minVal = arrays[0][0];\\n int maxVal = arrays[0][arrays[0].Count - 1];\\n for (int i = 1; i < arrays.Count; i++) {\\n n = arrays[i].Count;\\n res = Math.Max(res, Math.Max(Math.Abs(arrays[i][n - 1] - minVal), \\n Math.Abs(maxVal - arrays[i][0])));\\n minVal = Math.Min(minVal, arrays[i][0]);\\n maxVal = Math.Max(maxVal, arrays[i][n - 1]);\\n }\\n return res;\\n }\\n}\\n
\\n###C++
\\nclass Solution {\\npublic:\\n int maxDistance(vector<vector<int>>& arrays) {\\n int res = 0;\\n int n = arrays[0].size();\\n int min_val = arrays[0][0];\\n int max_val = arrays[0][arrays[0].size() - 1];\\n for (int i = 1; i < arrays.size(); i++) {\\n n = arrays[i].size();\\n res = max(res, max(abs(arrays[i][n - 1] - min_val), \\n abs(max_val - arrays[i][0])));\\n min_val = min(min_val, arrays[i][0]);\\n max_val = max(max_val, arrays[i][n - 1]);\\n }\\n return res;\\n }\\n};\\n
\\n###Go
\\nfunc maxDistance(arrays [][]int) int {\\n res := 0\\n n := len(arrays[0])\\n minVal := arrays[0][0]\\n maxVal := arrays[0][n - 1]\\n for i := 1; i < len(arrays); i++ {\\n n = len(arrays[i])\\n res = max(res, max(abs(arrays[i][n-1] - minVal),\\n abs(maxVal - arrays[i][0])))\\n minVal = min(minVal, arrays[i][0])\\n maxVal = max(maxVal, arrays[i][n-1])\\n }\\n return res\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
\\n###Python
\\nclass Solution:\\n def maxDistance(self, arrays: List[List[int]]) -> int:\\n res = 0\\n n = len(arrays[0])\\n min_val = arrays[0][0]\\n max_val = arrays[0][-1]\\n \\n for i in range(1, len(arrays)):\\n n = len(arrays[i])\\n res = max(res, max(abs(arrays[i][n - 1] - min_val), \\n abs(max_val - arrays[i][0])))\\n min_val = min(min_val, arrays[i][0])\\n max_val = max(max_val, arrays[i][-1])\\n \\n return res\\n
\\n###C
\\nint maxDistance(int** arrays, int arraysSize, int* arraysColSize) {\\n int res = 0;\\n int n = arraysColSize[0];\\n int min_val = arrays[0][0];\\n int max_val = arrays[0][arraysColSize[0] - 1];\\n for (int i = 1; i < arraysSize; i++) {\\n n = arraysColSize[i];\\n res = fmax(res, fmax(abs(arrays[i][n - 1] - min_val), \\n abs(max_val - arrays[i][0])));\\n min_val = fmin(min_val, arrays[i][0]);\\n max_val = fmax(max_val, arrays[i][n - 1]);\\n }\\n \\n return res;\\n}\\n
\\n###JavaScript
\\nvar maxDistance = function(arrays) {\\n let res = 0;\\n let n = arrays[0].length;\\n let minVal = arrays[0][0];\\n let maxVal = arrays[0][n - 1];\\n for (let i = 1; i < arrays.length; i++) {\\n n = arrays[i].length;\\n res = Math.max(res, Math.max(Math.abs(arrays[i][n - 1] - minVal), \\n Math.abs(maxVal - arrays[i][0])));\\n minVal = Math.min(minVal, arrays[i][0]);\\n maxVal = Math.max(maxVal, arrays[i][n - 1]);\\n }\\n return res;\\n};\\n
\\n###TypeScript
\\nfunction maxDistance(arrays: number[][]): number {\\n let res = 0;\\n let n = arrays[0].length;\\n let minVal = arrays[0][0];\\n let maxVal = arrays[0][n - 1];\\n \\n for (let i = 1; i < arrays.length; i++) {\\n n = arrays[i].length;\\n res = Math.max(res, Math.max(Math.abs(arrays[i][n - 1] - minVal), \\n Math.abs(maxVal - arrays[i][0])));\\n minVal = Math.min(minVal, arrays[i][0]);\\n maxVal = Math.max(maxVal, arrays[i][n - 1]);\\n }\\n return res;\\n};\\n
\\n###Rust
\\nimpl Solution {\\n pub fn max_distance(arrays: Vec<Vec<i32>>) -> i32 {\\n let mut res = 0;\\n let mut min_val = arrays[0][0];\\n let mut max_val = arrays[0][arrays[0].len() - 1];\\n for i in 1..arrays.len() {\\n let n = arrays[i].len();\\n res = res.max((arrays[i][n - 1] - min_val).abs());\\n res = res.max((max_val - arrays[i][0]).abs());\\n min_val = min_val.min(arrays[i][0]);\\n max_val = max_val.max(arrays[i][n - 1]);\\n }\\n res\\n }\\n}\\n
\\n复杂度分析
\\n请看【基础算法精讲 16】,制作不易,欢迎点赞关注~
\\nclass Solution:\\n def totalNQueens(self, n: int) -> int:\\n ans = 0\\n col = [False] * n\\n diag1 = [False] * (n * 2 - 1)\\n diag2 = [False] * (n * 2 - 1)\\n def dfs(r: int) -> None:\\n if r == n:\\n nonlocal ans\\n ans += 1 # 找到一个合法方案\\n return\\n for c, ok in enumerate(col):\\n if not ok and not diag1[r + c] and not diag2[r - c]:\\n col[c] = diag1[r + c] = diag2[r - c] = True\\n dfs(r + 1)\\n col[c] = diag1[r + c] = diag2[r - c] = False # 恢复现场\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n private int ans;\\n\\n public int totalNQueens(int n) {\\n boolean[] col = new boolean[n];\\n boolean[] diag1 = new boolean[n * 2 - 1];\\n boolean[] diag2 = new boolean[n * 2 - 1];\\n dfs(0, col, diag1, diag2);\\n return ans;\\n }\\n\\n private void dfs(int r, boolean[] col, boolean[] diag1, boolean[] diag2) {\\n int n = col.length;\\n if (r == n) {\\n ans++; // 找到一个合法方案\\n return;\\n }\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) {\\n col[c] = diag1[r + c] = diag2[rc] = true;\\n dfs(r + 1, col, diag1, diag2);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int totalNQueens(int n) {\\n int ans = 0;\\n vector<int> col(n), diag1(n * 2 - 1), diag2(n * 2 - 1);\\n auto dfs = [&](auto&& dfs, int r) {\\n if (r == n) {\\n ans++; // 找到一个合法方案\\n return;\\n }\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) {\\n col[c] = diag1[r + c] = diag2[rc] = true;\\n dfs(dfs, r + 1);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n };\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\nvoid dfs(int r, int n, bool* col, bool* diag1, bool* diag2, int* ans) {\\n if (r == n) {\\n (*ans)++; // 找到一个合法方案\\n return;\\n }\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) {\\n col[c] = diag1[r + c] = diag2[rc] = true;\\n dfs(r + 1, n, col, diag1, diag2, ans);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n}\\n\\nint totalNQueens(int n) {\\n int ans = 0;\\n bool* col = calloc(n, sizeof(bool));\\n bool* diag1 = calloc(2 * n - 1, sizeof(bool));\\n bool* diag2 = calloc(2 * n - 1, sizeof(bool));\\n dfs(0, n, col, diag1, diag2, &ans);\\n return ans;\\n}\\n
\\nfunc totalNQueens(n int) (ans int) {\\n col := make([]bool, n)\\n diag1 := make([]bool, n*2-1)\\n diag2 := make([]bool, n*2-1)\\n var dfs func(int)\\n dfs = func(r int) {\\n if r == n {\\n ans++ // 找到一个合法方案\\n return\\n }\\n for c, ok := range col {\\n rc := r - c + n - 1\\n if !ok && !diag1[r+c] && !diag2[rc] {\\n col[c], diag1[r+c], diag2[rc] = true, true, true\\n dfs(r + 1)\\n col[c], diag1[r+c], diag2[rc] = false, false, false // 恢复现场\\n }\\n }\\n }\\n dfs(0)\\n return\\n}\\n
\\nvar totalNQueens = function(n) {\\n let ans = 0;\\n const col = Array(n).fill(false);\\n const diag1 = Array(n * 2 - 1).fill(false);\\n const diag2 = Array(n * 2 - 1).fill(false);\\n function dfs(r) {\\n if (r === n) {\\n ans++; // 找到一个合法方案\\n return;\\n }\\n for (let c = 0; c < n; c++) {\\n const rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) {\\n col[c] = diag1[r + c] = diag2[rc] = true;\\n dfs(r + 1);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n dfs(0);\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn total_n_queens(n: i32) -> i32 {\\n fn dfs(r: usize, col: &mut [bool], diag1: &mut [bool], diag2: &mut [bool], ans: &mut i32) {\\n let n = col.len();\\n if r == n {\\n *ans += 1; // 找到一个合法方案\\n return;\\n }\\n for c in 0..n {\\n let rc = n + r - c - 1;\\n if !col[c] && !diag1[r + c] && !diag2[rc] {\\n col[c] = true;\\n diag1[r + c] = true;\\n diag2[rc] = true;\\n dfs(r + 1, col, diag1, diag2, ans);\\n col[c] = false;\\n diag1[r + c] = false;\\n diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n\\n let n = n as usize;\\n let mut ans = 0;\\n let mut col = vec![false; n];\\n let mut diag1 = vec![false; n * 2 - 1];\\n let mut diag2 = vec![false; n * 2 - 1];\\n dfs(0, &mut col, &mut diag1, &mut diag2, &mut ans);\\n ans\\n }\\n}\\n
\\n更多相似题目,见下面回溯题单的「§4.5 排列型回溯」。
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"视频讲解 请看【基础算法精讲 16】,制作不易,欢迎点赞关注~\\n\\nclass Solution:\\n def totalNQueens(self, n: int) -> int:\\n ans = 0\\n col = [False] * n\\n diag1 = [False] * (n * 2 - 1)\\n diag2 = [False] * (n * 2 - 1)\\n def dfs(r: int) -> None:\\n if r == n:…","guid":"https://leetcode.cn/problems/n-queens-ii//solution/hui-su-miao-sha-nhuang-hou-yi-ge-shi-pin-l41l","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-08-10T08:03:50.755Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历的简洁滑动窗口","url":"https://leetcode.cn/problems/count-complete-subarrays-in-an-array//solution/yi-ci-bian-li-de-jian-ji-hua-dong-chuang-cenf","content":"找到一个完全子数组nums[i...j],此时,nums[i...j]就是一个滑动窗口,此时nums[i...j...n-1] (j...n-1表示子数组右边界的范围为[j...n-1])都是完全子数组,所以ans+=n-j.
\\n为什么如果nums[i...j]是完全子数组,那么nums[i...j...n-1]也一定是完全子数组呢?
\\n因为如果nums[i...j]是完全子数组,那么根据题目对于完全子数组的定义,nums[i...j]中不同元素的数目一定就等于整个数组的不同元素数目,即nums[i...j]中不同元素的数目已经达到最大值,所以nums[i...j...n-1]中不同元素的数目也一定等于整个数组不同元素的数目,所以ans+=n-j
\\nclass Solution {\\n public int countCompleteSubarrays(int[] nums) {\\n int n=nums.length;\\n Set<Integer> set=new HashSet<>();\\n for(int i=0;i<n;i++){\\n set.add(nums[i]);\\n }\\n int m=set.size();\\n int[] cnt=new int[2001];\\n int res=0;\\n int ans=0;\\n for(int i=0,j=0;j<n;j++){\\n cnt[nums[j]]++;\\n if(cnt[nums[j]]==1){\\n res++;\\n }\\n while(res==m){\\n ans+=n-j;\\n cnt[nums[i]]--;\\n if(cnt[nums[i]]==0){\\n res--;\\n }\\n i++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n第一种思路(ans更新的是当前左端点,有多少个右端点符合条件):
\\n直接算出符合条件的子数组的个数。找到第一个满足条件的窗口,如果窗口右边的元素有k个,就有k+1(滑窗本身)个字符串都是满足条件的,r右边有n-r-1个元素,加上本身就是n-r,累加答案即可。为什么不考虑滑窗左边的元素呢?因为会重复统计。
class Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n k=len(set(nums))\\n n=len(nums)\\n ans=l=0\\n cnt=Counter()\\n for r,x in enumerate(nums):\\n cnt[x]+=1\\n while len(cnt)==k:\\n ans+=n-r#[l,r],[l,r+1],,,[l,n-1]\\n cnt[nums[l]]-=1\\n if cnt[nums[l]]==0:\\n del cnt[nums[l]]\\n l+=1\\n return ans\\n
\\n第二种思路(ans更新的是当前右端点,对应多少个左端点符合条件):
\\nclass Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n k=len(set(nums))\\n n=len(nums)\\n ans=l=0\\n cnt=Counter()\\n for r,x in enumerate(nums):\\n cnt[x]+=1\\n while len(cnt)==k:\\n cnt[nums[l]]-=1\\n if cnt[nums[l]]==0:\\n del cnt[nums[l]]\\n l+=1\\n ans+=l#[0,r],[1,r],,[l-1,r]\\n return ans\\n
\\n第三种思路:
\\n算出不符合条件的子数组的个数,用全部的子数组个数减去不符合条件的子数组个数。
\\n那么怎么算出全部的子数组个数?
\\n长度为1的子数组有n个
\\n长度为2的子数组有n-1个
\\n长度为3的子数组有n-2个
\\n...
\\n长度为n的子数组有1个
\\n所以全部的子数组个数=(n*(n+1))/2
class Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n k=len(set(nums))\\n n=len(nums)\\n ans=l=0\\n cnt=Counter()\\n for r,x in enumerate(nums):\\n cnt[x]+=1\\n while len(cnt)==k:#这里一旦符合条件,我们就右移左指针,所以保证ans统计的时候是统计不符合条件的子数组的个数\\n cnt[nums[l]]-=1\\n if cnt[nums[l]]==0:\\n del cnt[nums[l]]\\n l+=1\\n ans+=r-l+1#因为不符合条件的有[l,l],[l,l+1],[l,l+2]..[l,r]\\n return n*(n+1)//2-ans\\n
\\n","description":"第一种思路(ans更新的是当前左端点,有多少个右端点符合条件): 直接算出符合条件的子数组的个数。找到第一个满足条件的窗口,如果窗口右边的元素有k个,就有k+1(滑窗本身)个字符串都是满足条件的,r右边有n-r-1个元素,加上本身就是n-r,累加答案即可。为什么不考虑滑窗左边的元素呢?因为会重复统计。\\n\\nclass Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n k=len(set(nums))\\n n=len(nums)…","guid":"https://leetcode.cn/problems/count-complete-subarrays-in-an-array//solution/liang-chong-hua-dong-chuang-kou-onde-si-rry6n","author":"elastic-darwinxuq","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-07-30T04:58:02.100Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「越长越合法」型滑动窗口(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-complete-subarrays-in-an-array//solution/on-hua-dong-chuang-kou-by-endlesscheng-9ztb","content":"题干中的「整个数组」指的是 $\\\\textit{nums}$,不是子数组。
\\n设 $\\\\textit{nums}$ 中的不同元素个数为 $k$,我们要计算的是 $\\\\textit{nums}$ 中的子数组 $b$ 的个数,满足 $b$ 中不同元素个数等于 $k$。
\\n前置知识:滑动窗口【基础算法精讲 03】。
\\n由于子数组越长,包含的元素越多,越能满足题目要求;反之,子数组越短,包含的元素越少,越不能满足题目要求。有这种性质的题目,可以用滑动窗口解决。
\\n枚举子数组的右端点 $\\\\textit{right}$。同时用一个哈希表 $\\\\textit{cnt}$ 维护子数组内每个元素的出现次数。
\\n如果 $\\\\textit{nums}[right]$ 加入哈希表后,发现哈希表的大小等于 $k$,说明子数组满足要求,移动子数组的左端点 $\\\\textit{left}$,把 $\\\\textit{nums}[\\\\textit{left}]$ 的出现次数减一。如果 $\\\\textit{nums}[\\\\textit{left}]$ 的出现次数变成 $0$,则从 $\\\\textit{cnt}$ 中去掉,表示子数组内少了一种元素。
\\n内层循环结束后,$[\\\\textit{left},\\\\textit{right}]$ 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,$[\\\\textit{left}-1,\\\\textit{right}]$ 是满足题目要求的(哈希表的大小等于 $k$)。由于子数组越长,越能满足题目要求,所以除了 $[\\\\textit{left}-1,\\\\textit{right}]$,还有 $[\\\\textit{left}-2,\\\\textit{right}],[\\\\textit{left}-3,\\\\textit{right}],\\\\ldots,[0,\\\\textit{right}]$ 都是满足要求的。也就是说,当右端点固定在 $\\\\textit{right}$ 时,左端点在 $0,1,2,\\\\ldots,\\\\textit{left}-1$ 的所有子数组都是满足要求的,这一共有 $\\\\textit{left}$ 个。
\\n问:刚开始没有满足要求的情况呢?子数组元素比较少,从未进入内层循环。
\\n答:这种情况下 $\\\\textit{left}=0$,说明有 $0$ 个满足要求的子数组,不影响答案。
\\nclass Solution:\\n def countCompleteSubarrays(self, nums: List[int]) -> int:\\n k = len(set(nums))\\n cnt = defaultdict(int) # 比 Counter() 快\\n ans = left = 0\\n for x in nums:\\n cnt[x] += 1\\n while len(cnt) == k:\\n out = nums[left]\\n cnt[out] -= 1\\n if cnt[out] == 0:\\n del cnt[out]\\n left += 1\\n ans += left\\n return ans\\n
\\nclass Solution {\\n public int countCompleteSubarrays(int[] nums) {\\n Set<Integer> set = new HashSet<>();\\n for (int x : nums) {\\n set.add(x);\\n }\\n int k = set.size();\\n\\n Map<Integer, Integer> cnt = new HashMap<>(k);\\n int ans = 0;\\n int left = 0;\\n for (int x : nums) {\\n cnt.merge(x, 1, Integer::sum); // cnt[x]++\\n while (cnt.size() == k) {\\n int out = nums[left];\\n if (cnt.merge(out, -1, Integer::sum) == 0) { // --cnt[out] == 0\\n cnt.remove(out);\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int countCompleteSubarrays(vector<int>& nums) {\\n unordered_set<int> st(nums.begin(), nums.end());\\n int k = st.size();\\n unordered_map<int, int> cnt;\\n int ans = 0, left = 0;\\n for (int x : nums) {\\n cnt[x]++;\\n while (cnt.size() == k) {\\n int out = nums[left];\\n if (--cnt[out] == 0) {\\n cnt.erase(out);\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc countCompleteSubarrays(nums []int) (ans int) {\\n set := map[int]struct{}{}\\n for _, x := range nums {\\n set[x] = struct{}{}\\n }\\n k := len(set)\\n\\n cnt := make(map[int]int, k)\\n left := 0\\n for _, x := range nums {\\n ans += left\\n cnt[x]++\\n for len(cnt) == k {\\n ans++\\n out := nums[left]\\n cnt[out]--\\n if cnt[out] == 0 {\\n delete(cnt, out)\\n }\\n left++\\n }\\n }\\n return\\n}\\n
\\nvar countCompleteSubarrays = function(nums) {\\n const k = new Set(nums).size;\\n const cnt = new Map();\\n let ans = 0, left = 0;\\n for (const x of nums) {\\n cnt.set(x, (cnt.get(x) ?? 0) + 1);\\n while (cnt.size === k) {\\n const out = nums[left];\\n const c = cnt.get(out);\\n if (c === 1) {\\n cnt.delete(out);\\n } else {\\n cnt.set(out, c - 1);\\n }\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n};\\n
\\nuse std::collections::{HashSet, HashMap};\\n\\nimpl Solution {\\n pub fn count_complete_subarrays(nums: Vec<i32>) -> i32 {\\n let k = nums.iter().collect::<HashSet<_>>().len();\\n let mut cnt = HashMap::new();\\n let mut ans = 0;\\n let mut left = 0;\\n for &x in &nums {\\n *cnt.entry(x).or_insert(0) += 1;\\n while cnt.len() == k {\\n let out = nums[left];\\n let e = cnt.get_mut(&out).unwrap();\\n *e -= 1;\\n if *e == 0 {\\n cnt.remove(&out);\\n }\\n left += 1;\\n }\\n ans += left;\\n }\\n ans as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 题干中的「整个数组」指的是 $\\\\textit{nums}$,不是子数组。\\n\\n设 $\\\\textit{nums}$ 中的不同元素个数为 $k$,我们要计算的是 $\\\\textit{nums}$ 中的子数组 $b$ 的个数,满足 $b$ 中不同元素个数等于 $k$。\\n\\n思路\\n\\n前置知识:滑动窗口【基础算法精讲 03】。\\n\\n由于子数组越长,包含的元素越多,越能满足题目要求;反之,子数组越短,包含的元素越少,越不能满足题目要求。有这种性质的题目,可以用滑动窗口解决。\\n\\n枚举子数组的右端点 $\\\\textit{right}$。同时用一个哈希表 $\\\\textit{cnt…","guid":"https://leetcode.cn/problems/count-complete-subarrays-in-an-array//solution/on-hua-dong-chuang-kou-by-endlesscheng-9ztb","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-07-30T04:21:54.264Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"有关官方解答方法1中正向枚举的疑问与补充","url":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/you-guan-guan-fang-jie-da-fang-fa-1zhong-rfqu","content":"这里首先复述下官解方法1的做法。用dp[i]来表示枚举到第i个题目时,可以获得的最大分数。
\\n最终dp[i]的结果则为dp[i] = max(dp[i - 1], dp[j] + points[i]),代码实现如下:
\\nclass Solution:\\n def mostPoints(self, questions: List[List[int]]) -> int:\\n n = len(questions)\\n dp = [0] * n\\n dp[0] = questions[0][0]\\n for i in range(1, n):\\n not_select = dp[i - 1] # 不解决\\n select = questions[i][0] # 解决\\n plus = 0 # 如果从上一个已解决的题转化而来,可获得的额外分数\\n for j in range(0, i):\\n if j + questions[j][1] < i:\\n plus = max(plus, dp[j])\\n dp[i] = max(not_select, select + plus)\\n return dp[n - 1]\\n
\\n而以上解法是错误的(并非溢出问题),只能通过一半的用例,一个较短的错误用例如下:
\\n[[21,2],[1,2],[12,5],[7,2],[35,3],[32,2],[80,2],[91,5],[92,3],[27,3],[19,1],[37,3],[85,2],[33,4],[25,1],[91,4],[44,3],[93,3],[65,4],[82,3],[85,5],[81,3],[29,2],[25,1],[74,2],[58,1],[85,1],[84,2],[27,2],[47,5],[48,4],[3,2],[44,3],[60,5],[19,2],[9,4],[29,5],[15,3],[1,3],[60,2],[63,3],[79,3],[19,1],[7,1],[35,1],[55,4],[1,4],[41,1],[58,5]]\\n
\\n预计输出为:
\\n781\\n
\\n实际输出为:
\\n805\\n
\\n可以发现实际输出更大了,也就是多贪心了状态。那么问题出在哪呢?
\\n这里举一个更简单的例子:
\\n[[100,5],[1,1],[1,2],[1,1]]\\n
\\n预计输出应为100,也就是只选第0个问题回答,而以上代码的输出为101。将此时的代码的dp数组输出,有:
\\n[100, 100, 100, 101]\\n
\\n其计算逻辑为:
\\ndp[0] = points[0] = 100\\ndp[1] = dp[0] = 100\\ndp[2] = dp[1] = 100\\ndp[3] = dp[1] + points[3] = 101 (因为dp[1]的brainpower[i]为1,与答第2题冲突,但与答第3题不冲突)\\n
\\n可以发现,问题出在dp[1]上。因为dp[1]是由dp[0]转移而来,隐式的选择了dp[0],因此其实际的\\"脑力恢复期\\"为max(0 + brainpower[0], 1 + brainbrainpower[1]) = 5
,而非1 + brainbrainpower[1] = 2
。也就是说,判断dp[j]是否能由dp[i]转化而来,不能只依靠:
if j + questions[j][1] < i:\\n plus = max(plus, dp[j])\\n
\\n因为当遍历到第j个元素时,某个小于j的已选元素k的\\"脑力恢复期\\"可能仍未结束,因此不能只考虑选择j所造成的\\"脑力恢复期\\"。
\\n","description":"这里首先复述下官解方法1的做法。用dp[i]来表示枚举到第i个题目时,可以获得的最大分数。 如果不选第i个题,有dp[i]=dp[i−1]\\n如果选第i个题,需要遍历j$\\\\in$[0, i-1],找到满足j + brainpower[j] < i的值最大的dp[j],从其转移而来,有dp[i] = dp[j] + points[i]\\n\\n最终dp[i]的结果则为dp[i] = max(dp[i - 1], dp[j] + points[i]),代码实现如下:\\n\\nclass Solution:\\n def mostPoints(self…","guid":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/you-guan-guan-fang-jie-da-fang-fa-1zhong-rfqu","author":"xiongxyowo","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-07-27T02:08:33.329Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"开方+质数性质,(大于等于5的)质数一定和6的倍数相邻,一定是6x-1或6x-1","url":"https://leetcode.cn/problems/prime-in-diagonal//solution/kai-fang-zhi-shu-xing-zhi-da-yu-deng-yu-lhmu9","content":"\\n\\nProblem: 2614. 对角线上的质数
\\n
[TOC]
\\n证明过程如下:
令x≥1,将大于等于5的自然数表示如下: ······6x-1,6x,6x+1,6x+2,6x+3,6x+4······(相邻6个数为一组)
\\n在以上的数字中,6x、6x+2和6x+4是偶数,一定不是质数;6x+3可以分解为3(2x+1),不是质数,因此质数只能是6x-1和6x+1。
\\n\\n\\n$O(sqrt(n)/6)$
\\n
\\n\\n$O(1)$
\\n
###C
\\n\\nbool IsPrime(int n) {\\n if(n==1) return false;\\n if (n <= 3) {\\n return true;\\n }\\n // 只有6x-1和6x+1的数才有可能是质数\\n if (n % 6 != 1 && n % 6 != 5) {\\n return false;\\n }\\n // 判断这些数能否被小于sqrt(n)的奇数整除\\n for (int i = 5; i*i <= n; i += 6) {\\n if (n % i == 0 || n % (i + 2) == 0) {\\n return false;\\n }\\n }\\n return true;\\n}\\n\\nint diagonalPrime(int** nums, int numsSize, int* numsColSize){\\n int max=0;\\n bool flag;\\n for(int i=0;i<numsSize;i++){\\n for(int j=0;j<*numsColSize;j++){\\n if(i==j) {\\n flag = IsPrime(nums[i][j]);\\n if(flag) max=max>nums[i][j]?max:nums[i][j];\\n }\\n if(j==*numsColSize-i-1){\\n flag = IsPrime(nums[i][j]);\\n if(flag) max=max>nums[i][j]?max:nums[i][j];\\n }\\n }\\n }\\n return max;\\n}\\n
\\n","description":"Problem: 2614. 对角线上的质数 [TOC]\\n 证明过程如下:\\n\\n令x≥1,将大于等于5的自然数表示如下: ······6x-1,6x,6x+1,6x+2,6x+3,6x+4······(相邻6个数为一组)\\n\\n在以上的数字中,6x、6x+2和6x+4是偶数,一定不是质数;6x+3可以分解为3(2x+1),不是质数,因此质数只能是6x-1和6x+1。\\n\\n时间复杂度:\\n\\n$O(sqrt(n)/6)$\\n\\n空间复杂度:\\n\\n$O(1)$\\n\\n###C\\n\\n\\nbool IsPrime(int n) {\\n if(n==1) return false…","guid":"https://leetcode.cn/problems/prime-in-diagonal//solution/kai-fang-zhi-shu-xing-zhi-da-yu-deng-yu-lhmu9","author":"hoshiiai","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-07-07T14:55:15.578Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2176. 统计数组中相等且可以被整除的数对","url":"https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array//solution/2176-tong-ji-shu-zu-zhong-xiang-deng-qie-1q0j","content":"class Solution {\\npublic:\\n int countPairs(vector<int>& nums, int k) {\\n int ans = 0;\\n for (int i = 0; i < nums.size(); ++i) {\\n for (int j = i+1; j < nums.size(); ++j) {\\n if (nums[i] == nums[j] && (i*j)%k == 0) {\\n ans++;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n","description":"class Solution { public:\\n int countPairs(vector设 $1$ 在数组中的下标为 $p$,$n$ 在数组中的下标为 $q$。
\\n分类讨论:
\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n n = len(nums)\\n p = nums.index(1)\\n q = nums.index(n)\\n return p + n - 1 - q - (p > q)\\n
\\nclass Solution {\\n public int semiOrderedPermutation(int[] nums) {\\n int n = nums.length;\\n int p = 0;\\n int q = 0;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 1) {\\n p = i;\\n } else if (nums[i] == n) {\\n q = i;\\n }\\n }\\n return p + n - 1 - q - (p > q ? 1 : 0);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int semiOrderedPermutation(vector<int>& nums) {\\n auto [p, q] = ranges::minmax_element(nums);\\n return p - q + nums.size() - 1 - (p > q);\\n }\\n};\\n
\\nint semiOrderedPermutation(int* nums, int n) {\\n int p, q;\\n for (int i = 0; i < n; i++) {\\n if (nums[i] == 1) {\\n p = i;\\n } else if (nums[i] == n) {\\n q = i;\\n }\\n }\\n return p + n - 1 - q - (p > q);\\n}\\n
\\nfunc semiOrderedPermutation(nums []int) int {\\n n := len(nums)\\n p := slices.Index(nums, 1)\\n q := slices.Index(nums, n)\\n if p < q {\\n return p + n - 1 - q\\n }\\n return p + n - 2 - q // 1 向左移动的时候和 n 交换了一次\\n}\\n
\\nvar semiOrderedPermutation = function(nums) {\\n const n = nums.length;\\n const p = nums.indexOf(1);\\n const q = nums.indexOf(n);\\n return p + n - 1 - q - (p > q ? 1 : 0);\\n};\\n
\\nimpl Solution {\\n pub fn semi_ordered_permutation(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let p = nums.iter().position(|&x| x == 1).unwrap();\\n let q = nums.iter().position(|&x| x == n as i32).unwrap();\\n (p + n - 1 - q - (p > q) as usize) as _\\n }\\n}\\n
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"设 $1$ 在数组中的下标为 $p$,$n$ 在数组中的下标为 $q$。 分类讨论:\\n\\n如果 $pq$,那么 $1$ 和 $n$ 在移动过程中会相遇,互相穿过对方,也就是只花费一次操作,就让两个数都移动了一步(互相穿过),所以操作次数比上面的情况要少 $1$,即 $p + (n-1-q) - 1$。\\nclass Solution:\\n def…","guid":"https://leetcode.cn/problems/semi-ordered-permutation//solution/fen-lei-tao-lun-jian-ji-xie-fa-pythonjav-vynj","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-06-04T04:43:52.174Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学计算,python二行,双百 | 6424. 半有序排列","url":"https://leetcode.cn/problems/semi-ordered-permutation//solution/shu-xue-ji-suan-pythoner-xing-shuang-bai-0ypb","content":"\\n\\nProblem: 6424. 半有序排列
\\n[TOC]
\\n思路
\\n按题意,就是计算 1 左移到数组首位的步数 和 n 右移到数组尾位的步数:
\\n\\n
\\n- \\n
\\n1 左移到数组首位的步数为:
\\nnums.index(1)
- \\n
\\nn 右移到数组尾位的步数为:
\\nn - nums.index(n) - 1
注意: 如果起始时,1 在 n 的右侧, 1 左移至 n 相邻时,与 n 交换, n 会少移一步。
\\n所以,推导得出计算公式为:
\\n$$nums.index(1) + n - nums.index(n) - 1 - ( nums.index(1) > nums.index(n) )$$
\\nCode
\\npython二行,双百:
\\n时间44 ms击败100%;内存16 MB击败100%
\\n###Python3
\\n\\nclass Solution:\\n def semiOrderedPermutation(self, nums: List[int]) -> int:\\n id_1, id_n = nums.index(1), nums.index(n := len(nums))\\n return id_1 + n - id_n - 1 - (id_1 > id_n)\\n
您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 6424. 半有序排列 [TOC]\\n\\n按题意,就是计算 1 左移到数组首位的步数 和 n 右移到数组尾位的步数:\\n\\n1 左移到数组首位的步数为: nums.index(1)\\n\\nn 右移到数组尾位的步数为: n - nums.index(n) - 1\\n\\n注意: 如果起始时,1 在 n 的右侧, 1 左移至 n 相邻时,与 n 交换, n 会少移一步。\\n\\n所以,推导得出计算公式为:\\n\\n$$nums.index(1) + n - nums.index(n) - 1 - ( nums.index(1) > nums.index(n…","guid":"https://leetcode.cn/problems/semi-ordered-permutation//solution/shu-xue-ji-suan-pythoner-xing-shuang-bai-0ypb","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-06-04T04:37:23.325Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种写法:哈希集合 / 位运算(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimize-string-length//solution/o1-kong-jian-wei-yun-suan-xie-fa-pythonj-7t4p","content":"根据题意,只要有重复字母,就可以继续操作。
\\n如果每种字母都只剩下一个,则无法操作。
\\n所以答案为 $s$ 中不同字母的个数。
\\n写法一:哈希集合
\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n
\\nclass Solution {\\n public int minimizedStringLength(String s) {\\n return (int) s.chars().distinct().count();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimizedStringLength(string s) {\\n return unordered_set(s.begin(), s.end()).size();\\n }\\n};\\n
\\nfunc minimizedStringLength(s string) int {\\n set := map[rune]struct{}{}\\n for _, c := range s {\\n set[c] = struct{}{}\\n }\\n return len(set)\\n}\\n
写法二:位运算
\\n\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n mask = 0\\n for c in s:\\n mask |= 1 << (ord(c) - ord(\'a\')) # 把 c-\'a\' 加到集合中\\n return mask.bit_count() # 集合的大小\\n
\\nclass Solution {\\n public int minimizedStringLength(String s) {\\n int mask = 0;\\n for (char c : s.toCharArray()) {\\n mask |= 1 << (c - \'a\'); // 把 c-\'a\' 加到集合中\\n }\\n return Integer.bitCount(mask); // 集合的大小\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimizedStringLength(string s) {\\n unsigned mask = 0;\\n for (char c : s) {\\n mask |= 1 << (c - \'a\'); // 把 c-\'a\' 加到集合中\\n }\\n return popcount(mask); // 集合的大小\\n }\\n};\\n
\\nfunc minimizedStringLength(s string) int {\\n mask := uint(0)\\n for _, c := range s {\\n mask |= 1 << (c - \'a\') // 把 c-\'a\' 加到集合中\\n }\\n return bits.OnesCount(mask) // 集合的大小\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据题意,只要有重复字母,就可以继续操作。 如果每种字母都只剩下一个,则无法操作。\\n\\n所以答案为 $s$ 中不同字母的个数。\\n\\n写法一:哈希集合\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n\\nclass Solution {\\n public int minimizedStringLength(String s) {\\n return (int) s.chars().distinct().count…","guid":"https://leetcode.cn/problems/minimize-string-length//solution/o1-kong-jian-wei-yun-suan-xie-fa-pythonj-7t4p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-06-04T04:29:56.999Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"哈希表","url":"https://leetcode.cn/problems/minimize-string-length//solution/ha-xi-biao-by-bloom-sobtw","content":"\\n\\nProblem: 6462. 最小化字符串长度
\\n[TOC]
\\n思路
\\n最短字符串长度就是每种字符删得只剩一个,等价于求字符串 $S$ 中不同字符的数量
\\nCode
\\n###Python3
\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n int minimizedStringLength(string s) {\\n return unordered_set<char>(s.begin(), s.end()).size();\\n }\\n};\\n
###rust
\\n\\n","description":"Problem: 6462. 最小化字符串长度 [TOC]\\n\\n最短字符串长度就是每种字符删得只剩一个,等价于求字符串 $S$ 中不同字符的数量\\n\\n###Python3\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int minimizedStringLength(string s) {\\n return unordered…","guid":"https://leetcode.cn/problems/minimize-string-length//solution/ha-xi-biao-by-bloom-sobtw","author":"bloom-","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-06-04T04:28:02.186Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"求集合长度,python一行,100% | 6462. 最小化字符串长度","url":"https://leetcode.cn/problems/minimize-string-length//solution/qiu-ji-he-chang-du-pythonyi-xing-100-646-84bt","content":"impl Solution {\\n pub fn minimized_string_length(s: String) -> i32 {\\n s.chars().collect::<std::collections::HashSet<_>>().len() as i32\\n }\\n}\\n
\\n\\nProblem: 6462. 最小化字符串长度
\\n[TOC]
\\n思路
\\n题意翻成以下就是:
\\n(1)对s内的任意一个字符,都可以删除s内其它该字符;
\\n(2)可以执行任意次。
\\n那么,就是s中出现的字符,最后都剩下1个,求集合set(s)之后再求长度,即可。
\\nCode
\\npython一行,100%:
\\n时间44 ms击败100%;内存16.1 MB击败N/A
\\n###Python3
\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n
您若还有不同方法,欢迎贴在评论区,一起交流探讨! ^_^
\\n↓ 点个赞,点收藏,留个言,再划走,感谢您支持作者! ^_^
\\n","description":"Problem: 6462. 最小化字符串长度 [TOC]\\n\\n题意翻成以下就是:\\n\\n(1)对s内的任意一个字符,都可以删除s内其它该字符;\\n\\n(2)可以执行任意次。\\n\\n那么,就是s中出现的字符,最后都剩下1个,求集合set(s)之后再求长度,即可。\\n\\npython一行,100%:\\n\\n时间44 ms击败100%;内存16.1 MB击败N/A\\n\\n###Python3\\n\\nclass Solution:\\n def minimizedStringLength(self, s: str) -> int:\\n return len(set(s))\\n\\n\\n您若还有…","guid":"https://leetcode.cn/problems/minimize-string-length//solution/qiu-ji-he-chang-du-pythonyi-xing-100-646-84bt","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-06-04T04:12:23.345Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Elixir 计数","url":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/elixir-ji-shu-by-lambda2void-8tup","content":"解题思路
\\nElixir 计数模拟即可
\\n代码
\\n###elixir
\\n\\n","description":"解题思路 Elixir 计数模拟即可\\n\\n代码\\n\\n###elixir\\n\\ndefmodule Solution do\\n @spec edge_score(edges :: [integer]) :: integer\\n def edge_score(edges) do\\n acc_map = edges\\n |> Enum.with_index()\\n |> Map.new(fn {_,idx} -> {idx, 0} end)\\n edges\\n |> Enum.with_index()\\n |> Enum.reduce(acc_map…","guid":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/elixir-ji-shu-by-lambda2void-8tup","author":"lambda2void","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-30T10:48:52.946Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"三种方法:暴力 / 前后缀分解 / 位运算优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/cong-o503-dao-o502-by-endlesscheng-5wp4","content":"defmodule Solution do\\n @spec edge_score(edges :: [integer]) :: integer\\n def edge_score(edges) do\\n acc_map = edges\\n |> Enum.with_index()\\n |> Map.new(fn {_,idx} -> {idx, 0} end)\\n edges\\n |> Enum.with_index()\\n |> Enum.reduce(acc_map, fn {k, v}, acc -> Map.update!(acc, k, &(&1+v)) end)\\n |> Enum.sort_by(&{elem(&1, 1), -elem(&1,0)}, :desc)\\n |> Enum.at(0)\\n |> elem(0)\\n end\\nend\\n\\n
方法一:暴力计算
\\n计算 $\\\\textit{topLeft}[i][j]$:从 $(i-1,j-1)$ 开始,往左上方向 ↖ 遍历,直到到达矩阵边界。在这个过程中,把遍历到的数加到哈希集合中,最终 $\\\\textit{topLeft}[i][j]$ 就是哈希集合的大小。
\\n计算 $\\\\textit{bottomRight}[i][j]$:从 $(i+1,j+1)$ 开始,往右下方向 ↘ 遍历,直到到达矩阵边界。在这个过程中,把遍历到的数加到哈希集合中,最终 $\\\\textit{bottomRight}[i][j]$ 就是哈希集合的大小。
\\n\\nclass Solution:\\n def differenceOfDistinctValues(self, grid: List[List[int]]) -> List[List[int]]:\\n m, n = len(grid), len(grid[0])\\n ans = [[0] * n for _ in range(m)]\\n st = set()\\n for i in range(m):\\n for j in range(n):\\n # 计算 top_left[i][j]\\n st.clear()\\n x, y = i - 1, j - 1\\n while x >= 0 and y >= 0:\\n st.add(grid[x][y])\\n x -= 1\\n y -= 1\\n top_left = len(st)\\n\\n # 计算 bottom_right[i][j]\\n st.clear()\\n x, y = i + 1, j + 1\\n while x < m and y < n:\\n st.add(grid[x][y])\\n x += 1\\n y += 1\\n bottom_right = len(st)\\n\\n ans[i][j] = abs(top_left - bottom_right)\\n return ans\\n
\\nclass Solution {\\n public int[][] differenceOfDistinctValues(int[][] grid) {\\n int m = grid.length;\\n int n = grid[0].length;\\n int[][] ans = new int[m][n];\\n Set<Integer> st = new HashSet<>();\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n // 计算 topLeft[i][j]\\n st.clear();\\n for (int x = i - 1, y = j - 1; x >= 0 && y >= 0; x--, y--) {\\n st.add(grid[x][y]);\\n }\\n int topLeft = st.size();\\n\\n // 计算 bottomRight[i][j]\\n st.clear();\\n for (int x = i + 1, y = j + 1; x < m && y < n; x++, y++) {\\n st.add(grid[x][y]);\\n }\\n int bottomRight = st.size();\\n\\n ans[i][j] = Math.abs(topLeft - bottomRight);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n vector ans(m, vector<int>(n));\\n unordered_set<int> st;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n // 计算 top_left[i][j]\\n st.clear();\\n for (int x = i - 1, y = j - 1; x >= 0 && y >= 0; x--, y--) {\\n st.insert(grid[x][y]);\\n }\\n int top_left = st.size();\\n\\n // 计算 bottom_right[i][j]\\n st.clear();\\n for (int x = i + 1, y = j + 1; x < m && y < n; x++, y++) {\\n st.insert(grid[x][y]);\\n }\\n int bottom_right = st.size();\\n\\n ans[i][j] = abs(top_left - bottom_right);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc differenceOfDistinctValues(grid [][]int) [][]int {\\n m, n := len(grid), len(grid[0])\\n ans := make([][]int, m)\\n set := map[int]struct{}{}\\n for i := range m {\\n ans[i] = make([]int, n)\\n for j := range n {\\n // 计算 topLeft[i][j]\\n clear(set)\\n for x, y := i-1, j-1; x >= 0 && y >= 0; x, y = x-1, y-1 {\\n set[grid[x][y]] = struct{}{}\\n }\\n topLeft := len(set)\\n\\n // 计算 bottomRight[i][j]\\n clear(set)\\n for x, y := i+1, j+1; x < m && y < n; x, y = x+1, y+1 {\\n set[grid[x][y]] = struct{}{}\\n }\\n bottomRight := len(set)\\n\\n ans[i][j] = abs(topLeft - bottomRight)\\n }\\n }\\n return ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(mn\\\\cdot \\\\min(m,n))$,其中 $m$ 和 $n$ 分别为 $\\\\textit{grid}$ 的行数和列数。计算一个 $\\\\textit{topLeft}[i][j]$ 和 $\\\\textit{bottomRight}[i][j]$ 需要 $\\\\mathcal{O}(\\\\min(m,n))$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(\\\\min(m,n))$。返回值不计入。
\\n方法二:前后缀分解
\\n对于同一条对角线,方法一会多次遍历。比如计算 $\\\\textit{ans}[0][0]$ 的时候我们会遍历主对角线,计算 $\\\\textit{ans}[1][1]$ 的时候我们又会遍历主对角线。怎么减少遍历次数?
\\n考察某一条对角线 $d$,把它视作一个一维数组。对于 $d[i]$ 来说:
\\n\\n
\\n- $\\\\textit{topLeft}$ 是在 $d[i]$ 左边的不同元素个数。我们可以从左到右遍历 $d$,同时把元素加到一个哈希集合中。遍历到 $d[i]$ 时,哈希集合的大小就是 $\\\\textit{topLeft}$。
\\n- $\\\\textit{bottomRight}$ 是在 $d[i]$ 右边的不同元素个数。我们可以从右到左遍历 $d$,同时把元素加到一个哈希集合中。遍历到 $d[i]$ 时,哈希集合的大小就是 $\\\\textit{bottomRight}$。
\\n如何一条一条地枚举对角线?见【模板】遍历对角线。
\\n代码实现时,可以直接把 $\\\\textit{topLeft}$ 和 $\\\\textit{bottomRight}$ 保存到 $\\\\textit{ans}[i][j]$ 中。
\\n\\nclass Solution:\\n def differenceOfDistinctValues(self, grid: List[List[int]]) -> List[List[int]]:\\n m, n = len(grid), len(grid[0])\\n ans = [[0] * n for _ in range(m)]\\n st = set()\\n\\n # 第一排在右上,最后一排在左下\\n # 每排从左上到右下\\n # 令 k=i-j+n,那么右上角 k=1,左下角 k=m+n-1\\n for k in range(1, m + n):\\n # 核心:计算 j 的最小值和最大值\\n min_j = max(n - k, 0) # i=0 的时候,j=n-k,但不能是负数\\n max_j = min(m + n - 1 - k, n - 1) # i=m-1 的时候,j=m+n-1-k,但不能超过 n-1\\n\\n st.clear()\\n for j in range(min_j, max_j + 1):\\n i = k + j - n\\n ans[i][j] = len(st) # top_left[i][j] == len(st)\\n st.add(grid[i][j])\\n\\n st.clear()\\n for j in range(max_j, min_j - 1, -1):\\n i = k + j - n\\n ans[i][j] = abs(ans[i][j] - len(st)) # bottom_right[i][j] == len(st)\\n st.add(grid[i][j])\\n return ans\\n
\\nclass Solution {\\n public int[][] differenceOfDistinctValues(int[][] grid) {\\n int m = grid.length;\\n int n = grid[0].length;\\n int[][] ans = new int[m][n];\\n Set<Integer> set = new HashSet<>();\\n\\n // 第一排在右上,最后一排在左下\\n // 每排从左上到右下\\n // 令 k=i-j+n,那么右上角 k=1,左下角 k=m+n-1\\n for (int k = 1; k < m + n; k++) {\\n // 核心:计算 j 的最小值和最大值\\n int minJ = Math.max(n - k, 0); // i=0 的时候,j=n-k,但不能是负数\\n int maxJ = Math.min(m + n - 1 - k, n - 1); // i=m-1 的时候,j=m+n-1-k,但不能超过 n-1\\n\\n set.clear();\\n for (int j = minJ; j <= maxJ; j++) {\\n int i = k + j - n;\\n ans[i][j] = set.size(); // topLeft[i][j] == set.size()\\n set.add(grid[i][j]);\\n }\\n\\n set.clear();\\n for (int j = maxJ; j >= minJ; j--) {\\n int i = k + j - n;\\n ans[i][j] = Math.abs(ans[i][j] - set.size()); // bottomRight[i][j] == set.size()\\n set.add(grid[i][j]);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n vector ans(m, vector<int>(n));\\n unordered_set<int> st;\\n\\n // 第一排在右上,最后一排在左下\\n // 每排从左上到右下\\n // 令 k=i-j+n,那么右上角 k=1,左下角 k=m+n-1\\n for (int k = 1; k < m + n; k++) {\\n // 核心:计算 j 的最小值和最大值\\n int min_j = max(n - k, 0); // i=0 的时候,j=n-k,但不能是负数\\n int max_j = min(m + n - 1 - k, n - 1); // i=m-1 的时候,j=m+n-1-k,但不能超过 n-1\\n\\n st.clear();\\n for (int j = min_j; j <= max_j; j++) {\\n int i = k + j - n;\\n ans[i][j] = st.size(); // top_left[i][j] == st.size()\\n st.insert(grid[i][j]);\\n }\\n\\n st.clear();\\n for (int j = max_j; j >= min_j; j--) {\\n int i = k + j - n;\\n ans[i][j] = abs(ans[i][j] - (int) st.size()); // bottom_right[i][j] == st.size()\\n st.insert(grid[i][j]);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc differenceOfDistinctValues(grid [][]int) [][]int {\\n m, n := len(grid), len(grid[0])\\n ans := make([][]int, m)\\n for i := range ans {\\n ans[i] = make([]int, n)\\n }\\n set := map[int]struct{}{}\\n\\n // 第一排在右上,最后一排在左下\\n // 每排从左上到右下\\n // 令 k=i-j+n,那么右上角 k=1,左下角 k=m+n-1\\n for k := 1; k < m+n; k++ {\\n // 核心:计算 j 的最小值和最大值\\n minJ := max(n-k, 0) // i=0 的时候,j=n-k,但不能是负数\\n maxJ := min(m+n-1-k, n-1) // i=m-1 的时候,j=m+n-1-k,但不能超过 n-1\\n\\n clear(set)\\n for j := minJ; j <= maxJ; j++ {\\n i := k + j - n\\n ans[i][j] = len(set) // topLeft[i][j] == len(set)\\n set[grid[i][j]] = struct{}{}\\n }\\n\\n clear(set)\\n for j := maxJ; j >= minJ; j-- {\\n i := k + j - n\\n ans[i][j] = abs(ans[i][j] - len(set)) // bottomRight[i][j] == len(set)\\n set[grid[i][j]] = struct{}{}\\n }\\n }\\n return ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别为 $\\\\textit{grid}$ 的行数和列数。每个单元格访问两次。
\\n- 空间复杂度:$\\\\mathcal{O}(\\\\min(m,n))$。返回值不计入。
\\n方法三:位运算优化
\\n本题值域范围为 $[1,50]$,我们可以用 从集合论到位运算,常见位运算技巧分类总结 中的技巧,用二进制数($64$ 位整数)记录哪些数字出现过。遍历到 $x=\\\\textit{grid}[i][j]$ 时,方法二要加到集合中,方法三改成把二进制数的从低到高第 $x$ 位置为 $1$。集合大小就是二进制数中的 $1$ 的个数。
\\n\\nclass Solution:\\n def differenceOfDistinctValues(self, grid: List[List[int]]) -> List[List[int]]:\\n m, n = len(grid), len(grid[0])\\n ans = [[0] * n for _ in range(m)]\\n\\n for k in range(1, m + n):\\n min_j = max(n - k, 0)\\n max_j = min(m + n - 1 - k, n - 1)\\n\\n st = 0\\n for j in range(min_j, max_j + 1):\\n i = k + j - n\\n ans[i][j] = st.bit_count() # 计算 st 中 1 的个数\\n st |= 1 << grid[i][j] # 把 grid[i][j] 加到 st 中\\n\\n st = 0\\n for j in range(max_j, min_j - 1, -1):\\n i = k + j - n\\n ans[i][j] = abs(ans[i][j] - st.bit_count())\\n st |= 1 << grid[i][j]\\n return ans\\n
\\nclass Solution {\\n public int[][] differenceOfDistinctValues(int[][] grid) {\\n int m = grid.length;\\n int n = grid[0].length;\\n int[][] ans = new int[m][n];\\n\\n for (int k = 1; k < m + n; k++) {\\n int minJ = Math.max(n - k, 0);\\n int maxJ = Math.min(m + n - 1 - k, n - 1);\\n\\n long set = 0;\\n for (int j = minJ; j <= maxJ; j++) {\\n int i = k + j - n;\\n ans[i][j] = Long.bitCount(set); // 计算 set 中 1 的个数\\n set |= 1L << grid[i][j]; // 把 grid[i][j] 加到 set 中\\n }\\n\\n set = 0;\\n for (int j = maxJ; j >= minJ; j--) {\\n int i = k + j - n;\\n ans[i][j] = Math.abs(ans[i][j] - Long.bitCount(set));\\n set |= 1L << grid[i][j];\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {\\n int m = grid.size(), n = grid[0].size();\\n vector ans(m, vector<int>(n));\\n\\n for (int k = 1; k < m + n; k++) {\\n int min_j = max(n - k, 0);\\n int max_j = min(m + n - 1 - k, n - 1);\\n\\n uint64_t st = 0;\\n for (int j = min_j; j <= max_j; j++) {\\n int i = k + j - n;\\n ans[i][j] = popcount(st); // st 的大小\\n st |= 1ULL << grid[i][j]; // 把 grid[i][j] 加到 st 中\\n }\\n\\n st = 0;\\n for (int j = max_j; j >= min_j; j--) {\\n int i = k + j - n;\\n ans[i][j] = abs(ans[i][j] - popcount(st));\\n st |= 1ULL << grid[i][j];\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc differenceOfDistinctValues(grid [][]int) [][]int {\\n m, n := len(grid), len(grid[0])\\n ans := make([][]int, m)\\n for i := range ans {\\n ans[i] = make([]int, n)\\n }\\n\\n for k := 1; k < m+n; k++ {\\n minJ := max(n-k, 0)\\n maxJ := min(m+n-1-k, n-1)\\n\\n set := uint(0)\\n for j := minJ; j <= maxJ; j++ {\\n i := k + j - n\\n ans[i][j] = bits.OnesCount(set) // set 的大小\\n set |= 1 << grid[i][j] // 把 grid[i][j] 加到 set 中\\n }\\n\\n set = 0\\n for j := maxJ; j >= minJ; j-- {\\n i := k + j - n\\n ans[i][j] = abs(ans[i][j] - bits.OnesCount(set))\\n set |= 1 << grid[i][j]\\n }\\n }\\n return ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别为 $\\\\textit{grid}$ 的行数和列数。每个单元格访问两次。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。返回值不计入。
\\n更多相似题目,见下面动态规划题单中的「专题:前后缀分解」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:暴力计算 计算 $\\\\textit{topLeft}[i][j]$:从 $(i-1,j-1)$ 开始,往左上方向 ↖ 遍历,直到到达矩阵边界。在这个过程中,把遍历到的数加到哈希集合中,最终 $\\\\textit{topLeft}[i][j]$ 就是哈希集合的大小。\\n\\n计算 $\\\\textit{bottomRight}[i][j]$:从 $(i+1,j+1)$ 开始,往右下方向 ↘ 遍历,直到到达矩阵边界。在这个过程中,把遍历到的数加到哈希集合中,最终 $\\\\textit{bottomRight}[i][j]$ 就是哈希集合的大小。\\n\\nclass…","guid":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/cong-o503-dao-o502-by-endlesscheng-5wp4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T09:57:01.710Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心","url":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/tan-xin-by-xiaojian-zhong-kgcv","content":"思路
\\n可以观察到,
\\n\\n
\\n- 有 $k$ 个相邻 $0$-$1$ 对,就一定要翻转 $k$ 次,比如对于 $010101$ 需要翻转 $5$ 次。
\\n- 每次翻转能且仅能修正一个 $0$-$1$ 对,对其他 $0$-$1$ 对的存在与否不会造成影响。
\\n对于某个 $0$-$1$ 对,不失一般性,假设 $0$ 在前,$1$ 在后,即 $\\\\dots 01 \\\\dots$。要将其修正为 $\\\\dots 11 \\\\dots$ 或 $\\\\dots 00 \\\\dots$,要么翻转前半段 $\\\\dots 0$,要么翻转后半段 $1 \\\\dots$,代价都是对应段的长度。
\\n贪心:每次修正一个 $0$-$1$ 对的时候,翻转短的那半段。
\\n代码
\\nC++ 中注意答案类型为
\\nlong long
。\\nclass Solution:\\n def minimumCost(self, s: str) -> int:\\n N = len(s)\\n ans = 0\\n for i in range(N-1):\\n if s[i] != s[i+1]:\\n ans += min(i+1, N-(i+1))\\n return ans\\n
\\nclass Solution {\\npublic:\\n long long minimumCost(string s) {\\n int N = s.size();\\n long long ans = 0;\\n for (int i = 0; i < N-1; i++) {\\n if (s[i] != s[i+1]) ans += min(i+1, N-(i+1));\\n }\\n return ans;\\n }\\n};\\n
复杂度
\\n记 $n$ 为 $s$ 的长度,
\\n\\n
\\n","description":"思路 可以观察到,\\n\\n有 $k$ 个相邻 $0$-$1$ 对,就一定要翻转 $k$ 次,比如对于 $010101$ 需要翻转 $5$ 次。\\n每次翻转能且仅能修正一个 $0$-$1$ 对,对其他 $0$-$1$ 对的存在与否不会造成影响。\\n\\n对于某个 $0$-$1$ 对,不失一般性,假设 $0$ 在前,$1$ 在后,即 $\\\\dots 01 \\\\dots$。要将其修正为 $\\\\dots 11 \\\\dots$ 或 $\\\\dots 00 \\\\dots$,要么翻转前半段 $\\\\dots 0$,要么翻转后半段 $1 \\\\dots$,代价都是对应段的长度。\\n\\n贪心:每次修正一个 $0…","guid":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/tan-xin-by-xiaojian-zhong-kgcv","author":"xiaojian-zhong","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T06:39:34.485Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"脑筋急转弯(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/yi-ci-bian-li-jian-ji-xie-fa-pythonjavac-aut0","content":"- 时间复杂度:$O(n)$。
\\n- 空间复杂度:$O(1)$。
\\n如果 $s[i-1]\\\\ne s[i]$,那么必须反转,不然这两个字符无法相等。
\\n\\n
\\n- 要么反转前缀 $s[0]$ 到 $s[i-1]$,成本为 $i$。
\\n- 要么反转后缀 $s[i]$ 到 $s[n-1]$,成本为 $n-i$。
\\n反转后 $s[i-1] = s[i]$。
\\n注意到,反转操作只会改变 $s[i-1]$ 与 $s[i]$ 是否相等,不会改变其他相邻字符是否相等(相等的都反转还是相等,不相等的都反转还是不相等),所以每对相邻字符其实是互相独立的,我们只需要分别计算这些不相等的相邻字符的最小成本,即 $\\\\min(i,n-i)$,累加即为答案。
\\n\\nclass Solution:\\n def minimumCost(self, s: str) -> int:\\n n = len(s)\\n ans = 0\\n for i in range(1, n):\\n if s[i - 1] != s[i]:\\n ans += min(i, n - i)\\n return ans\\n
\\nclass Solution:\\n def minimumCost(self, s: str) -> int:\\n return sum(min(i, len(s) - i) for i, (x, y) in enumerate(pairwise(s), 1) if x != y)\\n
\\nclass Solution {\\n public long minimumCost(String S) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n long ans = 0;\\n for (int i = 1; i < n; i++) {\\n if (s[i - 1] != s[i]) {\\n ans += Math.min(i, n - i);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long minimumCost(string s) {\\n int n = s.size();\\n long long ans = 0;\\n for (int i = 1; i < n; i++) {\\n if (s[i - 1] != s[i]) {\\n ans += min(i, n - i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nlong long minimumCost(char* s) {\\n int n = strlen(s);\\n long long ans = 0;\\n for (int i = 1; i < n; i++) {\\n if (s[i - 1] != s[i]) {\\n ans += MIN(i, n - i);\\n }\\n }\\n return ans;\\n}\\n
\\nfunc minimumCost(s string) (ans int64) {\\n n := len(s)\\n for i := 1; i < n; i++ {\\n if s[i-1] != s[i] {\\n ans += int64(min(i, n-i))\\n }\\n }\\n return\\n}\\n
\\nvar minimumCost = function(s) {\\n const n = s.length;\\n let ans = 0;\\n for (let i = 1; i < n; i++) {\\n if (s[i - 1] !== s[i]) {\\n ans += Math.min(i, n - i);\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn minimum_cost(s: String) -> i64 {\\n let s = s.as_bytes();\\n let n = s.len();\\n let mut ans = 0;\\n for i in 1..n {\\n if s[i - 1] != s[i] {\\n ans += i.min(n - i) as i64;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面贪心与思维题单中的「§5.2 脑筋急转弯」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 【本题相关】贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"如果 $s[i-1]\\\\ne s[i]$,那么必须反转,不然这两个字符无法相等。 要么反转前缀 $s[0]$ 到 $s[i-1]$,成本为 $i$。\\n要么反转后缀 $s[i]$ 到 $s[n-1]$,成本为 $n-i$。\\n\\n反转后 $s[i-1] = s[i]$。\\n\\n注意到,反转操作只会改变 $s[i-1]$ 与 $s[i]$ 是否相等,不会改变其他相邻字符是否相等(相等的都反转还是相等,不相等的都反转还是不相等),所以每对相邻字符其实是互相独立的,我们只需要分别计算这些不相等的相邻字符的最小成本,即 $\\\\min(i,n-i)$,累加即为答案。\\n\\nclass…","guid":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/yi-ci-bian-li-jian-ji-xie-fa-pythonjavac-aut0","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T04:25:40.104Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划","url":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/dong-tai-gui-hua-by-xhchen-2-eq5j","content":"定义$suf_{i,j}$为将后缀s[i,n-1]通过第二种操作转化全为j的最小代价,有状态转移方程:
\\n$suf_{i,0}=\\\\left{\\\\begin{matrix} suf_{i+1,1} + n-i & \\\\text{ if } s_i=\'1\' \\\\ suf_{i+1,0} & \\\\text{ if } s_i=\'0\' \\\\end{matrix}\\\\right.$
\\n$suf_{i,1}=\\\\left{\\\\begin{matrix} suf_{i+1,0} + n-i & \\\\text{ if } s_i=\'0\' \\\\ suf_{i+1,1} & \\\\text{ if } s_i=\'1\' \\\\end{matrix}\\\\right.$
\\n类似地,定义$pre_{i,j}$为将前缀s[0,i]通过第一种操作转化全为j的最小代价,有状态转移方程:
\\n$pre_{i,0}=\\\\left{\\\\begin{matrix} pre_{i-1,1} + i+1 & \\\\text{ if } s_i=\'1\' \\\\ pre_{i-1,0} & \\\\text{ if } s_i=\'0\' \\\\end{matrix}\\\\right.$
\\n$pre_{i,1}=\\\\left{\\\\begin{matrix} pre_{i-1,0} + i+1 & \\\\text{ if } s_i=\'0\' \\\\ pre_{i+1,1} & \\\\text{ if } s_i=\'1\' \\\\end{matrix}\\\\right.$
\\n枚举前两种操作的划分位置即可
\\n###cpp
\\n\\n","description":"定义$suf_{i,j}$为将后缀s[i,n-1]通过第二种操作转化全为j的最小代价,有状态转移方程: $suf_{i,0}=\\\\left{\\\\begin{matrix} suf_{i+1,1} + n-i & \\\\text{ if } s_i=\'1\' \\\\ suf_{i+1,0} & \\\\text{ if } s_i=\'0\' \\\\end{matrix}\\\\right.$\\n\\n$suf_{i,1}=\\\\left{\\\\begin{matrix} suf_{i+1,0} + n-i & \\\\text{ if } s_i=\'0\' \\\\ suf_{i+1,1} & \\\\text{ if }…","guid":"https://leetcode.cn/problems/minimum-cost-to-make-all-characters-equal//solution/dong-tai-gui-hua-by-xhchen-2-eq5j","author":"xhchen2023","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T04:13:59.736Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"py记忆化,轻松实现O(mn)","url":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/pyji-yi-hua-qing-song-shi-xian-omn-by-fr-da4h","content":"class Solution {\\npublic:\\n long long minimumCost(string s) {\\n int n = s.size();\\n long long pre[n][2], suf[n][2];\\n suf[n - 1][0] = s[n - 1] == \'0\' ? 0 : 1;\\n suf[n - 1][1] = s[n - 1] == \'1\' ? 0 : 1;\\n for (int i = n - 2; i >= 0; i--) {\\n suf[i][0] = s[i] == \'1\' ? suf[i + 1][1] + n - i : suf[i + 1][0];\\n suf[i][1] = s[i] == \'0\' ? suf[i + 1][0] + n - i : suf[i + 1][1];\\n }\\n pre[0][0] = s[0] == \'0\' ? 0 : 1;\\n pre[0][1] = s[0] == \'1\' ? 0 : 1;\\n for (int i = 1; i < n; i++) {\\n pre[i][0] = s[i] == \'1\' ? pre[i - 1][1] + i + 1 : pre[i - 1][0];\\n pre[i][1] = s[i] == \'0\' ? pre[i - 1][0] + i + 1 : pre[i - 1][1];\\n }\\n long long res = min({pre[n - 1][0], pre[n - 1][1], suf[0][0], suf[0][1]});\\n for (int i = 0; i < n - 1; i++) {//s[0,i]上用第一种操作,s[i+1,n-1]上用第二种操作\\n res = min(res, pre[i][0] + suf[i + 1][0]);\\n res = min(res, pre[i][1] + suf[i + 1][1]);\\n }\\n return res;\\n }\\n};\\n
\\n","description":"class Solution: def differenceOfDistinctValues(self, G: List[List[int]]) -> List[List[int]]:\\n m, n = len(G), len(G[0])\\n @cache\\n def F(i: int, j: int, d: int) -> int: return F(i + d, j + d, d) | {G[i + d][j + d]} if 0 <= i + d < m and 0 <= j + d < n else…","guid":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/pyji-yi-hua-qing-song-shi-xian-omn-by-fr-da4h","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T04:10:01.771Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前缀和 & 模拟","url":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/qian-zhui-he-mo-ni-by-tsreaper-5pn5","content":"class Solution:\\n def differenceOfDistinctValues(self, G: List[List[int]]) -> List[List[int]]:\\n m, n = len(G), len(G[0])\\n @cache\\n def F(i: int, j: int, d: int) -> int: return F(i + d, j + d, d) | {G[i + d][j + d]} if 0 <= i + d < m and 0 <= j + d < n else set()\\n return [[abs(len(F(i, j, -1)) - len(F(i, j, 1))) for j in range(n)] for i in range(m)]\\n
解法:前缀和 & 模拟
\\n计算
\\nf[t][idx]
表示满足行号 - 列号 == t
的主对角线上,前idx - 1
个数中有几个不同的数。同样计算g[t][idx]
表示满足行号 - 列号 == t
的主对角线上,从第idx + 1
个数开始有几个不同的数。坐标
\\n(i, j)
是t == i - j
的主对角线上第min(i, j)
个格子(i
和j
都从 $1$ 开始),因此这一格的答案就是abs(f[i - j][min(i, j)] - g[i - j][min(i, j)])
。复杂度 $\\\\mathcal{O}(nm)$。
\\n当然,由于本题数据范围很小,因此竞赛过程中可以选择暴力计算前缀和,复杂度为 $\\\\mathcal{O}(nm\\\\min(n, m))$ 即 $\\\\mathcal{O}(nm\\\\sqrt{nm})$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:前缀和 & 模拟 计算 f[t][idx] 表示满足 行号 - 列号 == t 的主对角线上,前 idx - 1 个数中有几个不同的数。同样计算 g[t][idx] 表示满足 行号 - 列号 == t 的主对角线上,从第 idx + 1 个数开始有几个不同的数。\\n\\n坐标 (i, j) 是 t == i - j 的主对角线上第 min(i, j) 个格子(i 和 j 都从 $1$ 开始),因此这一格的答案就是 abs(f[i - j][min(i, j)] - g[i - j][min(i, j)])。\\n\\n复杂度 $\\\\mathcal{O}(nm)$。\\n\\n当…","guid":"https://leetcode.cn/problems/difference-of-number-of-distinct-values-on-diagonals//solution/qian-zhui-he-mo-ni-by-tsreaper-5pn5","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-28T04:08:11.336Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2680. 最大或值 位运算+贪心+前缀和(C++)","url":"https://leetcode.cn/problems/maximum-or//solution/6369-zui-da-huo-zhi-wei-yun-suan-tan-xin-2bil","content":"class Solution {\\npublic:\\n vector<vector<int>> differenceOfDistinctValues(vector<vector<int>>& grid) {\\n int n = grid.size(), m = grid[0].size();\\n\\n int f[n + m + 1][min(n, m) + 1];\\n // 计算以第一列的格子为开头的主对角线的前缀和\\n for (int i = 0; i < n; i++) {\\n // 因为 c++ 不能处理负数下标,我们把 t 加上 m 让它变成非负数\\n int t = i - 0 + m;\\n unordered_set<int> st;\\n for (int ii = i, jj = 0; ii < n && jj < m; ii++, jj++) {\\n f[t][min(ii, jj)] = st.size();\\n st.insert(grid[ii][jj]);\\n }\\n }\\n // 计算以第一行的格子为开头的主对角线的前缀和\\n for (int j = 1; j < m; j++) {\\n int t = 0 - j + m;\\n unordered_set<int> st;\\n for (int ii = 0, jj = j; ii < n && jj < m; ii++, jj++) {\\n f[t][min(ii, jj)] = st.size();\\n st.insert(grid[ii][jj]);\\n }\\n }\\n\\n int g[n + m + 1][min(n, m) + 1];\\n // 计算以最后一列的格子为结尾的主对角线的后缀和\\n for (int i = 0; i < n; i++) {\\n int t = i - (m - 1) + m;\\n unordered_set<int> st;\\n for (int ii = i, jj = m - 1; ii >= 0 && jj >= 0; ii--, jj--) {\\n g[t][min(ii, jj)] = st.size();\\n st.insert(grid[ii][jj]);\\n }\\n }\\n // 计算以最后一行的格子为结尾的主对角线的后缀和\\n for (int j = 0; j < m - 1; j++) {\\n int t = (n - 1) - j + m;\\n unordered_set<int> st;\\n for (int ii = n - 1, jj = j; ii >= 0 && jj >= 0; ii--, jj--) {\\n g[t][min(ii, jj)] = st.size();\\n st.insert(grid[ii][jj]);\\n }\\n }\\n\\n // 计算答案\\n vector<vector<int>> ans(n);\\n for (int i = 0; i < n; i++) for (int j = 0; j < m; j++) {\\n int t = i - j + m;\\n ans[i].push_back(abs(f[t][min(i, j)] - g[t][min(i, j)]));\\n }\\n return ans;\\n }\\n};\\n
思路
\\n首先分析一下可以发现一个贪心的性质:k次操作一定是在同一个数上的。
\\n每一次操作的乘2实际上就是将一个数左移一位,而最终的结果是对nums中每个数按位或的结果,所以最终的结果一定是位数越多越好。因此每次操作如果不是在一个数上的话,将分散在不同数上的操作集中某个数上一定比原先的位数更大。
\\n在这个性质的基础上,一个简单的解法就是遍历数组,计算每个数操作k次后的结果,取其最大值。但是直接计算的时间复杂度是O(n^2),超出了时间限制。有没有办法可以用O(1)时间复杂度获得每个数操作k次之后的结果呢?这里可以使用一种类似于前缀和的方法:对于原先的数组分别计算一遍从前往后和从后往前的按位或结果,用f和b表示。其中
\\nf[i] = nums[0] | nums[1] | ... | nums[i]
\\nb[i] = nums[i] | nums[i + 1] | ... nums[n - 1]
\\n这样在对于nums[i]操作k次的时候,只需要计算 f[i - 1] | nums[i] << k | b[i + 1]即可得到结果,最后取最大值即可。
\\n\\nclass Solution {\\npublic:\\n long long maximumOr(vector<int>& nums, int k) {\\n int n = nums.size();\\n long long ans = 0, tmp = 0;\\n vector<long long> f(n), b(n);\\n f[0] = nums[0];\\n b[n - 1] = nums[n - 1];\\n for (int i = 1; i < n; i++) {\\n f[i] = f[i - 1] | nums[i];\\n b[n - i - 1] = b[n - i] | nums[n - i - 1];\\n }\\n for (int i = 0; i < n; i++) {\\n if (i == 0 && i == n - 1)\\n tmp = 0;\\n else if (i == 0)\\n tmp = b[1];\\n else if (i == n - 1)\\n tmp = f[n - 2];\\n else\\n tmp = f[i - 1] | b[i + 1];\\n tmp |= (((long long)nums[i]) << k);\\n ans = max(tmp, ans);\\n }\\n return ans;\\n }\\n};\\n
复杂度分析
\\n\\n
\\n","description":"思路 首先分析一下可以发现一个贪心的性质:k次操作一定是在同一个数上的。\\n\\n每一次操作的乘2实际上就是将一个数左移一位,而最终的结果是对nums中每个数按位或的结果,所以最终的结果一定是位数越多越好。因此每次操作如果不是在一个数上的话,将分散在不同数上的操作集中某个数上一定比原先的位数更大。\\n\\n在这个性质的基础上,一个简单的解法就是遍历数组,计算每个数操作k次后的结果,取其最大值。但是直接计算的时间复杂度是O(n^2),超出了时间限制。有没有办法可以用O(1)时间复杂度获得每个数操作k次之后的结果呢?这里可以使用一种类似于前缀和的方法…","guid":"https://leetcode.cn/problems/maximum-or//solution/6369-zui-da-huo-zhi-wei-yun-suan-tan-xin-2bil","author":"BlankPower","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-13T16:16:49.183Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心+前后缀分解(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-or//solution/tan-xin-qian-hou-zhui-fen-jie-pythonjava-wrv1","content":"- 时间复杂度:O(n),n为nums长度
\\n- 空间复杂度:O(n)
\\n方法一:前后缀分解
\\n提示 1
\\n要让答案最大,首先应当最大化答案的二进制长度。
\\n提示 2
\\n把「乘 $2$」分配给多个数(雨露均沾),不如只分配给一个数,这样能得到更长(更大)的答案。
\\n提示 3
\\n枚举把 $\\\\textit{nums}[i]$ 乘 $k$ 次 $2$(左移 $k$ 次)。修改后,如何计算所有元素的或值?
\\n利用 238. 除自身以外数组的乘积 的 做法,预处理每个 $\\\\textit{nums}[i]$ 左侧元素的或值 $\\\\textit{pre}$,以及右侧元素的或值 $\\\\textit{suf}$,从而 $\\\\mathcal{O}(1)$ 得到把 $\\\\textit{nums}[i]$ 乘 $k$ 次 $2$ 后,所有元素的或值。
\\n代码实现时,只需预处理右侧元素的或值(后缀或),左侧元素的或值(前缀或)可以一边遍历 $\\\\textit{nums}$ 一边计算。
\\n\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n # suf[i] 表示 nums[i+1:] 的 OR\\n suf = [0] * n\\n for i in range(n - 2, -1, -1):\\n suf[i] = suf[i + 1] | nums[i + 1]\\n\\n # pre 表示 nums[:i] 的 OR\\n ans = pre = 0\\n for x, suf_or in zip(nums, suf):\\n ans = max(ans, pre | (x << k) | suf_or)\\n pre |= x\\n return ans\\n
\\nclass Solution {\\n public long maximumOr(int[] nums, int k) {\\n int n = nums.length;\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n int[] suf = new int[n];\\n for (int i = n - 2; i >= 0; i--) {\\n suf[i] = suf[i + 1] | nums[i + 1];\\n }\\n\\n long ans = 0;\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n int pre = 0;\\n for (int i = 0; i < n; i++) {\\n ans = Math.max(ans, pre | ((long) nums[i] << k) | suf[i]);\\n pre |= nums[i];\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumOr(vector<int>& nums, int k) {\\n int n = nums.size();\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n vector<int> suf(n);\\n for (int i = n - 2; i >= 0; i--) {\\n suf[i] = suf[i + 1] | nums[i + 1];\\n }\\n\\n long long ans = 0;\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n int pre = 0;\\n for (int i = 0; i < n; i++) {\\n ans = max(ans, pre | ((long long) nums[i] << k) | suf[i]);\\n pre |= nums[i];\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long maximumOr(int* nums, int numsSize, int k) {\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n int* suf = malloc(numsSize * sizeof(int));\\n suf[numsSize - 1] = 0;\\n for (int i = numsSize - 2; i >= 0; i--) {\\n suf[i] = suf[i + 1] | nums[i + 1];\\n }\\n\\n long long ans = 0;\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n int pre = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n ans = MAX(ans, pre | ((long long) x << k) | suf[i]);\\n pre |= x;\\n }\\n\\n free(suf);\\n return ans;\\n}\\n
\\nfunc maximumOr(nums []int, k int) int64 {\\n n := len(nums)\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n suf := make([]int, n)\\n for i := n - 2; i >= 0; i-- {\\n suf[i] = suf[i+1] | nums[i+1]\\n }\\n\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n ans, pre := 0, 0\\n for i, x := range nums {\\n ans = max(ans, pre|x<<k|suf[i])\\n pre |= x\\n }\\n return int64(ans)\\n}\\n
\\nvar maximumOr = function(nums, k) {\\n const n = nums.length;\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n const suf = Array(n);\\n suf[n - 1] = 0;\\n for (let i = n - 2; i >= 0; i--) {\\n suf[i] = suf[i + 1] | nums[i + 1];\\n }\\n\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n let ans = 0n, pre = 0n;\\n for (let i = 0; i < n; i++) {\\n const x = BigInt(nums[i]);\\n const res = pre | (x << BigInt(k)) | BigInt(suf[i]);\\n ans = res > ans ? res : ans;\\n pre |= x;\\n }\\n return Number(ans);\\n};\\n
\\nimpl Solution {\\n pub fn maximum_or(nums: Vec<i32>, k: i32) -> i64 {\\n let n = nums.len();\\n // suf[i] 表示 nums[i+1] 到 nums[n-1] 的 OR\\n let mut suf = vec![0; n];\\n for i in (0..n - 1).rev() {\\n suf[i] = suf[i + 1] | nums[i + 1];\\n }\\n\\n let mut ans = 0;\\n // pre 表示 nums[0] 到 nums[i-1] 的 OR\\n let mut pre = 0;\\n for (x, suf_or) in nums.into_iter().zip(suf) {\\n ans = ans.max(pre | ((x as i64) << k) | suf_or as i64);\\n pre |= x as i64;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二
\\n设 $\\\\textit{nums}$ 所有数的 OR 为 $\\\\textit{allOr}$。
\\n能否直接算出从 $\\\\textit{allOr}$ 中去掉 $x=\\\\textit{nums}[i]$ 后的结果?把结果与
\\nx << k
计算 OR,就是方法一计算的内容。可以,做法如下:
\\n\\n
\\n- 先通过异或运算,直接去掉 $x$,即
\\nallOr ^ x
。- 如果有多个 $\\\\textit{nums}[i]$ 在同一个比特位上都是 $1$,那么去掉 $x$ 后的其余 $n-1$ 个数的 OR,在这个比特位上也是 $1$。换句话说,无论去掉哪个 $x$,这些比特位恒为 $1$。用二进制数 $\\\\textit{fixed}$ 记录这些恒为 $1$ 的比特位。
\\n- 于是,去掉 $x$ 后的其余 $n-1$ 个数的 OR 等于
\\n(allOr ^ x) | fixed
,表示先直接去掉 $\\\\textit{allOr}$ 中的 $x$,再通过 $\\\\textit{fixed}$ 修正。如何计算 $\\\\textit{fixed}$?
\\n用「枚举右,维护左」的思想,在遍历 $\\\\textit{nums}$ 的过程中,
\\nallOr & x
中的 $1$ 必然出现了两次,将其 OR 到 $\\\\textit{fixed}$ 中。最后,遍历 $\\\\textit{nums}$,设 $x=\\\\textit{nums}[i]$,首先计算
\\n(allOr ^ x) | fixed
,然后把结果与x << k
计算 OR,更新答案的最大值。\\nclass Solution:\\n def maximumOr(self, nums: List[int], k: int) -> int:\\n all_or = fixed = 0\\n for x in nums:\\n # 如果在计算 all_or |= x 之前,all_or 和 x 有公共的 1\\n # 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= all_or & x # 把公共的 1 记录到 fixed 中\\n all_or |= x # 所有数的 OR\\n return max((all_or ^ x) | fixed | (x << k) for x in nums)\\n
\\nclass Solution {\\n public long maximumOr(int[] nums, int k) {\\n int allOr = 0;\\n int fixed = 0;\\n for (int x : nums) {\\n // 如果在计算 allOr |= x 之前,allOr 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= allOr & x; // 把公共的 1 记录到 fixed 中\\n allOr |= x; // 所有数的 OR\\n }\\n\\n long ans = 0;\\n for (int x : nums) {\\n ans = Math.max(ans, (allOr ^ x) | fixed | ((long) x << k));\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumOr(vector<int>& nums, int k) {\\n int all_or = 0, fixed = 0;\\n for (int x : nums) {\\n // 如果在计算 all_or |= x 之前,all_or 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= all_or & x; // 把公共的 1 记录到 fixed 中\\n all_or |= x; // 所有数的 OR\\n }\\n\\n long long ans = 0;\\n for (int x : nums) {\\n ans = max(ans, (all_or ^ x) | fixed | ((long long) x << k));\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long maximumOr(int* nums, int numsSize, int k) {\\n int all_or = 0, fixed = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n // 如果在计算 all_or |= x 之前,all_or 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= all_or & x; // 把公共的 1 记录到 fixed 中\\n all_or |= x; // 所有数的 OR\\n }\\n\\n long long ans = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n ans = MAX(ans, (all_or ^ x) | fixed | ((long long) x << k));\\n }\\n return ans;\\n}\\n
\\nfunc maximumOr(nums []int, k int) int64 {\\n allOr, fixed := 0, 0\\n for _, x := range nums {\\n // 如果在计算 allOr |= x 之前,allOr 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= allOr & x // 把公共的 1 记录到 fixed 中\\n allOr |= x // 所有数的 OR\\n }\\n\\n ans := 0\\n for _, x := range nums {\\n ans = max(ans, (allOr^x)|fixed|x<<k)\\n }\\n return int64(ans)\\n}\\n
\\nvar maximumOr = function(nums, k) {\\n let allOr = 0, fixed = 0;\\n for (const x of nums) {\\n // 如果在计算 allOr |= x 之前,allOr 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= allOr & x; // 把公共的 1 记录到 fixed 中\\n allOr |= x; // 所有数的 OR\\n }\\n\\n let ans = 0n;\\n for (const x of nums) {\\n const res = (BigInt(allOr ^ x) | BigInt(fixed) | (BigInt(x) << BigInt(k)));\\n ans = res > ans ? res : ans;\\n }\\n return Number(ans);\\n};\\n
\\nimpl Solution {\\n pub fn maximum_or(nums: Vec<i32>, k: i32) -> i64 {\\n let mut all_or = 0;\\n let mut fixed = 0;\\n for &x in &nums {\\n // 如果在计算 all_or |= x 之前,all_or 和 x 有公共的 1\\n // 那就意味着有多个 nums[i] 在这些比特位上都是 1\\n fixed |= all_or & x; // 把公共的 1 记录到 fixed 中\\n all_or |= x; // 所有数的 OR\\n }\\n nums.into_iter()\\n .map(|x| (all_or ^ x) as i64 | fixed as i64 | ((x as i64) << k))\\n .max()\\n .unwrap()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面动态规划题单中的「专题:前后缀分解」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:前后缀分解 提示 1\\n\\n要让答案最大,首先应当最大化答案的二进制长度。\\n\\n提示 2\\n\\n把「乘 $2$」分配给多个数(雨露均沾),不如只分配给一个数,这样能得到更长(更大)的答案。\\n\\n提示 3\\n\\n枚举把 $\\\\textit{nums}[i]$ 乘 $k$ 次 $2$(左移 $k$ 次)。修改后,如何计算所有元素的或值?\\n\\n利用 238. 除自身以外数组的乘积 的 做法,预处理每个 $\\\\textit{nums}[i]$ 左侧元素的或值 $\\\\textit{pre}$,以及右侧元素的或值 $\\\\textit{suf}$,从而 $\\\\mathcal{O}(1)$ 得到把 $…","guid":"https://leetcode.cn/problems/maximum-or//solution/tan-xin-qian-hou-zhui-fen-jie-pythonjava-wrv1","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-05-13T16:12:35.046Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【栈可能超时】使用双指针进行原地操作即可","url":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/zhan-ke-neng-chao-shi-shi-yong-shuang-zh-30w8","content":"\\n
\\n- 维护区间[0, l)为需要返回的数组
\\n- 如果s[i]不是‘*’,则把s[i]放在s[l], 同时l++
\\n- 如果s[i]是‘*’, 则l回退(模拟出栈)
\\n\\n","description":"维护区间[0, l)为需要返回的数组 如果s[i]不是‘*’,则把s[i]放在s[l], 同时l++\\n如果s[i]是‘*’, 则l回退(模拟出栈)\\nclass Solution {\\npublic:\\n string removeStars(string s) {\\n int n = s.size();\\n int l = 0;\\n for(int i = 0; i < n; i++){\\n if(s[i] != \'*\'){\\n swap(s[i], s[l++])…","guid":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/zhan-ke-neng-chao-shi-shi-yong-shuang-zh-30w8","author":"wwqn","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-28T08:41:52.150Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"java 一次遍历 注释详细","url":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/java-yi-ci-bian-li-zhu-shi-xiang-xi-by-h-pvhx","content":"class Solution {\\npublic:\\n string removeStars(string s) {\\n int n = s.size();\\n int l = 0;\\n for(int i = 0; i < n; i++){\\n if(s[i] != \'*\'){\\n swap(s[i], s[l++]);\\n }else{\\n l--;\\n }\\n }\\n return s.substr(0, l);\\n }\\n};\\n
\\n\\nProblem: 2414. 最长的字母序连续子字符串的长度
\\n[TOC]
\\n思路
\\n遍历字符串,判断下一个字符和当前字符是否连续,连续计数加1,不连续就重置计算为1.
\\n解题方法
\\n1.初始化变量。
\\n\\nint max=1;//最大连续字符数量\\nchar[] chars = s.toCharArray();\\nint num=1;//连续字符出现数量 \\n
2.遍历字符串,判断下一个字符和当前字符是否连续,连续计数加1,不连续就重置计算为1.
\\n\\nfor (int i = 0; i < chars.length-1; i++) {\\n if (chars[i]+1!=chars[i+1]){// 不连续 重置数量\\n num=1;\\n }else { //字符连续 计数加1\\n num++;\\n //计算最大值\\n max=max>num?max:num;\\n }\\n}\\n
复杂度
\\n\\n
\\n- \\n
\\n时间复杂度: $O(n)$
\\n- \\n
\\n空间复杂度: $O(n)$
\\nCode
\\n###Java
\\n\\nclass Solution {\\n public int longestContinuousSubstring(String s) {\\n int max=1;//最大连续字符数量\\n char[] chars = s.toCharArray();\\n int num=1;//连续字符出现数量\\n // 遍历到n-1个字符\\n for (int i = 0; i < chars.length-1; i++) {\\n if (chars[i]+1!=chars[i+1]){// 不连续 重置数量\\n num=1;\\n }else { //字符连续 计数加1\\n num++;\\n //计算最大值\\n max=max>num?max:num;\\n }\\n }\\n return max;\\n }\\n}\\n
执行效果
\\n\\n","description":"Problem: 2414. 最长的字母序连续子字符串的长度 [TOC]\\n\\n遍历字符串,判断下一个字符和当前字符是否连续,连续计数加1,不连续就重置计算为1.\\n\\n1.初始化变量。\\n\\nint max=1;//最大连续字符数量\\nchar[] chars = s.toCharArray();\\nint num=1;//连续字符出现数量 \\n\\n\\n2.遍历字符串,判断下一个字符和当前字符是否连续,连续计数加1,不连续就重置计算为1.\\n\\nfor (int i = 0; i < chars.length-1; i++) {\\n if (chars[i]+1…","guid":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/java-yi-ci-bian-li-zhu-shi-xiang-xi-by-h-pvhx","author":"huang-ji-hua","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-21T01:40:59.568Z","media":[{"url":"https://pic.leetcode.cn/1682040911-wtlNlA-image.png","type":"photo","width":611,"height":107}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"单零单百到双百(你的代码真的无敌了!)","url":"https://leetcode.cn/problems/row-with-maximum-ones//solution/dan-ling-dan-bai-dao-shuang-bai-ni-de-da-ew7f","content":"
\\n
{:width=500}
\\n{:width=500}
解题思路
\\n遍历每一行,统计该行中1的个数,如果当前行中1的个数大于之前的最大值,就更新最大值和对应的行号。最终返回最大值和行号即可。
\\n代码实现
\\n\\n","description":"{:width=500} {:width=500}\\n\\n遍历每一行,统计该行中1的个数,如果当前行中1的个数大于之前的最大值,就更新最大值和对应的行号。最终返回最大值和行号即可。\\n\\nclass Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<int> rowAndMaximumOnes(vector<vector<int>>& mat) {\\n int ans = 0, count = 0, mark = 0;\\n for (int i = 0; i < mat.size(); ++i) {\\n int count = 0;\\n for (int j = 0; j < mat[i].size(); ++j) if (mat[i][j] == 1) ++count;\\n if (ans < count) ans = count, mark = i;\\n }\\n return vector<int>{mark, ans};\\n }\\n};\\n
rowAndMaximumOnes(vector >& mat) {\\n int ans = 0, count = 0, mark = 0;\\n for (int i = 0; i < mat.size(); ++i…","guid":"https://leetcode.cn/problems/row-with-maximum-ones//solution/dan-ling-dan-bai-dao-shuang-bai-ni-de-da-ew7f","author":"kind-moore2dy","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-17T12:52:40.267Z","media":[{"url":"https://pic.leetcode.cn/1681735247-MmSZIN-image.png","type":"photo","width":613,"height":322,"blurhash":"LN3vXZkCazj]m~axj?jYkua}affR"},{"url":"https://pic.leetcode.cn/1681735292-JDdtRC-image.png","type":"photo","width":615,"height":333,"blurhash":"LK42[6kCa#ofm~f5f*j?ktazWCj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/row-with-maximum-ones//solution/bian-li-mei-yi-xing-by-endlesscheng-ltx3","content":" 遍历每一行,计算行的元素和 $s$。如果 $s$ 比最大和 $\\\\textit{maxSum}$ 大,更新 $\\\\textit{maxSum}=s$,顺带记录行号 $\\\\textit{rowIdx}$。
\\n由于我们是从上到下遍历行,所以找到的最大和的行号,一定是相同最大和的行号中最小的。
\\n\\nclass Solution:\\n def rowAndMaximumOnes(self, mat: List[List[int]]) -> List[int]:\\n row_idx = max_sum = -1\\n for i, row in enumerate(mat):\\n s = sum(row)\\n if s > max_sum:\\n row_idx, max_sum = i, s\\n return [row_idx, max_sum]\\n
\\nclass Solution {\\n public int[] rowAndMaximumOnes(int[][] mat) {\\n int rowIdx = -1;\\n int maxSum = -1;\\n for (int i = 0; i < mat.length; i++) {\\n int s = Arrays.stream(mat[i]).sum();\\n if (s > maxSum) {\\n rowIdx = i;\\n maxSum = s;\\n }\\n }\\n return new int[]{rowIdx, maxSum};\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> rowAndMaximumOnes(vector<vector<int>>& mat) {\\n int row_idx = -1, max_sum = -1;\\n for (int i = 0; i < mat.size(); i++) {\\n int s = reduce(mat[i].begin(), mat[i].end());\\n if (s > max_sum) {\\n row_idx = i;\\n max_sum = s;\\n }\\n }\\n return {row_idx, max_sum};\\n }\\n};\\n
\\nint* rowAndMaximumOnes(int** mat, int matSize, int* matColSize, int* returnSize) {\\n int row_idx = -1, max_sum = -1;\\n for (int i = 0; i < matSize; i++) {\\n int s = 0;\\n for (int j = 0; j < matColSize[i]; j++) {\\n s += mat[i][j];\\n }\\n if (s > max_sum) {\\n row_idx = i;\\n max_sum = s;\\n }\\n }\\n\\n *returnSize = 2;\\n int* ans = malloc(2 * sizeof(int));\\n ans[0] = row_idx;\\n ans[1] = max_sum;\\n return ans;\\n}\\n
\\nfunc rowAndMaximumOnes(mat [][]int) []int {\\n rowIdx, maxSum := -1, -1\\n for i, row := range mat {\\n s := 0\\n for _, x := range row {\\n s += x\\n }\\n if s > maxSum {\\n rowIdx, maxSum = i, s\\n }\\n }\\n return []int{rowIdx, maxSum}\\n}\\n
\\nvar rowAndMaximumOnes = function(mat) {\\n let rowIdx = -1, maxSum = -1;\\n for (let i = 0; i < mat.length; i++) {\\n const s = _.sum(mat[i]);\\n if (s > maxSum) {\\n rowIdx = i;\\n maxSum = s;\\n }\\n }\\n return [rowIdx, maxSum];\\n};\\n
\\nimpl Solution {\\n pub fn row_and_maximum_ones(mat: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut row_idx = 0;\\n let mut max_sum = -1;\\n for (i, row) in mat.into_iter().enumerate() {\\n let s = row.into_iter().sum();\\n if s > max_sum {\\n row_idx = i;\\n max_sum = s;\\n }\\n }\\n vec![row_idx as _, max_sum]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(mn)$,其中 $m$ 和 $n$ 分别为 $\\\\textit{mat}$ 的行数和列数。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。仅用到若干额外变量。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"遍历每一行,计算行的元素和 $s$。如果 $s$ 比最大和 $\\\\textit{maxSum}$ 大,更新 $\\\\textit{maxSum}=s$,顺带记录行号 $\\\\textit{rowIdx}$。 由于我们是从上到下遍历行,所以找到的最大和的行号,一定是相同最大和的行号中最小的。\\n\\nclass Solution:\\n def rowAndMaximumOnes(self, mat: List[List[int]]) -> List[int]:\\n row_idx = max_sum = -1\\n for i, row in…","guid":"https://leetcode.cn/problems/row-with-maximum-ones//solution/bian-li-mei-yi-xing-by-endlesscheng-ltx3","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-16T04:11:20.733Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"直接遍历","url":"https://leetcode.cn/problems/prime-in-diagonal//solution/zhi-jie-bian-li-by-da-jiang-shi-ai-ban-2bdr","content":"=注意:1不是质数,2是质数=
\\n
\\n思路很简单,自然就是遍历对角线元素,判断是否为质数,然后去最大即可
\\n任何一个自然数,总可以表示成以下六种形式之一:6n,6n+1,6n+2,6n+3,6n+4,6n+5(n=0,1,2...)我们可以发现,除了2和3,只有形如6n+1和6n+5的数有可能是质数。且形如6n+1和6n+5的数如果不是质数,它们的因数也会含有形如6n+1或者6n+5的数
\\n执行用时: 0 ms
\\n内存消耗: 50.4 MB###java
\\n\\n","description":"=注意:1不是质数,2是质数= 思路很简单,自然就是遍历对角线元素,判断是否为质数,然后去最大即可\\n 任何一个自然数,总可以表示成以下六种形式之一:6n,6n+1,6n+2,6n+3,6n+4,6n+5(n=0,1,2...)我们可以发现,除了2和3,只有形如6n+1和6n+5的数有可能是质数。且形如6n+1和6n+5的数如果不是质数,它们的因数也会含有形如6n+1或者6n+5的数\\n 执行用时: 0 ms\\n 内存消耗: 50.4 MB\\n\\n###java\\n\\npublic boolean isP(int n) {\\n if (n <= 1) return…","guid":"https://leetcode.cn/problems/prime-in-diagonal//solution/zhi-jie-bian-li-by-da-jiang-shi-ai-ban-2bdr","author":"da-jiang-shi-ai-ban","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-09T06:02:42.052Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"判断质数(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/prime-in-diagonal//solution/pan-duan-zhi-shu-by-endlesscheng-m6nt","content":"public boolean isP(int n) {\\n if (n <= 1) return false;\\n if (n == 2 || n == 3) return true;\\n if (n % 6 != 1 && n % 6 != 5)\\n return false;\\n for (int i = 5; i * i <= n ; i+=6)\\n if (n % i == 0 || (n % (i + 2)) == 0)\\n return false;\\n return true;\\n}\\npublic static int diagonalPrime(int[][] nums) {\\n int max = 0;\\n for (int i = 0; i < nums.length; i++) {\\n if (nums[i][i] > max && isP(nums[i][i])) {\\n max = nums[i][i];\\n }\\n if (nums[i][n - i - 1] > max && isP(nums[i][n - i - 1])) {\\n max = nums[i][n - i - 1];\\n }\\n } \\n return max;\\n}\\n
思路
\\n遍历两条对角线上的元素,如果是质数则更新答案的最大值。
\\n注意 $1$ 不是质数。
\\n如何判断质数
\\n对于整数 $n$,暴力做法是枚举 $[2,n-1]$ 中的整数 $i$,判断 $n\\\\bmod i=0$ 是否成立,如果成立则说明 $i$ 是 $n$ 的一个因子,$n$ 不是质数。
\\n但其实只需枚举 $[2,\\\\left\\\\lfloor\\\\sqrt{n}\\\\right\\\\rfloor]$ 中的整数 $i$。
\\n证明:反证法。从小到大枚举,假设我们枚举到了一个超过 $\\\\left\\\\lfloor\\\\sqrt{n}\\\\right\\\\rfloor$ 的整数 $j$,且 $j$ 是 $n$ 的因子,那么 $\\\\dfrac{n}{j}$ 也是 $n$ 的因子。由于 $\\\\dfrac{n}{j} < j$,我们会先枚举到 $i=\\\\dfrac{n}{j}$,再枚举到 $i=j$。但由于 $\\\\dfrac{n}{j}$ 是 $n$ 的因子,我们会在 $i=\\\\dfrac{n}{j}$ 时停止枚举,不可能继续枚举到 $i=j$,矛盾,所以原命题成立。
\\n细节
\\n如果元素 $x$ 没有超过答案 $\\\\textit{ans}$,那么无需判断 $x$ 是否为质数,因为它不会让答案变得更大。
\\n\\nclass Solution:\\n def is_prime(self, n: int) -> bool:\\n for i in range(2, isqrt(n) + 1):\\n if n % i == 0:\\n return False\\n return n >= 2 # 1 不是质数\\n\\n def diagonalPrime(self, nums: List[List[int]]) -> int:\\n ans = 0\\n for i, row in enumerate(nums):\\n for x in row[i], row[-1 - i]:\\n if x > ans and self.is_prime(x):\\n ans = x\\n return ans\\n
\\nclass Solution {\\n public int diagonalPrime(int[][] nums) {\\n int n = nums.length;\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n int x = nums[i][i];\\n if (x > ans && isPrime(x)) {\\n ans = x;\\n }\\n x = nums[i][n - 1 - i];\\n if (x > ans && isPrime(x)) {\\n ans = x;\\n }\\n }\\n return ans;\\n }\\n\\n private boolean isPrime(int n) {\\n for (int i = 2; i * i <= n; i++) {\\n if (n % i == 0) {\\n return false;\\n }\\n }\\n return n >= 2; // 1 不是质数\\n }\\n}\\n
\\nclass Solution {\\n bool is_prime(int n) {\\n for (int i = 2; i * i <= n; i++) {\\n if (n % i == 0) {\\n return false;\\n }\\n }\\n return n >= 2; // 1 不是质数\\n }\\n\\npublic:\\n int diagonalPrime(vector<vector<int>>& nums) {\\n int n = nums.size(), ans = 0;\\n for (int i = 0; i < n; i++) {\\n for (int x : {nums[i][i], nums[i][n - 1 - i]}) {\\n if (x > ans && is_prime(x)) {\\n ans = x;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint isPrime(int n) {\\n for (int i = 2; i * i <= n; i++) {\\n if (n % i == 0) {\\n return 0;\\n }\\n }\\n return n >= 2; // 1 不是质数\\n}\\n\\nint diagonalPrime(int** nums, int numsSize, int* numsColSize) {\\n int ans = 0;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i][i];\\n if (x > ans && isPrime(x)) {\\n ans = x;\\n }\\n x = nums[i][numsSize - 1 - i];\\n if (x > ans && isPrime(x)) {\\n ans = x;\\n }\\n }\\n return ans;\\n}\\n
\\nfunc isPrime(n int) bool {\\n for i := 2; i*i <= n; i++ {\\n if n%i == 0 {\\n return false\\n }\\n }\\n return n >= 2 // 1 不是质数\\n}\\n\\nfunc diagonalPrime(nums [][]int) (ans int) {\\n for i, row := range nums {\\n if x := row[i]; x > ans && isPrime(x) {\\n ans = x\\n }\\n if x := row[len(nums)-1-i]; x > ans && isPrime(x) {\\n ans = x\\n }\\n }\\n return\\n}\\n
\\nvar isPrime = function(n) {\\n for (let i = 2; i * i <= n; i++) {\\n if (n % i === 0) {\\n return false;\\n }\\n }\\n return n >= 2; // 1 不是质数\\n};\\n\\nvar diagonalPrime = function(nums) {\\n const n = nums.length;\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n for (const x of [nums[i][i], nums[i][n - 1 - i]]) {\\n if (x > ans && isPrime(x)) {\\n ans = x;\\n }\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n fn is_prime(n: i32) -> bool {\\n for i in 2..=((n as f64).sqrt() as i32) {\\n if n % i == 0 {\\n return false;\\n }\\n }\\n n >= 2 // 1 不是质数\\n }\\n\\n pub fn diagonal_prime(nums: Vec<Vec<i32>>) -> i32 {\\n let n = nums.len();\\n let mut ans = 0;\\n for (i, row) in nums.iter().enumerate() {\\n for x in [row[i], row[n - 1 - i]] {\\n if x > ans && Self::is_prime(x) {\\n ans = x;\\n }\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\sqrt{U})$,其中 $n$ 为 $\\\\textit{nums}$ 的长度,$U$ 为两条对角线上的最大值。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n注:本题 $n$ 很小,$U$ 比较大,直接暴力判断,比筛质数更好。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 【本题相关】数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 遍历两条对角线上的元素,如果是质数则更新答案的最大值。\\n\\n注意 $1$ 不是质数。\\n\\n如何判断质数\\n\\n对于整数 $n$,暴力做法是枚举 $[2,n-1]$ 中的整数 $i$,判断 $n\\\\bmod i=0$ 是否成立,如果成立则说明 $i$ 是 $n$ 的一个因子,$n$ 不是质数。\\n\\n但其实只需枚举 $[2,\\\\left\\\\lfloor\\\\sqrt{n}\\\\right\\\\rfloor]$ 中的整数 $i$。\\n\\n证明:反证法。从小到大枚举,假设我们枚举到了一个超过 $\\\\left\\\\lfloor\\\\sqrt{n}\\\\right\\\\rfloor$ 的整数 $j$,且 $j$ 是…","guid":"https://leetcode.cn/problems/prime-in-diagonal//solution/pan-duan-zhi-shu-by-endlesscheng-m6nt","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-09T04:21:49.013Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"灵神课后作业","url":"https://leetcode.cn/problems/minimum-cost-to-cut-a-stick//solution/ling-shen-ke-hou-zuo-ye-by-lucky-boy-c9-tkvj","content":"\\n\\n","description":"https://www.bilibili.com/video/BV1Gs4y1E7EU/?spm_id_from=333.999.top_right_bar_window_history.content.click&vd_source=217bacbee37820b5bf3ed2f4fb8f6c94 class Solution:\\n def minCost(self, n: int, cuts: List[int]) -> int:\\n cuts = [0] + sorted(cuts) + [n]\\n\\n len…","guid":"https://leetcode.cn/problems/minimum-cost-to-cut-a-stick//solution/ling-shen-ke-hou-zuo-ye-by-lucky-boy-c9-tkvj","author":"lucky-boy-c9","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-05T08:10:06.585Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python 在线bfs 解决边数很多的最短路问题","url":"https://leetcode.cn/problems/minimum-reverse-operations//solution/python-zai-xian-bfs-jie-jue-bian-shu-hen-y58m","content":"class Solution:\\n def minCost(self, n: int, cuts: List[int]) -> int:\\n cuts = [0] + sorted(cuts) + [n]\\n\\n len_ = len(cuts)\\n f = [[0] * len_ for _ in range(len_)] # i 有n个状态, 又有i + 1, 所以大小为n + 1\\n for i in range(len_ - 3, -1, -1):\\n for j in range(i + 2, len_):\\n f[i][j] = min(f[i][k] + f[k][j] + cuts[j] - cuts[i] for k in range(i + 1, j))\\n # res = inf \\n # for k in range(i + 1, j):\\n # cut = cuts[k]\\n # res = min(res, f[i][k] + f[k][j] + cuts[j] - cuts[i])\\n # f[i][j] = res\\n \\n return f[0][-1]\\n\\n # cuts = [0] + sorted(cuts) + [n]\\n # @cache\\n # def dfs(i, j): # (i, j)\\n # if i + 1 >= j:\\n # return 0\\n # res = inf \\n # for k in range(i + 1, j):\\n # res = min(res, dfs(i, k) + dfs(k, j) + cuts[j] - cuts[i])\\n # return res\\n # return dfs(0, len(cuts) - 1)\\n\\n # 不用sort\\n # @cache\\n # def dfs(i, j):\\n # if i + 1 >= j:\\n # return 0\\n # res = inf \\n # for cut in cuts:\\n # if i < cut < j:\\n # res = min(res, dfs(i, cut) + dfs(cut, j) + j - i)\\n # return res if res != inf else 0\\n # return dfs(0, n)\\n
解题思路
\\n分享一下自己对于这道题的思考。
\\n
\\n这道题的一个难点是求反转长度为k的子数组,从位置i可以到达的左右边界闭区间
。假设这个问题我们已经解决了。
\\n那么剩下的问题是一个边数为 $O(n*k)$ 的无权图最短路问题。\\n
\\n- 这种问题有两种解法:\\n
\\n\\n
\\n- 线段树优化区间建图,可以参考
\\n
\\nhttps://beet-aizu.github.io/library/graph/rangetorange.cpp
\\n但是这道题用这种方法做,奇偶性会带来麻烦;- 在线bfs,也就是不预先建图,而是通过两个函数 setUsed 和 findUnused 来在线寻找边 (这里函数还可以用 yield 生成器代替,可以参考 python 的在线拓扑排序库的设计。类似的还有一种在线最小生成树算法Boruvka,可以解决某些完全图的最小生成树问题)。\\n
\\n\\n
\\n- setUsed(u):将 u 位置标记为已经访问过。
\\n- findUnused(u): 找到和 u 邻接的一个未访问过的点。如果不存在, 返回None。
\\n###python3
\\n\\ndist = [INF] * n\\n dist[p] = 0\\n queue = deque([p])\\n while queue:\\n cur = queue.popleft()\\n while True:\\n next_ = findUnused(cur)\\n if next_ is None:\\n break\\n dist[next_] = dist[cur] + 1\\n queue.append(next_)\\n setUsed(next_)\\n ```\\n2. 在这道题中,应该用什么样的数据结构实现 **setUsed** 和 **findUnused** 呢?\\n需要一个具有快速求 **prev(前驱)**、**next(后继)**、**erase(删除)** 这三个接口的数据结构。\\n- 第一种实现,可以用 [并查集](https://leetcode.cn/problems/minimum-reverse-operations/solution/jian-dan-pu-ji-yi-xia-kuai-yi-dian-de-ds-czb4/),对应下面的 `Finder1`;\\n- 第二种实现,可以用 [SortedList或者基于红黑树的set](https://leetcode.cn/problems/minimum-reverse-operations/solution/liang-chong-zuo-fa-ping-heng-shu-bing-ch-vr0z/), 对应下面的 `Finder2`(可惜常数太大超时了);\\n- 第三种实现,可以用 [位运算压位加速查找](https://leetcode.cn/problems/minimum-reverse-operations/solution/on232de-ya-wei-bao-li-by-hqztrue-ownd/), 对应下面的 `Finder3`;\\n\\n这三种 Finder 的实现可以参考 https://github.com/981377660LMT/algorithm-study/tree/master/22_%E4%B8%93%E9%A2%98/implicit_graph/RangeFinder\\n### 代码\\n\\n\\n###python3\\n```python3\\nfrom collections import deque\\nfrom typing import List, Optional, Tuple\\n\\nINF = int(1e18)\\n\\n\\nclass Solution:\\n def minReverseOperations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:\\n def getRange(i: int) -> Tuple[int, int]:\\n \\"\\"\\"反转长度为k的子数组,从i可以到达的左右边界闭区间.\\"\\"\\"\\n return max(i - k + 1, k - i - 1), min(i + k - 1, 2 * n - k - i - 1)\\n\\n def setUsed(u: int) -> None:\\n \\"\\"\\"将u位置标记为已经访问过.\\"\\"\\"\\n finder[u & 1].erase(u)\\n\\n def findUnused(u: int) -> Optional[int]:\\n \\"\\"\\"找到一个未使用的位置.如果不存在,返回None.\\"\\"\\"\\n left, right = getRange(u)\\n pre = finder[(u + k + 1) & 1].prev(right)\\n if pre is not None and left <= pre <= right:\\n return pre\\n next_ = finder[(u + k + 1) & 1].next(left)\\n if next_ is not None and left <= next_ <= right:\\n return next_\\n\\n finder = [Finder(n) for _ in range(2)]\\n for i in range(n):\\n finder[i & 1].insert(i)\\n for i in banned:\\n finder[i & 1].erase(i)\\n finder[p & 1].erase(p)\\n\\n ########################################################\\n dist = [INF] * n\\n dist[p] = 0\\n queue = deque([p])\\n while queue:\\n cur = queue.popleft()\\n while True:\\n next_ = findUnused(cur)\\n if next_ is None:\\n break\\n dist[next_] = dist[cur] + 1\\n queue.append(next_)\\n setUsed(next_)\\n return [d if d != INF else -1 for d in dist]\\n\\n\\n\\n\\n\\nclass Finder:\\n \\"\\"\\"利用位运算寻找区间的某个位置左侧/右侧第一个未被访问过的位置.\\n 初始时,所有位置都未被访问过.\\n \\"\\"\\"\\n\\n __slots__ = \\"_n\\", \\"_lg\\", \\"_seg\\"\\n\\n @staticmethod\\n def _trailingZeros1024(x: int) -> int:\\n if x == 0:\\n return 1 << 10\\n return (x & -x).bit_length() - 1\\n\\n def __init__(self, n: int) -> None:\\n self._n = n\\n seg = []\\n while True:\\n seg.append([0] * ((n + (1 << 10) - 1) >> 10))\\n n = (n + (1 << 10) - 1) >> 10\\n if n <= 1:\\n break\\n self._seg = seg\\n self._lg = len(seg)\\n\\n def insert(self, i: int) -> None:\\n for h in range(self._lg):\\n self._seg[h][i >> 10] |= 1 << (i % (1 << 10))\\n i >>= 10\\n\\n def erase(self, i: int) -> None:\\n for h in range(self._lg):\\n self._seg[h][i >> 10] &= ~(1 << (i % (1 << 10)))\\n if self._seg[h][i >> 10]:\\n break\\n i >>= 10\\n\\n def next(self, i: int) -> Optional[int]:\\n \\"\\"\\"返回x右侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n if i < 0:\\n i = 0\\n if i >= self._n:\\n return\\n seg = self._seg\\n for h in range(self._lg):\\n if i >> 10 == len(seg[h]):\\n break\\n d = seg[h][i >> 10] >> (i % (1 << 10))\\n if d == 0:\\n i = i >> 10 + 1\\n continue\\n i += self._trailingZeros1024(d)\\n for g in range(h - 1, -1, -1):\\n i *= 1 << 10\\n i += self._trailingZeros1024(seg[g][i >> 10])\\n return i\\n\\n def prev(self, i: int) -> Optional[int]:\\n \\"\\"\\"返回x左侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n if i < 0:\\n return\\n if i >= self._n:\\n i = self._n - 1\\n seg = self._seg\\n for h in range(self._lg):\\n if i == -1:\\n break\\n d = seg[h][i >> 10] << ((1 << 10) - 1 - i % (1 << 10)) & ((1 << (1 << 10)) - 1)\\n if d == 0:\\n i = (i >> 10) - 1\\n continue\\n i += d.bit_length() - (1 << 10)\\n for g in range(h - 1, -1, -1):\\n i *= 1 << 10\\n i += (seg[g][i >> 10]).bit_length() - 1\\n return i\\n\\n def islice(self, begin: int, end: int):\\n \\"\\"\\"遍历[start,end)区间内的元素.\\"\\"\\"\\n x = begin - 1\\n while True:\\n x = self.next(x + 1)\\n if x is None or x >= end:\\n break\\n yield x\\n\\n def __contains__(self, i: int) -> bool:\\n return self._seg[0][i >> 10] & (1 << (i % (1 << 10))) != 0\\n\\n def __iter__(self):\\n yield from self.islice(0, self._n)\\n\\n def __repr__(self):\\n return f\\"FastSet({list(self)})\\"\\n\\n
###python3
\\n\\n\\nfrom typing import Optional\\n\\n\\nclass Finder:\\n \\"\\"\\"利用并查集寻找区间的某个位置左侧/右侧第一个未被访问过的位置.\\n 初始时,所有位置都未被访问过.\\n \\"\\"\\"\\n\\n __slots__ = \\"_n\\", \\"_lParent\\", \\"_rParent\\", \\"_removed\\"\\n\\n def __init__(self, n: int):\\n self._n = n\\n self._lParent = list(range(n + 2)) # 0 和 n + 1 为哨兵, 实际使用[1,n]\\n self._rParent = list(range(n + 2))\\n\\n def prev(self, x: int) -> Optional[int]:\\n \\"\\"\\"找到x左侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n res = self._lFind(x + 1)\\n return res - 1 if res != 0 else None\\n\\n def next(self, x: int) -> Optional[int]:\\n \\"\\"\\"x右侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n res = self._rFind(x + 1)\\n return res - 1 if res != self._n + 1 else None\\n\\n def erase(self, left: int, right: int) -> None:\\n \\"\\"\\"删除[left, right)区间内的元素.\\n 0<=left<=right<=n.\\n \\"\\"\\"\\n if left >= right:\\n return\\n left, right = left + 1, right + 1\\n\\n leftRoot, rightRoot = self._rFind(left), self._rFind(right)\\n while rightRoot != leftRoot:\\n self._rUnion(leftRoot, leftRoot + 1)\\n leftRoot = self._rFind(leftRoot + 1)\\n\\n leftRoot, rightRoot = self._lFind(left - 1), self._lFind(right - 1)\\n while rightRoot != leftRoot:\\n self._lUnion(rightRoot, rightRoot - 1)\\n rightRoot = self._lFind(rightRoot - 1)\\n\\n def _lUnion(self, x: int, y: int) -> None:\\n if x < y:\\n x, y = y, x\\n rootX = self._lFind(x)\\n rootY = self._lFind(y)\\n if rootX == rootY:\\n return\\n self._lParent[rootX] = rootY\\n\\n def _rUnion(self, x: int, y: int) -> None:\\n if x > y:\\n x, y = y, x\\n rootX = self._rFind(x)\\n rootY = self._rFind(y)\\n if rootX == rootY:\\n return\\n self._rParent[rootX] = rootY\\n\\n def _lFind(self, x: int) -> int:\\n while x != self._lParent[x]:\\n self._lParent[x] = self._lParent[self._lParent[x]]\\n x = self._lParent[x]\\n return x\\n\\n def _rFind(self, x: int) -> int:\\n while x != self._rParent[x]:\\n self._rParent[x] = self._rParent[self._rParent[x]]\\n x = self._rParent[x]\\n return x\\n
###python3
\\n\\nfrom typing import Optional\\nfrom sortedcontainers import SortedList\\n\\nINF = int(1e18)\\n\\n\\nclass Finder:\\n \\"\\"\\"利用SortedList寻找区间的某个位置左侧/右侧第一个未被访问过的位置.\\n 初始时,所有位置都未被访问过.\\n \\"\\"\\"\\n\\n __slots__ = \\"_st\\"\\n\\n def __init__(self):\\n self._st = SortedList()\\n\\n def prev(self, x: int) -> Optional[int]:\\n \\"\\"\\"找到x左侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n pos = self._st.bisect_right((x, INF))\\n if pos == 0:\\n return None\\n if self._st[pos - 1][1] >= x:\\n return x\\n return self._st[pos - 1][1]\\n\\n def next(self, x: int) -> Optional[int]:\\n \\"\\"\\"x右侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n pos = self._st.bisect_right((x, INF))\\n if pos != 0 and self._st[pos - 1][1] >= x:\\n return x\\n if pos != len(self._st):\\n return self._st[pos][0]\\n\\n def erase(self, left: int, right: int) -> None:\\n \\"\\"\\"删除左闭右开区间[left, right).\\"\\"\\"\\n right -= 1\\n if left > right:\\n return\\n it1 = self._st.bisect_left((left, -INF))\\n it2 = self._st.bisect_right((right, INF))\\n if it1 != 0 and left <= self._st[it1 - 1][1]:\\n it1 -= 1\\n if it1 == it2:\\n return\\n nl, nr = self._st[it1][0], self._st[it2 - 1][1]\\n if left < nl:\\n nl = left\\n if right > nr:\\n nr = right\\n del self._st[it1:it2]\\n if nl < left:\\n self._st.add((nl, left))\\n if right < nr:\\n self._st.add((right, nr))\\n\\n def insert(self, left: int, right: int) -> None:\\n \\"\\"\\"插入左闭右开区间[left, right).\\"\\"\\"\\n right -= 1\\n if left > right:\\n return\\n it1 = self._st.bisect_right((left, INF))\\n it2 = self._st.bisect_right((right, INF))\\n if it1 != 0 and left <= self._st[it1 - 1][1]:\\n it1 -= 1\\n if it1 != it2:\\n tmp1 = self._st[it1][0]\\n if tmp1 < left:\\n left = tmp1\\n tmp2 = self._st[it2 - 1][1]\\n if tmp2 > right:\\n right = tmp2\\n del self._st[it1:it2]\\n self._st.add((left, right))\\n\\n def __repr__(self) -> str:\\n sb = []\\n for left, right in self._st:\\n sb.append(f\\"({left}, {right})\\")\\n return f\\"SegmentSet([{\', \'.join(sb)}])\\"\\n
###python3
\\n\\n","description":"解题思路 分享一下自己对于这道题的思考。\\n 这道题的一个难点是求反转长度为k的子数组,从位置i可以到达的左右边界闭区间。假设这个问题我们已经解决了。\\n 那么剩下的问题是一个边数为 $O(n*k)$ 的无权图最短路问题。\\n\\n这种问题有两种解法:\\n线段树优化区间建图,可以参考\\n https://beet-aizu.github.io/library/graph/rangetorange.cpp\\n 但是这道题用这种方法做,奇偶性会带来麻烦;\\n在线bfs,也就是不预先建图,而是通过两个函数 setUsed 和 findUnused 来在线寻找边 (这里函数还可以用…","guid":"https://leetcode.cn/problems/minimum-reverse-operations//solution/python-zai-xian-bfs-jie-jue-bian-shu-hen-y58m","author":"981377660LMT","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-03T11:20:35.815Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双零哈希表简单思路分享","url":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/shuang-ling-ha-xi-biao-jian-dan-si-lu-fe-gprk","content":"class Finder:\\n \\"\\"\\"利用位运算寻找区间的某个位置左侧/右侧第一个未被访问过的位置.\\n 初始时,所有位置都未被访问过.\\n \\"\\"\\"\\n\\n __slots__ = \\"_n\\", \\"_lg\\", \\"_seg\\"\\n\\n @staticmethod\\n def _trailingZeros1024(x: int) -> int:\\n if x == 0:\\n return 1 << 10\\n return (x & -x).bit_length() - 1\\n\\n def __init__(self, n: int) -> None:\\n self._n = n\\n seg = []\\n while True:\\n seg.append([0] * ((n + (1 << 10) - 1) >> 10))\\n n = (n + (1 << 10) - 1) >> 10\\n if n <= 1:\\n break\\n self._seg = seg\\n self._lg = len(seg)\\n\\n def insert(self, i: int) -> None:\\n for h in range(self._lg):\\n self._seg[h][i >> 10] |= 1 << (i % (1 << 10))\\n i >>= 10\\n\\n def erase(self, i: int) -> None:\\n for h in range(self._lg):\\n self._seg[h][i >> 10] &= ~(1 << (i % (1 << 10)))\\n if self._seg[h][i >> 10]:\\n break\\n i >>= 10\\n\\n def next(self, i: int) -> Optional[int]:\\n \\"\\"\\"返回x右侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n if i < 0:\\n i = 0\\n if i >= self._n:\\n return\\n seg = self._seg\\n for h in range(self._lg):\\n if i >> 10 == len(seg[h]):\\n break\\n d = seg[h][i >> 10] >> (i % (1 << 10))\\n if d == 0:\\n i = i >> 10 + 1\\n continue\\n i += self._trailingZeros1024(d)\\n for g in range(h - 1, -1, -1):\\n i *= 1 << 10\\n i += self._trailingZeros1024(seg[g][i >> 10])\\n return i\\n\\n def prev(self, i: int) -> Optional[int]:\\n \\"\\"\\"返回x左侧第一个未被访问过的位置(包含x).\\n 如果不存在,返回None.\\n \\"\\"\\"\\n if i < 0:\\n return\\n if i >= self._n:\\n i = self._n - 1\\n seg = self._seg\\n for h in range(self._lg):\\n if i == -1:\\n break\\n d = seg[h][i >> 10] << ((1 << 10) - 1 - i % (1 << 10)) & ((1 << (1 << 10)) - 1)\\n if d == 0:\\n i = (i >> 10) - 1\\n continue\\n i += d.bit_length() - (1 << 10)\\n for g in range(h - 1, -1, -1):\\n i *= 1 << 10\\n i += (seg[g][i >> 10]).bit_length() - 1\\n return i\\n\\n def islice(self, begin: int, end: int):\\n \\"\\"\\"遍历[start,end)区间内的元素.\\"\\"\\"\\n x = begin - 1\\n while True:\\n x = self.next(x + 1)\\n if x is None or x >= end:\\n break\\n yield x\\n\\n def __contains__(self, i: int) -> bool:\\n return self._seg[0][i >> 10] & (1 << (i % (1 << 10))) != 0\\n\\n def __iter__(self):\\n yield from self.islice(0, self._n)\\n\\n def __repr__(self):\\n return f\\"FastSet({list(self)})\\"\\n
\\n
{:width=500}
\\n","description":"{:width=500} class Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<vector<int>> findMatrix(vector<int>& nums) {\\n vector<vector<int>> ans;\\n vector<int> path;\\n unordered_map<int, int> hash;\\n int count = 0;\\n for (auto &i : nums) ++hash[i];\\n \\n while (hash.size() > 0) {\\n for (auto &i : hash) if (hash[i.first] > 0) path.emplace_back(i.first), --hash[i.first];\\n ans.emplace_back(path);\\n path.clear();\\n for (auto &i : hash) if (hash[i.first] == 0) ++count;\\n if (count == hash.size()) break;\\n count = 0;\\n }\\n return ans;\\n }\\n};\\n
> findMatrix(vector & nums) {\\n vector > ans;\\n vector path;\\n unordered_map hash;\\n int count = 0;\\n for (auto &i : nums) ++hash[i];\\n \\n while…","guid":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/shuang-ling-ha-xi-biao-jian-dan-si-lu-fe-gprk","author":"kind-moore2dy","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-02T14:25:06.882Z","media":[{"url":"https://pic.leetcode.cn/1680445436-YZzlWU-image.png","type":"photo","width":618,"height":327,"blurhash":"LTQ,Xfxut6%M~QoeWDof9it7WBof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ | Rust:哈希表(两种角度)","url":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/c-rustha-xi-biao-liang-chong-jiao-du-by-tw9a2","content":" \\n\\nProblem: 6363. 转换二维数组
\\n[TOC]
\\n思路1
\\n\\n\\n根据示例挖掘规律,并使用合适的数据结构和算法思想解决问题:由示例可以看出数组最大层数是nums数组中最大的元素出现的次数,然后每层填充的都是各不相同的元素, 那么如何如何保证每层都是不同的?使用哈希表记录每个数字出现的次数,每层遍历依次哈希表(因为元素不为会重复),如果当前个数不为0,就加到当前层中去,然后修改对应的个数,每次遍历完哈希map,就添加一当前层到答案中
\\n时间复杂度:$O(n^2)$
\\n空间复杂度:$O(n)$
\\nCode
\\n###C++
\\n\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> findMatrix(vector<int>& nums) {\\n unordered_map<int, int>map;\\n vector<vector<int>>ans;\\n \\n for (auto& t : nums) map[t]++;\\n \\n auto [_, n] = *max_element(map.begin(), map.end(), [&](const pair<int, int>& a, const pair<int, int>& b) -> bool {\\n return a.second < b.second;\\n });\\n\\n //按照每行进行填充\\n while (n--) {\\n vector<int> tmp;\\n for (auto& [num, cnt] : map) {\\n if (cnt != 0) { \\n tmp.emplace_back(num);\\n cnt--;\\n }\\n }\\n ans.emplace_back(tmp);\\n }\\n return ans;\\n }\\n};\\n
###Rust
\\n\\n// 导入 HashMap\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> {\\n // 创建一个 HashMap,用于统计每个数字出现的次数\\n let mut map: HashMap<i32, i32> = HashMap::new();\\n // 创建一个二维数组用于存储结果\\n let mut ans: Vec<Vec<i32>> = Vec::new();\\n\\n // 统计每个数字出现的次数\\n for t in nums {\\n let count = map.entry(t).or_insert(0);\\n *count += 1;\\n }\\n\\n // 如果 HashMap 不为空\\n if let Some(max) = map.values().max() {\\n // 从最大次数开始往下遍历\\n let mut n = *max;\\n while n > 0 {\\n // 创建一个临时数组用于存储当前次数的数字\\n let mut tmp: Vec<i32> = Vec::new();\\n // 遍历 HashMap,将当前次数的数字加入临时数组,并将该数字出现次数减 1\\n for (num, cnt) in map.iter_mut() {\\n if *cnt > 0 {\\n tmp.push(*num);\\n *cnt -= 1;\\n }\\n }\\n // 将临时数组加入结果数组\\n ans.push(tmp);\\n n -= 1;\\n }\\n }\\n\\n ans\\n }\\n}\\n
思路2
\\n\\n\\n更nb的规律:哈希表存储后, 直接遍历nums数组,选择应该存储的位置放, 如果nums[i]出现的次数是3,则应该放到第2层位置!!!
\\n时间复杂度:$o(n)$
\\n空间复杂度:$O(n)$
\\nCode
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> findMatrix(vector<int>& nums) {\\n unordered_map<int, int>map;\\n\\n int m = 0;\\n for (auto& t : nums) {\\n map[t]++;\\n m = max(m, map[t]);\\n }\\n\\n vector<vector<int>> ans(m);\\n for (auto& x : nums) {\\n ans[map[x] - 1].push_back(x);\\n map[x]--;\\n }\\n return ans;\\n }\\n};\\n
###Rust
\\n\\n","description":"Problem: 6363. 转换二维数组 [TOC]\\n\\n根据示例挖掘规律,并使用合适的数据结构和算法思想解决问题:由示例可以看出数组最大层数是nums数组中最大的元素出现的次数,然后每层填充的都是各不相同的元素, 那么如何如何保证每层都是不同的?使用哈希表记录每个数字出现的次数,每层遍历依次哈希表(因为元素不为会重复),如果当前个数不为0,就加到当前层中去,然后修改对应的个数,每次遍历完哈希map,就添加一当前层到答案中\\n时间复杂度:$O(n^2)$\\n空间复杂度:$O(n)$\\nCode\\n\\n###C++\\n\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/c-rustha-xi-biao-liang-chong-jiao-du-by-tw9a2","author":"Jann_Horn","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-02T06:20:06.673Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一图秒懂(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/ha-xi-biao-mo-ni-by-endlesscheng-6rgb","content":"// 导入 HashMap 模块\\nuse std::collections::HashMap;\\n\\n// 实现 Solution 结构体\\nimpl Solution {\\n // 定义一个函数,接受一个 i32 类型的数组作为参数,并返回一个 i32 类型的二维数组\\n pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> {\\n // 创建一个新的 HashMap 对象,用于计算 nums 中每个元素出现的次数\\n let mut map = HashMap::new();\\n // 记录出现最多的元素的出现次数\\n let mut m = 0;\\n\\n // 遍历 nums 中的每个元素,并将其出现次数添加到 HashMap 中\\n for &t in nums.iter() {\\n let count = map.entry(t).or_insert(0); // 获取元素 t 对应的计数器\\n *count += 1; // 将计数器增加 1\\n m = m.max(*count); // 更新最大计数器值\\n }\\n\\n // 创建一个新的二维向量 ans,其中包含 m 个空向量\\n let mut ans = vec![vec![]; m as usize];\\n\\n // 遍历 nums 中的每个元素,并将其添加到相应的向量中\\n for &x in nums.iter() {\\n let idx = map[&x] - 1; // 获取元素 x 对应的索引\\n ans[idx as usize].push(x); // 将元素 x 添加到相应的向量中\\n let count = map.entry(x).or_insert(0); // 获取元素 x 对应的计数器\\n *count -= 1; // 将计数器减少 1,以确保每个元素都被添加到正确的向量中\\n }\\n\\n // 返回包含分组元素的二维向量\\n ans\\n }\\n}\\n
\\n
{:width=350}
\\nclass Solution:\\n def findMatrix(self, nums: List[int]) -> List[List[int]]:\\n ans = []\\n cnt = Counter(nums) # 统计每个元素的出现次数\\n while cnt: # 还有剩余元素\\n row = list(cnt)\\n ans.append(row)\\n # cnt 中的每个元素的出现次数都减一\\n for x in row:\\n cnt[x] -= 1\\n if cnt[x] == 0:\\n del cnt[x] # 去掉出现次数为 0 的元素\\n return ans\\n
\\nclass Solution {\\n public List<List<Integer>> findMatrix(int[] nums) {\\n // 统计每个元素的出现次数\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int x : nums) {\\n cnt.merge(x, 1, Integer::sum); // cnt[x]++\\n }\\n\\n List<List<Integer>> ans = new ArrayList<>();\\n while (!cnt.isEmpty()) { // 还有剩余元素\\n List<Integer> row = new ArrayList<>(cnt.keySet());\\n ans.add(row);\\n // cnt 中的每个元素的出现次数都减一\\n for (Integer x : row) {\\n int c = cnt.get(x) - 1;\\n if (c == 0) {\\n cnt.remove(x); // 去掉出现次数为 0 的元素\\n } else {\\n cnt.put(x, c); // 更新出现次数\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n public List<List<Integer>> findMatrix(int[] nums) {\\n // 统计每个元素的出现次数\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int x : nums) {\\n cnt.merge(x, 1, Integer::sum); // cnt[x]++\\n }\\n\\n List<List<Integer>> ans = new ArrayList<>();\\n while (!cnt.isEmpty()) { // 还有剩余元素\\n List<Integer> row = new ArrayList<>(cnt.size()); // 预分配空间\\n // 一边遍历哈希表,一边删除元素\\n Iterator<Map.Entry<Integer, Integer>> it = cnt.entrySet().iterator();\\n while (it.hasNext()) {\\n Map.Entry<Integer, Integer> e = it.next();\\n row.add(e.getKey());\\n int c = e.getValue() - 1; // 出现次数减一\\n if (c == 0) {\\n it.remove(); // 去掉出现次数为 0 的元素\\n } else {\\n e.setValue(c); // 更新出现次数\\n }\\n }\\n ans.add(row);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> findMatrix(vector<int>& nums) {\\n // 统计每个元素的出现次数\\n unordered_map<int, int> cnt;\\n for (int x : nums) {\\n cnt[x]++;\\n }\\n\\n vector<vector<int>> ans;\\n while (!cnt.empty()) {\\n vector<int> row;\\n // cnt 中的每个元素的出现次数都减一\\n // 一边遍历哈希表,一边删除元素\\n for (auto it = cnt.begin(); it != cnt.end();) {\\n row.push_back(it->first);\\n if (--it->second == 0) {\\n it = cnt.erase(it);\\n } else {\\n it++;\\n }\\n }\\n ans.push_back(row);\\n }\\n return ans;\\n }\\n};\\n
\\nfunc findMatrix(nums []int) (ans [][]int) {\\n // 统计每个元素的出现次数\\n cnt := map[int]int{}\\n for _, x := range nums {\\n cnt[x]++\\n }\\n\\n for len(cnt) > 0 {\\n row := make([]int, 0, len(cnt)) // 预分配空间\\n // cnt 中的每个元素的出现次数都减一\\n for x := range cnt {\\n row = append(row, x)\\n cnt[x]--\\n if cnt[x] == 0 {\\n delete(cnt, x) // 删除当前正在遍历的元素是安全的\\n }\\n }\\n ans = append(ans, row)\\n }\\n return\\n}\\n
\\nvar findMatrix = function(nums) {\\n // 统计每个元素的出现次数\\n const cnt = new Map();\\n for (const x of nums) {\\n cnt.set(x, (cnt.get(x) ?? 0) + 1);\\n }\\n\\n const ans = [];\\n while (cnt.size) { // 还有剩余元素\\n const row = [...cnt.keys()];\\n ans.push(row);\\n // cnt 中的每个元素的出现次数都减一\\n for (const x of row) {\\n const c = cnt.get(x) - 1;\\n if (c === 0) {\\n cnt.delete(x); // 去掉出现次数为 0 的元素\\n } else {\\n cnt.set(x, c); // 更新出现次数\\n }\\n }\\n }\\n return ans;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn find_matrix(nums: Vec<i32>) -> Vec<Vec<i32>> {\\n // 统计每个元素的出现次数\\n let mut cnt = HashMap::new();\\n for x in nums {\\n *cnt.entry(x).or_insert(0) += 1;\\n }\\n\\n let mut ans = vec![];\\n while !cnt.is_empty() { // 还有剩余元素\\n let row = cnt.keys().cloned().collect::<Vec<_>>();\\n ans.push(row.clone());\\n // cnt 中的每个元素的出现次数都减一\\n for x in row {\\n let c = cnt.get_mut(&x).unwrap();\\n *c -= 1;\\n if *c == 0 {\\n cnt.remove(&x); // 去掉出现次数为 0 的元素\\n }\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"{:width=350} class Solution:\\n def findMatrix(self, nums: List[int]) -> List[List[int]]:\\n ans = []\\n cnt = Counter(nums) # 统计每个元素的出现次数\\n while cnt: # 还有剩余元素\\n row = list(cnt)\\n ans.append(row)\\n # cnt 中的每个元素的出现次数都减一…","guid":"https://leetcode.cn/problems/convert-an-array-into-a-2d-array-with-conditions//solution/ha-xi-biao-mo-ni-by-endlesscheng-6rgb","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-02T05:24:10.563Z","media":[{"url":"https://pic.leetcode.cn/1741320320-YkycVn-lc2610-c.png","type":"photo","width":1482,"height":1280,"blurhash":"L9Rysg?bj[~q-;t7-;j[ofxuIURj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种做法:BFS+有序集合/并查集(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimum-reverse-operations//solution/liang-chong-zuo-fa-ping-heng-shu-bing-ch-vr0z","content":"提示 1
\\n核心想法是用 BFS 模拟这个过程。
\\n当我们位于下标 $i$ 时,下一步可以去哪?换句话说,通过一次操作,可以到达哪些下标?
\\n提示 2
\\n如果翻转子数组 $[L,R]$,那么:
\\n\\n
\\n- 下标 $L$ 翻转到下标 $R$。
\\n- 下标 $L+1$ 翻转到下标 $R-1$。
\\n- 下标 $L+2$ 翻转到下标 $R-2$。
\\n- 依此类推,可以发现翻转前后的下标之和恒等于 $L+R$,所以下标 $i$ 在翻转后的下标是 $L+R-i$。
\\n推论:
\\n\\n
\\n- 当子数组向右滑动时,每滑动一个单位,$L$ 和 $R$ 都增加 $1$,所以翻转后的下标会增加 $2$。
\\n- 当子数组向左滑动时,每滑动一个单位,$L$ 和 $R$ 都减少 $1$,所以翻转后的下标会减少 $2$。
\\n- 因此,$i$ 翻转后的下标组成了一个公差为 $2$ 的等差数列。
\\n$i$ 翻转后的下标,最小是多少,最大是多少?求出最小值和最大值,就能求出 $i$ 翻转后的所有下标。
\\n提示 3
\\n如果不考虑下标越界,那么:
\\n\\n
\\n- 当 $i$ 在子数组右端点时,可以翻转到子数组左端点 $i-k+1$,这是最小值。
\\n- 当 $i$ 在子数组左端点时,可以翻转到子数组右端点 $i+k-1$,这是最大值。
\\n但如果(举例)$k=4$,长为 $k$ 的子数组,右端点最小是 $k-1=3$,当 $i=0,1,2$ 的时候,$i$ 不可能在子数组右端点。同样地,左端点最大是 $n-k$,当 $i > n-k$ 的时候,$i$ 不可能在子数组左端点。
\\n这意味着 $i$ 比较小(比较大)的情况,应当特殊处理:
\\n\\n
\\n- $i < k-1$ 的情况,当子数组在最左边时,$L=0,\\\\ R=k-1$,$i$ 翻转后是 $L+R-i=0+(k-1)-i=k-i-1$,小于 $k-i-1$ 的下标无法翻转得到。
\\n- $i > n-k$ 的情况,当子数组在最右边时,$L=n-k,\\\\ R=n-1$,$i$ 翻转后是 $L+R-i=(n-k)+(n-1) - i=2n-k-i-1$,大于 $2n-k-i-1$ 的下标无法翻转到。
\\n综上所述:
\\n\\n
\\n- $i$ 翻转后的最小值为 $\\\\max(i-k+1,k-i-1)$。
\\n- $i$ 翻转后的最大值为 $\\\\min(i+k-1,2n-k-i-1)$。
\\n方法一:BFS + 有序集合
\\n由于等差数列的公差为 $2$,翻转后的下标要么都是偶数,要么都是奇数。我们用两个有序集合 $\\\\textit{indices}_0$ 和 $\\\\textit{indices}_1$ 分别维护没有访问过的偶数下标和奇数下标。注意这些下标不能在 $\\\\textit{banned}$ 中。由于 $p$ 是起点(已访问),所以也不在有序集合中。
\\n然后用 BFS 模拟。
\\n在有序集合上,一边遍历翻转后的下标 $j$,一边把 $j$ 从有序集合中删除。这样可以避免重复访问已经访问过的下标,也方便我们查找下一个没有访问过的下标。
\\n关于 BFS 的基本原理,见【基础算法精讲 13】。
\\n\\nclass Solution:\\n def minReverseOperations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:\\n ban = set(banned) | {p}\\n indices = [SortedList(), SortedList()] # import sortedcontainers\\n for i in range(n):\\n if i not in ban:\\n indices[i % 2].add(i)\\n indices[0].add(n) # 哨兵,下面无需判断越界\\n indices[1].add(n)\\n\\n ans = [-1] * n\\n ans[p] = 0 # 起点\\n q = deque([p])\\n while q:\\n i = q.popleft()\\n # indices[mn % 2] 中的从 mn 到 mx 的所有下标都可以从 i 翻转到\\n mn = max(i - k + 1, k - i - 1)\\n mx = min(i + k - 1, n * 2 - k - i - 1)\\n sl = indices[mn % 2]\\n idx = sl.bisect_left(mn)\\n while sl[idx] <= mx:\\n j = sl[idx]\\n ans[j] = ans[i] + 1 # 移动一步\\n q.append(j)\\n sl.pop(idx)\\n # 注意 pop(idx) 会使后续元素向左移,不需要写 idx += 1\\n return ans\\n
\\nclass Solution {\\n public int[] minReverseOperations(int n, int p, int[] banned, int k) {\\n Set<Integer> ban = new HashSet<>();\\n for (int b : banned) {\\n ban.add(b);\\n }\\n\\n TreeSet<Integer>[] indices = new TreeSet[]{new TreeSet<>(), new TreeSet<>()};\\n for (int i = 0; i < n; i++) {\\n if (i != p && !ban.contains(i)) {\\n indices[i % 2].add(i);\\n }\\n }\\n\\n int[] ans = new int[n];\\n Arrays.fill(ans, -1);\\n ans[p] = 0; // 起点\\n Queue<Integer> q = new ArrayDeque<>();\\n q.offer(p);\\n while (!q.isEmpty()) {\\n int i = q.poll();\\n // indices[mn % 2] 中的从 mn 到 mx 的所有下标都可以从 i 翻转到\\n int mn = Math.max(i - k + 1, k - i - 1);\\n int mx = Math.min(i + k - 1, n * 2 - k - i - 1);\\n TreeSet<Integer> set = indices[mn % 2];\\n for (Iterator<Integer> it = set.tailSet(mn).iterator(); it.hasNext(); it.remove()) {\\n int j = it.next();\\n if (j > mx) {\\n break;\\n }\\n ans[j] = ans[i] + 1; // 移动一步\\n q.offer(j);\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {\\n unordered_set<int> ban{banned.begin(), banned.end()};\\n set<int> indices[2];\\n for (int i = 0; i < n; i++) {\\n if (i != p && !ban.contains(i)) {\\n indices[i % 2].insert(i);\\n }\\n }\\n indices[0].insert(n); // 哨兵,下面无需判断 it != st.end()\\n indices[1].insert(n);\\n\\n vector<int> ans(n, -1);\\n ans[p] = 0; // 起点\\n queue<int> q;\\n q.push(p);\\n while (!q.empty()) {\\n int i = q.front(); q.pop();\\n // indices[mn % 2] 中的从 mn 到 mx 的所有下标都可以从 i 翻转到\\n int mn = max(i - k + 1, k - i - 1);\\n int mx = min(i + k - 1, n * 2 - k - i - 1);\\n auto& st = indices[mn % 2];\\n for (auto it = st.lower_bound(mn); *it <= mx; it = st.erase(it)) {\\n ans[*it] = ans[i] + 1; // 移动一步\\n q.push(*it);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc minReverseOperations(n int, p int, banned []int, k int) []int {\\n ban := map[int]struct{}{p: {}}\\n for _, b := range banned {\\n ban[b] = struct{}{}\\n }\\n\\n indices := [2]*redblacktree.Tree[int, struct{}]{\\n redblacktree.New[int, struct{}](),\\n redblacktree.New[int, struct{}](),\\n }\\n for i := range n {\\n if _, ok := ban[i]; !ok {\\n indices[i%2].Put(i, struct{}{})\\n }\\n }\\n indices[0].Put(n, struct{}{}) // 哨兵,下面无需判断 node != nil\\n indices[1].Put(n, struct{}{})\\n\\n ans := make([]int, n)\\n for i := range ans {\\n ans[i] = -1\\n }\\n ans[p] = 0 // 起点\\n q := []int{p}\\n for len(q) > 0 {\\n i := q[0]\\n q = q[1:]\\n // indices[mn%2] 中的从 mn 到 mx 的所有下标都可以从 i 翻转到\\n mn := max(i-k+1, k-i-1)\\n mx := min(i+k-1, n*2-k-i-1)\\n t := indices[mn%2]\\n for node, _ := t.Ceiling(mn); node.Key <= mx; node, _ = t.Ceiling(mn) {\\n j := node.Key\\n ans[j] = ans[i] + 1 // 移动一步\\n q = append(q, j)\\n t.Remove(j)\\n }\\n }\\n return ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$。每个下标入队出队各至多一次,每次(均摊)$\\\\mathcal{O}(\\\\log n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:BFS + 并查集
\\n前置知识:并查集。
\\n返璞归真,把方法一的有序集合,合并一个列表 $\\\\textit{indices}=[0,1,2,\\\\ldots,n-1]$。
\\n改成列表后,直接删除列表元素的话,时间复杂度是 $\\\\mathcal{O}(n)$ 的。如何高效地删除元素?
\\n举例说明。假设我们有下标 $0,1,2,3,4$,要把其中的 $2$ 删除。用并查集思考,改成把 $j=2$ 与 $j+2=4$ 合并,也就是调用 $\\\\texttt{merge}(j,j+2)$。这样下次遍历的时候,就可以利用并查集的 $\\\\texttt{find}$ 函数,跳过已删除的下标。比如想找 $\\\\ge 2$ 的没有访问过(没被删除)的最小偶数下标,只需要调用并查集的 $\\\\texttt{find}(2)$,结果是 $4$。
\\n代码实现时,$\\\\texttt{merge}(j,j+2)$ 可以优化成 $\\\\texttt{merge}(j,\\\\textit{mx}+2)$。既然范围内的数都要删除,直接一步到位,全部指向下一个没被删除的数,即 $\\\\textit{mx}+2$。为了保证 $\\\\textit{mx}+2$ 一定存在,可以添加哨兵 $n$ 和 $n+1$。也就是说,并查集的大小是 $n+2$ 而不是 $n$。
\\n\\nclass UnionFind:\\n def __init__(self, n: int):\\n self.fa = list(range(n))\\n\\n def find(self, x: int) -> int:\\n if self.fa[x] != x:\\n self.fa[x] = self.find(self.fa[x])\\n return self.fa[x]\\n\\n def merge(self, from_: int, to: int) -> None:\\n self.fa[self.find(from_)] = self.find(to)\\n\\nclass Solution:\\n def minReverseOperations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:\\n indices = UnionFind(n + 2)\\n indices.merge(p, p + 2) # 删除 p\\n for i in banned:\\n indices.merge(i, i + 2) # 删除 i\\n\\n ans = [-1] * n\\n ans[p] = 0\\n q = deque([p])\\n while q:\\n i = q.popleft()\\n mn = max(i - k + 1, k - i - 1)\\n mx = min(i + k - 1, n * 2 - k - i - 1)\\n j = indices.find(mn)\\n while j <= mx:\\n ans[j] = ans[i] + 1\\n q.append(j)\\n indices.merge(j, mx + 2) # 删除 j\\n j = indices.find(j + 2) # 快速跳到 >= j+2 的下一个下标\\n return ans\\n
\\nclass UnionFind:\\n def __init__(self, n: int):\\n self.fa = list(range(n))\\n\\n def find(self, x: int) -> int:\\n if self.fa[x] != x:\\n self.fa[x] = self.find(self.fa[x])\\n return self.fa[x]\\n\\nclass Solution:\\n def minReverseOperations(self, n: int, p: int, banned: List[int], k: int) -> List[int]:\\n indices = UnionFind(n + 2)\\n indices.fa[p] += 2 # 删除 p\\n for i in banned:\\n indices.fa[i] += 2 # 删除 i\\n\\n ans = [-1] * n\\n ans[p] = 0\\n q = deque([p])\\n while q:\\n i = q.popleft()\\n mn = max(i - k + 1, k - i - 1)\\n mx = min(i + k - 1, n * 2 - k - i - 1)\\n end = indices.find(mx + 2)\\n j = indices.find(mn)\\n while j <= mx:\\n ans[j] = ans[i] + 1\\n q.append(j)\\n indices.fa[j] = end # 删除 j\\n j = indices.find(j + 2) # 快速跳到 >= j+2 的下一个下标\\n return ans\\n
\\nclass UnionFind {\\n private final int[] fa;\\n\\n public UnionFind(int n) {\\n fa = new int[n];\\n for (int i = 0; i < n; i++) {\\n fa[i] = i;\\n }\\n }\\n\\n public int find(int x) {\\n if (fa[x] != x) {\\n fa[x] = find(fa[x]);\\n }\\n return fa[x];\\n }\\n\\n public void merge(int from, int to) {\\n fa[find(from)] = find(to);\\n }\\n}\\n\\nclass Solution {\\n public int[] minReverseOperations(int n, int p, int[] banned, int k) {\\n UnionFind indices = new UnionFind(n + 2);\\n indices.merge(p, p + 2); // 删除 p\\n for (int i : banned) {\\n indices.merge(i, i + 2); // 删除 i\\n }\\n\\n int[] ans = new int[n];\\n Arrays.fill(ans, -1);\\n ans[p] = 0;\\n Queue<Integer> q = new ArrayDeque<>(); // 注:如果改用数组模拟队列,可以再快一些\\n q.offer(p);\\n while (!q.isEmpty()) {\\n int i = q.poll();\\n int mn = Math.max(i - k + 1, k - i - 1);\\n int mx = Math.min(i + k - 1, n * 2 - k - i - 1);\\n for (int j = indices.find(mn); j <= mx; j = indices.find(j + 2)) { // 快速跳到 >= j+2 的下一个下标\\n ans[j] = ans[i] + 1;\\n q.offer(j);\\n indices.merge(j, mx + 2); // 删除 j\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass UnionFind {\\n vector<int> fa;\\n\\npublic:\\n UnionFind(int n) : fa(n) {\\n iota(fa.begin(), fa.end(), 0);\\n }\\n\\n int find(int x) {\\n if (fa[x] != x) {\\n fa[x] = find(fa[x]);\\n }\\n return fa[x];\\n }\\n\\n void merge(int from, int to) {\\n fa[find(from)] = find(to);\\n }\\n};\\n\\nclass Solution {\\npublic:\\n vector<int> minReverseOperations(int n, int p, vector<int>& banned, int k) {\\n UnionFind indices(n + 2);\\n indices.merge(p, p + 2); // 删除 p\\n for (int i : banned) {\\n indices.merge(i, i + 2); // 删除 i\\n }\\n\\n vector<int> ans(n, -1);\\n ans[p] = 0;\\n queue<int> q;\\n q.push(p);\\n while (!q.empty()) {\\n int i = q.front(); q.pop();\\n int mn = max(i - k + 1, k - i - 1);\\n int mx = min(i + k - 1, n * 2 - k - i - 1);\\n for (int j = indices.find(mn); j <= mx; j = indices.find(j + 2)) { // 快速跳到 >= j+2 的下一个下标\\n ans[j] = ans[i] + 1;\\n q.push(j);\\n indices.merge(j, mx + 2); // 删除 j\\n }\\n }\\n return ans;\\n }\\n};\\n
\\ntype unionFind struct {\\n fa []int\\n}\\n\\nfunc newUnionFind(n int) unionFind {\\n fa := make([]int, n)\\n for i := range fa {\\n fa[i] = i\\n }\\n return unionFind{fa}\\n}\\n\\nfunc (uf unionFind) find(x int) int {\\n if uf.fa[x] != x {\\n uf.fa[x] = uf.find(uf.fa[x])\\n }\\n return uf.fa[x]\\n}\\n\\nfunc (uf unionFind) merge(from, to int) {\\n uf.fa[uf.find(from)] = uf.find(to)\\n}\\n\\nfunc minReverseOperations(n, p int, banned []int, k int) []int {\\n indices := newUnionFind(n + 2)\\n indices.merge(p, p+2) // 删除 p\\n for _, i := range banned {\\n indices.merge(i, i+2) // 删除 i\\n }\\n\\n ans := make([]int, n)\\n for i := range ans {\\n ans[i] = -1\\n }\\n ans[p] = 0\\n q := []int{p}\\n for len(q) > 0 {\\n i := q[0]\\n q = q[1:]\\n mn := max(i-k+1, k-i-1)\\n mx := min(i+k-1, n*2-k-i-1)\\n for j := indices.find(mn); j <= mx; j = indices.find(j + 2) { // 快速跳到 >= j+2 的下一个下标\\n ans[j] = ans[i] + 1\\n q = append(q, j)\\n indices.merge(j, mx+2) // 删除 j\\n }\\n }\\n return ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$O(n\\\\log n)$。
\\n- 空间复杂度:$O(n)$。
\\n能否做到 $\\\\mathcal{O}(n)$?见 RMQ 标准算法和线性树上并查集。
\\n更多相似题目,见数据结构题单中的「§7.4 数组上的并查集」。
\\n并查集的模板代码也整理在数据结构题单中。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"提示 1 核心想法是用 BFS 模拟这个过程。\\n\\n当我们位于下标 $i$ 时,下一步可以去哪?换句话说,通过一次操作,可以到达哪些下标?\\n\\n提示 2\\n\\n如果翻转子数组 $[L,R]$,那么:\\n\\n下标 $L$ 翻转到下标 $R$。\\n下标 $L+1$ 翻转到下标 $R-1$。\\n下标 $L+2$ 翻转到下标 $R-2$。\\n依此类推,可以发现翻转前后的下标之和恒等于 $L+R$,所以下标 $i$ 在翻转后的下标是 $L+R-i$。\\n\\n推论:\\n\\n当子数组向右滑动时,每滑动一个单位,$L$ 和 $R$ 都增加 $1$,所以翻转后的下标会增加 $2$。\\n当子数组向左滑动时…","guid":"https://leetcode.cn/problems/minimum-reverse-operations//solution/liang-chong-zuo-fa-ping-heng-shu-bing-ch-vr0z","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-02T05:09:49.920Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"BFS,跳跃游戏加强版","url":"https://leetcode.cn/problems/minimum-reverse-operations//solution/bfstiao-yue-you-xi-jia-qiang-ban-by-tsre-juxn","content":"解法:BFS
\\n本题可以看作是 leetcode 45. 跳跃游戏 II 的加强版。
\\n一开始很容易想到一个 BFS 的方法。当位置 $i$ 从 BFS 队列中出队时,枚举翻转哪个子数组,就能算出位置 $i$ 能跳到哪些位置。这样做的复杂度是 $\\\\mathcal{O}(n(n - k))$ 的,因为共有 $n$ 个位置,且共有 $(n - k + 1)$ 个子数组可以翻转。
\\n上述 BFS 做法复杂度较高的原因是,我们检查了很多没用的“跳跃”(目标位置已经被跳过了,不需要再检查一次)。我们需要更多性质来优化这个做法。为了便于理解,我们先讨论
\\nbanned
数组为空的情况。首先,和跳跃游戏一样,位置 $i$ 可以“跳跃”到的位置是“连续”的(事实上,是和奇偶性有关的连续)。具体来说,位置 $i$ 可以跳跃到 ${i + L, i + L + 2, i + L + 4, \\\\cdots, i + R}$ 这些位置,其中
\\n\\n
\\n- $L = \\\\max(-(k - 1), k - 1 - 2i)$。
\\n- $R = \\\\min(k - 1, -(k - 1) + 2(n - i - 1))$。
\\n$L$ 和 $R$ 需要进行这些计算的原因是,翻转的范围不能超过原数组的范围。举个简单的例子,假设原数组为 ${0, 0, 0, 0, 0, 1, 0, 0, 0}$,$k = 8$,那么位置 $5$ 能跳到的最左的位置为 $5 + (7 - 2 \\\\times 5) = 2$(通过翻转子数组 $[0, 7]$ 达成)。位置 $5$ 不可能跳到位置 $0$,因为这要求我们翻转子数组 $[-1, 6]$,但是 $-1$ 已经超出了下标范围。
\\n既然可以跳到的位置是“连续”的,那么我们可以考虑用一个
\\nset<int>
来保存还没有被跳过的位置(奇数和偶数要分成两个set<int>
放),通过lower_bound
方法找出第一个大于等于 $(i + L)$ 的位置,并把后面连续的位置加入 BFS 队列即可。直到我们遇到一个大于 $(i + R)$ 的位置则退出枚举。因为
\\nset<int>
里保存的都是没有被跳过的位置,我们就不会重复枚举一个已经被跳过的位置,这样枚举的次数只有 $\\\\mathcal{O}(n)$ 次。总体复杂度 $\\\\mathcal{O}(n\\\\log n)$。\\n
banned
数组不为空的做法也能自然得出,只要一开始不把banned
里的数放进set<int>
里即可。参考代码(c++)
\\n###c++
\\n\\n","description":"解法:BFS 本题可以看作是 leetcode 45. 跳跃游戏 II 的加强版。\\n\\n一开始很容易想到一个 BFS 的方法。当位置 $i$ 从 BFS 队列中出队时,枚举翻转哪个子数组,就能算出位置 $i$ 能跳到哪些位置。这样做的复杂度是 $\\\\mathcal{O}(n(n - k))$ 的,因为共有 $n$ 个位置,且共有 $(n - k + 1)$ 个子数组可以翻转。\\n\\n上述 BFS 做法复杂度较高的原因是,我们检查了很多没用的“跳跃”(目标位置已经被跳过了,不需要再检查一次)。我们需要更多性质来优化这个做法。为了便于理解,我们先讨论 banned 数组…","guid":"https://leetcode.cn/problems/minimum-reverse-operations//solution/bfstiao-yue-you-xi-jia-qiang-ban-by-tsre-juxn","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-04-02T04:05:40.935Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ 位运算/位掩码/BitSet","url":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/cwei-yun-suan-by-vijaysue-d7xl","content":"class Solution {\\npublic:\\n vector<int> minReverseOperations(int n, int p, vector<int>& banned, int K) {\\n vector<int> ans(n, -1);\\n if (K == 1) {\\n // 处理特殊情况 k = 1\\n ans[p] = 0;\\n return ans;\\n }\\n\\n unordered_set<int> ban;\\n for (int x : banned) ban.insert(x);\\n\\n // 把除了 p 和 banned 的所有位置,按奇偶性放进两个 set 里\\n // 这些就是我们还没被跳到的位置\\n set<int> st[2];\\n for (int i = 0; i < n; i++) if (i != p && ban.count(i) == 0) st[i % 2].insert(i);\\n\\n // bfs\\n queue<int> q;\\n q.push(p); ans[p] = 0;\\n while (!q.empty()) {\\n int now = q.front(); q.pop();\\n \\n // 计算可以跳的范围\\n int L, R;\\n L = max(-(K - 1), K - 1 - now * 2);\\n R = min(K - 1, -(K - 1) + (n - now - 1) * 2);\\n\\n // 寻找第一个大于等于 now + L 的位置,并开始枚举后面连续的位置\\n int x = (now + (K - 1)) % 2;\\n auto it = st[x].lower_bound(now + L);\\n while (it != st[x].end()) {\\n // 遇到了第一个大于 now + R 的位置,结束枚举\\n if (*it > now + R) break;\\n // 这个位置还没被跳过,但是可以从 now 一步跳过来\\n // 更新答案,并从 set<int> 里去掉\\n ans[*it] = ans[now] + 1;\\n q.push(*it);\\n it = st[x].erase(it);\\n }\\n }\\n\\n return ans;\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n return {__builtin_popcount(n & 0x5555), __builtin_popcount(n & (0x5555 << 1))};\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n vector<int> ans(2);\\n bitset<32> bs(n);\\n string s = bs.to_string();\\n for (int i = 32; i >= 0; --i) ans[!(i & 1)] += s[i] == \'1\';\\n return ans;\\n }\\n};\\n
###C++
\\n\\n","description":"###C++ class Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n vector<int> ans(2);\\n for (int i = 0; n != 0; i ^= 1, n >>= 1)\\n ans[i] += n & 1;\\n return ans;\\n }\\n};\\n
evenOddBit(int n) {\\n return {__builtin_popcount(n & 0x5555), __builtin_popcount(n & (0x5555 << 1))};\\n }\\n};\\n\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n vector evenOddBit(int n) {\\n vector ans(2);\\n bitset<32>…","guid":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/cwei-yun-suan-by-vijaysue-d7xl","author":"vijaysue","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-27T13:21:54.727Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【6319. 奇偶位数】题解:按位与","url":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/6319-qi-ou-wei-shu-ti-jie-an-wei-yu-by-f-kikj","content":" 解题思路
\\n1、每次把n右移1位,把n的当前位和1做与运算。
\\n执行结果
\\n\\n
{:width=400}
代码
\\n###java
\\n\\n","description":"解题思路 1、每次把n右移1位,把n的当前位和1做与运算。\\n\\n执行结果\\n\\n{:width=400}\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int[] evenOddBit(int n) {\\n int[] rs = new int[2];\\n // 32位整数\\n for (int j = 0; j < 32; j++) {\\n // 把n的当前位和1做与运算\\n if ((n & 1) == 1…","guid":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/6319-qi-ou-wei-shu-ti-jie-an-wei-yu-by-f-kikj","author":"fa-xing-bu-luan","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-20T08:10:48.104Z","media":[{"url":"https://pic.leetcode.cn/1679299863-LtEuMy-%E6%8D%95%E8%8E%B7.JPG","type":"photo","width":414,"height":129,"blurhash":"L05hc4%MMf~XEcxui%9FJ5%L$,IV"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【小羊肖恩】最优时间复杂度 —— O(n) 动态规划","url":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/xiao-yang-xiao-en-zui-you-shi-jian-fu-za-qfow","content":"class Solution {\\n public int[] evenOddBit(int n) {\\n int[] rs = new int[2];\\n // 32位整数\\n for (int j = 0; j < 32; j++) {\\n // 把n的当前位和1做与运算\\n if ((n & 1) == 1) {\\n // 当前位为1\\n if (j % 2 == 0) {\\n // 偶数下标+1\\n rs[0] += 1;\\n } else {\\n // 奇数下标+1\\n rs[1] += 1;\\n }\\n }\\n // 把n右移1位\\n n >>= 1;\\n }\\n return rs;\\n }\\n}\\n
把数组元素拆为若干组连续的公差为 $k$ 的序列,可以直接对每个序列进行线性 DP (类似于打家劫舍)结果相乘即可。
\\n\\n","description":"把数组元素拆为若干组连续的公差为 $k$ 的序列,可以直接对每个序列进行线性 DP (类似于打家劫舍)结果相乘即可。 powers = [1]\\nfor _ in range(20):\\n powers.append(powers[-1] * 2)\\n\\ndef count(vals):\\n acc = 1\\n last = 0\\n for count in vals:\\n last = (acc - last) * (powers[count] - 1)\\n acc += last\\n return acc…","guid":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/xiao-yang-xiao-en-zui-you-shi-jian-fu-za-qfow","author":"Yawn_Sean","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-19T05:46:46.832Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:从 O(logn) 到 O(1)(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/er-jin-zhi-ji-ben-cao-zuo-pythonjavacgo-o82o2","content":"powers = [1]\\nfor _ in range(20):\\n powers.append(powers[-1] * 2)\\n\\ndef count(vals):\\n acc = 1\\n last = 0\\n for count in vals:\\n last = (acc - last) * (powers[count] - 1)\\n acc += last\\n return acc\\n\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int], k: int) -> int:\\n cnt = Counter(nums)\\n ans = 1\\n for item in cnt:\\n if item - k not in cnt:\\n vals = []\\n while item in cnt:\\n vals.append(cnt[item])\\n item += k\\n ans *= count(vals)\\n return ans - 1\\n
方法一:遍历二进制数
\\n把 $n$ 当成一个二进制数来遍历。
\\n遍历的顺序是从低位到高位。具体来说,通过
\\nn & 1
取二进制的最低位,然后把 $n$ 右移一位,继续计算n & 1
,这样可以取到次低位。如此循环,直到 $n=0$ 为止。在遍历的过程中,统计奇偶下标比特位中的 $1$ 的个数。
\\n\\nclass Solution:\\n def evenOddBit(self, n: int) -> List[int]:\\n ans = [0, 0]\\n i = 0\\n while n:\\n ans[i] += n & 1\\n n >>= 1\\n i ^= 1 # 切换奇偶\\n return ans\\n
\\nclass Solution {\\n public int[] evenOddBit(int n) {\\n int[] ans = new int[2];\\n for (int i = 0; n > 0; n >>= 1) {\\n ans[i] += n & 1;\\n i ^= 1; // 切换奇偶\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n vector<int> ans(2);\\n for (int i = 0; n; n >>= 1) {\\n ans[i] += n & 1;\\n i ^= 1; // 切换奇偶\\n }\\n return ans;\\n }\\n};\\n
\\nfunc evenOddBit(n int) []int {\\n ans := make([]int, 2)\\n for i := 0; n > 0; n >>= 1 {\\n ans[i] += n & 1\\n i ^= 1 // 切换奇偶\\n }\\n return ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(\\\\log n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n方法二:位掩码 + 库函数
\\n利用位掩码
\\n0x55555555
(二进制的 $01010101\\\\cdots$),与 $n$ 计算 AND,即可取出所有偶数下标比特,然后用库函数统计二进制中的 $1$ 的个数。把
\\n0x55555555
右移一位,与 $n$ 计算 AND,即可取出所有奇数下标比特,然后用库函数统计二进制中的 $1$ 的个数。\\n\\n注:因为 $n$ 比较小,你也可以用
\\n0x555
作为位掩码。\\nclass Solution:\\n def evenOddBit(self, n: int) -> List[int]:\\n MASK = 0x55555555\\n return [(n & MASK).bit_count(), (n & (MASK >> 1)).bit_count()]\\n
\\nclass Solution {\\n public int[] evenOddBit(int n) {\\n final int MASK = 0x55555555;\\n return new int[]{Integer.bitCount(n & MASK), Integer.bitCount(n & (MASK >> 1))};\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> evenOddBit(int n) {\\n const unsigned MASK = 0x55555555;\\n return {popcount(n & MASK), popcount(n & (MASK >> 1))};\\n }\\n};\\n
\\nfunc evenOddBit(n int) []int {\\n const mask = 0x55555555\\n return []int{bits.OnesCount(uint(n & mask)), bits.OnesCount(uint(n & (mask >> 1)))}\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(1)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:遍历二进制数 把 $n$ 当成一个二进制数来遍历。\\n\\n遍历的顺序是从低位到高位。具体来说,通过 n & 1 取二进制的最低位,然后把 $n$ 右移一位,继续计算 n & 1,这样可以取到次低位。如此循环,直到 $n=0$ 为止。\\n\\n在遍历的过程中,统计奇偶下标比特位中的 $1$ 的个数。\\n\\nclass Solution:\\n def evenOddBit(self, n: int) -> List[int]:\\n ans = [0, 0]\\n i = 0\\n while n:\\n ans[i]…","guid":"https://leetcode.cn/problems/number-of-even-and-odd-bits//solution/er-jin-zhi-ji-ben-cao-zuo-pythonjavacgo-o82o2","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-19T04:13:54.052Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:回溯 / 动态规划(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/tao-lu-zi-ji-xing-hui-su-pythonjavacgo-b-fcgs","content":"方法一:回溯
\\n前置知识:回溯算法套路①子集型回溯【基础算法精讲 14】,包含两种写法。
\\n在枚举 78. 子集 的基础上,额外加个判断。
\\n在选择 $x=\\\\textit{nums}[i]$ 的时候,如果之前选过 $x-k$ 或 $x+k$,则不能选,否则可以选。
\\n代码实现时,可以用哈希表(或者数组)记录选过的数及其出现次数,从而 $\\\\mathcal{O}(1)$ 判断 $x-k$ 和 $x+k$ 是否选过。
\\n写法一:输入视角,选或不选
\\n\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int], k: int) -> int:\\n ans = -1 # 去掉空集\\n cnt = defaultdict(int)\\n\\n # nums[i] 选或不选\\n def dfs(i: int) -> None:\\n if i == len(nums):\\n nonlocal ans\\n ans += 1\\n return\\n dfs(i + 1) # 不选\\n x = nums[i]\\n if cnt[x - k] == 0 and cnt[x + k] == 0: # 可以选\\n cnt[x] += 1 # 选\\n dfs(i + 1) # 讨论 nums[i+1] 选或不选\\n cnt[x] -= 1 # 撤销,恢复现场\\n\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n public int beautifulSubsets(int[] nums, int k) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n dfs(0, nums, k, cnt);\\n return ans;\\n }\\n\\n private int ans = -1; // 去掉空集\\n\\n // nums[i] 选或不选\\n private void dfs(int i, int[] nums, int k, Map<Integer, Integer> cnt) {\\n if (i == nums.length) {\\n ans++;\\n return;\\n }\\n dfs(i + 1, nums, k, cnt); // 不选\\n int x = nums[i];\\n if (cnt.getOrDefault(x - k, 0) == 0 && cnt.getOrDefault(x + k, 0) == 0) { // 可以选\\n cnt.merge(x, 1, Integer::sum); // 选\\n dfs(i + 1, nums, k, cnt); // 讨论 nums[i+1] 选或不选\\n cnt.merge(x, -1, Integer::sum); // 撤销,恢复现场\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int k) {\\n int ans = -1; // 去掉空集\\n unordered_map<int, int> cnt;\\n\\n // nums[i] 选或不选\\n auto dfs = [&](this auto&& dfs, int i) -> void {\\n if (i == nums.size()) {\\n ans++;\\n return;\\n }\\n dfs(i + 1); // 不选\\n int x = nums[i];\\n if (cnt[x - k] == 0 && cnt[x + k] == 0) { // 可以选\\n cnt[x]++; // 选\\n dfs(i + 1); // 讨论 nums[i+1] 选或不选\\n cnt[x]--; // 撤销,恢复现场\\n }\\n };\\n\\n dfs(0);\\n return ans;\\n }\\n};\\n
\\nfunc beautifulSubsets(nums []int, k int) int {\\n ans := -1 // 去掉空集\\n cnt := map[int]int{}\\n\\n // nums[i] 选或不选\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == len(nums) {\\n ans++\\n return\\n }\\n dfs(i + 1) // 不选\\n x := nums[i]\\n if cnt[x-k] == 0 && cnt[x+k] == 0 { // 可以选\\n cnt[x]++ // 选\\n dfs(i + 1) // 讨论 nums[i+1] 选或不选\\n cnt[x]-- // 撤销,恢复现场\\n }\\n }\\n\\n dfs(0)\\n return ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(2^n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。搜索树是一棵高为 $\\\\mathcal{O}(n)$ 的二叉树,有 $\\\\mathcal{O}(2^n)$ 个节点。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n写法二:答案视角,枚举选哪个
\\n\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int], k: int) -> int:\\n ans = -1 # 去掉空集\\n cnt = defaultdict(int)\\n\\n # 在 [i, n-1] 中选一个数\\n def dfs(i: int) -> None:\\n nonlocal ans\\n ans += 1\\n if i == len(nums):\\n return\\n for j in range(i, len(nums)): # 枚举选哪个\\n x = nums[j]\\n if cnt[x - k] == 0 and cnt[x + k] == 0: # 可以选\\n cnt[x] += 1 # 选\\n dfs(j + 1) # 下一个数在 [j+1, n-1] 中选\\n cnt[x] -= 1 # 撤销,恢复现场\\n\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n public int beautifulSubsets(int[] nums, int k) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n dfs(0, nums, k, cnt);\\n return ans;\\n }\\n\\n private int ans = -1; // 去掉空集\\n\\n private int dfs(int i, int[] nums, int k, Map<Integer, Integer> cnt) {\\n ans++;\\n if (i == nums.length) {\\n return ans;\\n }\\n for (int j = i; j < nums.length; j++) { // 枚举选哪个\\n int x = nums[j];\\n if (cnt.getOrDefault(x - k, 0) == 0 && cnt.getOrDefault(x + k, 0) == 0) { // 可以选\\n cnt.merge(x, 1, Integer::sum); // 选\\n ans = dfs(j + 1, nums, k, cnt); // 下一个数在 [j+1, n-1] 中选\\n cnt.merge(x, -1, Integer::sum); // 撤销,恢复现场\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int k) {\\n int ans = -1; // 去掉空集\\n unordered_map<int, int> cnt;\\n\\n // 在 [i, n-1] 中选一个数\\n auto dfs = [&](this auto&& dfs, int i) -> void {\\n ans++;\\n if (i == nums.size()) {\\n return;\\n }\\n for (int j = i; j < nums.size(); j++) { // 枚举选哪个\\n int x = nums[j];\\n if (cnt[x - k] == 0 && cnt[x + k] == 0) { // 可以选\\n cnt[x]++; // 选\\n dfs(j + 1); // 下一个数在 [j+1, n-1] 中选\\n cnt[x]--; // 撤销,恢复现场\\n }\\n }\\n };\\n\\n dfs(0);\\n return ans;\\n }\\n};\\n
\\nfunc beautifulSubsets(nums []int, k int) int {\\n ans := -1 // 去掉空集\\n cnt := map[int]int{}\\n\\n var dfs func(int)\\n dfs = func(i int) {\\n ans++\\n if i == len(nums) {\\n return\\n }\\n for j := i; j < len(nums); j++ { // 枚举选哪个\\n x := nums[j]\\n if cnt[x-k] == 0 && cnt[x+k] == 0 { // 可以选\\n cnt[x]++ // 选\\n dfs(j + 1) // 下一个数在 [j+1, n-1] 中选\\n cnt[x]-- // 撤销,恢复现场\\n }\\n }\\n }\\n\\n dfs(0)\\n return ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(2^n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:动态规划
\\n例如 $\\\\textit{nums}=[1,2,3,4,7,8],\\\\ k=2$,我们可以把数组按照模 $k$ 的结果,分成两组:
\\n\\n
\\n- $[2,4,8]$,这些数模 $k$ 等于 $0$。
\\n- $[1,3,7]$,这些数模 $k$ 等于 $1$。
\\n从第一组选一些数,从第二组选一些数。第一组中的数字 $x$ 和第二组中的数字 $y$,二者相差一定不等于 $k$(不同余)。
\\n这意味着我们只需考虑每组内选数字的方案数,然后根据乘法原理,把各个组的方案数相乘,即为答案。
\\n所以按照模 $k$ 的结果分组,每一组用有序集合(或者哈希表)统计元素及其出现次数。
\\n每一组怎么思考呢?
\\n把有序集合的 key 转成列表 $a$(或者把哈希表的 key 排序)。例如 $a=[1,3,7]$,相邻的数字如果相差恰好为 $k$,那么不能同时选。
\\n\\n设 $a$ 的长度为 $m$。考虑最大的数 $a[m-1]$ 选或不选:
\\n\\n
\\n- 不选 $a[m-1]$,那么问题变成 $m-1$ 个数的子问题。
\\n- 选 $a[m-1]$:\\n
\\n\\n
\\n- 设 $c$ 为 $a[m-1]$ 的出现次数,由于大小为 $c$ 的集合有 $2^c-1$ 个非空子集,所以选至少一个 $a[m-1]$ 的方案数为 $2^c-1$。
\\n- 如果 $a[m-1]-a[m-2] = k$,那么 $a[m-2]$ 不能选,问题变成 $m-2$ 个数的子问题。
\\n- 如果 $a[m-1]-a[m-2] \\\\ne k$,那么 $a[m-2]$ 可选可不选,问题变成 $m-1$ 个数的子问题。
\\n类似打家劫舍,定义 $f[i+1]$ 表示在 $a[0]$ 到 $a[i]$ 中选数的方案数:
\\n\\n
\\n- 不选 $a[i]$,那么问题变成在 $a[0]$ 到 $a[i-1]$ 中选数的方案数,即 $f[i+1] = f[i]$。
\\n- 选 $a[i]$ 且 $a[i]-a[i-1]=k$,那么问题变成在 $a[0]$ 到 $a[i-2]$ 中选数的方案数,即 $f[i+1] = f[i-1]\\\\cdot (2^{c_i}-1)$。
\\n- 选 $a[i]$ 且 $a[i]-a[i-1]\\\\ne k$,那么问题变成在 $a[0]$ 到 $a[i-1]$ 中选数的方案数,即 $f[i+1] = f[i]\\\\cdot (2^{c_i}-1)$。
\\n其中不选和选互斥,方案数根据加法原理相加。
\\n整理得
\\n$$
\\n
\\nf[i+1] =
\\n\\\\begin{cases}
\\nf[i] + f[i-1]\\\\cdot (2^{c_i}-1), & a[i]-a[i-1]=k \\\\
\\nf[i]\\\\cdot 2^{c_i}, & a[i]-a[i-1]\\\\ne k \\\\
\\n\\\\end{cases}
\\n$$其中 $c_i$ 为 $a[i]$ 的出现次数。
\\n初始值:
\\n\\n
\\n- $f[0]=1$。空集算一个方案。
\\n- $f[1] = 2^{c_0}$。因为 $a[0]$ 左边没有数字,需要单独计算选 $a[0]$ 的方案数,即 $2^{c_0}-1$,加上不选 $a[0]$ 的方案数 $1$,所以 $f[1] = 2^{c_0}$。
\\n这一组的答案:$f[m]$,即在 $a[0]$ 到 $a[m-1]$ 中选数的方案数。
\\n最后,根据乘法原理,把每组的答案相乘,即为答案。但是,虽然每一组都可以不选数,但不能总共一个数都不选,所以要把空集去掉,也就是最终答案要减一。
\\n\\n写法一
\\n\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int], k: int) -> int:\\n groups = defaultdict(Counter)\\n for x in nums:\\n # 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups[x % k][x] += 1\\n\\n ans = 1\\n for cnt in groups.values():\\n # 计算这一组的方案数\\n a = sorted(cnt.items())\\n m = len(a)\\n f = [0] * (m + 1)\\n f[0] = 1\\n f[1] = 1 << a[0][1]\\n for i in range(1, m):\\n if a[i][0] - a[i - 1][0] == k:\\n f[i + 1] = f[i] + f[i - 1] * ((1 << a[i][1]) - 1)\\n else:\\n f[i + 1] = f[i] << a[i][1]\\n ans *= f[m] # 每组方案数相乘\\n return ans - 1 # 去掉空集\\n
\\nclass Solution {\\n public int beautifulSubsets(int[] nums, int k) {\\n Map<Integer, TreeMap<Integer, Integer>> groups = new HashMap<>();\\n for (int x : nums) {\\n // 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups.computeIfAbsent(x % k, i -> new TreeMap<>()).merge(x, 1, Integer::sum);\\n }\\n\\n int ans = 1;\\n for (TreeMap<Integer, Integer> cnt : groups.values()) {\\n // 计算这一组的方案数\\n int m = cnt.size();\\n int[] f = new int[m + 1];\\n f[0] = 1;\\n int i = 1;\\n int pre = 0;\\n for (Map.Entry<Integer, Integer> e : cnt.entrySet()) {\\n int x = e.getKey();\\n int c = e.getValue();\\n if (i > 1 && x - pre == k) {\\n f[i] = f[i - 1] + f[i - 2] * ((1 << c) - 1);\\n } else {\\n f[i] = f[i - 1] << c;\\n }\\n pre = x;\\n i++;\\n }\\n ans *= f[m]; // 每组方案数相乘\\n }\\n return ans - 1; // 去掉空集\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int k) {\\n unordered_map<int, map<int, int>> groups;\\n for (int x : nums) {\\n // 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups[x % k][x]++;\\n }\\n\\n int ans = 1;\\n for (auto& [_, cnt] : groups) {\\n // 计算这一组的方案数\\n int m = cnt.size();\\n vector<int> f(m + 1);\\n auto it = cnt.begin();\\n f[0] = 1;\\n f[1] = 1 << it++->second;\\n for (int i = 1; i < m; i++, it++) {\\n auto [x, c] = *it;\\n if (x - prev(it)->first == k) {\\n f[i + 1] = f[i] + f[i - 1] * ((1 << c) - 1);\\n } else {\\n f[i + 1] = f[i] << c;\\n }\\n }\\n ans *= f[m]; // 每组方案数相乘\\n }\\n return ans - 1; // 去掉空集\\n }\\n};\\n
\\nfunc beautifulSubsets(nums []int, k int) int {\\n groups := map[int]map[int]int{}\\n for _, x := range nums {\\n // 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n if groups[x%k] == nil {\\n groups[x%k] = map[int]int{}\\n }\\n groups[x%k][x]++\\n }\\n\\n ans := 1\\n for _, cnt := range groups {\\n // 计算这一组的方案数\\n a := slices.Sorted(maps.Keys(cnt))\\n m := len(a)\\n f := make([]int, m+1)\\n f[0] = 1\\n f[1] = 1 << cnt[a[0]]\\n for i := 1; i < m; i++ {\\n c := cnt[a[i]]\\n if a[i]-a[i-1] == k {\\n f[i+1] = f[i] + f[i-1]*(1<<c-1)\\n } else {\\n f[i+1] = f[i] << c\\n }\\n }\\n ans *= f[m] // 每组方案数相乘\\n }\\n return ans - 1 // 去掉空集\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。瓶颈在排序/维护有序集合上。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n写法二:空间优化
\\n同打家劫舍,用两个变量 $f_0$ 和 $f_1$ 滚动计算。
\\n\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int], k: int) -> int:\\n groups = defaultdict(Counter)\\n for x in nums:\\n # 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups[x % k][x] += 1\\n\\n ans = 1\\n for cnt in groups.values():\\n # 计算这一组的方案数\\n a = sorted(cnt.items())\\n f0, f1 = 1, 1 << a[0][1]\\n for (pre, _), (x, c) in pairwise(a):\\n if x - pre == k:\\n f0, f1 = f1, f1 + f0 * ((1 << c) - 1)\\n else:\\n f0, f1 = f1, f1 << c\\n ans *= f1 # 每组方案数相乘\\n return ans - 1 # 去掉空集\\n
\\nclass Solution {\\n public int beautifulSubsets(int[] nums, int k) {\\n Map<Integer, TreeMap<Integer, Integer>> groups = new HashMap<>();\\n for (int x : nums) {\\n // 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups.computeIfAbsent(x % k, i -> new TreeMap<>()).merge(x, 1, Integer::sum);\\n }\\n\\n int ans = 1;\\n for (TreeMap<Integer, Integer> cnt : groups.values()) {\\n // 计算这一组的方案数\\n int f0 = 1;\\n int f1 = 1; // 下面第一轮循环无论进入哪个分支,都会算出 f1 = 1 << c0\\n int pre = 0; // 可以初始化成任意值\\n for (Map.Entry<Integer, Integer> e : cnt.entrySet()) {\\n int x = e.getKey();\\n int c = e.getValue();\\n int newF = x - pre == k ? f1 + f0 * ((1 << c) - 1) : f1 << c;\\n f0 = f1;\\n f1 = newF;\\n pre = x;\\n }\\n ans *= f1; // 每组方案数相乘\\n }\\n return ans - 1; // 去掉空集\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int k) {\\n unordered_map<int, map<int, int>> groups;\\n for (int x : nums) {\\n // 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\n groups[x % k][x]++;\\n }\\n\\n int ans = 1;\\n for (auto& [_, cnt] : groups) {\\n // 计算这一组的方案数\\n auto it = cnt.begin();\\n int f0 = 1, f1 = 1 << it->second;\\n for (it++; it != cnt.end(); it++) {\\n auto [x, c] = *it;\\n int new_f = x - prev(it)->first == k ? f1 + f0 * ((1 << c) - 1) : f1 << c;\\n f0 = f1;\\n f1 = new_f;\\n }\\n ans *= f1; // 每组方案数相乘\\n }\\n return ans - 1; // 去掉空集\\n }\\n};\\n
\\nfunc beautifulSubsets(nums []int, k int) int {\\ngroups := map[int]map[int]int{}\\nfor _, x := range nums {\\n// 模 k 同余的数分到同一组,记录元素 x 及其出现次数\\nif groups[x%k] == nil {\\ngroups[x%k] = map[int]int{}\\n}\\ngroups[x%k][x]++\\n}\\n\\nans := 1\\nfor _, cnt := range groups {\\n// 计算这一组的方案数\\na := slices.Sorted(maps.Keys(cnt))\\nf0, f1, newF := 1, 1<<cnt[a[0]], 0\\nfor i := 1; i < len(a); i++ {\\nc := cnt[a[i]]\\nif a[i]-a[i-1] == k {\\nnewF = f1 + f0*(1<<c-1)\\n} else {\\nnewF = f1 << c\\n}\\nf0 = f1\\nf1 = newF\\n}\\nans *= f1 // 每组方案数相乘\\n}\\nreturn ans - 1 // 去掉空集\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。瓶颈在排序/维护有序集合上。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:回溯 前置知识:回溯算法套路①子集型回溯【基础算法精讲 14】,包含两种写法。\\n\\n在枚举 78. 子集 的基础上,额外加个判断。\\n\\n在选择 $x=\\\\textit{nums}[i]$ 的时候,如果之前选过 $x-k$ 或 $x+k$,则不能选,否则可以选。\\n\\n代码实现时,可以用哈希表(或者数组)记录选过的数及其出现次数,从而 $\\\\mathcal{O}(1)$ 判断 $x-k$ 和 $x+k$ 是否选过。\\n\\n写法一:输入视角,选或不选\\nclass Solution:\\n def beautifulSubsets(self, nums: List[int]…","guid":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/tao-lu-zi-ji-xing-hui-su-pythonjavacgo-b-fcgs","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-19T04:09:34.179Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种解法:枚举 or 递推","url":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/liang-chong-jie-fa-mei-ju-or-di-tui-by-t-al4w","content":"解法 1:枚举
\\n因为 $n$ 只有 $20$,容易想到通过 dfs 的方式枚举所有子集并检查是否满足要求。
\\n当我们考虑第 $i$ 个数是否加入子集时,有以下两种可能:
\\n\\n
\\n- 把第 $i$ 个数加入子集,那么子集里不能存在
\\nnums[i] + k
和nums[i] - k
。可以用一个数组作为哈希表,记录目前子集里有哪些数,就可以 $\\\\mathcal{O}(1)$ 检查。- 不把第 $i$ 个数加入子集,无需检查。
\\n复杂度 $\\\\mathcal{O}(2^n)$。
\\n参考代码(c++)
\\n###c++
\\n\\nclass Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int k) {\\n int n = nums.size();\\n int mx = 0;\\n for (int x : nums) mx = max(mx, x);\\n\\n // vis[i] 表示目前子集中有几个 i\\n int vis[mx + 1];\\n memset(vis, 0, sizeof(vis));\\n\\n int ans = 0;\\n function<void(int)> dfs = [&](int pos) {\\n // dfs 结束,是合法子集\\n if (pos == n) { ans++; return; }\\n\\n // === 分支 1:尝试把第 pos 个数加入子集 ===\\n vis[nums[pos]]++;\\n // 检查是否存在 nums[pos] + k 和 nums[pos] - k\\n bool flag = true;\\n if (nums[pos] + k <= mx && vis[nums[pos] + k] > 0) flag = false;\\n if (nums[pos] - k >= 0 && vis[nums[pos] - k] > 0) flag = false;\\n // 若检查通过则可以把第 pos 个数加入子集\\n if (flag) dfs(pos + 1);\\n // 撤销第 pos 个数的影响\\n vis[nums[pos]]--;\\n\\n // === 分支 2:第 pos 个数不加入子集 ===\\n dfs(pos + 1);\\n };\\n dfs(0);\\n // 题目要求非空子集,答案减 1\\n return ans - 1;\\n }\\n};\\n
解法 2:递推
\\n当
\\nnums[i] - nums[j] == k
时,nums[i]
和nums[j]
才无法同时选择。也就是说,(nums[i] - nums[j]) mod k == 0
时,nums[i]
和nums[j]
才可能互相影响。因此我们可以把原序列按 $\\\\mod k$ 的值分组,两组之间的选法是不会互相影响的。因此,我们只需要单独计算每组的选法,并把每组选法乘起来就是答案。
\\n接下来考虑同一组的选法怎么计算。注意到同一组内任意两个 不同 的值之差只可能是 $k, 2k, ...$,因此我们把同一组内的数 先去重,再从小到大排序后,只有相邻的数才有可能无法同时选择。
\\n令 $a_i$ 表示同一组内 去重后 第 $i$ 小的数($i$ 从 $0$ 开始),$c_i$ 表示 $a_i$ 的出现次数,$f(i, 0/1)$ 表示 $a_i$ 是否加入子集,则递推方程为
\\n$$
\\n
\\nf(i, 0) = f(i - 1, 0) + f(i - 1, 1)
\\n$$解析:只要 $a_i$ 不选,那前面怎么选都没关系。如果只考虑所有 $a_i$,不选 $a_i$ 的方案数只有 $1$ 种。
\\n$$
\\n
\\nf(i, 1) = \\\\begin{cases}
\\n(f(i - 1, 0) + f(i - 1, 1)) \\\\times (2^{c_i} - 1) & \\\\text{if } a_i - a_{i - 1} \\\\ne k \\\\
\\nf(i - 1, 0) \\\\times (2^{c_i} - 1) & \\\\text{if } a_i - a_{i - 1} = k
\\n\\\\end{cases}
\\n$$解析:如果 $a_i$ 和 $a_{i - 1}$ 要同时选,那它们的差不能为 $k$。如果只考虑所有 $a_i$,至少选择一个 $a_i$ 的方案数有 $(2^{c_i} - 1)$ 种。
\\n初值 $f(0, 0) = 1$,$f(0, 1) = 2^{c_0} - 1$,$(f(n - 1, 0) + f(n - 1, 1))$ 就是答案。
\\n递推的复杂度是 $\\\\mathcal{O}(n)$ 的,总体复杂度 $\\\\mathcal{O}(n\\\\log n)$,因为要用一个有序的数据结构维护每一组内有哪些数。
\\n本题和经典递推题 leetcode 198.打家劫舍 的思路是一样的。大家可以参考该题做补充练习。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法 1:枚举 因为 $n$ 只有 $20$,容易想到通过 dfs 的方式枚举所有子集并检查是否满足要求。\\n\\n当我们考虑第 $i$ 个数是否加入子集时,有以下两种可能:\\n\\n把第 $i$ 个数加入子集,那么子集里不能存在 nums[i] + k 和 nums[i] - k。可以用一个数组作为哈希表,记录目前子集里有哪些数,就可以 $\\\\mathcal{O}(1)$ 检查。\\n不把第 $i$ 个数加入子集,无需检查。\\n\\n复杂度 $\\\\mathcal{O}(2^n)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n int…","guid":"https://leetcode.cn/problems/the-number-of-beautiful-subsets//solution/liang-chong-jie-fa-mei-ju-or-di-tui-by-t-al4w","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-19T04:09:24.907Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"参考灵神的,更清楚一些!!","url":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/can-kao-ling-shen-de-geng-qing-chu-yi-xi-uupy","content":"class Solution {\\npublic:\\n int beautifulSubsets(vector<int>& nums, int K) {\\n // 把每个数按 mod k 分组\\n unordered_map<int, map<int, int>> groups;\\n for (int x : nums) {\\n // t 是 x 属于哪一组\\n int t = (x % K + K) % K;\\n // groups[t][x] 表示第 t 组里,x 有几个\\n groups[t][x]++;\\n }\\n\\n const int MOD = 1e9 + 7;\\n // P[i]:2 的 i 次方\\n long long P[nums.size() + 1];\\n P[0] = 1;\\n for (int i = 1; i <= nums.size(); i++) P[i] = P[i - 1] * 2 % MOD;\\n \\n long long ans = 1;\\n // 对每个分组计算组内的选法\\n for (auto it = groups.begin(); it != groups.end(); it++) {\\n // map 是有序的\\n map<int, int> &mp = it->second;\\n int n = mp.size();\\n long long f[n][2];\\n memset(f, 0, sizeof(f));\\n // 初值\\n f[0][0] = 1;\\n f[0][1] = (P[mp.begin()->second] - 1 + MOD) % MOD;\\n // 套递推方程\\n int i = 1;\\n for (auto it2 = next(mp.begin()); it2 != mp.end(); it2++, i++) {\\n // last:上一个不同的数是几\\n // now:当前数是几\\n // cnt:当前数有几个\\n int last = prev(it2)->first, now = it2->first, cnt = it2->second;\\n f[i][0] = (f[i - 1][0] + f[i - 1][1]) % MOD;\\n if (now - last == K) f[i][1] = f[i - 1][0] * (P[cnt] - 1 + MOD) % MOD;\\n else f[i][1] = (f[i - 1][0] + f[i - 1][1]) * (P[cnt] - 1 + MOD) % MOD;\\n }\\n // 每一组的答案乘起来,就是最终答案\\n ans = ans * (f[n - 1][0] + f[n - 1][1]) % MOD;\\n }\\n // 题目要求非空子集,答案减 1\\n return (ans - 1 + MOD) % MOD;\\n }\\n};\\n
解题思路:前缀异或和、
\\n仔细分析题目意思就可知道:任何满足题目要求的子数组必须满足以下条件:子数组中每个二进制数位上出现的1的个数必须为偶数个。
\\n例如:[3,1,2]转化为二进制则为[011,001,010],其中第一位1的个数为2,第二位1的个数为2,第三位为0,所以由此可以想到异或操作,因为异或操作可以将1进行消除(1与1异或等于0),也就是说满足题目要求的子数组的异或和等于0
\\n此时,我们已经将题目转化为:求出满足异或和等于0的子数组的个数,但是这个时候暴解时间复杂度为n的3次方,可能超出时间限制
\\n我们需要了解异或的如下两条性质:
\\n\\n\\n任何数与本身异或都等于0
\\n
\\n任何数与0异或都等于其本身也就是说
\\n\\n\\na4 ^ a5 = (a1 ^ a2 ^ a3 ^ a4 ^ a5 ) ^ (a1 ^ a2 ^ a3)
\\n那么当
\\n\\n\\na1 ^ a2 ^ a3 ^ a4 ^ a5 = a1 ^ a2 ^ a3时,a4 ^ a5 = 0
\\n知道了上述性质,我们就可以根据前面的异或和找到以s[i]结尾的异或和等于0的子数组,只需要找到前面有哪些子数组异或和等于从零到i的异或和
\\n代码如下:
\\n\\n","description":"解题思路:前缀异或和、 仔细分析题目意思就可知道:任何满足题目要求的子数组必须满足以下条件:子数组中每个二进制数位上出现的1的个数必须为偶数个。\\n例如:[3,1,2]转化为二进制则为[011,001,010],其中第一位1的个数为2,第二位1的个数为2,第三位为0,所以由此可以想到异或操作,因为异或操作可以将1进行消除(1与1异或等于0),也就是说满足题目要求的子数组的异或和等于0\\n此时,我们已经将题目转化为:求出满足异或和等于0的子数组的个数,但是这个时候暴解时间复杂度为n的3次方,可能超出时间限制\\n我们需要了解异或的如下两条性质:\\n\\n任何数与本身异或…","guid":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/can-kao-ling-shen-de-geng-qing-chu-yi-xi-uupy","author":"yu-meng-tong-xing-d","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-12T06:20:58.604Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"计数","url":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/ji-shu-by-tsreaper-wbmq","content":"class Solution {\\npublic:\\n long long beautifulSubarrays(vector<int>& nums) {\\n int s = 0;\\n // set<int> dict;\\n unordered_map<int,int> dict; //该字典的first为异或和,second为异或和出现的次数\\n dict[0] = 1;\\n long long count = 0;\\n for(int i = 0; i < nums.size(); i++)\\n {\\n s = s ^ nums[i];\\n if(dict.count(s))\\n {\\n count += dict[s];\\n dict[s]++;\\n }\\n else\\n dict[s] = 1;\\n }\\n \\n return count;\\n }\\n};\\n
解法:计数
\\n由于每次操作要减去两个数的 $2^k$,那么所有数中,$2^k$ 出现的次数之和必须是偶数。换一种说法,就是所有数的异或和必须是 $0$。
\\n设
\\np[i]
表示前 $i$ 个数的异或值。若子数组nums[j..i]
的异或和为 $0$,则p[j - 1] ^ p[i] == 0
。这样题目就变为类似于 leetcode 560. 和为 K 的子数组。我们从小到大枚举下标 $i$,并维护
\\ncnt[x]
表示p[j] == x
且0 <= j < i
的下标 $j$ 有几个,每次答案增加cnt[p[i]]
即可。复杂度 $\\\\mathcal{O}(n)$。参考代码(c++)
\\n###c++
\\n\\n","description":"解法:计数 由于每次操作要减去两个数的 $2^k$,那么所有数中,$2^k$ 出现的次数之和必须是偶数。换一种说法,就是所有数的异或和必须是 $0$。\\n\\n设 p[i] 表示前 $i$ 个数的异或值。若子数组 nums[j..i] 的异或和为 $0$,则 p[j - 1] ^ p[i] == 0。\\n\\n这样题目就变为类似于 leetcode 560. 和为 K 的子数组。我们从小到大枚举下标 $i$,并维护 cnt[x] 表示 p[j] == x 且 0 <= j < i 的下标 $j$ 有几个,每次答案增加 cnt[p[i]] 即可。复杂度 $\\\\mathcal…","guid":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/ji-shu-by-tsreaper-wbmq","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-12T04:14:31.522Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"转化成 560 题(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/tao-lu-qian-zhui-he-ha-xi-biao-pythonjav-3fna","content":"class Solution {\\npublic:\\n long long beautifulSubarrays(vector<int>& nums) {\\n int n = nums.size();\\n unordered_map<int, int> cnt;\\n cnt[0] = 1;\\n\\n int now = 0;\\n long long ans = 0;\\n for (int i = 0; i < n; i++) {\\n now ^= nums[i];\\n ans += cnt[now];\\n cnt[now]++;\\n }\\n return ans;\\n }\\n};\\n
示例 1 说 $[4,3,1,2,4]$ 是个美丽子数组,来看看为什么。
\\n把每个数都写成二进制:
\\n$$
\\n
\\n\\\\begin{aligned}
\\n100 \\\\
\\n011 \\\\
\\n001 \\\\
\\n010 \\\\
\\n100 \\\\
\\n\\\\end{aligned}
\\n$$一次操作,可以把某一列中的两个 $1$ 都变成 $0$。
\\n多次操作,可以把某一列中的偶数个 $1$ 都变成 $0$。
\\n所以,如果每一列都有偶数个 $1$,我们就能把所有数都变成 $0$。反之,如果某一列有奇数个 $1$,就不行。
\\n注意到 $1\\\\oplus 1=0$($\\\\oplus$ 表示异或),偶数个 $1$ 的异或和等于 $0$。
\\n\\n
\\n- 如果每一列都有偶数个 $1$,那么所有数的异或和必然等于 $0$。
\\n- 如果某一列有奇数个 $1$,那么所有数的异或和必然不等于 $0$。
\\n所以美丽子数组等价于:
\\n\\n
\\n- 子数组的异或和等于 $0$。
\\n由于异或的运算性质类似加法,可以用 560. 和为 K 的子数组 的做法(前缀和+哈希表)解决。本题相当于 $k=0$。
\\n为什么代码要初始化 $\\\\textit{cnt}[0] = 1$?为什么要先更新 $\\\\textit{ans}$ 再更新 $\\\\textit{cnt}[s]$?请看 560 题 我的题解。
\\n\\nclass Solution:\\n def beautifulSubarrays(self, nums: List[int]) -> int:\\n ans = s = 0\\n cnt = defaultdict(int)\\n cnt[0] = 1\\n for x in nums:\\n s ^= x\\n ans += cnt[s]\\n cnt[s] += 1\\n return ans\\n
\\nclass Solution {\\n public long beautifulSubarrays(int[] nums) {\\n long ans = 0;\\n int s = 0;\\n Map<Integer, Integer> cnt = new HashMap<>(nums.length + 1); // 预分配空间\\n cnt.put(0, 1);\\n for (int x : nums) {\\n s ^= x;\\n int c = cnt.getOrDefault(s, 0);\\n ans += c;\\n cnt.put(s, c + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long beautifulSubarrays(vector<int>& nums) {\\n long long ans = 0;\\n int s = 0;\\n unordered_map<int, int> cnt{{0, 1}};\\n for (int x : nums) {\\n s ^= x;\\n ans += cnt[s]++;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc beautifulSubarrays(nums []int) (ans int64) {\\n s := 0\\n cnt := make(map[int]int, len(nums)+1) // 预分配空间\\n cnt[0] = 1\\n for _, x := range nums {\\n s ^= x\\n ans += int64(cnt[s])\\n cnt[s]++\\n }\\n return\\n}\\n
\\nvar beautifulSubarrays = function(nums) {\\n let ans = 0, s = 0;\\n const cnt = new Map();\\n cnt.set(0, 1);\\n for (const x of nums) {\\n s ^= x;\\n const c = cnt.get(s) ?? 0;\\n ans += c;\\n cnt.set(s, c + 1);\\n }\\n return ans;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn beautiful_subarrays(nums: Vec<i32>) -> i64 {\\n let mut ans = 0;\\n let mut s = 0;\\n let mut cnt = HashMap::with_capacity(nums.len() + 1); // 预分配空间\\n cnt.insert(0, 1);\\n for x in nums {\\n s ^= x;\\n let e = cnt.entry(s).or_insert(0);\\n ans += *e as i64;\\n *e += 1;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n更多相似题目,见下面数据结构题单中的「§1.2 前缀和与哈希表」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"示例 1 说 $[4,3,1,2,4]$ 是个美丽子数组,来看看为什么。 把每个数都写成二进制:\\n\\n$$\\n \\\\begin{aligned}\\n 100 \\\\\\n 011 \\\\\\n 001 \\\\\\n 010 \\\\\\n 100 \\\\\\n \\\\end{aligned}\\n $$\\n\\n一次操作,可以把某一列中的两个 $1$ 都变成 $0$。\\n\\n多次操作,可以把某一列中的偶数个 $1$ 都变成 $0$。\\n\\n所以,如果每一列都有偶数个 $1$,我们就能把所有数都变成 $0$。反之,如果某一列有奇数个 $1$,就不行。\\n\\n注意到 $1\\\\oplus 1=0$($\\\\oplus…","guid":"https://leetcode.cn/problems/count-the-number-of-beautiful-subarrays//solution/tao-lu-qian-zhui-he-ha-xi-biao-pythonjav-3fna","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-03-12T04:09:05.764Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【小羊肖恩】贪心 + 二分:简单提出数学上的说明思路","url":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/xiao-yang-xiao-en-tan-xin-er-fen-jian-da-0mj2","content":"我们发现总标记数量一致的情况下,添加标记的方式可能有多种多样的,这样的不确定性是我们不想要的,因此我们尝试研究在标记数量一定的情况下,我们应该如何添加标记。
\\n重要发现: 在可以标记 $2m$ 个数的情况下,我们可以取最小的 $m$ 个数和最大的 $m$ 个数两两配对,也可以实现标记 $2m$ 个数的目标。
\\n简单说明: 想要证明该结论可以分为以下几步:
\\n\\n
\\n- \\n
\\n从一个合法的标记方式出发,配对的数分为小数和大数两组,则小数排序后和大数排序后一一对应,必仍然满足条件。
\\n- \\n
\\n接下来贪心地把小数变小,大数变大即可。
\\n因此我们对于一个固定的下标对数 $m$ 可以简单地判断是否存在对应的标记方式。
\\n而实际上随着下标对数的增加,能否构造对应的标记方式单调地由 $True$ 变为 $False$,因此考虑二分解决问题。
\\n时间复杂度为 $\\\\mathcal{O}(n \\\\log n)$,空间复杂度为 $\\\\mathcal{O}(1)$。
\\n具体代码如下——
\\n\\n","description":"我们发现总标记数量一致的情况下,添加标记的方式可能有多种多样的,这样的不确定性是我们不想要的,因此我们尝试研究在标记数量一定的情况下,我们应该如何添加标记。 重要发现: 在可以标记 $2m$ 个数的情况下,我们可以取最小的 $m$ 个数和最大的 $m$ 个数两两配对,也可以实现标记 $2m$ 个数的目标。\\n\\n简单说明: 想要证明该结论可以分为以下几步:\\n\\n从一个合法的标记方式出发,配对的数分为小数和大数两组,则小数排序后和大数排序后一一对应,必仍然满足条件。\\n\\n接下来贪心地把小数变小,大数变大即可。\\n\\n因此我们对于一个固定的下标对数 $m…","guid":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/xiao-yang-xiao-en-tan-xin-er-fen-jian-da-0mj2","author":"Yawn_Sean","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-26T04:11:53.916Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:二分答案/同向双指针+贪心(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/er-fen-da-an-pythonjavacgo-by-endlessche-t9f5","content":"class Solution:\\n def maxNumOfMarkedIndices(self, nums: List[int]) -> int:\\n n = len(nums)\\n\\n nums.sort()\\n l, r = 0, len(nums) // 2\\n\\n while l <= r:\\n m = (l + r) // 2\\n\\n flag = True\\n for i in range(m):\\n if nums[i] * 2 > nums[n-m+i]:\\n flag = False\\n\\n if flag: l = m + 1\\n else: r = m - 1\\n\\n return r * 2\\n
方法一:二分答案
\\n提示 1
\\n如果 $2\\\\cdot\\\\textit{nums}\\\\le \\\\textit{nums}[j]$,则称 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[j]$ 匹配。
\\n如果可以匹配 $k$ 对,那么也可以匹配小于 $k$ 对,去掉一些数对即可做到。
\\n如果无法匹配 $k$ 对,那么也无法匹配大于 $k$ 对(反证法)。
\\n所以 $k$ 越大,越无法选出 $k$ 个能匹配的数对。有单调性,就可以二分答案。二分算法的理论讲解见【基础算法精讲 04】。
\\n提示 2
\\n现在问题变成:
\\n\\n
\\n- 能否从 $\\\\textit{nums}$ 中选出 $k$ 个能匹配的数对?
\\n要让哪些数匹配呢?
\\n结论:从小到大排序后,如果存在 $k$ 对匹配,那么一定可以让最小的 $k$ 个数与最大的 $k$ 个数匹配。
\\n证明:假设不是最小的 $k$ 个数与最大的 $k$ 个数匹配,那么我们总是可以把 $\\\\textit{nums}[i]$ 替换成比它小的且不在匹配中的数,这仍然是匹配的;同理,把 $\\\\textit{nums}[j]$ 替换成比它大的且不在匹配中的数,这仍然是匹配的。所以如果存在 $k$ 对匹配,那么一定可以让最小的 $k$ 个数和最大的 $k$ 个数匹配。
\\n反过来说,如果最小的 $k$ 个数无法和最大的 $k$ 个数匹配,则任意 $k$ 对都无法匹配。(也可以用反证法证明)
\\n从小到大排序后,$\\\\textit{nums}[0]$ 要与 $\\\\textit{nums}[n-k]$ 匹配。如果不这样做,$\\\\textit{nums}[0]$ 与在 $\\\\textit{nums}[n-k]$ 右侧的数匹配,相当于占了一个位置,那么后续要选个更大的 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[n-k]$ 匹配,这不一定能匹配得上。
\\n一般地,$\\\\textit{nums}[i]$ 要与 $\\\\textit{nums}[n-k+i]$ 匹配。
\\n如果对于所有的 $0\\\\le i < k$,都满足 $2\\\\cdot\\\\textit{nums}[i]\\\\le\\\\textit{nums}[n-k+i]$,那么就可以从 $\\\\textit{nums}$ 中选出 $k$ 个能匹配的数对。
\\n细节
\\n下面代码采用开区间二分,这仅仅是二分的一种写法,使用闭区间或者半闭半开区间都是可以的。
\\n\\n
\\n- 开区间左端点初始值:$0$。无论 $\\\\textit{nums}$ 是什么样,一定能选出 $0$ 个匹配。
\\n- 开区间右端点初始值:$\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor+1$。最多能选 $\\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$ 个匹配,再多一个就不行了。
\\n对于开区间写法,简单来说
\\ncheck(mid) == true
时更新的是谁,最后就返回谁。相比其他二分写法,开区间写法不需要思考加一减一等细节,更简单。推荐使用开区间写二分。\\nclass Solution:\\n def maxNumOfMarkedIndices(self, nums: List[int]) -> int:\\n nums.sort()\\n left, right = 0, len(nums) // 2 + 1 # 开区间\\n while left + 1 < right:\\n k = (left + right) // 2\\n if all(nums[i] * 2 <= nums[i - k] for i in range(k)):\\n left = k\\n else:\\n right = k\\n return left * 2 # 最多匹配 left 对,有 left * 2 个数\\n
\\n# 二分找最小的不满足要求的 k+1,二分结束后,k 就是最大的满足要求的数对个数\\nclass Solution:\\n def maxNumOfMarkedIndices(self, nums: List[int]) -> int:\\n nums.sort()\\n check = lambda k: any(nums[i] * 2 > nums[i - k - 1] for i in range(k + 1))\\n return bisect_left(range(len(nums) // 2), True, key=check) * 2\\n
\\nclass Solution {\\n public int maxNumOfMarkedIndices(int[] nums) {\\n Arrays.sort(nums);\\n int left = 0;\\n int right = nums.length / 2 + 1; // 开区间\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n if (check(nums, mid)) {\\n left = mid;\\n } else {\\n right = mid;\\n }\\n }\\n return left * 2; // 最多匹配 left 对,有 left * 2 个数\\n }\\n\\n private boolean check(int[] nums, int k) {\\n for (int i = 0; i < k; i++) {\\n if (nums[i] * 2 > nums[nums.length - k + i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxNumOfMarkedIndices(vector<int>& nums) {\\n ranges::sort(nums);\\n\\n auto check = [&](int k) -> bool {\\n for (int i = 0; i < k; i++) {\\n if (nums[i] * 2 > nums[nums.size() - k + i]) {\\n return false;\\n }\\n }\\n return true;\\n };\\n\\n int left = 0, right = nums.size() / 2 + 1; // 开区间\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n (check(mid) ? left : right) = mid;\\n }\\n return left * 2; // 最多匹配 left 对,有 left * 2 个数\\n }\\n};\\n
\\nint cmp(const void *a, const void *b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nbool check(int* nums, int numsSize, int k) {\\n for (int i = 0; i < k; i++) {\\n if (nums[i] * 2 > nums[numsSize - k + i]) {\\n return false;\\n }\\n }\\n return true;\\n}\\n\\nint maxNumOfMarkedIndices(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int left = 0, right = numsSize / 2 + 1; // 开区间\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n if (check(nums, numsSize, mid)) {\\n left = mid;\\n } else {\\n right = mid;\\n }\\n }\\n return left * 2; // 最多匹配 left 对,有 left * 2 个数\\n}\\n
\\nfunc maxNumOfMarkedIndices(nums []int) int {\\n slices.Sort(nums)\\n n := len(nums)\\n pairs := sort.Search(n/2, func(k int) bool {\\n k++\\n for i, x := range nums[:k] {\\n if x*2 > nums[n-k+i] {\\n return true\\n }\\n }\\n return false\\n })\\n return pairs * 2 // 最多匹配 pairs 对,有 pairs * 2 个数\\n}\\n
\\nvar maxNumOfMarkedIndices = function(nums) {\\n nums.sort((a, b) => a - b);\\n\\n function check(k) {\\n for (let i = 0; i < k; i++) {\\n if (nums[i] * 2 > nums[nums.length - k + i]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n let left = 0, right = Math.floor(nums.length / 2) + 1; // 开区间\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (check(mid)) {\\n left = mid;\\n } else {\\n right = mid;\\n }\\n }\\n return left * 2; // 最多匹配 left 对,有 left * 2 个数\\n};\\n
\\nimpl Solution {\\n pub fn max_num_of_marked_indices(mut nums: Vec<i32>) -> i32 {\\n nums.sort_unstable();\\n\\n let check = |k: usize| -> bool {\\n for i in 0..k {\\n if nums[i] * 2 > nums[nums.len() - k + i] {\\n return false;\\n }\\n }\\n true\\n };\\n\\n let mut left = 0;\\n let mut right = nums.len() / 2 + 1; // 开区间\\n while left + 1 < right {\\n let mid = (left + right) / 2;\\n if check(mid) {\\n left = mid;\\n } else {\\n right = mid;\\n }\\n }\\n (left * 2) as _ // 最多匹配 left 对,有 left * 2 个数\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销,仅用到若干额外变量。
\\n方法二:同向双指针
\\n由方法一的匹配方式可知,我们需要用 $\\\\textit{nums}$ 左半部分中的数,去匹配 $\\\\textit{nums}$ 右半部分中的数。
\\n在 $\\\\textit{nums}$ 的右半部分中,找到第一个满足 $2\\\\cdot\\\\textit{nums}[0]\\\\le \\\\textit{nums}[j]$ 的 $j$,那么 $\\\\textit{nums}[1]$ 只能匹配右半部分中的下标大于 $j$ 的数,依此类推。
\\n这可以用同向双指针实现。
\\n本题视频讲解(第三题)
\\n\\nclass Solution:\\n def maxNumOfMarkedIndices(self, nums: List[int]) -> int:\\n nums.sort()\\n i = 0\\n for x in nums[(len(nums) + 1) // 2:]:\\n if nums[i] * 2 <= x: # 找到一个匹配\\n i += 1\\n return i * 2\\n
\\nclass Solution {\\n public int maxNumOfMarkedIndices(int[] nums) {\\n Arrays.sort(nums);\\n int n = nums.length;\\n int i = 0;\\n for (int j = (n + 1) / 2; j < n; j++) {\\n if (nums[i] * 2 <= nums[j]) { // 找到一个匹配\\n i++;\\n }\\n }\\n return i * 2;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxNumOfMarkedIndices(vector<int>& nums) {\\n ranges::sort(nums);\\n int i = 0, n = nums.size();\\n for (int j = (n + 1) / 2; j < n; j++) {\\n if (nums[i] * 2 <= nums[j]) { // 找到一个匹配\\n i++;\\n }\\n }\\n return i * 2;\\n }\\n};\\n
\\nint cmp(const void *a, const void *b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nint maxNumOfMarkedIndices(int* nums, int numsSize) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n int i = 0;\\n for (int j = (numsSize + 1) / 2; j < numsSize; j++) {\\n if (nums[i] * 2 <= nums[j]) { // 找到一个匹配\\n i++;\\n }\\n }\\n return i * 2;\\n}\\n
\\nfunc maxNumOfMarkedIndices(nums []int) int {\\n slices.Sort(nums)\\n i := 0\\n for _, x := range nums[(len(nums)+1)/2:] {\\n if nums[i]*2 <= x { // 找到一个匹配\\n i++\\n }\\n }\\n return i * 2\\n}\\n
\\nvar maxNumOfMarkedIndices = function(nums) {\\n nums.sort((a, b) => a - b);\\n const n = nums.length;\\n let i = 0;\\n for (let j = Math.floor((n + 1) / 2); j < n; j++) {\\n if (nums[i] * 2 <= nums[j]) { // 找到一个匹配\\n i++;\\n }\\n }\\n return i * 2;\\n};\\n
\\nimpl Solution {\\n pub fn max_num_of_marked_indices(mut nums: Vec<i32>) -> i32 {\\n nums.sort_unstable();\\n let mut i = 0;\\n for &x in &nums[(nums.len() + 1) / 2..] {\\n if nums[i] * 2 <= x { // 找到一个匹配\\n i += 1;\\n }\\n }\\n (i * 2) as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销,仅用到若干额外变量。Python 忽略切片开销。
\\n更多相似题目,见下面的二分题单,以及贪心题单中的「§1.2 单序列配对」和「§1.3 双序列配对」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:二分答案 提示 1\\n\\n如果 $2\\\\cdot\\\\textit{nums}\\\\le \\\\textit{nums}[j]$,则称 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[j]$ 匹配。\\n\\n如果可以匹配 $k$ 对,那么也可以匹配小于 $k$ 对,去掉一些数对即可做到。\\n\\n如果无法匹配 $k$ 对,那么也无法匹配大于 $k$ 对(反证法)。\\n\\n所以 $k$ 越大,越无法选出 $k$ 个能匹配的数对。有单调性,就可以二分答案。二分算法的理论讲解见【基础算法精讲 04】。\\n\\n提示 2\\n\\n现在问题变成:\\n\\n能否从 $\\\\textit{nums…","guid":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/er-fen-da-an-pythonjavacgo-by-endlessche-t9f5","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-26T04:08:48.721Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二分 & 贪心","url":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/er-fen-tan-xin-by-tsreaper-g7oj","content":"解法:二分 & 贪心
\\n本题很明显具有二分性。我们首先二分答案,然后考虑如何检验这个答案 $X$。
\\n为了选出 $X$ 对数,同时又要满足
\\n2 * nums[i] <= nums[j]
的条件,显然 $X$ 个nums[i]
应该尽量小,$X$ 个nums[j]
应该尽量大。因此我们选择nums
中最小和最大的 $X$ 个数进行匹配。考虑四个数 $a$,$b$,$c$,$d$ 满足 $a \\\\le b \\\\le c \\\\le d$
\\n\\n
\\n- 若 $2a \\\\le d$ 且 $2b \\\\le c$,那么一定有 $2a \\\\le 2b \\\\le c$ 和 $2b \\\\le c \\\\le d$。
\\n- 如果反过来,$2a \\\\le c$ 且 $2b \\\\le d$,则不一定有 $2a \\\\le d$ 和 $2b \\\\le c$。
\\n因此 $a$ 和 $c$ 匹配,$b$ 和 $d$ 匹配才是最优的。我们检查最小的
\\nnums[i]
是否匹配最小的nums[j]
,次小的nums[i]
是否匹配次小的nums[j]
,...复杂度 $\\\\mathcal{O}(n\\\\log n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:二分 & 贪心 本题很明显具有二分性。我们首先二分答案,然后考虑如何检验这个答案 $X$。\\n\\n为了选出 $X$ 对数,同时又要满足 2 * nums[i] <= nums[j] 的条件,显然 $X$ 个 nums[i] 应该尽量小,$X$ 个 nums[j] 应该尽量大。因此我们选择 nums 中最小和最大的 $X$ 个数进行匹配。\\n\\n考虑四个数 $a$,$b$,$c$,$d$ 满足 $a \\\\le b \\\\le c \\\\le d$\\n\\n若 $2a \\\\le d$ 且 $2b \\\\le c$,那么一定有 $2a \\\\le 2b \\\\le c$ 和 $2b \\\\le c…","guid":"https://leetcode.cn/problems/find-the-maximum-number-of-marked-indices//solution/er-fen-tan-xin-by-tsreaper-g7oj","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-26T04:07:45.298Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"真实双百解法:排序O(nlogn)+双指针O(n) Python || C++","url":"https://leetcode.cn/problems/count-the-number-of-fair-pairs//solution/zhen-zheng-de-onjie-fa-shuang-zhi-zhen-b-ygdw","content":"class Solution {\\npublic:\\n int maxNumOfMarkedIndices(vector<int>& nums) {\\n int n = nums.size();\\n // 给 nums 排个序,方便后面选最小和最大\\n sort(nums.begin(), nums.end());\\n\\n // 检验答案 X 是否合法\\n auto check = [&](int X) {\\n vector<int> L, R;\\n // 挑出最小的 X 个 nums[i]\\n for (int i = 0; i < X; i++) L.push_back(nums[i]);\\n // 挑出最大的 X 个 nums[j]\\n for (int i = 1; i <= X; i++) R.push_back(nums[n - i]);\\n reverse(R.begin(), R.end());\\n // 第 i 小的 L[i] 和第 i 小的 R[i] 匹配\\n for (int i = 0; i < X; i++) if (2 * L[i] > R[i]) return false;\\n return true;\\n };\\n\\n // 二分答案\\n int head = 0, tail = n / 2;\\n while (head < tail) {\\n int mid = (head + tail + 1) >> 1;\\n if (check(mid)) head = mid;\\n else tail = mid - 1;\\n }\\n return head * 2;\\n }\\n};\\n
语言:Python3
\\n
\\n提交时间:2023.02.15 20:27
\\n时间:200 ms,击败97.7%
\\n内存:26.3 MB,击败98.11%解题思路
\\n排序:不影响数目
\\n
\\n原问题等价于<=upper
的数对数目减去<=lower - 1
的数对数目
\\nleft向右移动,统计每次nums[left]
+ nums[i] <= k的数目,总和即为res
\\nright移至 nums[left] + nums[right] <= k 处,那么下标
从left + 1
到right
的数 + nums[left]都是 <= k 的
\\n易知left向右移后,满足条件的right需要左移或不动以下代码易懂~
\\n代码
\\n\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n n = len(nums)\\n nums.sort()\\n\\n def my_count(k): # 统计 <= k 的数对数目,原问题等价于 <=upper 的数对数目减去 <=lower - 1 的数对数目\\n res = 0\\n left, right = 0, n - 1\\n while left < right:\\n t = nums[left] + nums[right]\\n if t <= k: # 达到统计条件\\n res += right - left \\n # 如果 nums[left] + nums[right] <= k, 那么下标从left + 1到right的数 + nums[left]都是 <= k 的\\n left += 1\\n # 继续统计 nums[left + 1] + nums[i] <= k 的数对\\n else: # right指针左移,使t减小\\n right -= 1\\n return res\\n\\n return my_count(upper) - my_count(lower - 1)\\n
\\nclass Solution {\\n // 统计 <= k 的数对数目,原问题等价于 <=upper 的数对数目减去 <=lower - 1 的数对数目\\n long long my_count(vector<int>& nums, int k) {\\n int left = 0, right = nums.size() - 1;\\n long long res = 0;\\n while (left < right) {\\n int t = nums[left] + nums[right];\\n if (t <= k) { // 达到统计条件\\n res += right - left;\\n // 如果 nums[left] + nums[right] <= k, 那么下标从left + 1到right的数 + nums[left]都是 <= k 的\\n left++;\\n // 继续统计 nums[left + 1] + nums[i] <= k 的数对\\n }\\n else \\n right--; // right指针左移,使t减小\\n }\\n return res;\\n }\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n sort(nums.begin(), nums.end());\\n return my_count(nums, upper) - my_count(nums, lower - 1);\\n }\\n};\\n
\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n n = len(nums)\\n nums.sort()\\n\\n def my_count(k):\\n res = 0\\n left, right = 0, n - 1\\n while left < right:\\n t = nums[left] + nums[right]\\n if t <= k:\\n res += right - left \\n left += 1\\n else:\\n right -= 1\\n return res\\n\\n return my_count(upper) - my_count(lower - 1)\\n
\\nclass Solution {\\n long long my_count(vector<int>& nums, int k) {\\n int left = 0, right = nums.size() - 1;\\n long long res = 0;\\n while (left < right) {\\n int t = nums[left] + nums[right];\\n if (t <= k) {\\n res += right - left;\\n left++;\\n }\\n else \\n right--;\\n }\\n return res;\\n }\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n sort(nums.begin(), nums.end());\\n return my_count(nums, upper) - my_count(nums, lower - 1);\\n }\\n};\\n
题外话:为什么有人说用双指针超时了? 答:
\\n“双指针”O(n)需要保证的是,left和right指针只往1个方向移动,两种情况:
\\n1、同向双指针,假设方向向右,right往右移,left跟在right后面,相当于O(2n),“滑动窗口”那些题就是这个类型。
\\n
\\n2、反向双指针,0和n-1移动至相遇。如果某个指针,既会向左又会向右移,或者反复“移动后又移回原位”,那么最坏情况就是O(n²),本质上还是两层for循环的暴力,做的优化相当于“剪枝”。
\\n本题因为需要同时兼顾
\\n","description":"语言:Python3 提交时间:2023.02.15 20:27\\n 时间:200 ms,击败97.7%\\n 内存:26.3 MB,击败98.11%\\n\\n解题思路\\n\\n排序:不影响数目\\n 原问题等价于<=upper的数对数目减去<=lower - 1的数对数目\\n left向右移动,统计每次nums[left] + nums[i] <= k的数目,总和即为res\\n right移至 nums[left] + nums[right] <= k 处,那么下标从left + 1到right的数 + nums[left]都是 <= k 的\\n 易知left向右移后…","guid":"https://leetcode.cn/problems/count-the-number-of-fair-pairs//solution/zhen-zheng-de-onjie-fa-shuang-zhi-zhen-b-ygdw","author":"si-gu-wo-hai-zai","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-15T13:16:31.701Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++,排序,双指针","url":"https://leetcode.cn/problems/count-the-number-of-fair-pairs//solution/cpai-xu-shuang-zhi-zhen-by-yan-hua-yi-le-8joc","content":">=lower
和<=upper
,所以导致某个指针有时需要移动回去。这个时候我们把上下界问题等价于:<=upper
的情况减去<=lower-1
的情况 就可以了。对于满足条件区间[i,j]的问题,我们一般转换成满足区间x<i-1和x<=j,然后将两个区间求得的结果相减。
\\n
\\n我们先将nums排序,然后参考两数之和的双指针的操作,寻找在符合要求的范围内的两个边界。边界的长度-1就是当前边界中符合要求的对数,然后从左右向中间减小边界,重复上述操作,直到左右指针相碰。
\\n
\\n例如:nums是[1,2,3,4,5,6],上界为8,那么开始时left指针指向1,right指向6,此时nums[left]+nums[right]<8,那么这个范围内所有的数都符合要求。接着left指针右移,直到nums[left]=3时,nums[left]+nums[right]>8,就移动right指针。
\\n\\n","description":"对于满足条件区间[i,j]的问题,我们一般转换成满足区间xclass Solution {\\n long long count(vector<int> &nums, int upper)\\n {\\n int n = nums.size(), left = 0, right = n - 1;\\n long long res = 0;\\n while (left < right)//最后只剩一个数不能成对\\n {\\n long long sum = nums[left] + nums[right];\\n if (sum <= upper)//判断当前双指针之和是否大于上界\\n { //不是的话,此范围内所有数都满足要求,范围内的对数为right-left\\n res += right - left;\\n left++;\\n }\\n else//如果大于上界,则right指针左移,sum会随之减小\\n right--;\\n }\\n return res;\\n }\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n int n=nums.size(),left=0,right=n-1;\\n sort(nums.begin(),nums.end());\\n long long small=count(nums,lower-1);\\n long long big=count(nums,upper);\\n return big-small;\\n }\\n};\\n
方法一:二分查找\\n 由于排序不影响答案,可以先(从小到大)排序,这样可以二分查找。
\\n\\n\\n$\\\\textit{nums}$ 是 $[1,2]$ 还是 $[2,1]$,算出来的答案都是一样的,因为加法满足交换律 $a+b=b+a$。
\\n排序后,枚举右边的 $\\\\textit{nums}[j]$,那么左边的 $\\\\textit{nums}[i]$ 需要满足 $0\\\\le i < j$ 以及
\\n$$
\\n
\\n\\\\textit{lower} - \\\\textit{nums}[j] \\\\le \\\\textit{nums}[i] \\\\le \\\\textit{upper} - \\\\textit{nums}[j]
\\n$$计算 $\\\\le \\\\textit{upper} - \\\\textit{nums}[j]$ 的元素个数,减去 $< \\\\textit{lower} - \\\\textit{nums}[j]$ 的元素个数,即为满足上式的元素个数。(联想一下前缀和)
\\n由于 $\\\\textit{nums}$ 是有序的,我们可以在 $[0,j-1]$ 中二分查找,原理见【基础算法精讲 04】:
\\n\\n
\\n- 找到 $> \\\\textit{upper} - \\\\textit{nums}[j]$ 的第一个数,设其下标为 $r$,那么下标在 $[0,r-1]$ 中的数都是 $\\\\le \\\\textit{upper} - \\\\textit{nums}[j]$ 的,这有 $r$ 个。如果 $[0,j-1]$ 中没有找到这样的数,那么二分结果为 $j$。这意味着 $[0,j-1]$ 中的数都是 $\\\\le \\\\textit{upper} - \\\\textit{nums}[j]$ 的,这有 $j$ 个。
\\n- 找到 $\\\\ge \\\\textit{lower} - \\\\textit{nums}[j]$ 的第一个数,设其下标为 $l$,那么下标在 $[0,l-1]$ 中的数都是 $< \\\\textit{lower} - \\\\textit{nums}[j]$ 的,这有 $l$ 个。如果 $[0,j-1]$ 中没有找到这样的数,那么二分结果为 $j$。这意味着 $[0,j-1]$ 中的数都是 $< \\\\textit{lower} - \\\\textit{nums}[j]$ 的,这有 $j$ 个。
\\n- 满足 $\\\\textit{lower} - \\\\textit{nums}[j] \\\\le \\\\textit{nums}[i] \\\\le \\\\textit{upper} - \\\\textit{nums}[j]$ 的 $\\\\textit{nums}[i]$ 的个数为 $r-l$,加入答案。
\\n\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n nums.sort()\\n ans = 0\\n for j, x in enumerate(nums):\\n # 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n r = bisect_right(nums, upper - x, 0, j)\\n l = bisect_left(nums, lower - x, 0, j)\\n ans += r - l \\n return ans\\n
\\nclass Solution {\\n public long countFairPairs(int[] nums, int lower, int upper) {\\n Arrays.sort(nums);\\n long ans = 0;\\n for (int j = 0; j < nums.length; j++) {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n int r = lowerBound(nums, j, upper - nums[j] + 1);\\n int l = lowerBound(nums, j, lower - nums[j]);\\n ans += r - l;\\n }\\n return ans;\\n }\\n\\n // 原理请看 https://www.bilibili.com/video/BV1AP41137w7/\\n private int lowerBound(int[] nums, int right, int target) {\\n int left = -1;\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n if (nums[mid] >= target) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n ranges::sort(nums);\\n long long ans = 0;\\n for (int j = 0; j < nums.size(); j++) {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n auto r = upper_bound(nums.begin(), nums.begin() + j, upper - nums[j]);\\n auto l = lower_bound(nums.begin(), nums.begin() + j, lower - nums[j]);\\n ans += r - l;\\n }\\n return ans;\\n }\\n};\\n
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\n// 原理请看 https://www.bilibili.com/video/BV1AP41137w7/\\nint lowerBound(int* nums, int right, int target) {\\n int left = -1;\\n while (left + 1 < right) {\\n int mid = left + (right - left) / 2;\\n if (nums[mid] >= target) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n}\\n\\nlong long countFairPairs(int* nums, int numsSize, int lower, int upper) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n long long ans = 0;\\n for (int j = 0; j < numsSize; j++) {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n int r = lowerBound(nums, j, upper - nums[j] + 1);\\n int l = lowerBound(nums, j, lower - nums[j]);\\n ans += r - l;\\n }\\n return ans;\\n}\\n
\\nfunc countFairPairs(nums []int, lower, upper int) (ans int64) {\\n slices.Sort(nums)\\n for j, x := range nums {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n r := sort.SearchInts(nums[:j], upper-x+1)\\n l := sort.SearchInts(nums[:j], lower-x)\\n ans += int64(r - l)\\n }\\n return\\n}\\n
\\nvar countFairPairs = function(nums, lower, upper) {\\n nums.sort((a, b) => a - b);\\n let ans = 0;\\n for (let j = 0; j < nums.length; j++) {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n const r = lowerBound(nums, j, upper - nums[j] + 1);\\n const l = lowerBound(nums, j, lower - nums[j]);\\n ans += r - l;\\n }\\n return ans;\\n};\\n\\n// 原理请看 https://www.bilibili.com/video/BV1AP41137w7/\\nvar lowerBound = function(nums, right, target) {\\n let left = -1;\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (nums[mid] >= target) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n};\\n
\\nimpl Solution {\\n pub fn count_fair_pairs(mut nums: Vec<i32>, lower: i32, upper: i32) -> i64 {\\n nums.sort_unstable();\\n let mut ans = 0;\\n for j in 0..nums.len() {\\n // 注意要在 [0, j-1] 中二分,因为题目要求两个下标 i < j\\n let l = nums[..j].partition_point(|&x| x < lower - nums[j]);\\n let r = nums[..j].partition_point(|&x| x <= upper - nums[j]);\\n ans += r - l;\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n方法二:相向三指针
\\n由于随着 $\\\\textit{nums}[j]$ 的变大,$\\\\textit{upper}-\\\\textit{nums}[j]$ 和 $\\\\textit{lower} - \\\\textit{nums}[j]$ 都在变小,有单调性,可以用相向三指针 $j,l,r$ 代替方法一中的二分查找:
\\n\\n
\\n- 初始化 $l=r=n$。
\\n- 从左到右遍历(排序后的)$\\\\textit{nums}$。
\\n- 找 $> \\\\textit{upper} - \\\\textit{nums}[j]$ 的第一个数:如果 $\\\\textit{nums}[r-1] > \\\\textit{upper}-\\\\textit{nums}[j]$,说明 $r$ 太大了,可以继续减小。循环结束后的 $r$,与 $j$ 取最小值后,就是方法一的二分查找计算出的 $r$。
\\n- 找 $\\\\ge \\\\textit{lower} - \\\\textit{nums}[j]$ 的第一个数:如果 $\\\\textit{nums}[l-1] \\\\ge \\\\textit{lower}-\\\\textit{nums}[j]$,说明 $l$ 太大了,可以继续减小。循环结束后的 $l$,与 $j$ 取最小值后,就是方法一的二分查找计算出的 $l$。
\\n\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n nums.sort()\\n ans = 0\\n l = r = len(nums)\\n for j, x in enumerate(nums):\\n while r and nums[r - 1] > upper - x:\\n r -= 1\\n while l and nums[l - 1] >= lower - x:\\n l -= 1\\n # 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += min(r, j) - min(l, j)\\n return ans\\n
\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n nums.sort()\\n ans = 0\\n l = r = len(nums)\\n for j, x in enumerate(nums):\\n while r and nums[r - 1] > upper - x:\\n r -= 1\\n while l and nums[l - 1] >= lower - x:\\n l -= 1\\n if l < j:\\n ans += (r if r < j else j) - l\\n return ans\\n
\\nclass Solution {\\n public long countFairPairs(int[] nums, int lower, int upper) {\\n Arrays.sort(nums);\\n long ans = 0;\\n int l = nums.length;\\n int r = nums.length;\\n for (int j = 0; j < nums.length; j++) {\\n while (r > 0 && nums[r - 1] > upper - nums[j]) {\\n r--;\\n }\\n while (l > 0 && nums[l - 1] >= lower - nums[j]) {\\n l--;\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += Math.min(r, j) - Math.min(l, j);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n ranges::sort(nums);\\n long long ans = 0;\\n int l = nums.size(), r = l;\\n for (int j = 0; j < nums.size(); j++) {\\n while (r && nums[r - 1] > upper - nums[j]) {\\n r--;\\n }\\n while (l && nums[l - 1] >= lower - nums[j]) {\\n l--;\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += min(r, j) - min(l, j);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nlong long countFairPairs(int* nums, int numsSize, int lower, int upper) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n long long ans = 0;\\n int l = numsSize, r = numsSize;\\n for (int j = 0; j < numsSize; j++) {\\n while (r && nums[r - 1] > upper - nums[j]) {\\n r--;\\n }\\n while (l && nums[l - 1] >= lower - nums[j]) {\\n l--;\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += MIN(r, j) - MIN(l, j);\\n }\\n return ans;\\n}\\n
\\nfunc countFairPairs(nums []int, lower, upper int) (ans int64) {\\n slices.Sort(nums)\\n l, r := len(nums), len(nums)\\n for j, x := range nums {\\n for r > 0 && nums[r-1] > upper-x {\\n r--\\n }\\n for l > 0 && nums[l-1] >= lower-x {\\n l--\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += int64(min(r, j)-min(l, j))\\n }\\n return\\n}\\n
\\nvar countFairPairs = function(nums, lower, upper) {\\n nums.sort((a, b) => a - b);\\n let ans = 0, l = nums.length, r = nums.length;\\n for (let j = 0; j < nums.length; j++) {\\n while (r && nums[r - 1] > upper - nums[j]) {\\n r--;\\n }\\n while (l && nums[l - 1] >= lower - nums[j]) {\\n l--;\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += Math.min(r, j) - Math.min(l, j);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn count_fair_pairs(mut nums: Vec<i32>, lower: i32, upper: i32) -> i64 {\\n nums.sort_unstable();\\n let mut ans = 0;\\n let mut l = nums.len();\\n let mut r = nums.len();\\n for (j, &x) in nums.iter().enumerate() {\\n while r > 0 && nums[r - 1] > upper - x {\\n r -= 1;\\n }\\n while l > 0 && nums[l - 1] >= lower - x {\\n l -= 1;\\n }\\n // 在方法一中,二分的结果必须 <= j,方法二同理\\n ans += r.min(j) - l.min(j);\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n方法三:两次相向双指针
\\n写法一
\\n我们也可以枚举左边的 $i$,统计右边有多少个合法的 $j$。
\\n枚举 $i$,计算满足 $j>i$ 且 $\\\\textit{nums}[j] \\\\le \\\\textit{upper} - \\\\textit{nums}[i]$ 的 $j$ 的个数,记作 $\\\\text{count}(\\\\textit{upper})$。
\\n枚举 $i$,计算满足 $j>i$ 且 $\\\\textit{nums}[j] < \\\\textit{lower} - \\\\textit{nums}[i]$,也就是 $\\\\textit{nums}[j] \\\\le \\\\textit{lower} - 1 - \\\\textit{nums}[i]$ 的 $j$ 的个数,记作 $\\\\text{count}(\\\\textit{lower}-1)$。
\\n答案就是 $\\\\text{count}(\\\\textit{upper}) - \\\\text{count}(\\\\textit{lower}-1)$。
\\n怎么计算 $\\\\text{count}(\\\\textit{upper})$?
\\n初始化 $j=n-1$。枚举 $i$,如果 $\\\\textit{nums}[j] > \\\\textit{upper} - \\\\textit{nums}[i]$,就减小 $j$,直到 $j=i$ 或者 $\\\\textit{nums}[j] \\\\le \\\\textit{upper} - \\\\textit{nums}[i]$ 为止。
\\n如果 $j=i$,那么继续循环也无法满足 $j>i$ 的要求,直接退出循环。
\\n由于数组是有序的,如果 $\\\\textit{nums}[i]+\\\\textit{nums}[j]\\\\le \\\\textit{upper}$,那么对于更小的 $j$,也同样满足这个不等式。所以 $[i+1,j]$ 范围内的下标都可以是 $j$,这有 $j-i$ 个,加入答案。
\\n\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n nums.sort()\\n def count(upper: int) -> int:\\n res = 0\\n j = len(nums) - 1\\n for i, x in enumerate(nums):\\n while j > i and nums[j] > upper - x:\\n j -= 1\\n if j == i:\\n break\\n res += j - i\\n return res\\n return count(upper) - count(lower - 1)\\n
\\nclass Solution {\\n public long countFairPairs(int[] nums, int lower, int upper) {\\n Arrays.sort(nums);\\n return count(nums, upper) - count(nums, lower - 1);\\n }\\n\\n private long count(int[] nums, int upper) {\\n long res = 0;\\n int j = nums.length - 1;\\n for (int i = 0; i < nums.length; i++) {\\n while (j > i && nums[j] > upper - nums[i]) {\\n j--;\\n }\\n if (j == i) {\\n break;\\n }\\n res += j - i;\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n ranges::sort(nums);\\n\\n auto count = [&](int upper) {\\n long long res = 0;\\n int j = nums.size() - 1;\\n for (int i = 0; i < nums.size(); i++) {\\n while (j > i && nums[j] > upper - nums[i]) {\\n j--;\\n }\\n if (j == i) {\\n break;\\n }\\n res += j - i;\\n }\\n return res;\\n };\\n\\n return count(upper) - count(lower - 1);\\n }\\n};\\n
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nlong long countFairPairs(int* nums, int numsSize, int lower, int upper) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n\\n long long count(int upper) {\\n long long res = 0;\\n int j = numsSize - 1;\\n for (int i = 0; i < numsSize; i++) {\\n while (j > i && nums[j] > upper - nums[i]) {\\n j--;\\n }\\n if (j == i) {\\n break;\\n }\\n res += j - i;\\n }\\n return res;\\n }\\n\\n return count(upper) - count(lower - 1);\\n}\\n
\\nfunc countFairPairs(nums []int, lower, upper int) int64 {\\n slices.Sort(nums)\\n count := func(upper int) (res int64) {\\n j := len(nums) - 1\\n for i, x := range nums {\\n for j > i && nums[j] > upper-x {\\n j--\\n }\\n if j == i {\\n break\\n }\\n res += int64(j - i)\\n }\\n return res\\n }\\n return count(upper) - count(lower-1)\\n}\\n
\\nvar countFairPairs = function(nums, lower, upper) {\\n nums.sort((a, b) => a - b);\\n\\n var count = function(upper) {\\n let res = 0;\\n let j = nums.length - 1;\\n for (let i = 0; i < nums.length; i++) {\\n while (j > i && nums[j] > upper - nums[i]) {\\n j--;\\n }\\n if (j === i) {\\n break;\\n }\\n res += j - i;\\n }\\n return res;\\n };\\n\\n return count(upper) - count(lower - 1);\\n};\\n
\\nimpl Solution {\\n pub fn count_fair_pairs(mut nums: Vec<i32>, lower: i32, upper: i32) -> i64 {\\n nums.sort_unstable();\\n let count = |upper: i32| -> i64 {\\n let mut res = 0;\\n let mut j = nums.len() - 1;\\n for (i, &x) in nums.iter().enumerate() {\\n while j > i && nums[j] > upper - x {\\n j -= 1;\\n }\\n if j == i {\\n break;\\n }\\n res += j - i;\\n }\\n res as _\\n };\\n count(upper) - count(lower - 1)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n写法二
\\n初始化 $i=0$,$j=n-1$。
\\n如果 $\\\\textit{nums}[i]+\\\\textit{nums}[j]\\\\le \\\\textit{upper}$,那么对于更小的 $j$,也同样满足这个不等式。所以 $[i+1,j]$ 范围内的下标 $j$ 都可以和 $i$ 配对,这有 $j-i$ 个,加入答案,然后把 $i$ 加一。
\\n如果 $\\\\textit{nums}[i]+\\\\textit{nums}[j] > \\\\textit{upper}$,那么对于更大的 $i$,也同样不满足题目要求。所以 $[i,j-1]$ 范围内的下标 $i$ 都无法与 $j$ 配对,直接把 $j$ 减一。
\\n视频讲解:【基础算法精讲 01】。
\\n\\nclass Solution:\\n def countFairPairs(self, nums: List[int], lower: int, upper: int) -> int:\\n nums.sort()\\n def count(upper: int) -> int:\\n res = 0\\n i, j = 0, len(nums) - 1\\n while i < j:\\n if nums[i] + nums[j] <= upper:\\n res += j - i\\n i += 1\\n else:\\n j -= 1\\n return res\\n return count(upper) - count(lower - 1)\\n
\\nclass Solution {\\n public long countFairPairs(int[] nums, int lower, int upper) {\\n Arrays.sort(nums);\\n return count(nums, upper) - count(nums, lower - 1);\\n }\\n\\n private long count(int[] nums, int upper) {\\n long res = 0;\\n int i = 0;\\n int j = nums.length - 1;\\n while (i < j) {\\n if (nums[i] + nums[j] <= upper) {\\n res += j - i;\\n i++;\\n } else {\\n j--;\\n }\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countFairPairs(vector<int>& nums, int lower, int upper) {\\n ranges::sort(nums);\\n\\n auto count = [&](int upper) {\\n long long res = 0;\\n int i = 0, j = nums.size() - 1;\\n while (i < j) {\\n if (nums[i] + nums[j] <= upper) {\\n res += j - i;\\n i++;\\n } else {\\n j--;\\n }\\n }\\n return res;\\n };\\n\\n return count(upper) - count(lower - 1);\\n }\\n};\\n
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nlong long countFairPairs(int* nums, int numsSize, int lower, int upper) {\\n qsort(nums, numsSize, sizeof(int), cmp);\\n\\n long long count(int upper) {\\n long long res = 0;\\n int i = 0, j = numsSize - 1;\\n while (i < j) {\\n if (nums[i] + nums[j] <= upper) {\\n res += j - i;\\n i++;\\n } else {\\n j--;\\n }\\n }\\n return res;\\n }\\n\\n return count(upper) - count(lower - 1);\\n}\\n
\\nfunc countFairPairs(nums []int, lower, upper int) int64 {\\n slices.Sort(nums)\\n count := func(upper int) (res int64) {\\n i, j := 0, len(nums)-1\\n for i < j {\\n if nums[i]+nums[j] <= upper {\\n res += int64(j - i)\\n i++\\n } else {\\n j--\\n }\\n }\\n return res\\n }\\n return count(upper) - count(lower-1)\\n}\\n
\\nvar countFairPairs = function(nums, lower, upper) {\\n nums.sort((a, b) => a - b);\\n\\n var count = function(upper) {\\n let res = 0;\\n let i = 0, j = nums.length - 1;\\n while (i < j) {\\n if (nums[i] + nums[j] <= upper) {\\n res += j - i;\\n i++;\\n } else {\\n j--;\\n }\\n }\\n return res;\\n };\\n\\n return count(upper) - count(lower - 1);\\n};\\n
\\nimpl Solution {\\n pub fn count_fair_pairs(mut nums: Vec<i32>, lower: i32, upper: i32) -> i64 {\\n nums.sort_unstable();\\n let count = |upper: i32| -> i64 {\\n let mut res = 0;\\n let mut i = 0;\\n let mut j = nums.len() - 1;\\n while i < j {\\n if nums[i] + nums[j] <= upper {\\n res += j - i;\\n i += 1;\\n } else {\\n j -= 1;\\n }\\n }\\n res as _\\n };\\n count(upper) - count(lower - 1)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n相似题目
\\n\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:二分查找 由于排序不影响答案,可以先(从小到大)排序,这样可以二分查找。\\n\\n$\\\\textit{nums}$ 是 $[1,2]$ 还是 $[2,1]$,算出来的答案都是一样的,因为加法满足交换律 $a+b=b+a$。\\n\\n排序后,枚举右边的 $\\\\textit{nums}[j]$,那么左边的 $\\\\textit{nums}[i]$ 需要满足 $0\\\\le i < j$ 以及\\n\\n$$\\n \\\\textit{lower} - \\\\textit{nums}[j] \\\\le \\\\textit{nums}[i] \\\\le \\\\textit{upper} - \\\\textit{nums}…","guid":"https://leetcode.cn/problems/count-the-number-of-fair-pairs//solution/er-fen-cha-zhao-de-ling-huo-yun-yong-by-wplbj","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-12T04:21:48.889Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种不同的思考角度(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/tong-xiang-shuang-zhi-zhen-ji-lu-di-yi-t-5hlh","content":"题意
\\n一维数轴上有 $n$ 个点,用两条长为 $k$ 的线段,一共最多可以覆盖多少个点?
\\n方法一:枚举右,维护左
\\n一条线段
\\n从特殊到一般,先想想只有一条线段要怎么做。
\\n如果线段的右端点没有奖品,我们可以把线段左移,使其右端点恰好有奖品,这不会让线段覆盖的奖品个数变少。所以只需枚举 $\\\\textit{prizePositions}[\\\\textit{right}]$ 为线段的右端点,然后需要算出最远(最小)覆盖的奖品的位置 $\\\\textit{prizePositions}[\\\\textit{left}]$,此时覆盖的奖品的个数为
\\n$$
\\n
\\n\\\\textit{right} - \\\\textit{left} + 1
\\n$$由于 $\\\\textit{right}$ 变大时,$\\\\textit{left}$ 也会变大,有单调性,可以用滑动窗口快速算出 $\\\\textit{left}$。原理见 滑动窗口【基础算法精讲 03】。
\\n⚠注意:$\\\\textit{prizePositions}[\\\\textit{left}]$ 不一定是线段的左端点。$\\\\textit{prizePositions}[\\\\textit{left}]$ 只是最左边的被线段覆盖的那个奖品的位置,线段左端点可能比 $\\\\textit{prizePositions}[\\\\textit{left}]$ 更小。
\\n两条线段
\\n两条线段一左一右。考虑枚举右(第二条线段),同时维护左(第一条线段)能覆盖的最多奖品个数。
\\n贪心地想,两条线段不相交肯定比相交更好,覆盖的奖品可能更多。
\\n设第二条线段右端点在 $\\\\textit{prizePositions}[\\\\textit{right}]$ 时,最远(最小)覆盖的奖品的位置为 $\\\\textit{prizePositions}[\\\\textit{left}]$。
\\n我们需要计算在 $\\\\textit{prizePositions}[\\\\textit{left}]$ 左侧的第一条线段最多可以覆盖多少个奖品。这可以保证两条线段不相交。
\\n定义 $\\\\textit{mx}[i+1]$ 表示第一条线段右端点 $\\\\le \\\\textit{prizePositions}[i]$ 时,最多可以覆盖多少个奖品。特别地,定义 $\\\\textit{mx}[0]=0$。
\\n如何计算 $\\\\textit{mx}$?
\\n考虑动态规划:
\\n\\n
\\n- 线段右端点等于 $\\\\textit{prizePositions}[i]$ 时,可以覆盖最多的奖品,即 $i - \\\\textit{left}_i + 1$。其中 $\\\\textit{left}_i$ 表示右端点覆盖奖品 $\\\\textit{prizePositions}[i]$ 时,最左边的被线段覆盖的奖品。
\\n- 线段右端点小于 $\\\\textit{prizePositions}[i]$ 时,可以覆盖最多的奖品,这等价于右端点 $\\\\le \\\\textit{prizePositions}[i-1]$ 时,最多可以覆盖多少个奖品,即 $\\\\textit{mx}[i]$。注:这里可以说明为什么状态要定义成 $\\\\textit{mx}[i+1]$ 而不是 $\\\\textit{mx}[i]$,这可以避免当 $i=0$ 时出现 $i-1=-1$ 这种情况。
\\n二者取最大值,得
\\n$$
\\n
\\n\\\\textit{mx}[i + 1] = \\\\max(\\\\textit{mx}[i], i - \\\\textit{left}_i + 1)
\\n$$上式也可以理解为 $i - \\\\textit{left}_i + 1$ 的前缀最大值。
\\n如何计算两条线段可以覆盖的奖品个数?
\\n\\n
\\n- 第二条线段覆盖的奖品个数为 $\\\\textit{right} - \\\\textit{left} + 1$。
\\n- 第一条线段覆盖的奖品个数为线段右端点 $\\\\le \\\\textit{prizePositions}[\\\\textit{left}-1]$ 时,最多覆盖的奖品个数,即 $\\\\textit{mx}[\\\\textit{left}]$。
\\n综上,两条线段可以覆盖的奖品个数为
\\n$$
\\n
\\n\\\\textit{mx}[\\\\textit{left}] + \\\\textit{right}-\\\\textit{left}+1
\\n$$枚举 $\\\\textit{right}$ 的过程中,取上式的最大值,即为答案。
\\n我们遍历了所有的奖品作为第二条线段的右端点,通过 $\\\\textit{mx}[\\\\textit{left}]$ 保证第一条线段与第二条线段不相交,且第一条线段覆盖了第二条线段左侧的最多奖品。那么这样遍历后,算出的答案就一定是所有情况中的最大值。
\\n⚠注意:可以在计算第二条线段的滑动窗口的同时,更新和第一条线段有关的 $\\\\textit{mx}$。这是因为两条线段一样长,第二条线段移动到 $\\\\textit{right}$ 时所覆盖的奖品个数,也是第一条线段移动到 $\\\\textit{right}$ 时所覆盖的奖品个数。
\\n如果脑中没有一幅直观的图像,可以看看 视频讲解【双周赛 97】的第三题。
\\n小优化:如果 $2k+1\\\\ge \\\\textit{prizePositions}[n-1] - \\\\textit{prizePositions}[0]$,说明所有奖品都可以被覆盖,直接返回 $n$。例如 $\\\\textit{prizePositions}=[0,1,2,3],\\\\ k=1$,那么第一条线段覆盖 $0$ 和 $1$,第二条线段覆盖 $2$ 和 $3$,即可覆盖所有奖品。
\\n\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n if k * 2 + 1 >= prizePositions[-1] - prizePositions[0]:\\n return n\\n ans = left = 0\\n mx = [0] * (n + 1)\\n for right, p in enumerate(prizePositions):\\n while p - prizePositions[left] > k:\\n left += 1\\n ans = max(ans, mx[left] + right - left + 1)\\n mx[right + 1] = max(mx[right], right - left + 1)\\n return ans\\n
\\nclass Solution {\\n public int maximizeWin(int[] prizePositions, int k) {\\n int n = prizePositions.length;\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0;\\n int left = 0;\\n int[] mx = new int[n + 1];\\n for (int right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.max(ans, mx[left] + right - left + 1);\\n mx[right + 1] = Math.max(mx[right], right - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maximizeWin(vector<int>& prizePositions, int k) {\\n int n = prizePositions.size();\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0, left = 0;\\n vector<int> mx(n + 1);\\n for (int right = 0; right < n; right++) {\\n while (prizePositions[right] - prizePositions[left] > k) {\\n left++;\\n }\\n ans = max(ans, mx[left] + right - left + 1);\\n mx[right + 1] = max(mx[right], right - left + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint maximizeWin(int* prizePositions, int n, int k) {\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0, left = 0;\\n int* mx = calloc(n + 1, sizeof(int));\\n for (int right = 0; right < n; right++) {\\n int p = prizePositions[right];\\n while (p - prizePositions[left] > k) {\\n left++;\\n }\\n ans = MAX(ans, mx[left] + right - left + 1);\\n mx[right + 1] = MAX(mx[right], right - left + 1);\\n }\\n free(mx);\\n return ans;\\n}\\n
\\nfunc maximizeWin(prizePositions []int, k int) (ans int) {\\n n := len(prizePositions)\\n if k*2+1 >= prizePositions[n-1]-prizePositions[0] {\\n return n\\n }\\n mx := make([]int, n+1)\\n left := 0\\n for right, p := range prizePositions {\\n for p-prizePositions[left] > k {\\n left++\\n }\\n ans = max(ans, mx[left]+right-left+1)\\n mx[right+1] = max(mx[right], right-left+1)\\n }\\n return\\n}\\n
\\nvar maximizeWin = function(prizePositions, k) {\\n const n = prizePositions.length;\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n const mx = Array(n + 1).fill(0);\\n let ans = 0, left = 0;\\n for (let right = 0; right < n; right++) {\\n const p = prizePositions[right];\\n while (p - prizePositions[left] > k) {\\n left++;\\n }\\n ans = Math.max(ans, mx[left] + right - left + 1);\\n mx[right + 1] = Math.max(mx[right], right - left + 1);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximize_win(prize_positions: Vec<i32>, k: i32) -> i32 {\\n let n = prize_positions.len();\\n if k * 2 + 1 >= prize_positions[n - 1] - prize_positions[0] {\\n return n as _;\\n }\\n let mut ans = 0;\\n let mut left = 0;\\n let mut mx = vec![0; n + 1];\\n for (right, &p) in prize_positions.iter().enumerate() {\\n while p - prize_positions[left] > k {\\n left += 1;\\n }\\n ans = ans.max(mx[left] + right - left + 1);\\n mx[right + 1] = mx[right].max(right - left + 1);\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{prizePositions}$ 的长度。虽然写了个二重循环,但是内层循环中对 $\\\\textit{left}$ 加一的总执行次数不会超过 $n$ 次,所以总的时间复杂度为 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:换一个角度
\\n两条线段一共涉及到 $4$ 个下标:
\\n\\n
\\n- 第一条线段覆盖的最小奖品下标。
\\n- 第一条线段覆盖的最大奖品下标。
\\n- 第二条线段覆盖的最小奖品下标。
\\n- 第二条线段覆盖的最大奖品下标。
\\n考虑「枚举中间」,也就是第一条线段覆盖的最大奖品下标,和第二条线段覆盖的最小奖品下标。
\\n第一条线段
\\n写一个和方法一一样的滑动窗口:
\\n\\n
\\n- 枚举覆盖的最大奖品下标为 $\\\\textit{right}$,维护覆盖的最小奖品下标 $\\\\textit{left}$。
\\n- 向右移动 $\\\\textit{right}$,如果发现 $\\\\textit{prizePositions}[\\\\textit{right}] - \\\\textit{prizePositions}[\\\\textit{left}] > k$,就向右移动 $\\\\textit{left}$。
\\n- 循环结束时,覆盖的奖品个数为 $\\\\textit{right}-\\\\textit{left}+1$。
\\n第二条线段
\\n仍然是滑动窗口,但改成枚举 $\\\\textit{left}$,维护 $\\\\textit{right}$。
\\n\\n
\\n- 向右移动 $\\\\textit{left}$,如果发现 $\\\\textit{prizePositions}[\\\\textit{right}] - \\\\textit{prizePositions}[\\\\textit{left}] \\\\le k$,就向右移动 $\\\\textit{right}$。
\\n- 循环结束时,$\\\\textit{right}-1$ 是覆盖的最大奖品下标,覆盖的奖品个数为 $\\\\textit{right}-\\\\textit{left}$。
\\n合二为一
\\n枚举 $\\\\textit{mid}$,既作为第一条线段的 $\\\\textit{right}$,又作为第二条线段的 $\\\\textit{left}$。
\\n同方法一,用滑动窗口枚举第二条线段,同时维护第一条线段能覆盖的最多奖品个数 $\\\\textit{mx}$。
\\n枚举 $\\\\textit{mid}$:
\\n\\n
\\n- 首先,跑第二条线段的滑动窗口。
\\n- 用 $\\\\textit{mx} + \\\\textit{right} - \\\\textit{mid}$ 更新答案的最大值。
\\n- 然后,跑第一条线段的滑动窗口。
\\n- 用 $\\\\textit{mid}-\\\\textit{left}+1$ 更新 $\\\\textit{mx}$ 的最大值。
\\n⚠注意:不能先跑第一条线段的滑动窗口,否则 $\\\\textit{mx} + \\\\textit{right} - \\\\textit{mid}$ 可能会把 $\\\\textit{mid}$ 处的奖品计入两次。
\\n\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n if k * 2 + 1 >= prizePositions[-1] - prizePositions[0]:\\n return n\\n ans = mx = left = right = 0\\n for mid, p in enumerate(prizePositions):\\n # 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while right < n and prizePositions[right] - p <= k:\\n right += 1\\n # 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = max(ans, mx + right - mid)\\n # 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while p - prizePositions[left] > k:\\n left += 1\\n # 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = max(mx, mid - left + 1)\\n return ans\\n
\\n# 把 max 改成手动 if 比较,效率更高一点\\nclass Solution:\\n def maximizeWin(self, prizePositions: List[int], k: int) -> int:\\n n = len(prizePositions)\\n if k * 2 + 1 >= prizePositions[-1] - prizePositions[0]:\\n return n\\n ans = mx = left = right = 0\\n for mid, p in enumerate(prizePositions):\\n while right < n and prizePositions[right] - p <= k:\\n right += 1\\n t = mx + right - mid\\n if t > ans: ans = t\\n while p - prizePositions[left] > k:\\n left += 1\\n t = mid - left + 1\\n if t > mx: mx = t\\n return ans\\n
\\nclass Solution {\\n public int maximizeWin(int[] prizePositions, int k) {\\n int n = prizePositions.length;\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0;\\n int mx = 0;\\n int left = 0;\\n int right = 0;\\n for (int mid = 0; mid < n; mid++) {\\n // 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while (right < n && prizePositions[right] - prizePositions[mid] <= k) {\\n right++;\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = Math.max(ans, mx + right - mid);\\n // 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while (prizePositions[mid] - prizePositions[left] > k) {\\n left++;\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = Math.max(mx, mid - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maximizeWin(vector<int>& prizePositions, int k) {\\n int n = prizePositions.size();\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0, mx = 0, left = 0, right = 0;\\n for (int mid = 0; mid < n; mid++) {\\n // 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while (right < n && prizePositions[right] - prizePositions[mid] <= k) {\\n right++;\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = max(ans, mx + right - mid);\\n // 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while (prizePositions[mid] - prizePositions[left] > k) {\\n left++;\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = max(mx, mid - left + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint maximizeWin(int* prizePositions, int n, int k) {\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n int ans = 0, mx = 0, left = 0, right = 0;\\n for (int mid = 0; mid < n; mid++) {\\n // 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while (right < n && prizePositions[right] - prizePositions[mid] <= k) {\\n right++;\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = MAX(ans, mx + right - mid);\\n // 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while (prizePositions[mid] - prizePositions[left] > k) {\\n left++;\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = MAX(mx, mid - left + 1);\\n }\\n return ans;\\n}\\n
\\nfunc maximizeWin(prizePositions []int, k int) (ans int) {\\n n := len(prizePositions)\\n if k*2+1 >= prizePositions[n-1]-prizePositions[0] {\\n return n\\n }\\n mx, left, right := 0, 0, 0\\n for mid, p := range prizePositions {\\n // 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n for right < n && prizePositions[right]-p <= k {\\n right++\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = max(ans, mx+right-mid)\\n // 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n for p-prizePositions[left] > k {\\n left++\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = max(mx, mid-left+1)\\n }\\n return\\n}\\n
\\nvar maximizeWin = function(prizePositions, k) {\\n let n = prizePositions.length;\\n if (k * 2 + 1 >= prizePositions[n - 1] - prizePositions[0]) {\\n return n;\\n }\\n let ans = 0, mx = 0, left = 0, right = 0;\\n for (let mid = 0; mid < n; mid++) {\\n // 把 prizePositions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while (right < n && prizePositions[right] - prizePositions[mid] <= k) {\\n right++;\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = Math.max(ans, mx + right - mid);\\n // 把 prizePositions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while (prizePositions[mid] - prizePositions[left] > k) {\\n left++;\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = Math.max(mx, mid - left + 1);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximize_win(prize_positions: Vec<i32>, k: i32) -> i32 {\\n let n = prize_positions.len();\\n if k * 2 + 1 >= prize_positions[n - 1] - prize_positions[0] {\\n return n as _;\\n }\\n let mut ans = 0;\\n let mut mx = 0;\\n let mut left = 0;\\n let mut right = 0;\\n for (mid, &p) in prize_positions.iter().enumerate() {\\n // 把 prize_positions[mid] 视作第二条线段的左端点,计算第二条线段可以覆盖的最大奖品下标\\n while right < n && prize_positions[right] - p <= k {\\n right += 1;\\n }\\n // 循环结束后,right-1 是第二条线段可以覆盖的最大奖品下标\\n ans = ans.max(mx + right - mid);\\n // 把 prize_positions[mid] 视作第一条线段的右端点,计算第一条线段可以覆盖的最小奖品下标\\n while p - prize_positions[left] > k {\\n left += 1;\\n }\\n // 循环结束后,left 是第一条线段可以覆盖的最小奖品下标\\n mx = mx.max(mid - left + 1);\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{prizePositions}$ 的长度。理由同方法一的复杂度分析。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n总结
\\n本题是一种更高级的「枚举右,维护左」,枚举的对象不是单个元素,而是子数组(滑动窗口),同时维护的也是子数组的最大长度。
\\n有一道和本题很像的题目:1031. 两个非重叠子数组的最大和,请用 $\\\\mathcal{O}(n)$ 的时间解决。
\\n更多「枚举右,维护左」的题目,见下面数据结构题单的第零章。
\\n更多不定长滑窗题目,见下面滑动窗口题单的第二章。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 一维数轴上有 $n$ 个点,用两条长为 $k$ 的线段,一共最多可以覆盖多少个点?\\n\\n方法一:枚举右,维护左\\n一条线段\\n\\n从特殊到一般,先想想只有一条线段要怎么做。\\n\\n如果线段的右端点没有奖品,我们可以把线段左移,使其右端点恰好有奖品,这不会让线段覆盖的奖品个数变少。所以只需枚举 $\\\\textit{prizePositions}[\\\\textit{right}]$ 为线段的右端点,然后需要算出最远(最小)覆盖的奖品的位置 $\\\\textit{prizePositions}[\\\\textit{left}]$,此时覆盖的奖品的个数为\\n\\n$$\\n \\\\textit…","guid":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/tong-xiang-shuang-zhi-zhen-ji-lu-di-yi-t-5hlh","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-04T16:13:19.636Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"two pointers & 枚举","url":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/two-pointers-mei-ju-by-tsreaper-bui2","content":"解法:two pointers & 枚举
\\n此类题首先想到一个经典结论:
\\n\\n\\n至少存在一种最优方案,使得线段的某一端点上有奖品。
\\n因为如果有一个最优方案使得线段的两个端点上都没有奖品,那么将线段往右移动,直到左端点碰到奖品不会使答案变差,甚至还有可能拿到右边的奖品。当然也可以选择将线段往左移动,直到右端点碰到奖品,原理是一样的。
\\n另外容易发现两个线段有重叠是不优的,因此我们可以枚举右边线段的左端点,问题就变为:
\\n\\n\\n对于一个从左到右移动的右边界,选择一条不超过该右边界的线段,使得线段覆盖尽量多的奖品。
\\n这个子问题可以通过 two pointers 解决,而右边线段覆盖的奖品数量同样也可以通过 two pointers 计算。
\\n复杂度 $\\\\mathcal{O}(n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:two pointers & 枚举 此类题首先想到一个经典结论:\\n\\n至少存在一种最优方案,使得线段的某一端点上有奖品。\\n\\n因为如果有一个最优方案使得线段的两个端点上都没有奖品,那么将线段往右移动,直到左端点碰到奖品不会使答案变差,甚至还有可能拿到右边的奖品。当然也可以选择将线段往左移动,直到右端点碰到奖品,原理是一样的。\\n\\n另外容易发现两个线段有重叠是不优的,因此我们可以枚举右边线段的左端点,问题就变为:\\n\\n对于一个从左到右移动的右边界,选择一条不超过该右边界的线段,使得线段覆盖尽量多的奖品。\\n\\n这个子问题可以通过 two pointers 解决…","guid":"https://leetcode.cn/problems/maximize-win-from-two-segments//solution/two-pointers-mei-ju-by-tsreaper-bui2","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-02-04T16:10:09.900Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【视频讲解】排列型回溯,简洁高效!(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/n-queens//solution/hui-su-tao-lu-miao-sha-nhuang-hou-shi-pi-mljv","content":"class Solution {\\npublic:\\n int maximizeWin(vector<int>& A, int K) {\\n int n = A.size();\\n \\n // 预处理 f[i] 表示以第 i 个奖品为右端点的线段能覆盖多少个奖品\\n int f[n];\\n int now = 0;\\n // 用 two pointers 计算 f[i] 的值\\n for (int i = 0, j = 0; i < n; i++) {\\n now++;\\n while (j <= i && A[i] - A[j] > K) {\\n now--;\\n j++;\\n }\\n f[i] = now;\\n }\\n // 把 f[i] 变成前缀和,表示右端点不超过第 i 个奖品的线段最多能覆盖多少个奖品\\n for (int i = 1; i < n; i++) f[i] = max(f[i], f[i - 1]);\\n\\n int ans = 0;\\n now = 0;\\n // 枚举右边线段的左端点,同时用 two pointers 计算线段覆盖了多少奖品\\n for (int i = n - 1, j = n - 1; i >= 0; i--) {\\n now++;\\n while (j >= i && A[j] - A[i] > K) {\\n now--;\\n j--;\\n }\\n // 转为子问题,直接使用预处理的答案进行计算\\n ans = max(ans, now + (i == 0 ? 0 : f[i - 1]));\\n }\\n return ans;\\n }\\n};\\n
视频讲解
\\n请看【基础算法精讲 16】,制作不易,欢迎点赞关注~
\\n答疑
\\n问:本题和 46. 全排列 的关系是什么?
\\n答:由于每行一列恰好放一个皇后,如果记录每行的皇后放在哪一列,就可以得到一个 $[0,n-1]$ 的排列 $\\\\textit{queens}$ 了。示例的 1 的两个图,分别对应排列 $[1, 3, 0, 2]$ 和 $[2, 0, 3, 1]$。所以本题本质上是在枚举列号的全排列。
\\n问:如何 $\\\\mathcal{O}(1)$ 判断两个皇后互相攻击?
\\n答:由于我们保证了每行每列恰好放一个皇后,所以只需检查斜方向。对于 ↗ 方向的格子,行号加列号是不变的。对于 ↖ 方向的格子,行号减列号是不变的。如果两个皇后,行号加列号相同,或者行号减列号相同,那么这两个皇后互相攻击。
\\n问:如何 $\\\\mathcal{O}(1)$ 判断当前位置被之前放置的某个皇后攻击到?
\\n答:额外用两个数组 $\\\\textit{diag}_1$ 和 $\\\\textit{diag}_2$ 分别标记之前放置的皇后的行号加列号,以及行号减列号。如果当前位置的行号加列号在 $\\\\textit{diag}_1$ 中(标记为 $\\\\texttt{true}$),或者当前位置的行号减列号在 $\\\\textit{diag}_2$ 中(标记为 $\\\\texttt{true}$),那么当前位置被之前放置的皇后攻击到,不能放皇后。
\\n\\nclass Solution:\\n def solveNQueens(self, n: int) -> List[List[str]]:\\n ans = []\\n queens = [0] * n # 皇后放在 (r,queens[r])\\n col = [False] * n\\n diag1 = [False] * (n * 2 - 1)\\n diag2 = [False] * (n * 2 - 1)\\n def dfs(r: int) -> None:\\n if r == n:\\n ans.append([\'.\' * c + \'Q\' + \'.\' * (n - 1 - c) for c in queens])\\n return\\n # 在 (r,c) 放皇后\\n for c, ok in enumerate(col):\\n if not ok and not diag1[r + c] and not diag2[r - c]: # 判断能否放皇后\\n queens[r] = c # 直接覆盖,无需恢复现场\\n col[c] = diag1[r + c] = diag2[r - c] = True # 皇后占用了 c 列和两条斜线\\n dfs(r + 1)\\n col[c] = diag1[r + c] = diag2[r - c] = False # 恢复现场\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n public List<List<String>> solveNQueens(int n) {\\n List<List<String>> ans = new ArrayList<>();\\n int[] queens = new int[n]; // 皇后放在 (r,queens[r])\\n boolean[] col = new boolean[n];\\n boolean[] diag1 = new boolean[n * 2 - 1];\\n boolean[] diag2 = new boolean[n * 2 - 1];\\n dfs(0, queens, col, diag1, diag2, ans);\\n return ans;\\n }\\n\\n private void dfs(int r, int[] queens, boolean[] col, boolean[] diag1, boolean[] diag2, List<List<String>> ans) {\\n int n = col.length;\\n if (r == n) {\\n List<String> board = new ArrayList<>(n); // 预分配空间\\n for (int c : queens) {\\n char[] row = new char[n];\\n Arrays.fill(row, \'.\');\\n row[c] = \'Q\';\\n board.add(new String(row));\\n }\\n ans.add(board);\\n return;\\n }\\n // 在 (r,c) 放皇后\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) { // 判断能否放皇后\\n queens[r] = c; // 直接覆盖,无需恢复现场\\n col[c] = diag1[r + c] = diag2[rc] = true; // 皇后占用了 c 列和两条斜线\\n dfs(r + 1, queens, col, diag1, diag2, ans);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<string>> solveNQueens(int n) {\\n vector<vector<string>> ans;\\n vector<int> queens(n); // 皇后放在 (r,queens[r])\\n vector<int> col(n), diag1(n * 2 - 1), diag2(n * 2 - 1); // vector<int> 效率比 vector<bool> 高\\n auto dfs = [&](auto&& dfs, int r) {\\n if (r == n) {\\n vector<string> board(n);\\n for (int i = 0; i < n; i++) {\\n board[i] = string(queens[i], \'.\') + \'Q\' + string(n - 1 - queens[i], \'.\');\\n }\\n ans.push_back(board);\\n return;\\n }\\n // 在 (r,c) 放皇后\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) { // 判断能否放皇后\\n queens[r] = c; // 直接覆盖,无需恢复现场\\n col[c] = diag1[r + c] = diag2[rc] = true; // 皇后占用了 c 列和两条斜线\\n dfs(dfs, r + 1);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n };\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\nvoid dfs(int r, int n, int* queens, bool* col, bool* diag1, bool* diag2, char*** ans, int* returnSize, int* returnColumnSizes) {\\n if (r == n) {\\n char** board = malloc(n * sizeof(char*));\\n for (int i = 0; i < n; i++) {\\n board[i] = malloc((n + 1) * sizeof(char));\\n memset(board[i], \'.\', n);\\n board[i][queens[i]] = \'Q\';\\n board[i][n] = \'\\\\0\';\\n }\\n ans[*returnSize] = board;\\n returnColumnSizes[(*returnSize)++] = n;\\n return;\\n }\\n // 在 (r,c) 放皇后\\n for (int c = 0; c < n; c++) {\\n int rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) { // 判断能否放皇后\\n queens[r] = c; // 直接覆盖,无需恢复现场\\n col[c] = diag1[r + c] = diag2[rc] = true; // 皇后占用了 c 列和两条斜线\\n dfs(r + 1, n, queens, col, diag1, diag2, ans, returnSize, returnColumnSizes);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n}\\n\\nchar*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {\\n *returnSize = 0;\\n // 本题数据范围下至多 352 个解\\n char*** ans = malloc(352 * sizeof(char**));\\n *returnColumnSizes = malloc(352 * sizeof(int));\\n\\n int* queens = malloc(n * sizeof(int)); // 皇后放在 (r,queens[r])\\n bool* col = calloc(n, sizeof(bool));\\n bool* diag1 = calloc(2 * n - 1, sizeof(bool));\\n bool* diag2 = calloc(2 * n - 1, sizeof(bool));\\n dfs(0, n, queens, col, diag1, diag2, ans, returnSize, *returnColumnSizes);\\n\\n free(queens);\\n free(col);\\n free(diag1);\\n free(diag2);\\n return ans;\\n}\\n
\\nfunc solveNQueens(n int) (ans [][]string) {\\n queens := make([]int, n) // 皇后放在 (r,queens[r])\\n col := make([]bool, n)\\n diag1 := make([]bool, n*2-1)\\n diag2 := make([]bool, n*2-1)\\n var dfs func(int)\\n dfs = func(r int) {\\n if r == n {\\n board := make([]string, n)\\n for i, c := range queens {\\n board[i] = strings.Repeat(\\".\\", c) + \\"Q\\" + strings.Repeat(\\".\\", n-1-c)\\n }\\n ans = append(ans, board)\\n return\\n }\\n // 在 (r,c) 放皇后\\n for c, ok := range col {\\n rc := r - c + n - 1\\n if !ok && !diag1[r+c] && !diag2[rc] { // 判断能否放皇后\\n queens[r] = c // 直接覆盖,无需恢复现场\\n col[c], diag1[r+c], diag2[rc] = true, true, true // 皇后占用了 c 列和两条斜线\\n dfs(r + 1)\\n col[c], diag1[r+c], diag2[rc] = false, false, false // 恢复现场\\n }\\n }\\n }\\n dfs(0)\\n return\\n}\\n
\\nvar solveNQueens = function(n) {\\n const ans = [];\\n const queens = Array(n).fill(0); // 皇后放在 (r,queens[r])\\n const col = Array(n).fill(false);\\n const diag1 = Array(n * 2 - 1).fill(false);\\n const diag2 = Array(n * 2 - 1).fill(false);\\n function dfs(r) {\\n if (r === n) {\\n ans.push(queens.map(c => \'.\'.repeat(c) + \'Q\' + \'.\'.repeat(n - 1 - c)));\\n return;\\n }\\n // 在 (r,c) 放皇后\\n for (let c = 0; c < n; c++) {\\n const rc = r - c + n - 1;\\n if (!col[c] && !diag1[r + c] && !diag2[rc]) { // 判断能否放皇后\\n queens[r] = c; // 直接覆盖,无需恢复现场\\n col[c] = diag1[r + c] = diag2[rc] = true; // 皇后占用了 c 列和两条斜线\\n dfs(r + 1);\\n col[c] = diag1[r + c] = diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n dfs(0);\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn solve_n_queens(n: i32) -> Vec<Vec<String>> {\\n fn dfs(r: usize, queens: &mut [usize], col: &mut [bool], diag1: &mut [bool], diag2: &mut [bool], ans: &mut Vec<Vec<String>>) {\\n let n = col.len();\\n if r == n {\\n let board = queens.iter()\\n .map(|&c| {\\n let mut row = vec![\'.\'; n];\\n row[c] = \'Q\';\\n row.into_iter().collect()\\n })\\n .collect::<Vec<_>>();\\n ans.push(board);\\n return;\\n }\\n // 在 (r,c) 放皇后\\n for c in 0..n {\\n let rc = n + r - c - 1;\\n if !col[c] && !diag1[r + c] && !diag2[rc] { // 判断能否放皇后\\n queens[r] = c; // 直接覆盖,无需恢复现场\\n // 皇后占用了 c 列和两条斜线\\n col[c] = true;\\n diag1[r + c] = true;\\n diag2[rc] = true;\\n dfs(r + 1, queens, col, diag1, diag2, ans);\\n col[c] = false;\\n diag1[r + c] = false;\\n diag2[rc] = false; // 恢复现场\\n }\\n }\\n }\\n\\n let n = n as usize;\\n let mut ans = vec![];\\n let mut queens = vec![0; n]; // 皇后放在 (r,queens[r])\\n let mut col = vec![false; n];\\n let mut diag1 = vec![false; n * 2 - 1];\\n let mut diag2 = vec![false; n * 2 - 1];\\n dfs(0, &mut queens, &mut col, &mut diag1, &mut diag2, &mut ans);\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n^2\\\\cdot n!)$。搜索树中至多有 $\\\\mathcal{O}(n!)$ 个叶子,每个叶子生成答案每次需要 $\\\\mathcal{O}(n^2)$ 的时间,所以时间复杂度为 $\\\\mathcal{O}(n^2\\\\cdot n!)$。实际上搜索树中远没有这么多叶子,$n=9$ 时只有 $352$ 种放置方案,远远小于 $9!=362880$。更加准确的方案数可以参考 OEIS A000170,为 $\\\\mathcal{O}\\\\left(\\\\dfrac{n!}{2.54^n}\\\\right)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。返回值的空间不计入。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"视频讲解 请看【基础算法精讲 16】,制作不易,欢迎点赞关注~\\n\\n答疑\\n\\n问:本题和 46. 全排列 的关系是什么?\\n\\n答:由于每行一列恰好放一个皇后,如果记录每行的皇后放在哪一列,就可以得到一个 $[0,n-1]$ 的排列 $\\\\textit{queens}$ 了。示例的 1 的两个图,分别对应排列 $[1, 3, 0, 2]$ 和 $[2, 0, 3, 1]$。所以本题本质上是在枚举列号的全排列。\\n\\n问:如何 $\\\\mathcal{O}(1)$ 判断两个皇后互相攻击?\\n\\n答:由于我们保证了每行每列恰好放一个皇后,所以只需检查斜方向。对于 ↗ 方向的格子…","guid":"https://leetcode.cn/problems/n-queens//solution/hui-su-tao-lu-miao-sha-nhuang-hou-shi-pi-mljv","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-28T10:30:58.670Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历求和以及优化解法","url":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/bian-li-qiu-he-yi-ji-by-kind-moore2dy-4z7l","content":"\\n
{:width=600}
第一种解法
\\n直接进行遍历求和,最后返回相减结果。
\\n
\\n缺点:如果数组中元素大小过大,会超出类型范围\\nclass Solution {\\npublic:\\n int differenceOfSum(vector<int>& nums) {\\n long long sum = 0, sum1 = 0;\\n for (auto &i : nums) {\\n sum += i;\\n while(i) {\\n sum1 += i % 10;\\n i /= 10;\\n }\\n }\\n return abs(sum - sum1);\\n }\\n};\\n
第二种解法
\\n弥补了第一种解法的缺点
\\n\\n","description":"{:width=600} 直接进行遍历求和,最后返回相减结果。\\n 缺点:如果数组中元素大小过大,会超出类型范围\\n\\nclass Solution {\\npublic:\\n int differenceOfSum(vectorclass Solution {\\npublic:\\n int differenceOfSum(vector<int>& nums) {\\n int sum = 0, sum1 = 0, ans = 0;\\n for (auto &i : nums) {\\n sum += i;\\n while(i) {\\n sum1 += i % 10;\\n i /= 10;\\n }\\n ans = sum - sum1;\\n }\\n return abs(ans);\\n }\\n};\\n
& nums) {\\n long long sum = 0, sum1 = 0;\\n for (auto &i : nums) {\\n sum += i;\\n while(i) {\\n sum1 += i % 10;…","guid":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/bian-li-qiu-he-yi-ji-by-kind-moore2dy-4z7l","author":"kind-moore2dy","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-25T01:54:33.414Z","media":[{"url":"https://pic.leetcode.cn/1674611700-CiXfDk-image.png","type":"photo","width":610,"height":329,"blurhash":"L0Eyb[t7fQt7xufQfQfQfQfQfQfQ"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"c++/python3/java (1)自定义排序","url":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/cpython3java-1-by-xinghe_xinghe-2mqj","content":" 思路和心得:
\\n(一)自定义排序
\\n\\nclass Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n score.sort(key = lambda row: row[k], reverse = True)\\n return score\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> sortTheStudents(vector<vector<int>>& score, int k) {\\n sort(score.begin(), score.end(), [&](const vector<int>& a, const vector<int>& b){\\n return a[k] > b[k];\\n });\\n return score;\\n }\\n};\\n
\\n","description":"思路和心得: class Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n score.sort(key = lambda row: row[k], reverse = True)\\n return score\\n\\nclass Solution {\\npublic:\\n vectorclass Solution {\\n public int[][] sortTheStudents(int[][] score, int k) {\\n Arrays.sort(score, (a, b) -> b[k] - a[k]);\\n return score;\\n } \\n}\\n
> sortTheStudents(vector >& score…","guid":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/cpython3java-1-by-xinghe_xinghe-2mqj","author":"XingHe_XingHe","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-22T06:13:07.652Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"根据第 K 场考试的分数排序","url":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/gen-ju-di-k-chang-kao-shi-de-fen-shu-pai-0rf6","content":" ###cpp
\\n\\n","description":"###cpp class Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<vector<int>> sortTheStudents(vector<vector<int>>& score, int k) {\\n sort(score.begin(), score.end(),[k](vector<int>& a, vector<int>& b) {return a[k] > b[k];});\\n return score;\\n }\\n};\\n
> sortTheStudents(vector >& score, int k) {\\n sort(score.begin(), score.end(),[k](vector & a, vector & b) {return a[k] > b[k];});\\n return score;\\n }\\n};","guid":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/gen-ju-di-k-chang-kao-shi-de-fen-shu-pai-0rf6","author":"vijaysue","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-22T04:10:09.554Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"自定义排序,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/yu-fa-ti-by-endlesscheng-lako","content":" 自定义排序,按照每一行的下标为 $k$ 的元素从大到小排序。
\\n\\nclass Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n score.sort(key=lambda row: -row[k])\\n return score\\n
\\nclass Solution {\\n public int[][] sortTheStudents(int[][] score, int k) {\\n Arrays.sort(score, (a, b) -> b[k] - a[k]);\\n return score;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<vector<int>> sortTheStudents(vector<vector<int>>& score, int k) {\\n ranges::sort(score, {}, [&](auto& row) { return -row[k]; });\\n return score;\\n }\\n};\\n
\\nint K;\\n\\nint cmp(const void* a, const void* b) {\\n return (*(int**)b)[K] - (*(int**)a)[K];\\n}\\n\\nint** sortTheStudents(int** score, int scoreSize, int* scoreColSize, int k, int* returnSize, int** returnColumnSizes) {\\n K = k;\\n qsort(score, scoreSize, sizeof(int*), cmp);\\n *returnSize = scoreSize;\\n *returnColumnSizes = scoreColSize;\\n return score;\\n}\\n
\\nfunc sortTheStudents(score [][]int, k int) [][]int {\\n slices.SortFunc(score, func(a, b []int) int { return b[k] - a[k] })\\n return score\\n}\\n
\\nvar sortTheStudents = function(score, k) {\\n return score.sort((a, b) => b[k] - a[k]);\\n};\\n
\\nimpl Solution {\\n pub fn sort_the_students(mut score: Vec<Vec<i32>>, k: i32) -> Vec<Vec<i32>> {\\n score.sort_unstable_by_key(|row| -row[k as usize]);\\n score\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(m\\\\log m)$,其中 $m$ 为 $\\\\textit{score}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$,忽略排序时的栈开销,仅用到若干额外变量。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"自定义排序,按照每一行的下标为 $k$ 的元素从大到小排序。 class Solution:\\n def sortTheStudents(self, score: List[List[int]], k: int) -> List[List[int]]:\\n score.sort(key=lambda row: -row[k])\\n return score\\n\\nclass Solution {\\n public int[][] sortTheStudents(int[][] score, int k…","guid":"https://leetcode.cn/problems/sort-the-students-by-their-kth-score//solution/yu-fa-ti-by-endlesscheng-lako","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-22T04:07:59.914Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【普通人包看懂的简单代码】滑动窗口","url":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/pu-tong-ren-bao-kan-dong-de-jian-dan-dai-6n4b","content":"思路
\\n\\n
\\n- 下标[left,right]区间内如果满足条件了,那么总长度n-right个都满足条件
\\n- 此时left++,以新的起点试图重新判断是否满足条件 再重复步骤1.
\\n\\n","description":"下标[left,right]区间内如果满足条件了,那么总长度n-right个都满足条件 此时left++,以新的起点试图重新判断是否满足条件 再重复步骤1.\\nclass Solution {\\npublic:\\n long long countGood(vectorclass Solution {\\npublic:\\n long long countGood(vector<int>& nums, int k) {\\n long long res=0;\\n int sum=0;//成对的总数\\n unordered_map<int,int>cnt;\\n for(int left=0,right=0;right<nums.size();right++){\\n cnt[nums[right]]++;\\n if(cnt[nums[right]]>=2){//可以成对就能为对的总数做贡献\\n sum+=cnt[nums[right]]-1;//两个数多一对;三个数多两对;四个数多三对 \\n }\\n if(sum>=k){//对数不少于k\\n while(sum>=k){\\n res+=nums.size()-right;//right及其往后的都是好数组\\n if(cnt[nums[left]]>=2){//滑出left时 如果left曾经对sum有过贡献 就得把它的贡献减掉\\n sum-=cnt[nums[left]]-1;\\n }\\n cnt[nums[left]]--;\\n left++;//滑出left\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
& nums, int k) {\\n long long res=0;\\n int sum=0;//成对的总数\\n unordered_map cnt;\\n for(int left=0,right=0…","guid":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/pu-tong-ren-bao-kan-dong-de-jian-dan-dai-6n4b","author":"demouo","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-15T08:02:26.643Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"6291. 数组元素和与数字和的绝对差。python一行实现。","url":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/6291-shu-zu-yuan-su-he-yu-shu-zi-he-de-j-zaqh","content":" python一行处理实现。
\\n时间356 ms击败100%;内存13.6 MB击败100%
\\n\\nclass Solution(object):\\n def differenceOfSum(self, nums):\\n \\"\\"\\"\\n :type nums: List[int]\\n :rtype: int\\n \\"\\"\\"\\n return abs(sum(nums) - (sum(sum([[int(c) for c in str(n)] for n in nums], []))))\\n
↓ 觉得不错的话,欢迎点赞、评论、收藏! ^_^
\\n","description":"python一行处理实现。 时间356 ms击败100%;内存13.6 MB击败100%\\n\\nclass Solution(object):\\n def differenceOfSum(self, nums):\\n \\"\\"\\"\\n :type nums: List[int]\\n :rtype: int\\n \\"\\"\\"\\n return abs(sum(nums) - (sum(sum([[int(c) for c in str(n)] for n in nums], []))))\\n\\n\\n↓ 觉得不错的话…","guid":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/6291-shu-zu-yuan-su-he-yu-shu-zi-he-de-j-zaqh","author":"admiring-meninskyuli","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-15T06:39:12.046Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"c++滑动窗口","url":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/chua-dong-chuang-kou-by-thdlrt-tlrd","content":"\\n
\\n- 思路\\n
\\n\\n
\\n- 直接枚举区间[i,j]会超时,可以使用滑动窗口来实现
\\n- 如果[i,j]是好子数组,那么[i,j+1],[i,j+2]...都是好子数组
\\n- 因此我们只需要枚举左边界i,找到最小的j使得[i,j]是好子数组,也就得到了nums.size()-j个以i开始的好子数组
\\n- 计算区间内相等元素对的数目:\\n
\\n\\n
\\n- 如果一个数字有k个(k>1)那么相等对有k*(k-1)/2个
\\n- 因此k加一时数对数目增加k*(k-1)/2-(k-1)*(k-2)/2=k-1个
\\n- 减少时同理
\\n\\n","description":"思路 直接枚举区间[i,j]会超时,可以使用滑动窗口来实现\\n如果[i,j]是好子数组,那么[i,j+1],[i,j+2]...都是好子数组\\n因此我们只需要枚举左边界i,找到最小的j使得[i,j]是好子数组,也就得到了nums.size()-j个以i开始的好子数组\\n计算区间内相等元素对的数目:\\n如果一个数字有k个(k>1)那么相等对有k*(k-1)/2个\\n因此k加一时数对数目增加k*(k-1)/2-(k-1)*(k-2)/2=k-1个\\n减少时同理\\nclass Solution {\\npublic:\\n long long…","guid":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/chua-dong-chuang-kou-by-thdlrt-tlrd","author":"thdlrt","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-15T04:17:16.857Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"遍历+利用性质简化代码","url":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/bian-li-by-endlesscheng-hi82","content":"class Solution {\\npublic:\\n long long countGood(vector<int>& nums, int k) {\\n long long ans=0;\\n unordered_map<int,int>check;//维护滑动窗口内数据数目\\n int sum=0;\\n for(int i=0,j=0;j<nums.size();j++)\\n {\\n check[nums[j]]++;\\n if(check[nums[j]]>1)//更新数对数目\\n sum+=check[nums[j]]-1;\\n if(sum>=k)\\n {\\n while(sum>=k)\\n {\\n ans+=nums.size()-j;//更新i开始的好子数组数目\\n if(check[nums[i]]>1)\\n sum-=check[nums[i]]-1;\\n check[nums[i]]--;\\n i++;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
下午两点【B站@灵茶山艾府】直播讲题,关注UP不迷路~
\\n
\\n由于元素值一定不小于其数位和,所以答案就是元素和减去数位和。
\\n代码实现时可以用同一个变量。
\\n\\nclass Solution:\\n def differenceOfSum(self, nums: List[int]) -> int:\\n ans = 0\\n for x in nums:\\n ans += x\\n while x:\\n ans -= x % 10\\n x //= 10\\n return ans\\n
\\nfunc differenceOfSum(nums []int) (ans int) {\\nfor _, x := range nums {\\nfor ans += x; x > 0; x /= 10 {\\nans -= x % 10\\n}\\n}\\nreturn\\n}\\n
复杂度分析
\\n\\n
\\n","description":"下午两点【B站@灵茶山艾府】直播讲题,关注UP不迷路~ 由于元素值一定不小于其数位和,所以答案就是元素和减去数位和。\\n\\n代码实现时可以用同一个变量。\\n\\nclass Solution:\\n def differenceOfSum(self, nums: List[int]) -> int:\\n ans = 0\\n for x in nums:\\n ans += x\\n while x:\\n ans -= x % 10\\n x…","guid":"https://leetcode.cn/problems/difference-between-element-sum-and-digit-sum-of-an-array//solution/bian-li-by-endlesscheng-hi82","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-15T04:13:52.452Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「越长越合法」型滑动窗口(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/shuang-zhi-zhen-by-endlesscheng-lkd9","content":"- 时间复杂度:$O(n\\\\log U)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度,$U=\\\\max(\\\\textit{nums})$。
\\n- 空间复杂度:$O(1)$,仅用到若干额外变量。
\\n前置知识:滑动窗口【基础算法精讲 03】。
\\n核心思路:
\\n\\n
\\n- 如果窗口中有 $c$ 个元素 $x$,再进来一个 $x$,会新增 $c$ 个相等数对。
\\n- 如果窗口中有 $c$ 个元素 $x$,再去掉一个 $x$,会减少 $c-1$ 个相等数对。
\\n用一个哈希表 $\\\\textit{cnt}$ 维护子数组(窗口)中的每个元素的出现次数,以及相同数对的个数 $\\\\textit{pairs}$。
\\n外层循环:从小到大枚举子数组右端点 $\\\\textit{right}$。现在准备把 $x=\\\\textit{nums}[\\\\textit{right}]$ 移入窗口,那么窗口中有 $\\\\textit{cnt}[x]$ 个数和 $x$ 相同,所以 $\\\\textit{pairs}$ 会增加 $\\\\textit{cnt}[x]$。然后把 $\\\\textit{cnt}[x]$ 加一。
\\n内层循环:如果发现 $\\\\textit{pairs}\\\\ge k$,说明子数组符合要求,右移左端点 $\\\\textit{left}$,先把 $\\\\textit{cnt}[\\\\textit{nums}[\\\\textit{left}]]$ 减少一,然后把 $\\\\textit{pairs}$ 减少 $\\\\textit{cnt}[\\\\textit{nums}[\\\\textit{left}]]$。
\\n内层循环结束后,$[\\\\textit{left},\\\\textit{right}]$ 这个子数组是不满足题目要求的,但在退出循环之前的最后一轮循环,$[\\\\textit{left}-1,\\\\textit{right}]$ 是满足题目要求的。由于子数组越长,越能满足题目要求,所以除了 $[\\\\textit{left}-1,\\\\textit{right}]$,还有 $[\\\\textit{left}-2,\\\\textit{right}],[\\\\textit{left}-3,\\\\textit{right}],\\\\ldots,[0,\\\\textit{right}]$ 都是满足要求的。也就是说,当右端点固定在 $\\\\textit{right}$ 时,左端点在 $0,1,2,\\\\ldots,\\\\textit{left}-1$ 的所有子数组都是满足要求的,这一共有 $\\\\textit{left}$ 个。
\\n\\nclass Solution:\\n def countGood(self, nums: List[int], k: int) -> int:\\n cnt = defaultdict(int) # 比 Counter() 快\\n ans = left = pairs = 0\\n for x in nums:\\n pairs += cnt[x]\\n cnt[x] += 1\\n while pairs >= k:\\n cnt[nums[left]] -= 1\\n pairs -= cnt[nums[left]]\\n left += 1\\n ans += left\\n return ans\\n
\\nclass Solution {\\n public long countGood(int[] nums, int k) {\\n long ans = 0;\\n Map<Integer, Integer> cnt = new HashMap<>();\\n int pairs = 0;\\n int left = 0;\\n for (int x : nums) {\\n int c = cnt.getOrDefault(x, 0);\\n pairs += c; // 进\\n cnt.put(x, c + 1);\\n while (pairs >= k) {\\n x = nums[left];\\n c = cnt.get(x);\\n pairs -= c - 1; // 出\\n cnt.put(x, c - 1);\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n public long countGood(int[] nums, int k) {\\n long ans = 0;\\n Map<Integer, Integer> cnt = new HashMap<>();\\n int pairs = 0;\\n int left = 0;\\n for (int x : nums) {\\n pairs += cnt.merge(x, 1, Integer::sum) - 1; // pairs += cnt[x]++\\n while (pairs >= k) {\\n pairs -= cnt.merge(nums[left], -1, Integer::sum); // pairs -= --cnt[nums[left]]\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countGood(vector<int>& nums, int k) {\\n long long ans = 0;\\n unordered_map<int, int> cnt;\\n int pairs = 0, left = 0;\\n for (int x : nums) {\\n pairs += cnt[x]++;\\n while (pairs >= k) {\\n pairs -= --cnt[nums[left]];\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc countGood(nums []int, k int) (ans int64) {\\n cnt := map[int]int{}\\n pairs, left := 0, 0\\n for _, x := range nums {\\n pairs += cnt[x]\\n cnt[x]++\\n for pairs >= k {\\n cnt[nums[left]]--\\n pairs -= cnt[nums[left]]\\n left++\\n }\\n ans += int64(left)\\n }\\n return\\n}\\n
\\nvar countGood = function(nums, k) {\\n const cnt = new Map();\\n let ans = 0, pairs = 0, left = 0;\\n for (const x of nums) {\\n const c = cnt.get(x) ?? 0;\\n pairs += c; // 进\\n cnt.set(x, c + 1);\\n while (pairs >= k) {\\n const x = nums[left];\\n const c = cnt.get(x);\\n pairs -= c - 1; // 出\\n cnt.set(x, c - 1);\\n left++;\\n }\\n ans += left;\\n }\\n return ans;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn count_good(nums: Vec<i32>, k: i32) -> i64 {\\n let mut ans = 0;\\n let mut cnt = HashMap::new();\\n let mut pairs = 0;\\n let mut left = 0;\\n for &x in &nums {\\n let e = cnt.entry(x).or_insert(0);\\n pairs += *e;\\n *e += 1;\\n while pairs >= k {\\n let e = cnt.get_mut(&nums[left]).unwrap();\\n *e -= 1;\\n pairs -= *e;\\n left += 1;\\n }\\n ans += left;\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。虽然写了个二重循环,但是内层循环中对 $\\\\textit{left}$ 加一的总执行次数不会超过 $n$ 次,所以总的时间复杂度为 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n更多相似题目,见下面滑动窗口题单中的「§2.3.1 越长越合法」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置知识:滑动窗口【基础算法精讲 03】。 核心思路:\\n\\n如果窗口中有 $c$ 个元素 $x$,再进来一个 $x$,会新增 $c$ 个相等数对。\\n如果窗口中有 $c$ 个元素 $x$,再去掉一个 $x$,会减少 $c-1$ 个相等数对。\\n\\n用一个哈希表 $\\\\textit{cnt}$ 维护子数组(窗口)中的每个元素的出现次数,以及相同数对的个数 $\\\\textit{pairs}$。\\n\\n外层循环:从小到大枚举子数组右端点 $\\\\textit{right}$。现在准备把 $x=\\\\textit{nums}[\\\\textit{right}]$ 移入窗口,那么窗口中有…","guid":"https://leetcode.cn/problems/count-the-number-of-good-subarrays//solution/shuang-zhi-zhen-by-endlesscheng-lkd9","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-15T04:08:25.292Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【视频】回溯不会写?套路在此!(Python/Java/C++/Go/JS)","url":"https://leetcode.cn/problems/palindrome-partitioning//solution/hui-su-bu-hui-xie-tao-lu-zai-ci-pythonja-fues","content":"视频讲解
\\n请看【基础算法精讲 14】,制作不易,欢迎点赞关注~
\\n方法一:输入的视角(逗号选或不选)
\\n假设每对相邻字符之间有个逗号,那么就看每个逗号是选还是不选。
\\n也可以理解成:是否要把 $s[i]$ 当成分割出的子串的最后一个字符。注意 $s[n-1]$ 一定是最后一个字符,一定要选。
\\n\\nclass Solution:\\n def partition(self, s: str) -> List[List[str]]:\\n n = len(s)\\n ans = []\\n path = []\\n\\n # start 表示当前这段回文子串的开始位置\\n def dfs(i: int, start: int) -> None:\\n if i == n:\\n ans.append(path.copy()) # 复制 path\\n return\\n\\n # 不选 i 和 i+1 之间的逗号(i=n-1 时一定要选)\\n if i < n - 1:\\n dfs(i + 1, start)\\n\\n # 选 i 和 i+1 之间的逗号(把 s[i] 作为子串的最后一个字符)\\n t = s[start: i + 1]\\n if t == t[::-1]: # 判断是否回文\\n path.append(t)\\n dfs(i + 1, i + 1) # 下一个子串从 i+1 开始\\n path.pop() # 恢复现场\\n\\n dfs(0, 0)\\n return ans\\n
\\nclass Solution {\\n private final List<List<String>> ans = new ArrayList<>();\\n private final List<String> path = new ArrayList<>();\\n private String s;\\n\\n public List<List<String>> partition(String s) {\\n this.s = s;\\n dfs(0, 0);\\n return ans;\\n }\\n\\n // start 表示当前这段回文子串的开始位置\\n private void dfs(int i, int start) {\\n if (i == s.length()) {\\n ans.add(new ArrayList<>(path)); // 复制 path\\n return;\\n }\\n\\n // 不选 i 和 i+1 之间的逗号(i=n-1 时一定要选)\\n if (i < s.length() - 1) {\\n dfs(i + 1, start);\\n }\\n\\n // 选 i 和 i+1 之间的逗号(把 s[i] 作为子串的最后一个字符)\\n if (isPalindrome(start, i)) {\\n path.add(s.substring(start, i + 1));\\n dfs(i + 1, i + 1); // 下一个子串从 i+1 开始\\n path.remove(path.size() - 1); // 恢复现场\\n }\\n }\\n\\n private boolean isPalindrome(int left, int right) {\\n while (left < right) {\\n if (s.charAt(left++) != s.charAt(right--)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\nclass Solution {\\n bool isPalindrome(string& s, int left, int right) {\\n while (left < right) {\\n if (s[left++] != s[right--]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\npublic:\\n vector<vector<string>> partition(string s) {\\n int n = s.length();\\n vector<vector<string>> ans;\\n vector<string> path;\\n\\n // start 表示当前这段回文子串的开始位置\\n auto dfs = [&](auto&& dfs, int i, int start) {\\n if (i == n) {\\n ans.emplace_back(path);\\n return;\\n }\\n\\n // 不选 i 和 i+1 之间的逗号(i=n-1 时一定要选)\\n if (i < n - 1) {\\n dfs(dfs, i + 1, start);\\n }\\n\\n // 选 i 和 i+1 之间的逗号(把 s[i] 作为子串的最后一个字符)\\n if (isPalindrome(s, start, i)) {\\n path.push_back(s.substr(start, i - start + 1));\\n dfs(dfs, i + 1, i + 1); // 下一个子串从 i+1 开始\\n path.pop_back(); // 恢复现场\\n }\\n };\\n\\n dfs(dfs, 0, 0);\\n return ans;\\n }\\n};\\n
\\nfunc isPalindrome(s string, left, right int) bool {\\n for left < right {\\n if s[left] != s[right] {\\n return false\\n }\\n left++\\n right--\\n }\\n return true\\n}\\n\\nfunc partition(s string) (ans [][]string) {\\n n := len(s)\\n path := []string{}\\n\\n // start 表示当前这段回文子串的开始位置\\n var dfs func(int, int)\\n dfs = func(i, start int) {\\n if i == n {\\n ans = append(ans, append([]string(nil), path...)) // 复制 path\\n return\\n }\\n\\n // 不选 i 和 i+1 之间的逗号(i=n-1 时一定要选)\\n if i < n-1 {\\n dfs(i+1, start)\\n }\\n\\n // 选 i 和 i+1 之间的逗号(把 s[i] 作为子串的最后一个字符)\\n if isPalindrome(s, start, i) {\\n path = append(path, s[start:i+1])\\n dfs(i+1, i+1) // 下一个子串从 i+1 开始\\n path = path[:len(path)-1] // 恢复现场\\n }\\n }\\n\\n dfs(0, 0)\\n return\\n}\\n
\\nvar isPalindrome = function(s, left, right) {\\n while (left < right) {\\n if (s.charAt(left++) !== s.charAt(right--)) {\\n return false;\\n }\\n }\\n return true;\\n}\\n\\nvar partition = function(s) {\\n const n = s.length;\\n const ans = [];\\n const path = [];\\n\\n // start 表示当前这段回文子串的开始位置\\n function dfs(i, start) {\\n if (i === n) {\\n ans.push(path.slice()); // 复制 path\\n return;\\n }\\n\\n // 不选 i 和 i+1 之间的逗号(i=n-1 时一定要选)\\n if (i < n - 1)\\n dfs(i + 1, start);\\n\\n // 选 i 和 i+1 之间的逗号(把 s[i] 作为子串的最后一个字符)\\n if (isPalindrome(s, start, i)) {\\n path.push(s.substring(start, i + 1));\\n dfs(i + 1, i + 1); // 下一个子串从 i+1 开始\\n path.pop(); // 恢复现场\\n }\\n }\\n\\n dfs(0, 0);\\n return ans;\\n};\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n2^n)$,其中 $n$ 为 $s$ 的长度。每次都是选或不选,递归次数为一个满二叉树的节点个数,那么一共会递归 $\\\\mathcal{O}(2^n)$ 次(等比数列和),再算上判断回文和加入答案时需要 $\\\\mathcal{O}(n)$ 的时间,所以时间复杂度为 $\\\\mathcal{O}(n2^n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。返回值的空间不计。
\\n方法二:答案的视角(枚举子串结束位置)
\\n\\nclass Solution:\\n def partition(self, s: str) -> List[List[str]]:\\n n = len(s)\\n ans = []\\n path = []\\n\\n def dfs(i: int) -> None:\\n if i == n:\\n ans.append(path.copy()) # 复制 path\\n return\\n for j in range(i, n): # 枚举子串的结束位置\\n t = s[i: j + 1]\\n if t == t[::-1]: # 判断是否回文\\n path.append(t)\\n dfs(j + 1)\\n path.pop() # 恢复现场\\n\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n private final List<List<String>> ans = new ArrayList<>();\\n private final List<String> path = new ArrayList<>();\\n private String s;\\n\\n public List<List<String>> partition(String s) {\\n this.s = s;\\n dfs(0);\\n return ans;\\n }\\n\\n private void dfs(int i) {\\n if (i == s.length()) {\\n ans.add(new ArrayList<>(path)); // 复制 path\\n return;\\n }\\n for (int j = i; j < s.length(); j++) { // 枚举子串的结束位置\\n if (isPalindrome(i, j)) {\\n path.add(s.substring(i, j + 1));\\n dfs(j + 1);\\n path.remove(path.size() - 1); // 恢复现场\\n }\\n }\\n }\\n \\n private boolean isPalindrome(int left, int right) {\\n while (left < right) {\\n if (s.charAt(left++) != s.charAt(right--)) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
\\nclass Solution {\\n bool isPalindrome(string& s, int left, int right) {\\n while (left < right) {\\n if (s[left++] != s[right--]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\npublic:\\n vector<vector<string>> partition(string s) {\\n int n = s.length();\\n vector<vector<string>> ans;\\n vector<string> path;\\n\\n auto dfs = [&](auto&& dfs, int i) {\\n if (i == n) {\\n ans.emplace_back(path);\\n return;\\n }\\n for (int j = i; j < n; j++) { // 枚举子串的结束位置\\n if (isPalindrome(s, i, j)) {\\n path.push_back(s.substr(i, j - i + 1));\\n dfs(dfs, j + 1);\\n path.pop_back(); // 恢复现场\\n }\\n }\\n };\\n\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\nfunc isPalindrome(s string, left, right int) bool {\\n for left < right {\\n if s[left] != s[right] {\\n return false\\n }\\n left++\\n right--\\n }\\n return true\\n}\\n\\nfunc partition(s string) (ans [][]string) {\\n n := len(s)\\n path := []string{}\\n\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == n {\\n ans = append(ans, append([]string(nil), path...)) // 复制 path\\n return\\n }\\n for j := i; j < n; j++ { // 枚举子串的结束位置\\n if isPalindrome(s, i, j) {\\n path = append(path, s[i:j+1])\\n dfs(j + 1)\\n path = path[:len(path)-1] // 恢复现场\\n }\\n }\\n }\\n\\n dfs(0)\\n return\\n}\\n
\\nvar isPalindrome = function(s, left, right) {\\n while (left < right) {\\n if (s.charAt(left++) !== s.charAt(right--)) {\\n return false;\\n }\\n }\\n return true;\\n}\\n\\nvar partition = function(s) {\\n const n = s.length;\\n const ans = [];\\n const path = [];\\n\\n function dfs(i) {\\n if (i === n) {\\n ans.push(path.slice()); // 复制 path\\n return;\\n }\\n for (let j = i; j < n; j++) { // 枚举子串的结束位置\\n if (isPalindrome(s, i, j)) {\\n path.push(s.substring(i, j + 1));\\n dfs(j + 1);\\n path.pop(); // 恢复现场\\n }\\n }\\n }\\n\\n dfs(0);\\n return ans;\\n};\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n2^n)$,其中 $n$ 为 $s$ 的长度。答案的长度至多为逗号子集的个数,即 $\\\\mathcal{O}(2^n)$,因此会递归 $\\\\mathcal{O}(2^n)$ 次,再算上判断回文和加入答案时需要 $\\\\mathcal{O}(n)$ 的时间,所以时间复杂度为 $\\\\mathcal{O}(n2^n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。返回值的空间不计。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口(定长/不定长/多指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心算法(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"视频讲解 请看【基础算法精讲 14】,制作不易,欢迎点赞关注~\\n\\n方法一:输入的视角(逗号选或不选)\\n\\n假设每对相邻字符之间有个逗号,那么就看每个逗号是选还是不选。\\n\\n也可以理解成:是否要把 $s[i]$ 当成分割出的子串的最后一个字符。注意 $s[n-1]$ 一定是最后一个字符,一定要选。\\n\\nclass Solution:\\n def partition(self, s: str) -> List[List[str]]:\\n n = len(s)\\n ans = []\\n path…","guid":"https://leetcode.cn/problems/palindrome-partitioning//solution/hui-su-bu-hui-xie-tao-lu-zai-ci-pythonja-fues","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2023-01-13T01:39:16.087Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go] 一题一解:有序集合 + 哈希表","url":"https://leetcode.cn/problems/exam-room//solution/by-lcbin-tstp","content":"方法一:有序集合 + 哈希表
\\n考虑到每次 $seat()$ 时都需要找到最大距离的座位,我们可以使用有序集合来保存座位区间。有序集合的每个元素为一个二元组 $(l, r)$,表示 $l$ 和 $r$ 之间(不包括 $l$ 和 $r$)的座位可以坐学生。初始时有序集合中只有一个元素 $(-1, n)$,表示 $(-1, n)$ 之间的座位可以坐学生。
\\n另外,我们使用两个哈希表
\\nleft
和right
来维护每个有学生的座位的左右邻居学生,方便我们在 $leave(p)$ 时合并两个座位区间。\\nfrom sortedcontainers import SortedList\\n\\n\\nclass ExamRoom:\\n\\n def __init__(self, n: int):\\n def dist(x):\\n l, r = x\\n return r - l - 1 if l == -1 or r == n else (r - l) >> 1\\n\\n self.n = n\\n self.ts = SortedList(key=lambda x: (-dist(x), x[0]))\\n self.left = {}\\n self.right = {}\\n self.add((-1, n))\\n\\n def seat(self) -> int:\\n s = self.ts[0]\\n p = (s[0] + s[1]) >> 1\\n if s[0] == -1:\\n p = 0\\n elif s[1] == self.n:\\n p = self.n - 1\\n self.delete(s)\\n self.add((s[0], p))\\n self.add((p, s[1]))\\n return p\\n\\n def leave(self, p: int) -> None:\\n l, r = self.left[p], self.right[p]\\n self.delete((l, p))\\n self.delete((p, r))\\n self.add((l, r))\\n\\n def add(self, s):\\n self.ts.add(s)\\n self.left[s[1]] = s[0]\\n self.right[s[0]] = s[1]\\n\\n def delete(self, s):\\n self.ts.remove(s)\\n self.left.pop(s[1])\\n self.right.pop(s[0])\\n\\n\\n# Your ExamRoom object will be instantiated and called as such:\\n# obj = ExamRoom(n)\\n# param_1 = obj.seat()\\n# obj.leave(p)\\n
\\nclass ExamRoom {\\n private TreeSet<int[]> ts = new TreeSet<>((a, b) -> {\\n int d1 = dist(a), d2 = dist(b);\\n return d1 == d2 ? a[0] - b[0] : d2 - d1;\\n });\\n private Map<Integer, Integer> left = new HashMap<>();\\n private Map<Integer, Integer> right = new HashMap<>();\\n private int n;\\n\\n public ExamRoom(int n) {\\n this.n = n;\\n add(new int[] {-1, n});\\n }\\n\\n public int seat() {\\n int[] s = ts.first();\\n int p = (s[0] + s[1]) >> 1;\\n if (s[0] == -1) {\\n p = 0;\\n } else if (s[1] == n) {\\n p = n - 1;\\n }\\n del(s);\\n add(new int[] {s[0], p});\\n add(new int[] {p, s[1]});\\n return p;\\n }\\n\\n public void leave(int p) {\\n int l = left.get(p), r = right.get(p);\\n del(new int[] {l, p});\\n del(new int[] {p, r});\\n add(new int[] {l, r});\\n }\\n\\n private int dist(int[] s) {\\n int l = s[0], r = s[1];\\n return l == -1 || r == n ? r - l - 1 : (r - l) >> 1;\\n }\\n\\n private void add(int[] s) {\\n ts.add(s);\\n left.put(s[1], s[0]);\\n right.put(s[0], s[1]);\\n }\\n\\n private void del(int[] s) {\\n ts.remove(s);\\n left.remove(s[1]);\\n right.remove(s[0]);\\n }\\n}\\n\\n/**\\n * Your ExamRoom object will be instantiated and called as such:\\n * ExamRoom obj = new ExamRoom(n);\\n * int param_1 = obj.seat();\\n * obj.leave(p);\\n */\\n
\\nint N;\\n\\nint dist(const pair<int, int>& p) {\\n auto [l, r] = p;\\n if (l == -1 || r == N) return r - l - 1;\\n return (r - l) >> 1;\\n}\\n\\nstruct cmp {\\n bool operator()(const pair<int, int>& a, const pair<int, int>& b) const {\\n int d1 = dist(a), d2 = dist(b);\\n return d1 == d2 ? a.first < b.first : d1 > d2;\\n };\\n};\\n\\nclass ExamRoom {\\npublic:\\n ExamRoom(int n) {\\n N = n;\\n this->n = n;\\n add({-1, n});\\n }\\n\\n int seat() {\\n auto s = *ts.begin();\\n int p = (s.first + s.second) >> 1;\\n if (s.first == -1) {\\n p = 0;\\n } else if (s.second == n) {\\n p = n - 1;\\n }\\n del(s);\\n add({s.first, p});\\n add({p, s.second});\\n return p;\\n }\\n\\n void leave(int p) {\\n int l = left[p], r = right[p];\\n del({l, p});\\n del({p, r});\\n add({l, r});\\n }\\n\\nprivate:\\n set<pair<int, int>, cmp> ts;\\n unordered_map<int, int> left;\\n unordered_map<int, int> right;\\n int n;\\n\\n void add(pair<int, int> s) {\\n ts.insert(s);\\n left[s.second] = s.first;\\n right[s.first] = s.second;\\n }\\n\\n void del(pair<int, int> s) {\\n ts.erase(s);\\n left.erase(s.second);\\n right.erase(s.first);\\n }\\n};\\n\\n/**\\n * Your ExamRoom object will be instantiated and called as such:\\n * ExamRoom* obj = new ExamRoom(n);\\n * int param_1 = obj->seat();\\n * obj->leave(p);\\n */\\n
\\ntype ExamRoom struct {\\nrbt *redblacktree.Tree\\nleft map[int]int\\nright map[int]int\\nn int\\n}\\n\\nfunc Constructor(n int) ExamRoom {\\ndist := func(s []int) int {\\nif s[0] == -1 || s[1] == n {\\nreturn s[1] - s[0] - 1\\n}\\nreturn (s[1] - s[0]) >> 1\\n}\\ncmp := func(a, b interface{}) int {\\nx, y := a.([]int), b.([]int)\\nd1, d2 := dist(x), dist(y)\\nif d1 == d2 {\\nreturn x[0] - y[0]\\n}\\nreturn d2 - d1\\n}\\nthis := ExamRoom{redblacktree.NewWith(cmp), map[int]int{}, map[int]int{}, n}\\nthis.add([]int{-1, n})\\nreturn this\\n}\\n\\nfunc (this *ExamRoom) Seat() int {\\ns := this.rbt.Left().Key.([]int)\\np := (s[0] + s[1]) >> 1\\nif s[0] == -1 {\\np = 0\\n} else if s[1] == this.n {\\np = this.n - 1\\n}\\nthis.del(s)\\nthis.add([]int{s[0], p})\\nthis.add([]int{p, s[1]})\\nreturn p\\n}\\n\\nfunc (this *ExamRoom) Leave(p int) {\\nl, _ := this.left[p]\\nr, _ := this.right[p]\\nthis.del([]int{l, p})\\nthis.del([]int{p, r})\\nthis.add([]int{l, r})\\n}\\n\\nfunc (this *ExamRoom) add(s []int) {\\nthis.rbt.Put(s, struct{}{})\\nthis.left[s[1]] = s[0]\\nthis.right[s[0]] = s[1]\\n}\\n\\nfunc (this *ExamRoom) del(s []int) {\\nthis.rbt.Remove(s)\\ndelete(this.left, s[1])\\ndelete(this.right, s[0])\\n}\\n\\n/**\\n * Your ExamRoom object will be instantiated and called as such:\\n * obj := Constructor(n);\\n * param_1 := obj.Seat();\\n * obj.Leave(p);\\n */\\n
时间复杂度 $O(\\\\log n)$,空间复杂度 $O(n)$。其中 $n$ 为考场的座位数。
\\n
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:有序集合 + 哈希表 考虑到每次 $seat()$ 时都需要找到最大距离的座位,我们可以使用有序集合来保存座位区间。有序集合的每个元素为一个二元组 $(l, r)$,表示 $l$ 和 $r$ 之间(不包括 $l$ 和 $r$)的座位可以坐学生。初始时有序集合中只有一个元素 $(-1, n)$,表示 $(-1, n)$ 之间的座位可以坐学生。\\n\\n另外,我们使用两个哈希表 left 和 right 来维护每个有学生的座位的左右邻居学生,方便我们在 $leave(p)$ 时合并两个座位区间。\\n\\nfrom sortedcontainers import…","guid":"https://leetcode.cn/problems/exam-room//solution/by-lcbin-tstp","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-30T01:07:15.128Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[两种解法] 有序集合 & 优先队列优化","url":"https://leetcode.cn/problems/exam-room//solution/liang-chong-jie-fa-you-xu-ji-he-you-xian-bjrv","content":"解法一:有序集合
\\n首先,对于给定的区间$[L, R]$,即$L,R$的座位已经选择,当我们要在这个区间进行放置的时候,为了使离他最近的人之间的距离达到最大化,我们应该将座位选择在中点即$(L+R)/2$,所能够得到的距离定义为$(R-L)/2$。
\\n\\n例如: \\n[1, 4]\\n我们应该选择在2点,距离最近的人距离为1, 即1 2 . 4\\n[1, 5]\\n我们应该选择在3点,距离最近的人距离为2,即1.3.5\\n
Seat():
\\n
\\n接下来,我们创建一个有序集合来保存已经选了的座位,最开始当集合为空时,必然选择下标为0的座位。若集合不为空,则遍历集合中所有的区间,选择出一个距离最大的中点出来,最后再特判一下选择0点和n-1点的情况。\\n
\\n对于0点和n-1点来说:\\n0点的距离为:集合中第一个元素-0, 即set.first()\\nn-1点的距离为:(n-1) - 集合中最后一个元素,即n-1-set.last()\\n
Leave():
\\n
\\n对于删除操作,我们直接在有序集合中将要删除的座位移除即可。\\n
\\n- 时间复杂度:seat():$O(n)$, leave(): $O(logn)$
\\n- 空间复杂度:$O(n)$
\\n\\nclass ExamRoom {\\n TreeSet<Integer> set;\\n int n;\\n public ExamRoom(int n) {\\n this.n = n;\\n set = new TreeSet<>(); \\n }\\n public int seat() {\\n if (set.size() == 0) {set.add(0); return 0;} //没有人时,一定返回0\\n int pre = set.first(), ans = set.first(), idx = 0; //初始话为选择最左的长度\\n for (int x : set) {\\n if (ans < (x - pre) / 2) {\\n ans = (x - pre) / 2;\\n idx = (x + pre) / 2;\\n }\\n pre = x;\\n }\\n //最右进行判断\\n int d = n - 1 - set.last();\\n if (ans < d) {ans = d; idx = n - 1;}\\n set.add(idx);\\n return idx;\\n }\\n\\n public void leave(int p) { \\n set.remove(p);\\n }\\n}\\n\\n
解法二:有序集合 + 优先队列
\\nSeat():
\\n
\\n在解法一的基础上,我们可以通过优先队列来优化我们区间选择的操作,在解法一我们通过遍历来求的拥有最大距离的区间,再特判一下最左最右端点。我们可以将区间直接放在优先队列中,通过优先队列每次弹出距离最大的一个区间,再选择区间的中点即可。
\\n我们每次从优先队列中弹出一个区间$[L,R]$,这是所有区间中距离最大的一个区间,我们将它与选择$0$点和选择$n-1$点的距离进行比较,若更大,那么我们就选择中点为$mid=(L+R)/2$,会产生新的区间$[L,mid]$和$[mid, R]$,放入优先队列中;若选择最左最右点,那么也会产生新的区间$[0,set.first()]$或$[set.last(), n - 1]$。Leave():
\\n
\\n对于删除操作,当我们删除集合中第一个元素和最后一个元素时,并不会产生新的区间。当删除集合中间的元素时,如$[L,mid,R]$,我们删除mid点会产生新的区间$[L, R]$将新区间添加进我们的优先队列中即可。
\\n而我们还需要在队列中将以前的区间$[L,mid]和[mid, R]$进行删除,我们使用延迟删除的技巧,因此在$seat()$中我们从队列中弹出的区间可能是需要删除的,我们判断区间的两个端点是否在集合set中,若不在代表需要删除,并且区间中间不能有任何座位被选取。\\n
\\n- 时间复杂度:seat():$O(logn)$, leave(): $O(logn)$
\\n- 空间复杂度:$O(n)$
\\n\\nclass ExamRoom {\\n PriorityQueue<int[]> q;\\n TreeSet<Integer> set;\\n int n;\\n public ExamRoom(int n) {\\n this.n = n;\\n q = new PriorityQueue<>((a, b) -> {\\n int d1 = (a[1] - a[0]) / 2, d2 = (b[1] - b[0]) / 2;\\n return d1 == d2 ? a[0] - b[0] : d2 - d1; //当长度相等时,坐标更小先弹出,当不相等时,长度更大的先弹出\\n });\\n set = new TreeSet<>(); //创建有序集合\\n }\\n\\n public int seat() {\\n if (set.size() == 0) { set.add(0); return 0;} //1.没有人时,一定返回0\\n int d1 = set.first(), d2 = n - 1 - set.last(); //获取最左和最右放置学生能获取的长度\\n while (set.size() >= 2) { //2.大于等于两个人的时候,可以选择最左最右 或者中间的区间\\n int[] t = q.poll();\\n if (!set.contains(t[0]) || !set.contains(t[1]) || set.higher(t[0]) != t[1]) continue; //无效区间,某个端点已经被删除\\n int d3 = (t[1] - t[0]) / 2; \\n if (d3 <= d1 || d3 < d2) {q.add(new int[]{t[0], t[1]}); break;}; //选择最左或者最右\\n int mid = (t[0] + t[1]) / 2; //选择终点\\n q.add(new int[]{t[0], mid});\\n q.add(new int[]{mid, t[1]});\\n set.add(mid); \\n return mid;\\n } \\n //3.选择最左或者最右的位置\\n int l = 0, r = set.first(), sel = 0;\\n if (d1 < d2) {l = set.last(); r = n - 1; sel = n - 1;}\\n q.add(new int[]{l, r});\\n set.add(sel);\\n return sel;\\n }\\n\\n public void leave(int p) { \\n if (p != set.first() && p != set.last()) q.add(new int[]{set.lower(p), set.higher(p)}); //如果不是删除两端点, 那么会增加新区间\\n set.remove(p); \\n }\\n}\\n
\\n如果有问题,欢迎评论区交流, 如果有帮助到你,请给题解点个赞和收藏哈~~~
\\n","description":"解法一:有序集合 首先,对于给定的区间$[L, R]$,即$L,R$的座位已经选择,当我们要在这个区间进行放置的时候,为了使离他最近的人之间的距离达到最大化,我们应该将座位选择在中点即$(L+R)/2$,所能够得到的距离定义为$(R-L)/2$。\\n\\n\\n\\n例如: \\n[1, 4]\\n我们应该选择在2点,距离最近的人距离为1, 即1 2 . 4\\n[1, 5]\\n我们应该选择在3点,距离最近的人距离为2,即1.3.5\\n\\n\\nSeat(): \\n\\n\\n 接下来,我们创建一个有序集合来保存已经选了的座位,最开始当集合为空时,必然选择下标为0的座位。若集合不为空,则遍历集合中所有的区间…","guid":"https://leetcode.cn/problems/exam-room//solution/liang-chong-jie-fa-you-xu-ji-he-you-xian-bjrv","author":"Tizzi","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-30T01:00:30.612Z","media":[{"url":"https://pic.leetcode.cn/1672362051-CvEgjQ-%E5%9B%BE%E7%89%87.png","type":"photo","width":1953,"height":703,"blurhash":"LGSY{q?bt7?b~qxuoLt7ayWBofj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"考场就座","url":"https://leetcode.cn/problems/exam-room//solution/kao-chang-jiu-zuo-by-leetcode-solution-074y","content":"方法一:延迟删除 + 有序集合 + 优先队列
\\n假设有两个学生,他们的位置分别为 $s_1$ 和 $s_2$,我们用区间 $[s_1, s_2]$ 表示他们之间的空闲座位区间。如果固定某一个区间,那么座位选择该区间的中点 $s=s_1 + \\\\lfloor \\\\frac{s_2-s_1}{2} \\\\rfloor$ 能够使新进入的学生与离他最近的人之间的距离达到最大化。
\\n由题意可知,我们需要实时地维护这些区间的顺序关系,并且能实时地获取这些区间中最优区间(最优区间:能够使新进入的学生与离他最近的人之间的距离达到最大化),同时还要求实时地删除某个学生占用的座位以及修改对应的区间关系。现成的数据结构并不能很好地满足这些需求,我们尝试将删除区间这一操作延迟到获取最优区间时执行。
\\n使用有序集合 $\\\\textit{seats}$ 保存已经有学生的座位编号,优先队列 $\\\\textit{pq}$ 保存座位区间(假设优先队列中的两个区间分别为 $[s_1, s_2]$ 和 $[s_3, s_4]$,那么如果 $\\\\lfloor \\\\frac{s_2 - s_1}{2} \\\\rfloor \\\\gt \\\\lfloor \\\\frac{s_4 - s_3}{2} \\\\rfloor$ 或者 $\\\\lfloor \\\\frac{s_2 - s_1}{2} \\\\rfloor = \\\\lfloor \\\\frac{s_4 - s_3}{2} \\\\rfloor \\\\space and \\\\space s_1 \\\\lt s_3$,那么区间 $[s_1, s_2]$ 比区间 $[s_3, s_4]$ 更优)。
\\n\\n
\\n- \\n
\\n对于 $\\\\text{seat}$ 函数:
\\n学生进入考场时,有三种情况:
\\n\\n
\\n- \\n
\\n考场没有一个学生,那么学生只能坐在座位 $0$;
\\n将座位 $0$ 插入有序集合 $\\\\textit{seats}$,并且返回座位 $0$。
\\n- \\n
\\n考场有超过两位学生,并且选择这些学生所在的座位组成的区间比直接坐在考场的最左或者最右的座位更优;
\\n首先判断优先队列中最优的区间是否有效(有效指当前区间的左右两个端点的座位有学生,中间的所有座位都没有学生),如果无效,删除该区间。设当前有效区间为 $[s_1, s_2]$,最左的座位跟最左的有学生的座位的距离为 $\\\\textit{left}$,最右的座位跟最右的有学生的座位的距离为 $\\\\textit{right}$,如果 $\\\\lfloor \\\\frac{s2 - s1}{2} \\\\rfloor \\\\gt \\\\textit{left}$ 且 $\\\\lfloor \\\\frac{s2 - s1}{2} \\\\rfloor \\\\ge \\\\textit{right}$,那么选择当前最优区间比直接坐在考场的最左或者最右的座位更优,学生坐下的座位为 $s=s_1 + \\\\lfloor \\\\frac{s_2-s_1}{2} \\\\rfloor$,将当前区间从优先队列 $\\\\textit{pq}$ 中移除,然后分别将新增加的两个区间 $[s_1, s]$ 和 $[s, s_2]$ 插入优先队列 $\\\\textit{pq}$,将 $s$ 插入有序集合 $\\\\textit{seats}$,返回座位 $s$。
\\n- \\n
\\n考场少于两位学生,或者直接坐在考场的最左或者最右的座位比选择这些学生组成的区间更优。
\\n如果是最左的座位更优,那么将新增加的区间插入优先队列 $\\\\textit{pq}$,最左的座位插入有序集合 $\\\\textit{seats}$,并且返回最左的座位;最右的座位的做法类似。
\\n- \\n
\\n对于 $\\\\text{leave}$ 函数:
\\n如果要删除的座位 $p$ 上的学生不是所有学生的最左或者最右的学生,那么删除该学生会产生新的区间,我们将该区间放入优先队列 $\\\\textit{pq}$ 中,然后在有序集合 $\\\\textit{seats}$ 中删除该学生;否则只需要在有序集合 $\\\\textit{seats}$ 中删除该学生。对于删除座位后已经无效的区间,我们只需要在 $\\\\text{seat}$ 函数中判断区间是否有效即可。
\\n###C++
\\n\\nstruct Comp {\\n bool operator()(const pair<int, int> &p1, const pair<int, int> &p2) {\\n int d1 = p1.second - p1.first, d2 = p2.second - p2.first;\\n return d1 / 2 < d2 / 2 || (d1 / 2 == d2 / 2 && p1.first > p2.first);\\n }\\n};\\n\\nclass ExamRoom {\\nprivate:\\n int n;\\n set<int> seats;\\n priority_queue<pair<int, int>, vector<pair<int, int>>, Comp> pq;\\n\\npublic:\\n ExamRoom(int n) : n(n) {\\n \\n }\\n\\n int seat() {\\n if (seats.empty()) {\\n seats.insert(0);\\n return 0;\\n }\\n int left = *seats.begin(), right = n - 1 - *seats.rbegin();\\n while (seats.size() >= 2) {\\n auto p = pq.top();\\n if (seats.count(p.first) > 0 && seats.count(p.second) > 0 && \\n *next(seats.find(p.first)) == p.second) { // 不属于延迟删除的区间\\n int d = p.second - p.first;\\n if (d / 2 < right || d / 2 <= left) { // 最左或最右的座位更优\\n break;\\n }\\n pq.pop();\\n pq.push({p.first, p.first + d / 2});\\n pq.push({p.first + d / 2, p.second});\\n seats.insert(p.first + d / 2);\\n return p.first + d / 2;\\n }\\n pq.pop(); // leave 函数中延迟删除的区间在此时删除\\n }\\n if (right > left) { // 最右的位置更优\\n pq.push({*seats.rbegin(), n - 1});\\n seats.insert(n - 1);\\n return n - 1;\\n } else {\\n pq.push({0, *seats.begin()});\\n seats.insert(0);\\n return 0;\\n }\\n }\\n\\n void leave(int p) {\\n if (p != *seats.begin() && p != *seats.rbegin()) {\\n auto it = seats.find(p);\\n pq.push({*prev(it), *next(it)});\\n }\\n seats.erase(p);\\n }\\n};\\n
###Java
\\n\\nclass ExamRoom {\\n int n;\\n TreeSet<Integer> seats;\\n PriorityQueue<int[]> pq;\\n\\n public ExamRoom(int n) {\\n this.n = n;\\n this.seats = new TreeSet<Integer>();\\n this.pq = new PriorityQueue<int[]>((a, b) -> {\\n int d1 = a[1] - a[0], d2 = b[1] - b[0];\\n return d1 / 2 < d2 / 2 || (d1 / 2 == d2 / 2 && a[0] > b[0]) ? 1 : -1;\\n });\\n }\\n\\n public int seat() {\\n if (seats.isEmpty()) {\\n seats.add(0);\\n return 0;\\n }\\n int left = seats.first(), right = n - 1 - seats.last();\\n while (seats.size() >= 2) {\\n int[] p = pq.peek();\\n if (seats.contains(p[0]) && seats.contains(p[1]) && seats.higher(p[0]) == p[1]) { // 不属于延迟删除的区间\\n int d = p[1] - p[0];\\n if (d / 2 < right || d / 2 <= left) { // 最左或最右的座位更优\\n break;\\n }\\n pq.poll();\\n pq.offer(new int[]{p[0], p[0] + d / 2});\\n pq.offer(new int[]{p[0] + d / 2, p[1]});\\n seats.add(p[0] + d / 2);\\n return p[0] + d / 2;\\n }\\n pq.poll(); // leave 函数中延迟删除的区间在此时删除\\n }\\n if (right > left) { // 最右的位置更优\\n pq.offer(new int[]{seats.last(), n - 1});\\n seats.add(n - 1);\\n return n - 1;\\n } else {\\n pq.offer(new int[]{0, seats.first()});\\n seats.add(0);\\n return 0;\\n }\\n }\\n\\n public void leave(int p) {\\n if (p != seats.first() && p != seats.last()) {\\n int prev = seats.lower(p), next = seats.higher(p);\\n pq.offer(new int[]{prev, next});\\n }\\n seats.remove(p);\\n }\\n}\\n
###C#
\\n\\npublic class ExamRoom {\\n private int n;\\n private SortedSet<int> seats;\\n private PriorityQueue<int[], int[]> pq;\\n\\n public ExamRoom(int n) {\\n this.n = n;\\n this.seats = new SortedSet<int>();\\n this.pq = new PriorityQueue<int[], int[]>(Comparer<int[]>.Create((p1, p2) => {\\n int d1 = p1[1] - p1[0], d2 = p2[1] - p2[0];\\n return d1 / 2 == d2 / 2 ? (p1[0] > p2[0] ? 1 : -1) : (d1 / 2 < d2 / 2 ? 1 : -1);\\n }));\\n }\\n \\n public int Seat() {\\n if (seats.Count == 0) {\\n seats.Add(0);\\n return 0;\\n }\\n int left = seats.Min, right = n - 1 - seats.Max;\\n while (seats.Count >= 2) {\\n var p = pq.Peek();\\n if (seats.Contains(p[0]) && seats.Contains(p[1]) && seats.GetViewBetween(p[0] + 1, n - 1).Min == p[1]) { // 不属于延迟删除的区间\\n int d = p[1] - p[0];\\n if (d / 2 < right || d / 2 <= left) { // 最左或最右的座位更优\\n break;\\n }\\n pq.Dequeue();\\n pq.Enqueue(new int[] {p[0], p[0] + d / 2}, new int[] {p[0], p[0] + d / 2});\\n pq.Enqueue(new int[] {p[0] + d / 2, p[1]}, new int[] {p[0] + d / 2, p[1]});\\n seats.Add(p[0] + d / 2);\\n return p[0] + d / 2;\\n }\\n pq.Dequeue(); // leave 函数中延迟删除的区间在此时删除\\n }\\n if (right > left) { // 最右的位置更优\\n pq.Enqueue(new int[] {seats.Max, n - 1}, new int[] {seats.Max, n - 1});\\n seats.Add(n - 1);\\n return n - 1;\\n } else {\\n pq.Enqueue(new int[] {0, seats.Min}, new int[] {0, seats.Min});\\n seats.Add(0);\\n return 0;\\n }\\n }\\n \\n public void Leave(int p) {\\n if (p != seats.Min && p != seats.Max) {\\n int prev = seats.GetViewBetween(0, p - 1).Max;\\n int next = seats.GetViewBetween(p + 1, n - 1).Min;\\n pq.Enqueue(new int[] {prev, next}, new int[] {prev, next});\\n }\\n seats.Remove(p);\\n }\\n}\\n
###Python
\\n\\nfrom sortedcontainers import SortedSet\\nimport heapq\\n\\nclass Interval:\\n def __init__(self, start, end):\\n self.start = start\\n self.end = end\\n\\n def __lt__(self, other):\\n d1 = (self.end - self.start) // 2\\n d2 = (other.end - other.start) // 2\\n if d1 == d2:\\n return self.start < other.start\\n return d1 > d2\\n\\nclass ExamRoom:\\n def __init__(self, n: int):\\n self.n = n\\n self.seats = SortedSet()\\n self.pq = []\\n\\n def seat(self) -> int:\\n if not self.seats:\\n self.seats.add(0)\\n return 0\\n\\n left = self.seats[0]\\n right = self.n - 1 - self.seats[-1]\\n while len(self.seats) >= 2:\\n p = self.pq[0]\\n start, end = p.start, p.end\\n if start in self.seats and end in self.seats and self.seats[self.seats.index(start) + 1] == end: # 不属于延迟删除的区间\\n d = end - start\\n if d // 2 < right or d // 2 <= left: # 最左或最右的座位更优\\n break\\n heapq.heappop(self.pq)\\n mid = start + d // 2\\n heapq.heappush(self.pq, Interval(start, mid))\\n heapq.heappush(self.pq, Interval(mid, end))\\n self.seats.add(mid)\\n return mid\\n heapq.heappop(self.pq) # leave 函数中延迟删除的区间在此时删除\\n\\n if right > left: # 最右的位置更优\\n heapq.heappush(self.pq, Interval(self.seats[-1], self.n - 1))\\n self.seats.add(self.n - 1)\\n return self.n - 1\\n else:\\n heapq.heappush(self.pq, Interval(0, self.seats[0]))\\n self.seats.add(0)\\n return 0\\n\\n def leave(self, p: int) -> None:\\n if p != self.seats[0] and p != self.seats[-1]:\\n prev = self.seats[self.seats.index(p) - 1]\\n next = self.seats[self.seats.index(p) + 1]\\n heapq.heappush(self.pq, Interval(prev, next))\\n self.seats.remove(p)\\n
###Rust
\\n\\nuse std::cmp::Ordering;\\nuse std::collections::{BTreeSet, BinaryHeap};\\n\\n#[derive(Debug, Eq, PartialEq)]\\nstruct Interval {\\n start: i32,\\n end: i32,\\n}\\n\\nimpl Interval {\\n fn new(start: i32, end: i32) -> Self {\\n Interval {start, end}\\n }\\n\\n fn length(&self) -> i32 {\\n (self.end - self.start) / 2\\n }\\n}\\n\\nimpl Ord for Interval {\\n fn cmp(&self, other: &Self) -> Ordering {\\n let length_cmp = self.length().cmp(&other.length());\\n if length_cmp == Ordering::Equal {\\n other.start.cmp(&self.start)\\n } else {\\n length_cmp\\n }\\n }\\n}\\n\\nimpl PartialOrd for Interval {\\n fn partial_cmp(&self, other: &Self) -> Option<Ordering> {\\n Some(self.cmp(other))\\n }\\n}\\n\\nstruct ExamRoom {\\n n: i32,\\n seats: BTreeSet<i32>,\\n pq: BinaryHeap<Interval>,\\n}\\n\\nimpl ExamRoom {\\n fn new(n: i32) -> Self {\\n ExamRoom {\\n n,\\n seats: BTreeSet::new(),\\n pq: BinaryHeap::new(),\\n }\\n }\\n \\n fn seat(&mut self) -> i32 {\\n if self.seats.is_empty() {\\n self.seats.insert(0);\\n return 0;\\n }\\n\\n let left = *self.seats.iter().next().unwrap();\\n let right = self.n - 1 - *self.seats.iter().rev().next().unwrap();\\n while self.seats.len() >= 2 {\\n if let Some(interval) = self.pq.peek() {\\n let start = interval.start;\\n let end = interval.end;\\n if self.seats.contains(&start) && self.seats.contains(&end) &&\\n *self.seats.range((start + 1)..).next().unwrap() == end { // 不属于延迟删除的区间\\n let d = end - start;\\n if d / 2 < right || d / 2 <= left { // 最左或最右的座位更优\\n break;\\n }\\n\\n self.pq.pop();\\n let mid = start + d / 2;\\n self.pq.push(Interval::new(start, mid));\\n self.pq.push(Interval::new(mid, end));\\n self.seats.insert(mid);\\n return mid;\\n }\\n }\\n self.pq.pop(); // leave 函数中延迟删除的区间在此时删除\\n }\\n\\n if right > left { // 最右的位置更优\\n let last = *self.seats.iter().rev().next().unwrap();\\n self.pq.push(Interval::new(last, self.n - 1));\\n self.seats.insert(self.n - 1);\\n self.n - 1\\n } else {\\n let first = *self.seats.iter().next().unwrap();\\n self.pq.push(Interval::new(0, first));\\n self.seats.insert(0);\\n 0\\n }\\n }\\n \\n fn leave(&mut self, p: i32) {\\n if p != *self.seats.iter().next().unwrap() && p != *self.seats.iter().rev().next().unwrap() {\\n let prev = *self.seats.range(..p).rev().next().unwrap();\\n let next = *self.seats.range((p + 1)..).next().unwrap();\\n self.pq.push(Interval::new(prev, next));\\n }\\n self.seats.remove(&p);\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:延迟删除 + 有序集合 + 优先队列 假设有两个学生,他们的位置分别为 $s_1$ 和 $s_2$,我们用区间 $[s_1, s_2]$ 表示他们之间的空闲座位区间。如果固定某一个区间,那么座位选择该区间的中点 $s=s_1 + \\\\lfloor \\\\frac{s_2-s_1}{2} \\\\rfloor$ 能够使新进入的学生与离他最近的人之间的距离达到最大化。\\n\\n由题意可知,我们需要实时地维护这些区间的顺序关系,并且能实时地获取这些区间中最优区间(最优区间:能够使新进入的学生与离他最近的人之间的距离达到最大化…","guid":"https://leetcode.cn/problems/exam-room//solution/kao-chang-jiu-zuo-by-leetcode-solution-074y","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-29T06:35:37.873Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"c++滑动窗口","url":"https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right//solution/c-by-thdlrt-a8wl","content":"- \\n
\\n时间复杂度:
\\n\\n
\\n- \\n
\\n$\\\\text{seat}$ 函数:均摊时间复杂度 $O(\\\\log m)$,其中 $m$ 是调用 $\\\\text{seat}$ 函数的次数。因为优先队列最多保存不超过 $2 \\\\times m$ 个元素,所以一次 $\\\\textit{seat}$ 函数平均只有不超过 $2$ 次的优先队列延迟删除操作,对优先队列和有序集合操作的时间复杂度都是 $O(\\\\log m)$。
\\n- \\n
\\n$\\\\text{leave}$ 函数:$O(\\\\log m)$。删除有序集合 $\\\\textit{seats}$ 的一个元素和优先队列插入一个元素的时间复杂度都是 $O(\\\\log m)$。
\\n- \\n
\\n空间复杂度:$O(m)$。有序集合 $\\\\textit{seats}$ 和优先队列 $\\\\textit{pq}$ 中最多保存不超过 $2 \\\\times m$ 个元素。
\\n问题转化为从中间去掉一段连续的序列,使得剩下的元素中abc均大于等于k
\\n
\\n也就是中间的区间中abc的数目小于其总数减去k\\n","description":"问题转化为从中间去掉一段连续的序列,使得剩下的元素中abc均大于等于k 也就是中间的区间中abc的数目小于其总数减去k\\n\\nclass Solution {\\npublic:\\n int takeCharacters(string s, int k) {\\n int a=count(s.begin(),s.end(),\'a\'),b=count(s.begin(),s.end(),\'b\'),c=count(s.begin(),s.end(),\'c\'),n=s.size();//计算总数\\n if(aclass Solution {\\npublic:\\n int takeCharacters(string s, int k) {\\n int a=count(s.begin(),s.end(),\'a\'),b=count(s.begin(),s.end(),\'b\'),c=count(s.begin(),s.end(),\'c\'),n=s.size();//计算总数\\n if(a<k||b<k||c<k)\\n return -1;\\n a-=k,b-=k,c-=k;\\n vector<int>num(3),check{a,b,c};//num记录abc的数目,check为上限\\n int ans=INT_MAX,i,j;\\n for(i=0,j=0;j<s.size();j++)//寻找滑动窗口的最大长度\\n {\\n num[s[j]-\'a\']++;\\n if(num[s[j]-\'a\']>check[s[j]-\'a\'])\\n {\\n while(num[s[j]-\'a\']>check[s[j]-\'a\'])\\n {\\n num[s[i]-\'a\']--;\\n i++;\\n }\\n }\\n ans=min(n-(j-i+1),ans);\\n }\\n return ans;\\n }\\n};\\n
解法:二分 & 枚举\\n 首先二分答案 $x$,接下来枚举从 $s$ 的最左侧取几个字符(设为 $y$),那么从 $s$ 的右侧自然就取走了 $(x - y)$ 个字符。检查是否存在一个 $0 \\\\le y \\\\le x$ 使得取出的所有字符满足条件即可。复杂度 $\\\\mathcal{O}(n\\\\log n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:二分 & 枚举 首先二分答案 $x$,接下来枚举从 $s$ 的最左侧取几个字符(设为 $y$),那么从 $s$ 的右侧自然就取走了 $(x - y)$ 个字符。检查是否存在一个 $0 \\\\le y \\\\le x$ 使得取出的所有字符满足条件即可。复杂度 $\\\\mathcal{O}(n\\\\log n)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n int takeCharacters(string s, int K) {\\n // 特殊情况\\n if (K == 0) return 0…","guid":"https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right//solution/by-tsreaper-0z0f","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-25T04:10:24.768Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"逆向思维(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right//solution/on-shuang-zhi-zhen-by-endlesscheng-4g9p","content":"class Solution {\\npublic:\\n int takeCharacters(string s, int K) {\\n // 特殊情况\\n if (K == 0) return 0;\\n\\n int n = s.size();\\n // 判断无解\\n int cnt[3] = {0};\\n for (char c : s) cnt[c - \'a\']++;\\n if (cnt[0] < K || cnt[1] < K || cnt[2] < K) return -1;\\n\\n // 检查二分的答案 LIM 是否符合要求\\n auto check = [&](int LIM) {\\n // 先假设 LIM 个字符全部从左边取出,看是否符合要求\\n int cnt[3] = {0};\\n for (int i = 0; i < LIM; i++) cnt[s[i] - \'a\']++;\\n if (cnt[0] >= K && cnt[1] >= K && cnt[2] >= K) return true;\\n // 每次左边少取一个字符,右边多取一个字符,看是否符合要求\\n for (int i = 1; i <= LIM; i++) {\\n cnt[s[LIM - i] - \'a\']--;\\n cnt[s[n - i] - \'a\']++;\\n if (cnt[0] >= K && cnt[1] >= K && cnt[2] >= K) return true;\\n }\\n return false;\\n };\\n\\n // 二分答案\\n int head = 1, tail = n;\\n while (head < tail) {\\n int mid = (head + tail) >> 1;\\n if (check(mid)) tail = mid;\\n else head = mid + 1;\\n }\\n return head;\\n }\\n};\\n
思路
\\n比如 $s$ 中有 $3$ 个 $\\\\texttt{a}$,$4$ 个 $\\\\texttt{b}$,$5$ 个 $\\\\texttt{c}$,$k=2$,每种字母至少取走 $2$ 个,等价于剩下的字母至多有 $1$ 个 $\\\\texttt{a}$,$2$ 个 $\\\\texttt{b}$ 和 $3$ 个 $\\\\texttt{c}$。
\\n由于只能从 $s$ 最左侧和最右侧取走字母,所以剩下的字母是 $s$ 的子串。
\\n设 $s$ 中的 $\\\\texttt{a},\\\\texttt{b},\\\\texttt{c}$ 的个数分别为 $x,y,z$,现在问题变成:
\\n\\n
\\n- 计算 $s$ 的最长子串长度,该子串满足 $\\\\texttt{a},\\\\texttt{b},\\\\texttt{c}$ 的个数分别至多为 $x-k,y-k,z-k$。
\\n由于子串越短越能满足要求,越长越不能满足要求,有单调性,可以用滑动窗口解决。如果你不了解滑动窗口,可以看视频【基础算法精讲 03】。
\\n实现技巧
\\n与其维护窗口内的字母个数,不如直接维护窗口外的字母个数,这也是我们取走的字母个数。
\\n\\n
\\n- 一开始,假设我们取走了所有的字母。或者说,初始窗口是空的,窗口外的字母个数就是 $s$ 的每个字母的出现次数。
\\n- 右端点字母进入窗口后,该字母取走的个数减一。
\\n- 如果减一后,窗口外该字母的个数小于 $k$,说明子串太长了,或者取走的字母个数太少了,那么就不断右移左端点,把左端点字母移出窗口,相当于我们取走移出窗口的字母,直到该字母个数等于 $k$,退出内层循环。
\\n- 内层循环结束后,用窗口长度 $\\\\textit{right}-\\\\textit{left}+1$ 更新子串长度的最大值。
\\n最后,原问题的答案为 $n$ 减去子串长度的最大值。
\\n特别地,如果 $s$ 中某个字母的个数不足 $k$,那么无法满足题目要求,返回 $-1$。
\\n\\nclass Solution:\\n def takeCharacters(self, s: str, k: int) -> int:\\n cnt = Counter(s) # 一开始,把所有字母都取走\\n if any(cnt[c] < k for c in \\"abc\\"):\\n return -1 # 字母个数不足 k\\n\\n mx = left = 0\\n for right, c in enumerate(s):\\n cnt[c] -= 1 # 移入窗口,相当于不取走 c\\n while cnt[c] < k: # 窗口之外的 c 不足 k\\n cnt[s[left]] += 1 # 移出窗口,相当于取走 s[left]\\n left += 1\\n mx = max(mx, right - left + 1)\\n return len(s) - mx\\n
\\nclass Solution {\\n public int takeCharacters(String S, int k) {\\n char[] s = S.toCharArray();\\n int[] cnt = new int[3];\\n for (char c : s) {\\n cnt[c - \'a\']++; // 一开始,把所有字母都取走\\n }\\n if (cnt[0] < k || cnt[1] < k || cnt[2] < k) {\\n return -1; // 字母个数不足 k\\n }\\n\\n int mx = 0; // 子串最大长度\\n int left = 0;\\n for (int right = 0; right < s.length; right++) {\\n int c = s[right] - \'a\';\\n cnt[c]--; // 移入窗口,相当于不取走 c\\n while (cnt[c] < k) { // 窗口之外的 c 不足 k\\n cnt[s[left] - \'a\']++; // 移出窗口,相当于取走 s[left]\\n left++;\\n }\\n mx = Math.max(mx, right - left + 1);\\n }\\n return s.length - mx;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int takeCharacters(string s, int k) {\\n int cnt[3]{};\\n for (char c : s) {\\n cnt[c - \'a\']++; // 一开始,把所有字母都取走\\n }\\n if (cnt[0] < k || cnt[1] < k || cnt[2] < k) {\\n return -1; // 字母个数不足 k\\n }\\n\\n int mx = 0, left = 0;\\n for (int right = 0; right < s.length(); right++) {\\n char c = s[right] - \'a\';\\n cnt[c]--; // 移入窗口,相当于不取走 c\\n while (cnt[c] < k) { // 窗口之外的 c 不足 k\\n cnt[s[left] - \'a\']++; // 移出窗口,相当于取走 s[left]\\n left++;\\n }\\n mx = max(mx, right - left + 1);\\n }\\n return s.length() - mx;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint takeCharacters(char* s, int k) {\\n int cnt[3] = {};\\n for (int i = 0; s[i]; i++) {\\n cnt[s[i] - \'a\']++; // 一开始,把所有字母都取走\\n }\\n if (cnt[0] < k || cnt[1] < k || cnt[2] < k) {\\n return -1; // 字母个数不足 k\\n }\\n\\n int mx = 0, left = 0, right = 0;\\n for (; s[right]; right++) {\\n char c = s[right] - \'a\';\\n cnt[c]--; // 移入窗口,相当于不取走 c\\n while (cnt[c] < k) { // 窗口之外的 c 不足 k\\n cnt[s[left] - \'a\']++; // 移出窗口,相当于取走 s[left]\\n left++;\\n }\\n mx = MAX(mx, right - left + 1);\\n }\\n return right - mx;\\n}\\n
\\nfunc takeCharacters(s string, k int) int {\\ncnt := [3]int{}\\nfor _, c := range s {\\ncnt[c-\'a\']++ // 一开始,把所有字母都取走\\n}\\nif cnt[0] < k || cnt[1] < k || cnt[2] < k {\\nreturn -1 // 字母个数不足 k\\n}\\n\\nmx, left := 0, 0\\nfor right, c := range s {\\nc -= \'a\'\\ncnt[c]-- // 移入窗口,相当于不取走 c\\nfor cnt[c] < k { // 窗口之外的 c 不足 k\\ncnt[s[left]-\'a\']++ // 移出窗口,相当于取走 s[left]\\nleft++\\n}\\nmx = max(mx, right-left+1)\\n}\\nreturn len(s) - mx\\n}\\n
\\nvar takeCharacters = function(s, k) {\\n const ordA = \'a\'.charCodeAt(0);\\n const cnt = [0, 0, 0];\\n for (const c of s) {\\n cnt[c.charCodeAt(0) - ordA]++; // 一开始,把所有字母都取走\\n }\\n if (cnt[0] < k || cnt[1] < k || cnt[2] < k) {\\n return -1; // 字母个数不足 k\\n }\\n\\n let mx = 0, left = 0;\\n for (let right = 0; right < s.length; right++) {\\n let c = s[right].charCodeAt(0) - ordA;\\n cnt[c]--; // 移入窗口,相当于不取走 c\\n while (cnt[c] < k) { // 窗口之外的 c 不足 k\\n cnt[s[left].charCodeAt(0) - ordA]++; // 移出窗口,相当于取走 s[left]\\n left++;\\n }\\n mx = Math.max(mx, right - left + 1);\\n }\\n return s.length - mx;\\n};\\n
\\nimpl Solution {\\n pub fn take_characters(s: String, k: i32) -> i32 {\\n let mut cnt = [0; 3];\\n for c in s.bytes() {\\n cnt[(c - b\'a\') as usize] += 1; // 一开始,把所有字母都取走\\n }\\n if cnt[0] < k || cnt[1] < k || cnt[2] < k {\\n return -1; // 字母个数不足 k\\n }\\n\\n let mut mx = 0;\\n let mut left = 0;\\n let s = s.as_bytes();\\n for (right, &c) in s.iter().enumerate() {\\n let c = (c - b\'a\') as usize;\\n cnt[c] -= 1; // 移入窗口,相当于不取走 c\\n while cnt[c] < k { // 窗口之外的 c 不足 k\\n cnt[(s[left] - b\'a\') as usize] += 1; // 移出窗口,相当于取走 s[left]\\n left += 1;\\n }\\n mx = mx.max(right - left + 1);\\n }\\n (s.len() - mx) as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n+|\\\\Sigma|)$,其中 $n$ 为 $s$ 的长度。虽然写了个二重循环,但是内层循环中对 $\\\\textit{left}$ 加一的总执行次数不会超过 $n$ 次,所以二重循环的时间复杂度为 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(|\\\\Sigma|)$,其中 $|\\\\Sigma|=3$ 为字符集合的大小。
\\n更多类似题目,见下面的滑动窗口题单。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 比如 $s$ 中有 $3$ 个 $\\\\texttt{a}$,$4$ 个 $\\\\texttt{b}$,$5$ 个 $\\\\texttt{c}$,$k=2$,每种字母至少取走 $2$ 个,等价于剩下的字母至多有 $1$ 个 $\\\\texttt{a}$,$2$ 个 $\\\\texttt{b}$ 和 $3$ 个 $\\\\texttt{c}$。\\n\\n由于只能从 $s$ 最左侧和最右侧取走字母,所以剩下的字母是 $s$ 的子串。\\n\\n设 $s$ 中的 $\\\\texttt{a},\\\\texttt{b},\\\\texttt{c}$ 的个数分别为 $x,y,z$,现在问题变成:\\n\\n计算 $s…","guid":"https://leetcode.cn/problems/take-k-of-each-character-from-left-and-right//solution/on-shuang-zhi-zhen-by-endlesscheng-4g9p","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-25T04:08:50.360Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"袋子里最少数目的球","url":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/dai-zi-li-zui-shao-shu-mu-de-qiu-by-leet-boay","content":"方法一:二分查找
\\n思路与算法
\\n我们可以将题目中的要求转换成判定问题,即:
\\n\\n\\n给定 $\\\\textit{maxOperations}$ 次操作次数,能否可以使得单个袋子里球数目的最大值不超过 $y$。
\\n如果 $y = y_0$ 是一个满足要求的答案,那么所有大于 $y_0$ 的 $y$ 同样也是满足要求的。因此存在一个 $y = y_\\\\textit{opt}$,使得当 $y \\\\geq y_\\\\textit{opt}$ 时都是满足要求的,当 $y < y_\\\\textit{opt}$ 时都是不满足要求的。这个 $y_\\\\textit{opt}$ 就是最终的答案。
\\n因此,我们可以通过二分查找的方式得到答案。二分查找的下界为 $1$,上界为数组 $\\\\textit{nums}$ 中的最大值,即单个袋子中最多的球数。
\\n当我们二分查找到 $y$ 时,对于第 $i$ 个袋子,其中有 $\\\\textit{nums}[i]$ 个球,那么需要的操作次数为:
\\n$$
\\n
\\n\\\\lfloor \\\\frac{\\\\textit{nums}[i]-1}{y} \\\\rfloor
\\n$$其中 $\\\\lfloor x \\\\rfloor$ 表示将 $x$ 进行下取整。它的含义为:
\\n\\n
\\n- 当 $\\\\textit{nums}[i] \\\\leq y$ 时,我们无需进行操作;
\\n- 当 $y < \\\\textit{nums}[i] \\\\leq 2y$ 时,我们需要进行 $1$ 次操作;
\\n- 当 $2y < \\\\textit{nums}[i] \\\\leq 3y$ 时,我们需要进行 $2$ 次操作;
\\n- $\\\\cdots$
\\n那么总操作次数即为:
\\n$$
\\n
\\nP = \\\\sum_{i} \\\\lfloor \\\\frac{\\\\textit{nums}[i]-1}{y} \\\\rfloor
\\n$$当 $P \\\\leq \\\\textit{maxOperations}$ 时,我们调整二分查找的上界,否则调整二分查找的下界。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minimumSize(vector<int>& nums, int maxOperations) {\\n int left = 1, right = *max_element(nums.begin(), nums.end());\\n int ans = 0;\\n while (left <= right) {\\n int y = (left + right) / 2;\\n long long ops = 0;\\n for (int x: nums) {\\n ops += (x - 1) / y;\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n }\\n else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int minimumSize(int[] nums, int maxOperations) {\\n int left = 1, right = Arrays.stream(nums).max().getAsInt();\\n int ans = 0;\\n while (left <= right) {\\n int y = (left + right) / 2;\\n long ops = 0;\\n for (int x : nums) {\\n ops += (x - 1) / y;\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n } else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinimumSize(int[] nums, int maxOperations) {\\n int left = 1, right = nums.Max();\\n int ans = 0;\\n while (left <= right) {\\n int y = (left + right) / 2;\\n long ops = 0;\\n foreach (int x in nums) {\\n ops += (x - 1) / y;\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n } else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def minimumSize(self, nums: List[int], maxOperations: int) -> int:\\n left, right, ans = 1, max(nums), 0\\n while left <= right:\\n y = (left + right) // 2\\n ops = sum((x - 1) // y for x in nums)\\n if ops <= maxOperations:\\n ans = y\\n right = y - 1\\n else:\\n left = y + 1\\n \\n return ans\\n
###C
\\n\\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\\n\\nint minimumSize(int* nums, int numsSize, int maxOperations) {\\n int left = 1, right = nums[0];\\n for (int i = 1; i < numsSize; i++) {\\n right = MAX(right, nums[i]);\\n }\\n int ans = 0;\\n while (left <= right) {\\n int y = (left + right) / 2;\\n long long ops = 0;\\n for (int i = 0; i < numsSize; i++) {\\n ops += (nums[i] - 1) / y;\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n }\\n else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar minimumSize = function(nums, maxOperations) {\\n let left = 1, right = _.max(nums);\\n let ans = 0;\\n while (left <= right) {\\n const y = Math.floor((left + right) / 2);\\n let ops = 0;\\n for (const x of nums) {\\n ops += Math.floor((x - 1) / y);\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n } else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n};\\n
###go
\\n\\nfunc minimumSize(nums []int, maxOperations int) int {\\nmax := 0\\nfor _, x := range nums {\\nif x > max {\\nmax = x\\n}\\n}\\nreturn sort.Search(max, func(y int) bool {\\nif y == 0 {\\nreturn false\\n}\\nops := 0\\nfor _, x := range nums {\\nops += (x - 1) / y\\n}\\nreturn ops <= maxOperations\\n})\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二分查找 思路与算法\\n\\n我们可以将题目中的要求转换成判定问题,即:\\n\\n给定 $\\\\textit{maxOperations}$ 次操作次数,能否可以使得单个袋子里球数目的最大值不超过 $y$。\\n\\n如果 $y = y_0$ 是一个满足要求的答案,那么所有大于 $y_0$ 的 $y$ 同样也是满足要求的。因此存在一个 $y = y_\\\\textit{opt}$,使得当 $y \\\\geq y_\\\\textit{opt}$ 时都是满足要求的,当 $y < y_\\\\textit{opt}$ 时都是不满足要求的。这个 $y_\\\\textit{opt}$ 就是最终的答案。…","guid":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/dai-zi-li-zui-shao-shu-mu-de-qiu-by-leet-boay","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-19T02:03:22.373Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【C/C++】哈希表 + 位运算(状态压缩) 简洁明了","url":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/by-loving-booth9o9-zl02","content":"- \\n
\\n时间复杂度:$O(n \\\\log C)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$C$ 是数组 $\\\\textit{nums}$ 中的最大值,不超过 $10^9$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n解题思路
\\n1.哈希表 + 位运算(状态压缩)
\\n由于单词只由小写字母构成,所以可以把单词中字符是否出现的状态用int的低26位表示,不同单词经过位运算操作后得到相同的值,
\\n
\\n那么就可以说明这两个单词由相同类型的字符组成。我们可以维护一个哈希表,key记录单词压缩后的int值, value记录单词个数。
\\n答案要求的是满足条件的对数!所以应该把它们依次加起来,注意一定是先加入答案 再自增!
\\n比如某个状态的value为4 表示该字符组合的单词有4个 共有 3 + 2 + 1 + 0 = 6对!不会把4加入答案!代码
\\n###c
\\n\\ntypedef struct {\\n int key;\\n int value;\\n UT_hash_handle hh;\\n} el;\\n\\nint similarPairs(char ** words, int wordsSize){\\n el* head = NULL;\\n int ans = 0;\\n for (int i = 0; i < wordsSize; ++i) {\\n int bit = 0, len = strlen(words[i]);\\n for (int j = 0; j < len; ++j) bit |= (1 << (words[i][j] - \'a\'));\\n el* cur = NULL;\\n HASH_FIND_INT(head, &bit, cur);\\n if (NULL == cur) {\\n cur = (el*)malloc(sizeof(el));\\n cur->key = bit; cur->value = 0;\\n HASH_ADD_INT(head, key, cur);\\n }\\n ans += cur->value++;\\n }\\n el* cur, *tmp;\\n HASH_ITER(hh, head, cur, tmp) {\\n HASH_DEL(head, cur);\\n free(cur);\\n }\\n return ans;\\n}\\n
###c++
\\n\\nclass Solution {\\npublic:\\n int similarPairs(vector<string>& words) {\\nunordered_map<int, int> mp;\\nint n = words.size(), ans = 0;\\nfor (auto& word : words) {\\nint bit = 0;\\nfor (auto& ch : word) bit |= (1 << (ch - \'a\'));\\nans += mp[bit]++;\\n}\\nreturn ans;\\n }\\n};\\n
时间复杂度 O(nm) n=words的长度 m=所有字符和
\\n","description":"解题思路 1.哈希表 + 位运算(状态压缩)\\n\\n由于单词只由小写字母构成,所以可以把单词中字符是否出现的状态用int的低26位表示,不同单词经过位运算操作后得到相同的值,\\n 那么就可以说明这两个单词由相同类型的字符组成。我们可以维护一个哈希表,key记录单词压缩后的int值, value记录单词个数。\\n 答案要求的是满足条件的对数!所以应该把它们依次加起来,注意一定是先加入答案 再自增!\\n 比如某个状态的value为4 表示该字符组合的单词有4个 共有 3 + 2 + 1 + 0 = 6对!不会把4加入答案!\\n\\n代码\\n\\n###c\\n\\ntypedef struct {…","guid":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/by-loving-booth9o9-zl02","author":"aixpis","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-18T12:49:00.432Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学","url":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/by-tsreaper-kp1v","content":"
\\n空间复杂度 O(n) 主要是哈希表消耗的空间解法:数学
\\n设包含相同字符集的字符串共有 $x$ 个,则答案就是 $\\\\frac{x(x - 1)}{2}$ 之和。复杂度 $\\\\mathcal{O}(\\\\sum|s|)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:数学 设包含相同字符集的字符串共有 $x$ 个,则答案就是 $\\\\frac{x(x - 1)}{2}$ 之和。复杂度 $\\\\mathcal{O}(\\\\sum|s|)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n int similarPairs(vectorclass Solution {\\npublic:\\n int similarPairs(vector<string>& words) {\\n unordered_map<int, int> mp;\\n for (string &s : words) {\\n int msk = 0;\\n for (char c : s) msk |= 1 << (c - \'a\');\\n mp[msk]++;\\n }\\n int ans = 0;\\n for (auto it = mp.begin(); it != mp.end(); it++) {\\n int x = it->second;\\n ans += x * (x - 1) / 2;\\n }\\n return ans;\\n }\\n};\\n
& words) {\\n unordered_map mp;\\n for (string &s : words) {\\n int msk = 0…","guid":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/by-tsreaper-kp1v","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-18T04:10:24.770Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线性做法:枚举右,维护左(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/xian-xing-zuo-fa-ha-xi-biao-wei-yun-suan-lii4","content":" 推荐先把 1512. 好数对的数目 做了。
\\n为方便统计,把字符串 $s$ 中出现过的字母视作一个集合,把这个集合压缩成一个二进制数 $\\\\textit{mask}$。其中 $\\\\textit{mask}$ 第 $i$ 位为 $1$ 表示第 $i$ 个小写字母在 $s$ 中,为 $0$ 表示不在。这个技巧的详细解释见 从集合论到位运算,常见位运算技巧分类总结!
\\n遍历 $\\\\textit{words}$ 的同时,用一个哈希表 $\\\\textit{cnt}$ 维护 $\\\\textit{words}[i]$ 对应的 $\\\\textit{mask}$ 的出现次数。和 1512 题一样,先把 $\\\\textit{cnt}[\\\\textit{mask}]$ 加到答案中,然后把 $\\\\textit{cnt}[\\\\textit{mask}]$ 加一。这个顺序可以保证我们只会统计 $i<j$ 的下标对,不会把 $i=j$ 的情况也统计进去。
\\n\\nclass Solution:\\n def similarPairs(self, words: List[str]) -> int:\\n ans = 0\\n cnt = defaultdict(int)\\n for s in words:\\n mask = 0\\n for c in s:\\n mask |= 1 << (ord(c) - ord(\'a\'))\\n ans += cnt[mask]\\n cnt[mask] += 1\\n return ans\\n
\\nclass Solution {\\n public int similarPairs(String[] words) {\\n Map<Integer, Integer> cnt = new HashMap<>();\\n int ans = 0;\\n for (String s : words) {\\n int mask = 0;\\n for (char c : s.toCharArray()) {\\n mask |= 1 << (c - \'a\');\\n }\\n int c = cnt.getOrDefault(mask, 0);\\n ans += c;\\n cnt.put(mask, c + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int similarPairs(vector<string>& words) {\\n unordered_map<int, int> cnt;\\n int ans = 0;\\n for (auto& s : words) {\\n int mask = 0;\\n for (char c : s) {\\n mask |= 1 << (c - \'a\');\\n }\\n ans += cnt[mask]++;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc similarPairs(words []string) (ans int) {\\n cnt := map[int]int{}\\n for _, s := range words {\\n mask := 0\\n for _, c := range s {\\n mask |= 1 << (c - \'a\')\\n }\\n ans += cnt[mask]\\n cnt[mask]++\\n }\\n return\\n}\\n
\\nvar similarPairs = function(words) {\\n const cnt = new Map();\\n let ans = 0;\\n for (const s of words) {\\n let mask = 0;\\n for (const c of s) {\\n mask |= 1 << (c.charCodeAt(0) - \'a\'.charCodeAt(0));\\n }\\n const c = cnt.get(mask) ?? 0\\n ans += c;\\n cnt.set(mask, c + 1);\\n }\\n return ans;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn similar_pairs(words: Vec<String>) -> i32 {\\n let mut cnt = HashMap::new();\\n let mut ans = 0;\\n for s in words {\\n let mut mask = 0;\\n for c in s.bytes() {\\n mask |= 1 << (c - b\'a\');\\n }\\n ans += *cnt.get(&mask).unwrap_or(&0);\\n *cnt.entry(mask).or_insert(0) += 1;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(L)$,其中 $L$ 为 $\\\\textit{words}$ 中所有字符串的长度之和。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{words}$ 的长度。哈希表需要 $\\\\mathcal{O}(n)$ 的空间。
\\n更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"推荐先把 1512. 好数对的数目 做了。 为方便统计,把字符串 $s$ 中出现过的字母视作一个集合,把这个集合压缩成一个二进制数 $\\\\textit{mask}$。其中 $\\\\textit{mask}$ 第 $i$ 位为 $1$ 表示第 $i$ 个小写字母在 $s$ 中,为 $0$ 表示不在。这个技巧的详细解释见 从集合论到位运算,常见位运算技巧分类总结!\\n\\n遍历 $\\\\textit{words}$ 的同时,用一个哈希表 $\\\\textit{cnt}$ 维护 $\\\\textit{words}[i]$ 对应的 $\\\\textit{mask}$ 的出现次数。和 1512…","guid":"https://leetcode.cn/problems/count-pairs-of-similar-strings//solution/xian-xing-zuo-fa-ha-xi-biao-wei-yun-suan-lii4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-18T04:07:59.857Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"free 操作O(1) 的懒删除做法","url":"https://leetcode.cn/problems/design-memory-allocator//solution/lanshanchu-by-fengwei2002-wqxo","content":"就和 OS 里面的删除一块存储空间一样,只需要进行一个标记即可
\\n如果存在标记或者没有被覆盖过便可以直接覆盖
\\n\\nclass Allocator {\\npublic:\\n vector<int> a;\\n int cap; \\n unordered_map<int, int> hs;\\n unordered_set<int> freeID;\\n unordered_map<int, int> cnt;\\n\\n Allocator(int n) {\\n cap = n; \\n a.resize(cap);\\n for (int i = 0; i < n; i++) {\\n hs[i] = 0;\\n }\\n // 存储每一个位置是被哪一个 id 使用的\\n }\\n \\n int allocate(int size, int mID) {\\n auto _cnt = cnt;\\n for (int i = 0; i < cap; i++) {\\n int j = i;\\n while (j - i < size and j < cap) {\\n _cnt[hs[j]] -= 1;\\n if (freeID.count(hs[j]) or hs[j] == 0 or _cnt[hs[j]] < 0) {\\n j += 1;\\n } else {\\n break;\\n }\\n // 被删除过,或者本身没有用过\\n }\\n if (j - i == size) {\\n for (int x = i; x < i + size; x ++) {\\n hs[x] = mID;\\n }\\n freeID.erase(mID);\\n cnt[mID] += size;\\n return i;\\n } else {\\n i = j; \\n }\\n }\\n return -1;\\n }\\n \\n int free(int mID) {\\n freeID.insert(mID);\\n int t = cnt[mID];\\n cnt[mID] = 0; // 删除之后 cnt 也要归零\\n return t;\\n }\\n};\\n
\\n","description":"就和 OS 里面的删除一块存储空间一样,只需要进行一个标记即可 如果存在标记或者没有被覆盖过便可以直接覆盖\\n\\nclass Allocator {\\npublic:\\n vectortype Allocator struct {\\na []int // 存储具体的数组\\ncap int // 存储总容量\\nhs map[int]int // 存储每一个位置对应的 mID\\nfreeID map[int]bool // 存储每一个 mID 当前是否有对应数据段\\ncnt map[int]int // 存储每一个 mID 对应的总数量\\n}\\n\\nfunc Constructor(n int) Allocator {\\nt := Allocator{\\na: make([]int, n),\\ncap: n,\\nhs: make(map[int]int),\\nfreeID: make(map[int]bool),\\ncnt: make(map[int]int),\\n}\\nfor i := 0; i < t.cap; i++ {\\nt.hs[i] = 0\\n}\\nreturn t\\n}\\n\\nfunc (this *Allocator) Allocate(size int, mID int) int {\\n _cnt := make(map[int]int)\\n for key, value := range this.cnt {\\n _cnt[key] = value\\n }\\nfor i := 0; i < this.cap; i++ {\\nj := i\\nfor j-i < size && j < this.cap {\\n _cnt[this.hs[j]] -= 1\\nif val, _ := this.freeID[this.hs[j]]; val == true || this.hs[j] == 0 || _cnt[this.hs[j]] < 0 {\\nj += 1\\n} else {\\nbreak\\n}\\n}\\nif j-i == size {\\nfor x := i; x < i+size; x++ {\\nthis.hs[x] = mID\\n}\\ndelete(this.freeID, mID)\\nthis.cnt[mID] += size\\nreturn i\\n} else {\\n i = j\\n }\\n}\\nreturn -1\\n}\\n\\nfunc (this *Allocator) Free(mID int) int {\\nthis.freeID[mID] = true\\nt := this.cnt[mID]\\nthis.cnt[mID] = 0\\nreturn t\\n}\\n
a;\\n int cap; \\n unordered_map hs;\\n unordered_set freeID;\\n unordered_map cnt;\\n\\n Allocator(int n) {\\n cap = n; \\n a.resize(cap);…","guid":"https://leetcode.cn/problems/design-memory-allocator//solution/lanshanchu-by-fengwei2002-wqxo","author":"funcdfs","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-11T04:30:55.381Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:模拟 / 线段树二分(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/design-memory-allocator//solution/bao-li-mo-ni-by-endlesscheng-bqba","content":" 方法一:模拟
\\n\\n
\\n- 初始化:创建一个大小为 $n$ 的数组(记作 $a$),初始值全为 $0$。
\\n- $\\\\texttt{allocate}$:找最小的 $i$,满足 $[i,i+\\\\textit{size}-1]$ 全为 $0$,然后把这个区间全部赋值为 $\\\\textit{mID}$(注意题目保证 $\\\\textit{mID}>0$)。如果没有这样的区间,返回 $-1$。怎么找这样的 $i$,可以在遍历 $a$ 的过程中,维护连续为 $0$ 的元素个数 $\\\\textit{free}$。遇到非 $0$ 数字就把 $\\\\textit{free}$ 置为 $0$,否则把 $\\\\textit{free}$ 加一。如果遍历到 $a[i]$ 发现 $\\\\textit{free}=\\\\textit{size}$,则要找的区间为 $[i-\\\\textit{size}+1,i]$。
\\n- $\\\\texttt{freeMemory}$:遍历 $a$,把所有等于 $\\\\textit{mID}$ 的数置为 $0$,同时统计等于 $\\\\textit{mID}$ 的数的个数,作为答案。
\\n\\nclass Allocator:\\n def __init__(self, n: int):\\n self.memory = [0] * n\\n\\n def allocate(self, size: int, mID: int) -> int:\\n free = 0\\n for i, id in enumerate(self.memory):\\n if id > 0: # 已分配\\n free = 0 # 重新计数\\n continue\\n free += 1\\n if free == size: # 找到了\\n self.memory[i - size + 1: i + 1] = [mID] * size\\n return i - size + 1\\n return -1 # 无法分配内存\\n\\n def freeMemory(self, mID: int) -> int:\\n ans = 0\\n for i, id in enumerate(self.memory):\\n if id == mID:\\n ans += 1\\n self.memory[i] = 0 # 标记为空闲内存\\n return ans\\n
\\nclass Allocator {\\n private final int[] memory;\\n\\n public Allocator(int n) {\\n memory = new int[n];\\n }\\n\\n public int allocate(int size, int mID) {\\n int free = 0;\\n for (int i = 0; i < memory.length; i++) {\\n if (memory[i] > 0) { // 已分配\\n free = 0; // 重新计数\\n continue;\\n }\\n free++;\\n if (free == size) { // 找到了\\n Arrays.fill(memory, i - size + 1, i + 1, mID);\\n return i - size + 1;\\n }\\n }\\n return -1; // 无法分配内存\\n }\\n\\n public int freeMemory(int mID) {\\n int ans = 0;\\n for (int i = 0; i < memory.length; i++) {\\n if (memory[i] == mID) {\\n ans++;\\n memory[i] = 0; // 标记为空闲内存\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Allocator {\\n vector<int> memory;\\n\\npublic:\\n Allocator(int n) : memory(n) {}\\n\\n int allocate(int size, int mID) {\\n int free = 0;\\n for (int i = 0; i < memory.size(); i++) {\\n if (memory[i] > 0) { // 已分配\\n free = 0; // 重新计数\\n continue;\\n }\\n free++;\\n if (free == size) { // 找到了\\n fill(memory.begin() + (i - size + 1), memory.begin() + (i + 1), mID);\\n return i - size + 1;\\n }\\n }\\n return -1; // 无法分配内存\\n }\\n\\n int freeMemory(int mID) {\\n int ans = 0;\\n for (int i = 0; i < memory.size(); i++) {\\n if (memory[i] == mID) {\\n ans++;\\n memory[i] = 0; // 标记为空闲内存\\n }\\n }\\n return ans;\\n }\\n};\\n
\\ntype Allocator []int\\n\\nfunc Constructor(n int) Allocator {\\n return make([]int, n)\\n}\\n\\nfunc (a Allocator) Allocate(size, mID int) int {\\n free := 0\\n for i, id := range a {\\n if id > 0 { // 已分配\\n free = 0 // 重新计数\\n continue\\n }\\n free++\\n if free == size { // 找到了\\n for j := i - size + 1; j <= i; j++ {\\n a[j] = mID\\n }\\n return i - size + 1\\n }\\n }\\n return -1 // 无法分配内存\\n}\\n\\nfunc (a Allocator) FreeMemory(mID int) (ans int) {\\n for i, id := range a {\\n if id == mID {\\n ans++\\n a[i] = 0 // 标记为空闲内存\\n }\\n }\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:所有操作均为 $\\\\mathcal{O}(n)$。总体时间复杂度 $\\\\mathcal{O}(qn)$,其中 $q$ 为 $\\\\texttt{allocate}$ 和 $\\\\texttt{freeMemory}$ 的调用次数之和。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:Lazy 线段树 + 线段树二分(选读)
\\n前置知识:Lazy 线段树,线段树二分。
\\n前置题目:2213. 由单个字符重复的最长子字符串(思想和本题类似)
\\n把空闲内存单元记作 $0$,分配的内存单元记作 $1$。
\\n\\n
\\n- $\\\\texttt{allocate}$ 等价于找最左边的区间(的左端点),满足区间全为 $0$ 且长度 $\\\\ge \\\\textit{size}$。
\\n- $\\\\texttt{freeMemory}$ 等价于把 $\\\\textit{mID}$ 对应的所有区间置为 $0$。
\\n用线段树维护区间内的最长连续 $0$ 的个数 $\\\\textit{max}_0$,这样方便我们计算 $\\\\texttt{allocate}$。
\\n先说怎么计算 $\\\\textit{max}_0$。
\\n初始值为区间长度,因为一开始所有内存单元都是空闲的。
\\n当前节点的 $\\\\textit{max}_0$,等于以下三者的最大值:
\\n\\n
\\n- 左儿子的 $\\\\textit{max}_0$。
\\n- 右儿子的 $\\\\textit{max}_0$。
\\n- 左儿子的后缀连续 $0$ 的个数,加上右儿子的前缀连续 $0$ 的个数。
\\n所以需要额外维护区间前缀连续 $0$ 的个数 $\\\\textit{pre}_0$,区间后缀连续 $0$ 的个数 $\\\\textit{suf}_0$。
\\n然后来说怎么计算 $\\\\texttt{allocate}$。
\\n从根节点递归这棵线段树:
\\n\\n
\\n- 先找左子树,如果左子树中有符合要求的区间,那么返回这个区间左端点。
\\n- 否则,看看左子树的 $\\\\textit{suf}_0$ 加上右子树的 $\\\\textit{pre}_0$ 是否 $\\\\ge \\\\textit{size}$。如果满足,那么返回区间左端点。
\\n- 否则,递归右子树。
\\n递归边界:
\\n\\n
\\n- 如果区间 $\\\\textit{max}_0 < \\\\textit{size}$,无需递归,肯定没有符合要求的区间,返回 $-1$。
\\n- 如果区间长度等于 $1$(这意味着 $\\\\textit{size}=1$),返回区间左端点。
\\n此外,还需要一个哈希表,其 key 为 $\\\\textit{mID}$,value 为列表,记录 $\\\\textit{mID}$ 对应的所有内存区间。
\\n\\n
\\n- $\\\\texttt{allocate}$:把 $\\\\textit{mID}$ 及其对应区间加入哈希表。
\\n- $\\\\texttt{freeMemory}$:遍历 $\\\\textit{mID}$ 对应的区间列表,用线段树把区间置为 $0$,同时累加区间长度作为答案。最后把 $\\\\textit{mID}$ 从哈希表中删除。
\\n\\nclass Node:\\n __slots__ = \'pre0\', \'suf0\', \'max0\', \'todo\'\\n\\n\\nclass SegTree:\\n def __init__(self, n: int) -> None:\\n self.n = n\\n self.t = [Node() for _ in range(2 << (n - 1).bit_length())]\\n self.build(1, 0, n - 1)\\n\\n def do(self, i: int, l: int, r: int, v: int) -> None:\\n size = 0 if v > 0 else r - l + 1\\n self.t[i].pre0 = size\\n self.t[i].suf0 = size\\n self.t[i].max0 = size\\n self.t[i].todo = v\\n\\n # 下传懒标记\\n def spread(self, o: int, l: int, r: int) -> None:\\n v = self.t[o].todo\\n if v != -1:\\n m = (l + r) // 2\\n self.do(o * 2, l, m, v)\\n self.do(o * 2 + 1, m + 1, r, v)\\n self.t[o].todo = -1\\n\\n # 初始化线段树\\n def build(self, o: int, l: int, r: int) -> None:\\n self.do(o, l, r, -1)\\n if l == r:\\n return\\n m = (l + r) // 2\\n self.build(o * 2, l, m)\\n self.build(o * 2 + 1, m + 1, r)\\n\\n # 把区间 [ql, qr] 都置为 v\\n def update(self, o: int, l: int, r: int, ql: int, qr: int, v: int) -> None:\\n if ql <= l and r <= qr:\\n self.do(o, l, r, v)\\n return\\n self.spread(o, l, r)\\n m = (l + r) // 2\\n if ql <= m:\\n self.update(o * 2, l, m, ql, qr, v)\\n if m < qr:\\n self.update(o * 2 + 1, m + 1, r, ql, qr, v)\\n\\n # 合并左右子树的信息\\n lo = self.t[o * 2]\\n ro = self.t[o * 2 + 1]\\n # 区间前缀连续 0 的个数\\n self.t[o].pre0 = lo.pre0\\n if lo.pre0 == m - l + 1:\\n self.t[o].pre0 += ro.pre0 # 和右子树的 pre0 拼起来\\n # 区间后缀连续 0 的个数\\n self.t[o].suf0 = ro.suf0\\n if ro.suf0 == r - m:\\n self.t[o].suf0 += lo.suf0 # 和左子树的 suf0 拼起来\\n # 区间最长连续 0 的个数\\n self.t[o].max0 = max(lo.max0, ro.max0, lo.suf0 + ro.pre0)\\n\\n # 线段树二分,找最左边的区间左端点,满足区间全为 0 且长度 >= size\\n # 如果不存在这样的区间,返回 -1\\n def find_first(self, o: int, l: int, r: int, size: int) -> int:\\n if self.t[o].max0 < size:\\n return -1\\n if l == r:\\n return l\\n self.spread(o, l, r)\\n m = (l + r) // 2\\n idx = self.find_first(o * 2, l, m, size) # 递归左子树\\n if idx < 0:\\n # 左子树的后缀 0 个数 + 右子树的前缀 0 个数 >= size\\n if self.t[o * 2].suf0 + self.t[o * 2 + 1].pre0 >= size:\\n return m - self.t[o * 2].suf0 + 1\\n idx = self.find_first(o * 2 + 1, m + 1, r, size) # 递归右子树\\n return idx\\n\\n\\nclass Allocator:\\n def __init__(self, n: int):\\n self.n = n\\n self.tree = SegTree(n)\\n self.blocks = defaultdict(list)\\n\\n def allocate(self, size: int, mID: int) -> int:\\n i = self.tree.find_first(1, 0, self.n - 1, size)\\n if i < 0: # 无法分配内存\\n return -1\\n # 分配内存 [i, i+size-1]\\n self.blocks[mID].append((i, i + size - 1))\\n self.tree.update(1, 0, self.n - 1, i, i + size - 1, 1)\\n return i\\n\\n def freeMemory(self, mID: int) -> int:\\n ans = 0\\n for l, r in self.blocks[mID]:\\n ans += r - l + 1\\n self.tree.update(1, 0, self.n - 1, l, r, 0) # 释放内存\\n del self.blocks[mID]\\n return ans\\n
\\nclass SegTree {\\n private final int[] pre0; // 区间前缀连续 0 的个数\\n private final int[] suf0; // 区间后缀连续 0 的个数\\n private final int[] max0; // 区间最长连续 0 的个数\\n private final int[] todo; // 懒标记\\n\\n public SegTree(int n) {\\n int size = 2 << (32 - Integer.numberOfLeadingZeros(n - 1));\\n pre0 = new int[size];\\n suf0 = new int[size];\\n max0 = new int[size];\\n todo = new int[size];\\n build(1, 0, n - 1);\\n }\\n\\n // 把 [ql, qr] 都置为 v\\n public void update(int o, int l, int r, int ql, int qr, int v) {\\n if (ql <= l && r <= qr) {\\n do_(o, l, r, v);\\n return;\\n }\\n spread(o, l, r);\\n int m = (l + r) / 2;\\n int lo = o * 2;\\n int ro = lo + 1;\\n if (ql <= m) {\\n update(lo, l, m, ql, qr, v);\\n }\\n if (m < qr) {\\n update(ro, m + 1, r, ql, qr, v);\\n }\\n\\n // 合并左右子树的信息\\n pre0[o] = pre0[lo];\\n if (pre0[lo] == m - l + 1) {\\n pre0[o] += pre0[ro]; // 和右子树的 pre0 拼起来\\n }\\n suf0[o] = suf0[ro];\\n if (suf0[ro] == r - m) {\\n suf0[o] += suf0[lo]; // 和左子树的 suf0 拼起来\\n }\\n max0[o] = Math.max(Math.max(max0[lo], max0[ro]), suf0[lo] + pre0[ro]);\\n }\\n\\n // 线段树二分,找最左边的区间左端点,满足区间全为 0 且长度 >= size\\n // 如果不存在这样的区间,返回 -1\\n public int findFirst(int o, int l, int r, int size) {\\n if (max0[o] < size) {\\n return -1;\\n }\\n if (l == r) {\\n return l;\\n }\\n spread(o, l, r);\\n int m = (l + r) / 2;\\n int lo = o * 2;\\n int ro = lo + 1;\\n int idx = findFirst(lo, l, m, size); // 递归左子树\\n if (idx < 0) {\\n // 左子树的后缀 0 个数 + 右子树的前缀 0 个数 >= size\\n if (suf0[lo] + pre0[ro] >= size) {\\n return m - suf0[lo] + 1;\\n }\\n idx = findFirst(ro, m + 1, r, size); // 递归右子树\\n }\\n return idx;\\n }\\n\\n // 初始化线段树\\n private void build(int o, int l, int r) {\\n do_(o, l, r, -1);\\n if (l == r) {\\n return;\\n }\\n int m = (l + r) / 2;\\n build(o * 2, l, m);\\n build(o * 2 + 1, m + 1, r);\\n }\\n\\n private void do_(int i, int l, int r, int v) {\\n int size = v <= 0 ? r - l + 1 : 0;\\n pre0[i] = suf0[i] = max0[i] = size;\\n todo[i] = v;\\n }\\n\\n // 下传懒标记\\n private void spread(int o, int l, int r) {\\n int v = todo[o];\\n if (v != -1) {\\n int m = (l + r) / 2;\\n do_(o * 2, l, m, v);\\n do_(o * 2 + 1, m + 1, r, v);\\n todo[o] = -1;\\n }\\n }\\n}\\n\\nclass Allocator {\\n private final int n;\\n private final SegTree tree;\\n private final Map<Integer, List<int[]>> blocks = new HashMap<>();\\n\\n public Allocator(int n) {\\n this.n = n;\\n this.tree = new SegTree(n);\\n }\\n\\n public int allocate(int size, int mID) {\\n int i = tree.findFirst(1, 0, n - 1, size);\\n if (i < 0) { // 无法分配内存\\n return -1;\\n }\\n // 分配内存 [i, i+size-1]\\n blocks.computeIfAbsent(mID, k -> new ArrayList<>()).add(new int[]{i, i + size - 1});\\n tree.update(1, 0, n - 1, i, i + size - 1, 1);\\n return i;\\n }\\n\\n public int freeMemory(int mID) {\\n int ans = 0;\\n List<int[]> list = blocks.get(mID);\\n if (list != null) {\\n for (int[] range : list) {\\n ans += range[1] - range[0] + 1;\\n tree.update(1, 0, n - 1, range[0], range[1], 0); // 释放内存\\n }\\n blocks.remove(mID);\\n }\\n return ans;\\n }\\n}\\n
\\nclass SegTree {\\n struct Node {\\n int pre0, suf0, max0, todo;\\n };\\n\\n vector<Node> t;\\n\\n void do_(int i, int l, int r, int v) {\\n auto& o = t[i];\\n int size = v <= 0 ? r - l + 1 : 0;\\n o.pre0 = o.suf0 = o.max0 = size;\\n o.todo = v;\\n }\\n\\n // 下传懒标记\\n void spread(int o, int l, int r) {\\n int& v = t[o].todo;\\n if (v != -1) {\\n int m = (l + r) / 2;\\n do_(o * 2, l, m, v);\\n do_(o * 2 + 1, m + 1, r, v);\\n v = -1;\\n }\\n }\\n\\n // 初始化线段树\\n void build(int o, int l, int r) {\\n do_(o, l, r, -1);\\n if (l == r) {\\n return;\\n }\\n int m = (l + r) / 2;\\n build(o * 2, l, m);\\n build(o * 2 + 1, m + 1, r);\\n }\\n\\npublic:\\n SegTree(int n) {\\n t.resize(2 << bit_width((unsigned) n - 1));\\n build(1, 0, n - 1);\\n }\\n\\n // 把 [ql, qr] 都置为 v\\n void update(int o, int l, int r, int ql, int qr, int v) {\\n if (ql <= l && r <= qr) {\\n do_(o, l, r, v);\\n return;\\n }\\n spread(o, l, r);\\n int m = (l + r) / 2;\\n if (ql <= m) {\\n update(o * 2, l, m, ql, qr, v);\\n }\\n if (m < qr) {\\n update(o * 2 + 1, m + 1, r, ql, qr, v);\\n }\\n\\n // 合并左右子树的信息\\n Node& lo = t[o * 2];\\n Node& ro = t[o * 2 + 1];\\n // 区间前缀连续 0 的个数\\n t[o].pre0 = lo.pre0;\\n if (lo.pre0 == m - l + 1) {\\n t[o].pre0 += ro.pre0; // 和右子树的 pre0 拼起来\\n }\\n // 区间后缀连续 0 的个数\\n t[o].suf0 = ro.suf0;\\n if (ro.suf0 == r - m) {\\n t[o].suf0 += lo.suf0; // 和左子树的 suf0 拼起来\\n }\\n // 区间最长连续 0 的个数\\n t[o].max0 = max({lo.max0, ro.max0, lo.suf0 + ro.pre0});\\n }\\n\\n // 线段树二分,找最左边的区间左端点,满足区间全为 0 且长度 >= size\\n // 如果不存在这样的区间,返回 -1\\n int find_first(int o, int l, int r, int size) {\\n if (t[o].max0 < size) {\\n return -1;\\n }\\n if (l == r) {\\n return l;\\n }\\n spread(o, l, r);\\n int m = (l + r) / 2;\\n int idx = find_first(o * 2, l, m, size); // 递归左子树\\n if (idx < 0) {\\n // 左子树的后缀 0 个数 + 右子树的前缀 0 个数 >= size\\n if (t[o * 2].suf0 + t[o * 2 + 1].pre0 >= size) {\\n return m - t[o * 2].suf0 + 1;\\n }\\n idx = find_first(o * 2 + 1, m + 1, r, size); // 递归右子树\\n }\\n return idx;\\n }\\n};\\n\\nclass Allocator {\\n int n;\\n SegTree tree;\\n unordered_map<int, vector<pair<int, int>>> blocks;\\n\\npublic:\\n Allocator(int n) : n(n), tree(n) {}\\n\\n int allocate(int size, int mID) {\\n int i = tree.find_first(1, 0, n - 1, size);\\n if (i < 0) { // 无法分配内存\\n return -1;\\n }\\n blocks[mID].emplace_back(i, i + size - 1);\\n tree.update(1, 0, n - 1, i, i + size - 1, 1); // 分配内存 [i, i+size-1]\\n return i;\\n }\\n\\n int freeMemory(int mID) {\\n int ans = 0;\\n for (auto& [l, r] : blocks[mID]) {\\n ans += r - l + 1;\\n tree.update(1, 0, n - 1, l, r, 0); // 释放内存\\n }\\n blocks.erase(mID);\\n return ans;\\n }\\n};\\n
\\ntype segTree []struct {\\n l, r, pre0, suf0, max0, todo int\\n}\\n\\nfunc newSegTree(n int) segTree {\\n t := make(segTree, 2<<bits.Len(uint(n-1)))\\n t.build(1, 0, n-1)\\n return t\\n}\\n\\nfunc (t segTree) do(i, v int) {\\n o := &t[i]\\n size := 0\\n if v <= 0 {\\n size = o.r - o.l + 1\\n }\\n o.pre0 = size\\n o.suf0 = size\\n o.max0 = size\\n o.todo = v\\n}\\n\\n// 下传懒标记\\nfunc (t segTree) spread(o int) {\\n v := t[o].todo\\n if v != -1 {\\n t.do(o<<1, v)\\n t.do(o<<1|1, v)\\n t[o].todo = -1\\n }\\n}\\n\\n// 初始化线段树\\nfunc (t segTree) build(o, l, r int) {\\n t[o].l, t[o].r = l, r\\n t.do(o, -1)\\n if l == r {\\n return\\n }\\n m := (l + r) >> 1\\n t.build(o<<1, l, m)\\n t.build(o<<1|1, m+1, r)\\n}\\n\\n// 把 [l, r] 都置为 v\\nfunc (t segTree) update(o, l, r, v int) {\\n if l <= t[o].l && t[o].r <= r {\\n t.do(o, v)\\n return\\n }\\n t.spread(o)\\n m := (t[o].l + t[o].r) >> 1\\n if l <= m {\\n t.update(o<<1, l, r, v)\\n }\\n if m < r {\\n t.update(o<<1|1, l, r, v)\\n }\\n\\n // 合并左右子树的信息\\n lo, ro := t[o<<1], t[o<<1|1]\\n // 区间前缀连续 0 的个数\\n t[o].pre0 = lo.pre0\\n if lo.pre0 == m-t[o].l+1 {\\n t[o].pre0 += ro.pre0 // 和右子树的 pre0 拼起来\\n }\\n // 区间后缀连续 0 的个数\\n t[o].suf0 = ro.suf0\\n if ro.suf0 == t[o].r-m {\\n t[o].suf0 += lo.suf0 // 和左子树的 suf0 拼起来\\n }\\n // 区间最长连续 0 的个数\\n t[o].max0 = max(lo.max0, ro.max0, lo.suf0+ro.pre0)\\n}\\n\\n// 线段树二分,找最左边的区间左端点,满足区间全为 0 且长度 >= size\\n// 如果不存在这样的区间,返回 -1\\nfunc (t segTree) findFirst(o, size int) int {\\n if t[o].max0 < size {\\n return -1\\n }\\n if t[o].l == t[o].r {\\n return t[o].l\\n }\\n t.spread(o)\\n idx := t.findFirst(o<<1, size) // 递归左子树\\n if idx < 0 {\\n // 左子树的后缀 0 个数 + 右子树的前缀 0 个数 >= size\\n if t[o<<1].suf0+t[o<<1|1].pre0 >= size {\\n m := (t[o].l + t[o].r) >> 1\\n return m - t[o<<1].suf0 + 1\\n }\\n idx = t.findFirst(o<<1|1, size) // 递归右子树\\n }\\n return idx\\n}\\n\\n// 上面为线段树代码\\n\\ntype interval struct {\\n l, r int\\n}\\n\\ntype Allocator struct {\\n tree segTree\\n blocks map[int][]interval\\n}\\n\\nfunc Constructor(n int) Allocator {\\n return Allocator{\\n tree: newSegTree(n),\\n blocks: map[int][]interval{},\\n }\\n}\\n\\nfunc (a Allocator) Allocate(size, mID int) int {\\n i := a.tree.findFirst(1, size)\\n if i < 0 { // 无法分配内存\\n return -1\\n }\\n a.blocks[mID] = append(a.blocks[mID], interval{i, i + size - 1})\\n a.tree.update(1, i, i+size-1, 1) // 分配内存 [i, i+size-1]\\n return i\\n}\\n\\nfunc (a Allocator) FreeMemory(mID int) (ans int) {\\n for _, p := range a.blocks[mID] {\\n ans += p.r - p.l + 1\\n a.tree.update(1, p.l, p.r, 0) // 释放内存\\n }\\n delete(a.blocks, mID)\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(n)$。$\\\\texttt{allocate}$ $\\\\mathcal{O}(\\\\log n)$。$\\\\texttt{freeMemory}$ 均摊 $\\\\mathcal{O}(\\\\log n)$,因为释放内存单元的次数不会超过 $\\\\texttt{allocate}$ 的调用次数。总体时间复杂度 $\\\\mathcal{O}(n+q\\\\log n)$,其中 $q$ 为 $\\\\texttt{allocate}$ 和 $\\\\texttt{freeMemory}$ 的调用次数之和。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。注意哈希表的大小不会超过 $n$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:模拟 初始化:创建一个大小为 $n$ 的数组(记作 $a$),初始值全为 $0$。\\n$\\\\texttt{allocate}$:找最小的 $i$,满足 $[i,i+\\\\textit{size}-1]$ 全为 $0$,然后把这个区间全部赋值为 $\\\\textit{mID}$(注意题目保证 $\\\\textit{mID}>0$)。如果没有这样的区间,返回 $-1$。怎么找这样的 $i$,可以在遍历 $a$ 的过程中,维护连续为 $0$ 的元素个数 $\\\\textit{free}$。遇到非 $0$ 数字就把 $\\\\textit{free}$ 置为 $0$,否则把 $…","guid":"https://leetcode.cn/problems/design-memory-allocator//solution/bao-li-mo-ni-by-endlesscheng-bqba","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-11T04:24:09.433Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟 & 滑动窗口","url":"https://leetcode.cn/problems/design-memory-allocator//solution/by-tsreaper-1dip","content":"解法:模拟 & 滑动窗口
\\n对于
\\nallocate
操作,用一个长度为size
的滑动窗口找到第一个连续未被分配的,长度为size
的区间。复杂度 $\\\\mathcal{O}(n)$。对于
\\nfree
操作,直接枚举所有内存单元,清空 id 为mID
的内存单元即可。复杂度 $\\\\mathcal{O}(n)$。因此总体复杂度为 $\\\\mathcal{O}(nq)$,其中 $q$ 是操作次数。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:模拟 & 滑动窗口 对于 allocate 操作,用一个长度为 size 的滑动窗口找到第一个连续未被分配的,长度为 size 的区间。复杂度 $\\\\mathcal{O}(n)$。\\n\\n对于 free 操作,直接枚举所有内存单元,清空 id 为 mID 的内存单元即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n因此总体复杂度为 $\\\\mathcal{O}(nq)$,其中 $q$ 是操作次数。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Allocator {\\n int n;\\n vectorclass Allocator {\\n int n;\\n vector<int> A;\\n\\n // 将从下标 idx 开始,长度为 size 的连续内存单元的 id 设为 mID\\n int gao(int idx, int size, int mID) {\\n for (int i = 0; i < size; i++) A[idx + i] = mID;\\n return idx;\\n }\\n\\npublic:\\n Allocator(int n) {\\n this->n = n;\\n A = vector<int>(n);\\n }\\n \\n int allocate(int size, int mID) {\\n if (size > n) return -1;\\n\\n // 初始化滑动窗口,并判断第一个窗口是否符合要求\\n int sm = 0;\\n for (int i = 0; i < size; i++) sm += A[i];\\n if (sm == 0) return gao(0, size, mID);\\n\\n // 枚举滑动窗口的终点,并判断窗口是否符合要求\\n for (int i = size; i < n; i++) {\\n sm += A[i] - A[i - size];\\n if (sm == 0) return gao(i - size + 1, size, mID);\\n }\\n\\n return -1;\\n }\\n \\n int free(int mID) {\\n // 直接枚举所有内存单元\\n int ret = 0;\\n for (int i = 0; i < n; i++) if (A[i] == mID) A[i] = 0, ret++;\\n return ret;\\n }\\n};\\n\\n/**\\n * Your Allocator object will be instantiated and called as such:\\n * Allocator* obj = new Allocator(n);\\n * int param_1 = obj->allocate(size,mID);\\n * int param_2 = obj->free(mID);\\n */\\n
A;\\n\\n // 将从下标 idx…","guid":"https://leetcode.cn/problems/design-memory-allocator//solution/by-tsreaper-1dip","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-11T04:08:15.913Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python3/Java/C++/Go] 一题一解:找规律","url":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/by-lcbin-ovft","content":" 方法一:找规律
\\n观察棋盘我们发现,颜色相同的两个格子 $(x_1, y_1)$ 和 $(x_2, y_2)$ 满足 $x_1 + y_1$ 和 $x_2 + y_2$ 均为奇数或偶数。
\\n因此,我们可以根据
\\ncoordinates
获取对应的坐标 $(x, y)$,如果 $x + y$ 为奇数,则格子为白色,返回true
,否则返回false
。\\nclass Solution:\\n def squareIsWhite(self, coordinates: str) -> bool:\\n return (ord(coordinates[0]) + ord(coordinates[1])) % 2 == 1\\n
\\nclass Solution {\\n public boolean squareIsWhite(String coordinates) {\\n return (coordinates.charAt(0) + coordinates.charAt(1)) % 2 == 1; \\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool squareIsWhite(string coordinates) {\\n return (coordinates[0] + coordinates[1]) % 2;\\n }\\n};\\n
\\nfunc squareIsWhite(coordinates string) bool {\\nreturn (coordinates[0]+coordinates[1])%2 == 1\\n}\\n
时空复杂度均为 $O(1)$。
\\n
\\n有任何问题,欢迎评论区交流,欢迎评论区提供其它解题思路(代码),也可以点个赞支持一下作者哈😄~
\\n","description":"方法一:找规律 观察棋盘我们发现,颜色相同的两个格子 $(x_1, y_1)$ 和 $(x_2, y_2)$ 满足 $x_1 + y_1$ 和 $x_2 + y_2$ 均为奇数或偶数。\\n\\n因此,我们可以根据 coordinates 获取对应的坐标 $(x, y)$,如果 $x + y$ 为奇数,则格子为白色,返回 true,否则返回 false。\\n\\nclass Solution:\\n def squareIsWhite(self, coordinates: str) -> bool:\\n return (ord(coordinates[0])…","guid":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/by-lcbin-ovft","author":"lcbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-08T01:01:59.257Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【爪哇缪斯】图解LeetCode","url":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/zhua-wa-mou-si-by-muse-77-rciy","content":"解题思路
\\n1> 采用异或方式
\\n首先:针对题目中的棋盘,我们可以针对 行 和 列 ,做出如下假设性的
\\n拆分
操作:\\n
\\n\\n【棋盘中列的假设】:
\\na列
白色、b列
黑色、c列
白色、d列
黑色……
\\n【棋盘中行的假设】:1行
黑色、2行
白色、3行
黑色、4行
白色……然后,我们再观察棋盘中的格子,发现如下规律:
\\n\\n\\n【规律1:白色 + 黑色 = 黑色】
\\n
\\n以a1
为例,此格子是黑色的,由于这个格子是由a列+1行组成,即:a列(白色) + 1行(黑色) = a1(黑色)
;
\\n【规律2:白色 + 白色 = 白色】
\\n以a2
为例,此格子是白色的,由于这个格子是由a列+2行组成,即:a列(白色) + 2行(白色) = a2(白色)
;
\\n【规律3:黑色 + 黑色 = 白色】
\\n以b1
为例,此格子是白色的,由于这个格子是由b列+1行组成,即:b列(黑色) + 1行(黑色) = b1(白色)
;最后,我们发现这种规律与 异或操作 是一样的,即:白色用0表示,黑色用1表示:
\\n\\n\\n【规律1:白色 + 黑色 = 黑色】0^1等于1
\\n
\\n【规律2:白色 + 白色 = 白色】0^0等于0
\\n【规律3:黑色 + 黑色 = 白色】1^1等于0所以,我们只需要将coordinates指定的行和列转换为0或1,然后计算异或即可。
\\n2> 采用奇偶方式
\\n我们以
\\na1
格子为例,a的ASCII码为97
,1的ASCII码为49
,那么ASCII(a) + ASCII(1) = 97 + 49 = 146,那么当总和是偶数
时,则格子是黑色
;如果总和是奇数
时,则格子是白色
。此处就不再画图赘述了。
\\n代码实现
\\n1> 采用异或方式
\\n###java
\\n\\nclass Solution {\\n public boolean squareIsWhite(String coordinates) { // 白色用0表示,黑色用1表示\\n int letter = (coordinates.charAt(0) - \'a\') % 2; // a列:白色 b列:黑色 c列:白色 ……\\n int number = (coordinates.charAt(1) - \'1\' + 1) % 2; // 1行:黑色 2行:白色 3行:黑色 ……\\n return (letter ^ number) == 0; \\n }\\n}\\n
\\n
2> 采用奇偶方式
\\n###java
\\n\\nclass Solution {\\n public boolean squareIsWhite(String coordinates) {\\n return (coordinates.charAt(0) + coordinates.charAt(1)) % 2 == 1; // 白色用奇数表示,黑色用偶数表示\\n }\\n}\\n
\\n
今天的文章内容就这些了:
\\n\\n\\n写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
\\n更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \\\\(^o^)/ ~ 「干货分享,每天更新」
\\n","description":"1> 采用异或方式 首先:针对题目中的棋盘,我们可以针对 行 和 列 ,做出如下假设性的拆分操作:\\n\\n【棋盘中列的假设】:a列白色、b列黑色、c列白色、d列黑色……\\n 【棋盘中行的假设】:1行黑色、2行白色、3行黑色、4行白色……\\n\\n然后,我们再观察棋盘中的格子,发现如下规律:\\n\\n【规律1:白色 + 黑色 = 黑色】\\n 以a1为例,此格子是黑色的,由于这个格子是由a列+1行组成,即:a列(白色) + 1行(黑色) = a1(黑色);\\n 【规律2:白色 + 白色 = 白色】\\n 以a2为例,此格子是白色的,由于这个格子是由a列+2行组成,即:a列(白色…","guid":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/zhua-wa-mou-si-by-muse-77-rciy","author":"muse-77","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-08T00:47:04.447Z","media":[{"url":"https://pic.leetcode.cn/1670460519-hDmKUk-image.png","type":"photo","width":1442,"height":1080,"blurhash":"LbOp*|WBae~pjFWBofoL~q-;IVRj"},{"url":"https://pic.leetcode.cn/1670460538-AkWRxy-image.png","type":"photo","width":1500,"height":321,"blurhash":"LTRMSixut6-p~8fkRka#BYoyocbG"},{"url":"https://pic.leetcode.cn/1670462151-aCkQwd-image.png","type":"photo","width":2096,"height":495,"blurhash":"LURMVqt8WB%M~7ofj[jZBFj]oMWX"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"判断国际象棋棋盘中一个格子的颜色","url":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/pan-duan-guo-ji-xiang-qi-qi-pan-zhong-yi-8dv4","content":"方法一:数学
\\n思路
\\n经过观察可以发现,从左下角开始,棋盘的行数和列数(均从 $1$ 开始计数)之和如果为奇数,则为白色格子,如果和为偶数,则为黑色格子。可以根据这个结论判断格子颜色。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def squareIsWhite(self, coordinates: str) -> bool:\\n return (ord(coordinates[0]) - ord(\'a\') + 1 + int(coordinates[1])) % 2 == 1\\n
###Java
\\n\\nclass Solution {\\n public boolean squareIsWhite(String coordinates) {\\n return ((coordinates.charAt(0) - \'a\' + 1) + (coordinates.charAt(1) - \'0\')) % 2 == 1;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public bool SquareIsWhite(string coordinates) {\\n return ((coordinates[0] - \'a\' + 1) + (coordinates[1] - \'0\')) % 2 == 1;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n bool squareIsWhite(string coordinates) {\\n return ((coordinates[0] - \'a\' + 1) + (coordinates[1] - \'0\')) % 2 == 1;\\n }\\n};\\n
###C
\\n\\nbool squareIsWhite(char * coordinates){\\n return ((coordinates[0] - \'a\' + 1) + (coordinates[1] - \'0\')) % 2 == 1;\\n}\\n
###JavaScript
\\n\\nvar squareIsWhite = function(coordinates) {\\n return ((coordinates[0].charCodeAt() - \'a\'.charCodeAt() + 1) + (coordinates[1].charCodeAt() - \'0\'.charCodeAt())) % 2 === 1;\\n};\\n
###go
\\n\\nfunc squareIsWhite(coordinates string) bool {\\n return ((coordinates[0]-\'a\'+1)+(coordinates[1]-\'0\'))%2 == 1\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:数学 思路\\n\\n经过观察可以发现,从左下角开始,棋盘的行数和列数(均从 $1$ 开始计数)之和如果为奇数,则为白色格子,如果和为偶数,则为黑色格子。可以根据这个结论判断格子颜色。\\n\\n代码\\n\\n###Python\\n\\nclass Solution:\\n def squareIsWhite(self, coordinates: str) -> bool:\\n return (ord(coordinates[0]) - ord(\'a\') + 1 + int(coordinates[1])) % 2 == 1\\n\\n\\n###Java\\n\\nclass…","guid":"https://leetcode.cn/problems/determine-color-of-a-chessboard-square//solution/pan-duan-guo-ji-xiang-qi-qi-pan-zhong-yi-8dv4","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-12-07T01:55:21.837Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单模拟题","url":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/by-ac_oier-3mxf","content":"- \\n
\\n时间复杂度:$O(1)$。仅使用常数时间。
\\n- \\n
\\n空间复杂度:$O(1)$。仅使用常数空间。
\\n模拟
\\n数据范围 $n = 1e5$,因此最大盒子编号
\\n99999 = 5 * 9 = 45
,我们可以用一个大小为 $50$ 的数组cnts
来统计每个编号盒子中小球的数量,$cnts[idx] = x$ 含义为编号为 $idx$ 的盒子有 $x$ 个小球。代码:
\\n###Java
\\n\\nclass Solution {\\n public int countBalls(int l, int r) {\\n int ans = 0;\\n int[] cnts = new int[50];\\n for (int i = l; i <= r; i++) {\\n int j = i, cur = 0;\\n while (j != 0) {\\n cur += j % 10; j /= 10;\\n }\\n if (++cnts[cur] > ans) ans = cnts[cur];\\n }\\n return ans;\\n }\\n}\\n
###TypeScript
\\n\\nfunction countBalls(l: number, r: number): number {\\n let ans = 0\\n const cnts = new Array<number>(50).fill(0)\\n for (let i = l; i <= r; i++) {\\n let j = i, cur = 0\\n while (j != 0) {\\n cur += j % 10\\n j = Math.floor(j / 10)\\n }\\n if (++cnts[cur] > ans) ans = cnts[cur]\\n }\\n return ans\\n}\\n
###Python3
\\n\\nclass Solution:\\n def countBalls(self, l: int, r: int) -> int:\\n ans = 0\\n cnts = [0] * 50\\n for i in range(l, r + 1):\\n j, cur = i, 0\\n while j != 0:\\n j, cur = j // 10, cur + j % 10\\n cnts[cur] += 1\\n if cnts[cur] > ans:\\n ans = cnts[cur]\\n return ans\\n
\\n
\\n- 时间复杂度:$O(n\\\\log{r})$
\\n- 空间复杂度:$O(C)$,其中 $C = 50$ 为最大盒子编号
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 数据范围 $n = 1e5$,因此最大盒子编号 99999 = 5 * 9 = 45,我们可以用一个大小为 $50$ 的数组 cnts 来统计每个编号盒子中小球的数量,$cnts[idx] = x$ 含义为编号为 $idx$ 的盒子有 $x$ 个小球。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int countBalls(int l, int r) {\\n int ans = 0;\\n int[] cnts = new int[50];\\n for (int i = l…","guid":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/by-ac_oier-3mxf","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-11-23T01:43:58.336Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【爪哇缪斯】图解LeetCode","url":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/-by-muse-77-ru13","content":"解题思路
\\n1> 模拟
\\n根据题目描述,我们可以最先想到的方式就是暴力破解,即:从
\\nlowLimit
开始,到hightLimit
结束,计算每个数字的每一位,然后将其进行加和操作,总和就是该数字所在的盒子编号,那么该编号盒子小球数量加1即可。将所有数字都计算完毕后,再遍历所有盒子,找出最大的盒子中球的数量作为最终结果返回。由于题目的“提示”部分已经指出
\\n1 <= lowLimit <= highLimit <= 10^5
,那么最大盒子编号应该是小球“99999
”放置的位置,即:9+9+9+9+9=45
。那么我们可以创建长度为46的数组,即:int[] resultMap = new int[46]
,数组中下标index
表示盒子编号,resultMap[index]
表示盒子中的小球数量。2> 找规律
\\n我们可以根据题意,将小球从编号为1开始,放入每个箱子中,我们会发现如下规律:
\\n\\n\\n当小球A是“9”的时候,它的在编号为
\\n9
的箱子里,那么下一个小球B“10”所在的位置,就是编号为1
的箱子。
\\n当小球A是“19”的时候,它的在编号为10
的箱子里,那么下一个小球B“20”所在的位置,就是编号为2
的箱子。
\\n当小球A是“29”的时候,它的在编号为11
的箱子里,那么下一个小球B“30”所在的位置,就是编号为3
的箱子。
\\n……
\\n当小球A是“99”的时候,它的在编号为18
的箱子里,那么下一个小球B“100”所在的位置,就是编号为1
的箱子。
\\n……
\\n当小球A是“999”的时候,它的在编号为27
的箱子里,那么下一个小球B“1000”所在的位置,就是编号为1
的箱子。
\\n以此类推……因此从上面的例子中,我们可以找出如下规律,即:B球所在箱子编号 = A球所在箱子编号 - (9 * [末尾9的个数])+ 1
\\n那么根据这个规律,我们就可以只针对末尾是9的小球进行特殊定位计算,而其他小球所在的位置,只需要根据前面小球位置+1即可。下图是小球示例图:
\\n\\n
代码实现
\\n1> 模拟
\\n###java
\\n\\nclass Solution {\\n public int countBalls(int lowLimit, int highLimit) {\\n int result = 0;\\n int[] resultMap = new int[46];\\n for(int i = lowLimit; i <= highLimit; i++) {\\n int num = i, index = 0;\\n while(num > 0) {\\n index += num % 10;\\n num = num / 10;\\n }\\n resultMap[index] += 1;\\n }\\n for (int r : resultMap) result = Math.max(result, r);\\n return result;\\n }\\n}\\n
\\n
2> 找规律
\\n###java
\\n\\nclass Solution {\\n public int countBalls(int lowLimit, int highLimit) {\\n int[] resultMap = new int[46];\\n int firstIndex = 0, result = 0;\\n for (int num = lowLimit; num > 0; num = num / 10) firstIndex += num % 10;\\n resultMap[firstIndex] = 1; // 初始化第一个数字lowLimit所在编号盒子的小球数量\\n for (int i = lowLimit; i < highLimit; i++) {\\n for (int prevNum = i; prevNum % 10 == 9; prevNum /= 10) // 根据前一个数的末位是否为9,来重新定位下一个数的位置\\n firstIndex -= 9; // 前移9位\\n resultMap[++firstIndex]++;\\n }\\n for (int rm : resultMap) result = Math.max(result, rm);\\n return result;\\n }\\n}\\n
\\n
今天的文章内容就这些了:
\\n\\n\\n写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
\\n更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \\\\(^o^)/ ~ 「干货分享,每天更新」
\\n","description":"1> 模拟 根据题目描述,我们可以最先想到的方式就是暴力破解,即:从lowLimit开始,到hightLimit结束,计算每个数字的每一位,然后将其进行加和操作,总和就是该数字所在的盒子编号,那么该编号盒子小球数量加1即可。将所有数字都计算完毕后,再遍历所有盒子,找出最大的盒子中球的数量作为最终结果返回。\\n\\n由于题目的“提示”部分已经指出1 <= lowLimit <= highLimit <= 10^5,那么最大盒子编号应该是小球“99999”放置的位置,即:9+9+9+9+9=45。那么我们可以创建长度为46的数组,即:int[] resultMap…","guid":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/-by-muse-77-ru13","author":"muse-77","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-11-23T01:13:14.344Z","media":[{"url":"https://pic.leetcode.cn/1669166068-iEIBVR-image.png","type":"photo","width":2096,"height":702,"blurhash":"LARfkD-;Ri-;IybJxuWZpMWEWBWE"},{"url":"https://pic.leetcode.cn/1669166085-vnwsgL-image.png","type":"photo","width":1500,"height":503,"blurhash":"LNRfe0xun~-;},axWCaePCozkCWV"},{"url":"https://pic.leetcode.cn/1669166092-ejJSaU-image.png","type":"photo","width":1500,"height":411,"blurhash":"LRRp2vxabbxu~5n%ofoJAxozaekD"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单状态机 DP 运用题","url":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/gong-shui-san-xie-by-ac_oier-kuv4","content":"状态机 DP
\\n定义 $f[i][j]$ 为无须考虑前 $i - 1$ 列(含义为前 $i - 1$ 列已铺满),当前第 $i$ 列状态为 $j$ 时的方案数。
\\n其中 $j$ 取值范围为 $[0, 4)$ 分别对应了当前列的填充情况:
\\n\\n
为了方便,我们人为规定列数从 $1$ 开始。
\\n由于骨牌只能在 $2 \\\\times n$ 的棋盘内填充(不能延伸出棋盘两端),因此我们有显而易见的初始化状态:
\\n$$
\\n
\\nf[1][0] = f[1][1] = 1
\\n$$分别对应「第一列不放置任何骨牌」和「第一列竖着放一块 $1 \\\\times 2$ 骨牌」合法方案。
\\n而 $f[1][2]$ 和 $f[1][3]$ 由于没法在棋盘左侧以外的位置放置骨牌,不存在合法方案,其值均为 $0$。
\\n同时可知 $f[n][1]$ 为我们最终答案,含义为所有列都恰好铺完,不溢出棋盘右侧位置。
\\n不失一般性考虑 $f[i][j]$ 该如何计算,其实就是一个简单的状态机转移分情况讨论:
\\n\\n
\\n- \\n
\\n$f[i][0]$ : 需要前 $i - 1$ 列铺满,同时第 $i$ 列没有被铺,只能由 $f[i - 1][1]$ 转移而来,即有 $f[i][0] = f[i - 1][1]$
\\n\\n\\n这里需要尤其注意:虽然我们能够在上一步留空第 $i - 1$ 列,然后在 $i - 1$ 列竖放一块 $1 \\\\times 2$ 的骨牌(如下图)
\\n\\n
\\n\\n但我们不能从 $f[i - 1][0]$ 转移到 $f[i][0]$,因为此时放置的骨牌,仅对第 $i - 1$ 列产生影响,不会对第 $i$ 列产生影响,该决策所产生的方案数,已在 $f[i - 1][X]$ 时被统计
\\n- \\n
\\n$f[i][1]$ : 可由 $f[i - 1][j]$ 转移而来(见下图),其中 $j \\\\in [0, 4)$,即有 $f[i][1] = \\\\sum_{j = 0}^{3} f[i - 1][j]$
\\n\\n
- \\n
\\n$f[i][2]$ : 可由 $f[i - 1][0]$ 和 $f[i - 1][3]$ 转移而来
\\n- \\n
\\n$f[i][3]$ : 可由 $f[i - 1][0]$ 和 $f[i - 1][2]$ 转移而来
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n int MOD = (int)1e9+7;\\n public int numTilings(int n) {\\n int[][] f = new int[n + 10][4];\\n f[1][0] = f[1][1] = 1;\\n for (int i = 2; i <= n; i++) {\\n f[i][0] = f[i - 1][1];\\n int cur = 0;\\n for (int j = 0; j < 4; j++) cur = (cur + f[i - 1][j]) % MOD;\\n f[i][1] = cur;\\n f[i][2] = (f[i - 1][0] + f[i - 1][3]) % MOD;\\n f[i][3] = (f[i - 1][0] + f[i - 1][2]) % MOD;\\n }\\n return f[n][1];\\n }\\n}\\n
###TypeScript
\\n\\nfunction numTilings(n: number): number {\\n const MOD = 1e9+7\\n const f = new Array<Array<number>>()\\n for (let i = 0; i <= n; i++) f[i] = new Array<number>(4).fill(0)\\n f[1][0] = f[1][1] = 1\\n for (let i = 2; i <= n; i++) {\\n f[i][0] = f[i - 1][1]\\n let cur = 0\\n for (let j = 0; j < 4; j++) cur = (cur + f[i - 1][j]) % MOD\\n f[i][1] = cur\\n f[i][2] = (f[i - 1][0] + f[i - 1][3]) % MOD\\n f[i][3] = (f[i - 1][0] + f[i - 1][2]) % MOD\\n }\\n return f[n][1]\\n}\\n
###Python3
\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n f = [[0] * 4 for _ in range(n + 10)]\\n f[1][0] = f[1][1] = 1\\n for i in range(2, n + 1):\\n f[i][0] = f[i - 1][1]\\n f[i][1] = sum([f[i - 1][j] for j in range(4)])\\n f[i][2] = f[i - 1][0] + f[i - 1][3]\\n f[i][3] = f[i - 1][0] + f[i - 1][2]\\n return f[n][1] % 1000000007\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n滚动数组优化
\\n利用 $f[i][X]$ 仅依赖于 $f[i - 1][X]$,我们可以采用「滚动数组」方式将其空间优化至 $O(1)$。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n int MOD = (int)1e9+7;\\n public int numTilings(int n) {\\n int[][] f = new int[2][4];\\n f[1][0] = f[1][1] = 1;\\n for (int i = 2; i <= n; i++) {\\n int a = i & 1, b = (i - 1) & 1;\\n f[a][0] = f[b][1];\\n int cur = 0;\\n for (int j = 0; j < 4; j++) cur = (cur + f[b][j]) % MOD;\\n f[a][1] = cur;\\n f[a][2] = (f[b][0] + f[b][3]) % MOD;\\n f[a][3] = (f[b][0] + f[b][2]) % MOD;\\n }\\n return f[n & 1][1];\\n }\\n}\\n
###TypeScript
\\n\\nfunction numTilings(n: number): number {\\n const MOD = 1e9+7\\n const f = new Array<Array<number>>()\\n for (let i = 0; i <= 1; i++) f[i] = new Array<number>(4).fill(0)\\n f[1][0] = f[1][1] = 1\\n for (let i = 2; i <= n; i++) {\\n const a = i & 1, b = (i - 1) & 1\\n f[a][0] = f[b][1]\\n let cur = 0\\n for (let j = 0; j < 4; j++) cur = (cur + f[b][j]) % MOD\\n f[a][1] = cur\\n f[a][2] = (f[b][0] + f[b][3]) % MOD\\n f[a][3] = (f[b][0] + f[b][2]) % MOD\\n }\\n return f[n & 1][1]\\n}\\n
###Python3
\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n f = [[0] * 4 for _ in range(2)]\\n f[1][0] = f[1][1] = 1\\n for i in range(2, n + 1):\\n a, b = i & 1, (i - 1) & 1\\n f[a][0] = f[b][1]\\n f[a][1] = sum([f[b][j] for j in range(4)])\\n f[a][2] = f[b][0] + f[b][3]\\n f[a][3] = f[b][0] + f[b][2]\\n return f[n & 1][1] % 1000000007\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(1)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"状态机 DP 定义 $f[i][j]$ 为无须考虑前 $i - 1$ 列(含义为前 $i - 1$ 列已铺满),当前第 $i$ 列状态为 $j$ 时的方案数。\\n\\n其中 $j$ 取值范围为 $[0, 4)$ 分别对应了当前列的填充情况:\\n\\n为了方便,我们人为规定列数从 $1$ 开始。\\n\\n由于骨牌只能在 $2 \\\\times n$ 的棋盘内填充(不能延伸出棋盘两端),因此我们有显而易见的初始化状态:\\n\\n$$\\n f[1][0] = f[1][1] = 1\\n $$\\n\\n分别对应「第一列不放置任何骨牌」和「第一列竖着放一块 $1 \\\\times 2$ 骨牌」合法方案。\\n\\n而 $f[1…","guid":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/gong-shui-san-xie-by-ac_oier-kuv4","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-11-12T01:54:18.530Z","media":[{"url":"https://pic.leetcode.cn/1668221823-xIysQK-image.png","type":"photo","width":1393,"height":918,"blurhash":"LQQc#Nxr?5?KxuWBobofWFWCa#ob"},{"url":"https://pic.leetcode.cn/1668224248-NwNCNm-image.png","type":"photo","width":1277,"height":517,"blurhash":"LSPZ[f~K%2o+Sgs*ofa%}[9+R*xS"},{"url":"https://pic.leetcode.cn/1668225376-fiLnws-image.png","type":"photo","width":1471,"height":870,"blurhash":"LJQ0mzo=~1~VxvR*t3s,a$WXj?oI"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"找不到规律?请看图!(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/by-endlesscheng-umpp","content":"定义 $f[i]$ 表示平铺 $2 \\\\times i$ 面板的方案数。答案为 $f[n]$。
\\n尝试计算 $f$ 的前几项,从中找到规律,得到 $f[n]$ 的递推式。(请点击图片放大查看)
\\n\\n
文字版解释
\\n对于 $f[i]$,考虑最右边的可以独立切割出来的矩形,有三种情况:
\\n\\n
\\n- 一个竖着的 $1\\\\times 2$。去掉这个瓷砖,问题变成平铺 $2 \\\\times (i-1)$ 面板的方案数,即 $f[i-1]$。
\\n- 两个横着的 $2\\\\times 1$。去掉这两瓷砖,问题变成平铺 $2 \\\\times (i-2)$ 面板的方案数,即 $f[i-2]$。
\\n- 考虑最右边那一对 L 型的铺设方法,这一对 L 型必须一左一右,中间的都是 $2\\\\times 1$ 横着摆。中间的 $2\\\\times 1$ 的个数为 $0,1,2,\\\\ldots,i-3$,对应的子问题分别为 $f[i-3],f[i-4],f[i-5],\\\\ldots,f[0]$,每一种又有上下对称的平铺方案。
\\n累加得
\\n$$
\\n
\\nf[i] = f[i-1] + f[i-2] + 2\\\\sum_{j=0}^{i-3} f[j]
\\n$$化简方式见图。
\\n代码实现时,可以定义 $f[0]=1$,这样我们可以从 $f[3]$ 开始算。
\\n写法一
\\n\\nMOD = 1_000_000_007\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n if n == 1:\\n return 1\\n f = [0] * (n + 1)\\n f[0] = f[1] = 1\\n f[2] = 2\\n for i in range(3, n + 1):\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD\\n return f[n]\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n long[] f = new long[n + 1];\\n f[0] = f[1] = 1;\\n f[2] = 2;\\n for (int i = 3; i <= n; i++) {\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD;\\n }\\n return (int) f[n];\\n }\\n}\\n
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n vector<long long> f(n + 1);\\n f[0] = f[1] = 1;\\n f[2] = 2;\\n for (int i = 3; i <= n; i++) {\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD;\\n }\\n return f[n];\\n }\\n};\\n
\\nfunc numTilings(n int) int {\\n if n == 1 {\\n return 1\\n }\\n f := make([]int, n+1)\\n f[0], f[1], f[2] = 1, 1, 2\\n for i := 3; i <= n; i++ {\\n f[i] = (f[i-1]*2 + f[i-3]) % 1_000_000_007\\n }\\n return f[n]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n写法二
\\n也可以用 $3$ 个变量滚动计算 $f$。原理见 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】。
\\n\\nMOD = 1_000_000_007\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n if n == 1:\\n return 1\\n a, b, c = 1, 1, 2\\n for _ in range(3, n + 1):\\n a, b, c = b, c, (c * 2 + a) % MOD\\n return c\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n long a = 1, b = 1, c = 2;\\n for (int i = 3; i <= n; i++) {\\n long f = (c * 2 + a) % MOD;\\n a = b;\\n b = c;\\n c = f;\\n }\\n return (int) c;\\n }\\n}\\n
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n long long a = 1, b = 1, c = 2;\\n for (int i = 3; i <= n; i++) {\\n long f = (c * 2 + a) % MOD;\\n a = b;\\n b = c;\\n c = f;\\n }\\n return c;\\n }\\n};\\n
\\nfunc numTilings(n int) int {\\n if n == 1 {\\n return 1\\n }\\n a, b, c := 1, 1, 2\\n for i := 3; i <= n; i++ {\\n a, b, c = b, c, (c*2+a)%1_000_000_007\\n }\\n return c\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n写法三
\\n由于 $n\\\\le 1000$,可以预处理 $1000$ 以内的 $f$ 值。
\\n由于力扣不计入预处理的时间,所以这种写法的时间复杂度是 $\\\\mathcal{O}(1)$。
\\n\\nMOD = 1_000_000_007\\n\\nf = [0] * 1001\\nf[0] = f[1] = 1\\nf[2] = 2\\nfor i in range(3, len(f)):\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n return f[n]\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final long[] f = new long[1001];\\n\\n static {\\n f[0] = f[1] = 1;\\n f[2] = 2;\\n for (int i = 3; i < f.length; i++) {\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD;\\n }\\n }\\n\\n public int numTilings(int n) {\\n return (int) f[n];\\n }\\n}\\n
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 1001;\\nlong long f[MX];\\n\\nint init = []() {\\n f[0] = f[1] = 1;\\n f[2] = 2;\\n for (int i = 3; i < MX; i++) {\\n f[i] = (f[i - 1] * 2 + f[i - 3]) % MOD;\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int numTilings(int n) {\\n return f[n];\\n }\\n};\\n
\\nvar f = [1001]int{1, 1, 2}\\n\\nfunc init() {\\n for i := 3; i < len(f); i++ {\\n f[i] = (f[i-1]*2 + f[i-3]) % 1_000_000_007\\n }\\n}\\n\\nfunc numTilings(n int) int {\\n return f[n]\\n}\\n
复杂度分析
\\n忽略预处理的时间和空间。
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(1)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n写法四:矩阵快速幂
\\n下面的方法,即使 $n=10^{18}$ 也能轻松通过。
\\n把状态转移方程用矩阵乘法表示,即
\\n$$
\\n
\\n\\\\begin{bmatrix}
\\nf[i] \\\\
\\nf[i-1] \\\\
\\nf[i-2] \\\\
\\n\\\\end{bmatrix}
\\n= \\\\begin{bmatrix}
\\n2 & 0 & 1 \\\\
\\n1 & 0 & 0 \\\\
\\n0 & 1 & 0 \\\\
\\n\\\\end{bmatrix}
\\n\\\\begin{bmatrix}
\\nf[i-1] \\\\
\\nf[i-2] \\\\
\\nf[i-3] \\\\
\\n\\\\end{bmatrix}
\\n$$把上式中的三个矩阵分别记作 $F[i],M,F[i-1]$,即
\\n$$
\\n
\\nF[i] = M\\\\times F[i-1]
\\n$$那么有
\\n$$
\\n
\\n\\\\begin{aligned}
\\nF[n] ={} & M\\\\times F[n-1] \\\\
\\n={} & M\\\\times M\\\\times F[n-2] \\\\
\\n={} & M\\\\times M\\\\times M\\\\times F[n-3] \\\\
\\n\\\\vdots & \\\\
\\n={} & M^{n-2}\\\\times F[2]
\\n\\\\end{aligned}
\\n$$其中
\\n$$
\\n
\\nF[2] =
\\n\\\\begin{bmatrix}
\\nf[2] \\\\
\\nf[1] \\\\
\\nf[0] \\\\
\\n\\\\end{bmatrix}\\\\begin{bmatrix}
\\n
\\n2 \\\\
\\n1 \\\\
\\n1 \\\\
\\n\\\\end{bmatrix}
\\n$$$M^n$ 可以用快速幂计算,原理请看【图解】一张图秒懂快速幂。
\\n最终答案为 $F[n]$ 的第一项,即 $f[n]$。
\\n\\nMOD = 1_000_000_007\\n\\n# a @ b,其中 @ 是矩阵乘法\\ndef mul(a: List[List[int]], b: List[List[int]]) -> List[List[int]]:\\n return [[sum(x * y for x, y in zip(row, col)) % MOD for col in zip(*b)]\\n for row in a]\\n\\n# a^n @ f\\ndef pow_mul(a: List[List[int]], n: int, f: List[List[int]]) -> List[List[int]]:\\n res = f\\n while n:\\n if n & 1:\\n res = mul(a, res)\\n a = mul(a, a)\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n if n == 1:\\n return 1\\n f2 = [[2], [1], [1]]\\n m = [[2, 0, 1], [1, 0, 0], [0, 1, 0]]\\n fn = pow_mul(m, n - 2, f2)\\n return fn[0][0]\\n
\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\n# a^n @ f\\ndef pow_mul(a: np.ndarray, n: int, f: np.ndarray) -> np.ndarray:\\n res = f\\n while n:\\n if n & 1:\\n res = a @ res % MOD\\n a = a @ a % MOD\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n if n == 1:\\n return 1\\n f2 = np.array([2, 1, 1], dtype=object)\\n m = np.array([[2, 0, 1], [1, 0, 0], [0, 1, 0]], dtype=object)\\n fn = pow_mul(m, n - 2, f2)\\n return fn[0]\\n
\\nimport numpy as np\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n if n == 1:\\n return 1\\n f2 = np.array([2, 1, 1], dtype=object)\\n m = np.array([[2, 0, 1], [1, 0, 0], [0, 1, 0]], dtype=object)\\n # 注:没法中途取模,参与运算的数字很大,效率低,所以我并不推荐这种写法\\n fn = np.linalg.matrix_power(m, n - 2) @ f2\\n return fn[0] % 1_000_000_007\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n long[][] f2 = {{2}, {1}, {1}};\\n long[][] m = {\\n {2, 0, 1},\\n {1, 0, 0},\\n {0, 1, 0},\\n };\\n long[][] fn = powMul(m, n - 2, f2);\\n return (int) fn[0][0];\\n }\\n\\n // a^n * f\\n private long[][] powMul(long[][] a, int n, long[][] f) {\\n long[][] res = f;\\n while (n > 0) {\\n if ((n & 1) > 0) {\\n res = mul(a, res);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n }\\n\\n // 返回矩阵 a 和矩阵 b 相乘的结果\\n private long[][] mul(long[][] a, long[][] b) {\\n long[][] c = new long[a.length][b[0].length];\\n for (int i = 0; i < a.length; i++) {\\n for (int k = 0; k < a[i].length; k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < b[k].length; j++) {\\n c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;\\n }\\n }\\n }\\n return c;\\n }\\n}\\n
\\nconst int MOD = 1\'000\'000\'007;\\n\\nusing matrix = vector<vector<long long>>;\\n\\n// 返回矩阵 a 和矩阵 b 相乘的结果\\nmatrix mul(matrix& a, matrix& b) {\\n int n = a.size(), m = b[0].size();\\n matrix c = matrix(n, vector<long long>(m));\\n for (int i = 0; i < n; i++) {\\n for (int k = 0; k < a[i].size(); k++) {\\n if (a[i][k] == 0) {\\n continue;\\n }\\n for (int j = 0; j < m; j++) {\\n c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD;\\n }\\n }\\n }\\n return c;\\n}\\n\\n// a^n * f\\nmatrix pow_mul(matrix a, int n, matrix& f) {\\n matrix res = f;\\n while (n) {\\n if (n & 1) {\\n res = mul(a, res);\\n }\\n a = mul(a, a);\\n n >>= 1;\\n }\\n return res;\\n}\\n\\nclass Solution {\\npublic:\\n int numTilings(int n) {\\n if (n == 1) {\\n return 1;\\n }\\n matrix f2 = {{2}, {1}, {1}};\\n matrix m = {\\n {2, 0, 1},\\n {1, 0, 0},\\n {0, 1, 0},\\n };\\n matrix fn = pow_mul(m, n - 2, f2);\\n return fn[0][0];\\n }\\n};\\n
\\nconst mod = 1_000_000_007\\n\\ntype matrix [][]int\\n\\nfunc newMatrix(n, m int) matrix {\\n a := make(matrix, n)\\n for i := range a {\\n a[i] = make([]int, m)\\n }\\n return a\\n}\\n\\n// 返回矩阵 a 和矩阵 b 相乘的结果\\nfunc (a matrix) mul(b matrix) matrix {\\n c := newMatrix(len(a), len(b[0]))\\n for i, row := range a {\\n for k, x := range row {\\n if x == 0 {\\n continue\\n }\\n for j, y := range b[k] {\\n c[i][j] = (c[i][j] + x*y) % mod\\n }\\n }\\n }\\n return c\\n}\\n\\n// a^n * f\\nfunc (a matrix) powMul(n int, f matrix) matrix {\\n res := f\\n for ; n > 0; n /= 2 {\\n if n%2 > 0 {\\n res = a.mul(res)\\n }\\n a = a.mul(a)\\n }\\n return res\\n}\\n\\nfunc numTilings(n int) int {\\n if n == 1 {\\n return 1\\n }\\n f2 := matrix{{2}, {1}, {1}}\\n m := matrix{\\n {2, 0, 1},\\n {1, 0, 0},\\n {0, 1, 0},\\n };\\n fn := m.powMul(n-2, f2)\\n return fn[0][0]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(D^3\\\\log n)$。其中 $D=3$。
\\n- 空间复杂度:$\\\\mathcal{O}(D^2)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"定义 $f[i]$ 表示平铺 $2 \\\\times i$ 面板的方案数。答案为 $f[n]$。 尝试计算 $f$ 的前几项,从中找到规律,得到 $f[n]$ 的递推式。(请点击图片放大查看)\\n\\n文字版解释\\n\\n对于 $f[i]$,考虑最右边的可以独立切割出来的矩形,有三种情况:\\n\\n一个竖着的 $1\\\\times 2$。去掉这个瓷砖,问题变成平铺 $2 \\\\times (i-1)$ 面板的方案数,即 $f[i-1]$。\\n两个横着的 $2\\\\times 1$。去掉这两瓷砖,问题变成平铺 $2 \\\\times (i-2)$ 面板的方案数,即 $f[i-2]$。\\n考虑最右边那…","guid":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/by-endlesscheng-umpp","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-11-11T23:30:41.967Z","media":[{"url":"https://pic.leetcode.cn/1668157188-nBzesC-790-5.png","type":"photo","width":3657,"height":3867,"blurhash":"LCSZ2;%g=@~W~ps:WCWC?EaxIqWV"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"多米诺和托米诺平铺","url":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/duo-mi-nuo-he-tuo-mi-nuo-ping-pu-by-leet-7n0j","content":"方法一:动态规划
\\n考虑这么一种平铺的方式:在第 $i$ 列前面的正方形都被瓷砖覆盖,在第 $i$ 列后面的正方形都没有被瓷砖覆盖($i$ 从 $1$ 开始计数)。那么第 $i$ 列的正方形有四种被覆盖的情况:
\\n\\n
\\n- \\n
\\n一个正方形都没有被覆盖,记为状态 $0$;
\\n- \\n
\\n只有上方的正方形被覆盖,记为状态 $1$;
\\n- \\n
\\n只有下方的正方形被覆盖,记为状态 $2$;
\\n- \\n
\\n上下两个正方形都被覆盖,记为状态 $3$。
\\n使用 $\\\\textit{dp}[i][s]$ 表示平铺到第 $i$ 列时,各个状态 $s$ 对应的平铺方法数量。考虑第 $i-1$ 列和第 $i$ 列正方形,它们之间的状态转移如下图(红色条表示新铺的瓷砖):
\\n\\n
初始时 $\\\\textit{dp}[0][0] = 0, \\\\textit{dp}[0][1] = 0, \\\\textit{dp}[0][2] = 0, \\\\textit{dp}[0][3] = 1$,对应的状态转移方程($i \\\\gt 0$)为:
\\n$$
\\n
\\n\\\\begin{aligned}
\\n\\\\textit{dp}[i][0] &= \\\\textit{dp}[i-1][3] \\\\
\\n\\\\textit{dp}[i][1] &= \\\\textit{dp}[i-1][0] + \\\\textit{dp}[i-1][2] \\\\
\\n\\\\textit{dp}[i][2] &= \\\\textit{dp}[i-1][0] + \\\\textit{dp}[i-1][1] \\\\
\\n\\\\textit{dp}[i][3] &= \\\\textit{dp}[i-1][0] + \\\\textit{dp}[i-1][1] + \\\\textit{dp}[i-1][2] + \\\\textit{dp}[i-1][3] \\\\
\\n\\\\end{aligned}
\\n$$最后平铺到第 $n$ 列时,上下两个正方形都被覆盖的状态 $\\\\textit{dp}[n][3]$ 对应的平铺方法数量就是总平铺方法数量。
\\n###Python
\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n MOD = 10 ** 9 + 7\\n dp = [[0] * 4 for _ in range(n + 1)]\\n dp[0][3] = 1\\n for i in range(1, n + 1):\\n dp[i][0] = dp[i - 1][3]\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD\\n dp[i][3] = (((dp[i - 1][0] + dp[i - 1][1]) % MOD + dp[i - 1][2]) % MOD + dp[i - 1][3]) % MOD\\n return dp[n][3]\\n
###C++
\\n\\nconst long long mod = 1e9 + 7;\\nclass Solution {\\npublic:\\n int numTilings(int n) {\\n vector<vector<long long>> dp(n + 1, vector<long long>(4));\\n dp[0][3] = 1;\\n for (int i = 1; i <= n; i++) {\\n dp[i][0] = dp[i - 1][3];\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod;\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % mod;\\n dp[i][3] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][3]) % mod;\\n }\\n return dp[n][3];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n static final int MOD = 1000000007;\\n\\n public int numTilings(int n) {\\n int[][] dp = new int[n + 1][4];\\n dp[0][3] = 1;\\n for (int i = 1; i <= n; i++) {\\n dp[i][0] = dp[i - 1][3];\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD;\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD;\\n dp[i][3] = (((dp[i - 1][0] + dp[i - 1][1]) % MOD + dp[i - 1][2]) % MOD + dp[i - 1][3]) % MOD;\\n }\\n return dp[n][3];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n const int MOD = 1000000007;\\n\\n public int NumTilings(int n) {\\n int[][] dp = new int[n + 1][];\\n for (int i = 0; i <= n; i++) {\\n dp[i] = new int[4];\\n }\\n dp[0][3] = 1;\\n for (int i = 1; i <= n; i++) {\\n dp[i][0] = dp[i - 1][3];\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % MOD;\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % MOD;\\n dp[i][3] = (((dp[i - 1][0] + dp[i - 1][1]) % MOD + dp[i - 1][2]) % MOD + dp[i - 1][3]) % MOD;\\n }\\n return dp[n][3];\\n }\\n}\\n
###JavaScript
\\n\\nvar numTilings = function(n) {\\n const mod = 1e9 + 7;\\n const dp = new Array(n + 1).fill(0).map(() => new Array(4).fill(0));\\n dp[0][3] = 1;\\n for (let i = 1; i <= n; i++) {\\n dp[i][0] = dp[i - 1][3];\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod;\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % mod;\\n dp[i][3] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][3]) % mod;\\n }\\n return dp[n][3];\\n};\\n
###C
\\n\\nconst long long mod = 1e9 + 7;\\n\\nint numTilings(int n) {\\n long long dp[n + 1][4];\\n memset(dp, 0, sizeof(dp));\\n dp[0][3] = 1;\\n for (int i = 1; i <= n; i++) {\\n dp[i][0] = dp[i - 1][3];\\n dp[i][1] = (dp[i - 1][0] + dp[i - 1][2]) % mod;\\n dp[i][2] = (dp[i - 1][0] + dp[i - 1][1]) % mod;\\n dp[i][3] = (dp[i - 1][0] + dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][3]) % mod;\\n }\\n return dp[n][3];\\n}\\n
###go
\\n\\nfunc numTilings(n int) int {\\n const mod int = 1e9 + 7\\n dp := make([][4]int, n+1)\\n dp[0][3] = 1\\n for i := 1; i <= n; i++ {\\n dp[i][0] = dp[i-1][3]\\n dp[i][1] = (dp[i-1][0] + dp[i-1][2]) % mod\\n dp[i][2] = (dp[i-1][0] + dp[i-1][1]) % mod\\n dp[i][3] = (((dp[i-1][0]+dp[i-1][1])%mod+dp[i-1][2])%mod + dp[i-1][3]) % mod\\n }\\n return dp[n][3]\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是总列数。
\\n- \\n
\\n空间复杂度:$O(n)$。保存 $\\\\textit{dp}$ 数组需要 $O(n)$ 的空间。
\\n方法二:矩阵快速幂
\\n关于矩阵快速幂的讲解可以参考官方题解「70. 爬楼梯」的方法二,本文不再作详细说明。由方法一可知,平铺到某一列时的所有覆盖状态可以用一个列向量 $x$ 来表示,那么初始时 $x = [0 \\\\ 0 \\\\ 0 \\\\ 1]^T$。一次状态转移等价于在左边乘上矩阵:
\\n$$
\\n
\\nA =
\\n\\\\begin{bmatrix}
\\n0&0&0&1 \\\\
\\n1&0&1&0 \\\\
\\n1&1&0&0 \\\\
\\n1&1&1&1 \\\\
\\n\\\\end{bmatrix}
\\n$$那么 $n$ 次状态转移后,所有覆盖状态对应的列向量为 $A^n x$,其中 $A^n$ 可以使用矩阵快速幂来计算。根据 $x$ 的值,可以知道最终所有的覆盖状态对应的列向量为 $A^n$ 的第 $3$ 列,返回该列向量的第 $3$ 个元素即可。
\\n###Python
\\n\\nclass Solution:\\n def numTilings(self, n: int) -> int:\\n MOD = 10 ** 9 + 7\\n\\n def multiply(a: List[List[int]], b: List[List[int]]) -> List[List[int]]:\\n rows, columns, temp = len(a), len(b[0]), len(b)\\n c = [[0] * columns for _ in range(rows)]\\n for i in range(rows):\\n for j in range(columns):\\n for k in range(temp):\\n c[i][j] = (c[i][j] + a[i][k] * b[k][j]) % MOD\\n return c\\n\\n def matrixPow(mat: List[List[int]], n: int) -> List[List[int]]:\\n ret = [\\n [1, 0, 0, 0],\\n [0, 1, 0, 0],\\n [0, 0, 1, 0],\\n [0, 0, 0, 1],\\n ]\\n while n:\\n if n & 1:\\n ret = multiply(ret, mat)\\n n >>= 1\\n mat = multiply(mat, mat)\\n return ret\\n\\n mat = [\\n [0, 0, 0, 1],\\n [1, 0, 1, 0],\\n [1, 1, 0, 0],\\n [1, 1, 1, 1],\\n ]\\n res = matrixPow(mat, n)\\n return res[3][3]\\n
###C++
\\n\\nconst long long mod = 1e9 + 7;\\nclass Solution {\\npublic:\\n vector<vector<long long>> mulMatrix(const vector<vector<long long>> &m1, const vector<vector<long long>> &m2) {\\n int n1 = m1.size(), n2 = m2.size(), n3 = m2[0].size();\\n vector<vector<long long>> res(n1, vector<long long>(n3));\\n for (int i = 0; i < n1; i++) {\\n for (int k = 0; k < n3; k++) {\\n for (int j = 0; j < n2; j++) {\\n res[i][k] = (res[i][k] + m1[i][j] * m2[j][k]) % mod;\\n }\\n }\\n }\\n return res;\\n }\\n\\n int numTilings(int n) {\\n vector<vector<long long>> mat = {\\n {0, 0, 0, 1},\\n {1, 0, 1, 0},\\n {1, 1, 0, 0},\\n {1, 1, 1, 1}\\n };\\n vector<vector<long long>> matn = {\\n {1, 0, 0, 0},\\n {0, 1, 0, 0},\\n {0, 0, 1, 0},\\n {0, 0, 0, 1}\\n };\\n while (n) {\\n if (n & 1) {\\n matn = mulMatrix(matn, mat);\\n }\\n mat = mulMatrix(mat, mat);\\n n >>= 1;\\n }\\n return matn[3][3];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n static final int MOD = 1000000007;\\n\\n public int numTilings(int n) {\\n int[][] mat = {\\n {0, 0, 0, 1},\\n {1, 0, 1, 0},\\n {1, 1, 0, 0},\\n {1, 1, 1, 1}\\n };\\n int[][] matn = {\\n {1, 0, 0, 0},\\n {0, 1, 0, 0},\\n {0, 0, 1, 0},\\n {0, 0, 0, 1}\\n };\\n while (n > 0) {\\n if ((n & 1) != 0) {\\n matn = mulMatrix(matn, mat);\\n }\\n mat = mulMatrix(mat, mat);\\n n >>= 1;\\n }\\n return matn[3][3];\\n }\\n\\n public int[][] mulMatrix(int[][] m1, int[][] m2) {\\n int n1 = m1.length, n2 = m2.length, n3 = m2[0].length;\\n int[][] res = new int[n1][n3];\\n for (int i = 0; i < n1; i++) {\\n for (int k = 0; k < n3; k++) {\\n for (int j = 0; j < n2; j++) {\\n res[i][k] = (int) ((res[i][k] + (long) m1[i][j] * m2[j][k]) % MOD);\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n const int MOD = 1000000007;\\n\\n public int NumTilings(int n) {\\n int[][] mat = {\\n new int[]{0, 0, 0, 1},\\n new int[]{1, 0, 1, 0},\\n new int[]{1, 1, 0, 0},\\n new int[]{1, 1, 1, 1}\\n };\\n int[][] matn = {\\n new int[]{1, 0, 0, 0},\\n new int[]{0, 1, 0, 0},\\n new int[]{0, 0, 1, 0},\\n new int[]{0, 0, 0, 1}\\n };\\n while (n > 0) {\\n if ((n & 1) != 0) {\\n matn = MulMatrix(matn, mat);\\n }\\n mat = MulMatrix(mat, mat);\\n n >>= 1;\\n }\\n return matn[3][3];\\n }\\n\\n public int[][] MulMatrix(int[][] m1, int[][] m2) {\\n int n1 = m1.Length, n2 = m2.Length, n3 = m2[0].Length;\\n int[][] res = new int[n1][];\\n for (int i = 0; i < n1; i++) {\\n res[i] = new int[n3];\\n for (int k = 0; k < n3; k++) {\\n for (int j = 0; j < n2; j++) {\\n res[i][k] = (int) ((res[i][k] + (long) m1[i][j] * m2[j][k]) % MOD);\\n }\\n }\\n }\\n return res;\\n }\\n}\\n
###C
\\n\\nconst long long mod = 1e9 + 7;\\n\\nstruct Matrix {\\n long long mat[4][4];\\n};\\n\\nstruct Matrix mulMatrix(const struct Matrix *m1, const struct Matrix *m2) {\\n struct Matrix res;\\n memset(&res, 0, sizeof(res));\\n for (int i = 0; i < 4; i++) {\\n for (int k = 0; k < 4; k++) {\\n for (int j = 0; j < 4; j++) {\\n res.mat[i][k] = (res.mat[i][k] + m1->mat[i][j] * m2->mat[j][k]) % mod;\\n }\\n }\\n }\\n return res;\\n}\\n\\nint numTilings(int n) {\\n long long mat1[4][4] = {\\n {0, 0, 0, 1},\\n {1, 0, 1, 0},\\n {1, 1, 0, 0},\\n {1, 1, 1, 1}\\n };\\n long long mat2[4][4] = {\\n {1, 0, 0, 0},\\n {0, 1, 0, 0},\\n {0, 0, 1, 0},\\n {0, 0, 0, 1}\\n };\\n struct Matrix mat, matn;\\n memcpy(mat.mat, mat1, sizeof(mat1));\\n memcpy(matn.mat, mat2, sizeof(mat2));\\n while (n) {\\n if (n & 1) {\\n matn = mulMatrix(&matn, &mat);\\n }\\n mat = mulMatrix(&mat, &mat);\\n n >>= 1;\\n }\\n return matn.mat[3][3];\\n}\\n
###go
\\n\\nconst mod int = 1e9 + 7\\n\\ntype matrix [4][4]int\\n\\nfunc (a matrix) mul(b matrix) matrix {\\n c := matrix{}\\n for i, row := range a {\\n for j := range b[0] {\\n for k, v := range row {\\n c[i][j] = (c[i][j] + v*b[k][j]) % mod\\n }\\n }\\n }\\n return c\\n}\\n\\nfunc (a matrix) pow(n int) matrix {\\n res := matrix{}\\n for i := range res {\\n res[i][i] = 1\\n }\\n for ; n > 0; n >>= 1 {\\n if n&1 > 0 {\\n res = res.mul(a)\\n }\\n a = a.mul(a)\\n }\\n return res\\n}\\n\\nfunc numTilings(n int) int {\\n m := matrix{\\n {0, 0, 0, 1},\\n {1, 0, 1, 0},\\n {1, 1, 0, 0},\\n {1, 1, 1, 1},\\n }\\n return m.pow(n)[3][3]\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:动态规划 考虑这么一种平铺的方式:在第 $i$ 列前面的正方形都被瓷砖覆盖,在第 $i$ 列后面的正方形都没有被瓷砖覆盖($i$ 从 $1$ 开始计数)。那么第 $i$ 列的正方形有四种被覆盖的情况:\\n\\n一个正方形都没有被覆盖,记为状态 $0$;\\n\\n只有上方的正方形被覆盖,记为状态 $1$;\\n\\n只有下方的正方形被覆盖,记为状态 $2$;\\n\\n上下两个正方形都被覆盖,记为状态 $3$。\\n\\n使用 $\\\\textit{dp}[i][s]$ 表示平铺到第 $i$ 列时,各个状态 $s$ 对应的平铺方法数量。考虑第 $i-1$ 列和第 $i…","guid":"https://leetcode.cn/problems/domino-and-tromino-tiling//solution/duo-mi-nuo-he-tuo-mi-nuo-ping-pu-by-leet-7n0j","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-11-11T05:39:58.088Z","media":[{"url":"https://assets.leetcode-cn.com/solution-static/790/1.png","type":"photo","width":721,"height":481,"blurhash":"LKDR:5tR0yOXAW-pxGniaKNa%2M{"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Two Pointers","url":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/by-tsreaper-czkz","content":"- \\n
\\n时间复杂度:$O(\\\\log n)$,其中 $n$ 是总列数。矩阵快速幂的时间复杂度为 $O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n解法:Two Pointers
\\n原题 https://atcoder.jp/contests/abc247/tasks/abc247_e ,原题题解里还有一个容斥的解法也可以看看。
\\n注意到定界子数组里一定不包含小于
\\nminK
以及大于maxK
的数。从原数组里把不符合要求的数都去掉后,原数组被分成好多个子数组,每个子数组里的元素都在[minK, maxK]
之间。最终答案就是所有子数组的答案之和。接下来我们只考虑元素在[minK, maxK]
之间的子数组。由于
\\nminK
是数组里的最小值,maxK
是数组里的最大值,定界子数组的定义可以变为:\\n\\n子数组里出现过
\\nminK
,同时还出现过maxK
。这就是经典的 two pointers 问题。维护两个指针
\\ni
和j
,计算以i
为结尾,且minK
和maxK
不同时出现的最长的子数组是多长即可。复杂度 $\\\\mathcal{O}(n)$。参考代码(c++)
\\n###c++
\\n\\nclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& A, int X, int Y) {\\n int n = A.size();\\n long long ans = 0;\\n\\n auto gao = [&](int L, int R) {\\n int cntX = 0, cntY = 0;\\n for (int i = L, j = L; i <= R; i++) {\\n if (A[i] == X) cntX++;\\n if (A[i] == Y) cntY++;\\n // 计算以 i 为结尾,且 minK 和 maxK 没有同时出现的最长子数组\\n while (j <= i && cntX > 0 && cntY > 0) {\\n if (A[j] == X) cntX--;\\n if (A[j] == Y) cntY--;\\n j++;\\n }\\n // 更新答案:以 i 为结尾的所有子数组数量,减去不符合要求的子数组数量\\n ans += (i - L + 1) - (i - j + 1);\\n }\\n };\\n\\n int cnt = 0;\\n for (int i = 0; i < n; i++) {\\n // 将数组分成若干子数组求解,每个子数组的元素都在 [X, Y] 之间\\n if (A[i] < X || A[i] > Y) gao(i - cnt, i - 1), cnt = 0;\\n else cnt++;\\n }\\n gao(n - cnt, n - 1);\\n return ans;\\n }\\n};\\n
另注
\\n这种同时限制子数组最小最大值的问题有一种通用的计数方法,详见 https://codeforces.com/problemset/problem/1730/E 。
\\n","description":"解法:Two Pointers 原题 https://atcoder.jp/contests/abc247/tasks/abc247_e ,原题题解里还有一个容斥的解法也可以看看。\\n\\n注意到定界子数组里一定不包含小于 minK 以及大于 maxK 的数。从原数组里把不符合要求的数都去掉后,原数组被分成好多个子数组,每个子数组里的元素都在 [minK, maxK] 之间。最终答案就是所有子数组的答案之和。接下来我们只考虑元素在 [minK, maxK] 之间的子数组。\\n\\n由于 minK 是数组里的最小值,maxK 是数组里的最大值,定界子数组的定义可以变为:…","guid":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/by-tsreaper-czkz","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-10-16T04:07:46.889Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分析性质 + 一次遍历","url":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/fen-xi-ding-jie-zi-shu-zu-de-xing-zhi-yi-qusi","content":"定界子数组满足性质:
\\n\\n
\\n- 子数组不能包含越界的数字($nums[i] > maxK$ 或 $nums[i] < minK$);
\\n- 子数组必须同时包含 $maxK$ 和 $minK$。
\\n根据上述条件,我们从左到右遍历数组,统计以 $i$ 为右端点的定界子数组数量:
\\n\\n
\\n- 维护左侧第一个越界数字的位置 $l$,表示左端点不能等于或越过 $l$;
\\n- 同时,分别维护 $maxK$ 和 $minK$ 在左侧第一次出现的位置 $r_1$ 和 $r_2$,表示左端点必须在 $\\\\min(r_1, r_2)$ 及其左侧,否则子数组中会缺少 $maxK$ 或 $minK$;
\\n- 因此,以 $i$ 为右边界的子数组数量(如果存在)= $\\\\min(r_1, r_2) - l$。
\\n\\nclass Solution:\\n def countSubarrays(self, nums: List[int], minK: int, maxK: int) -> int:\\n l, r1, r2, ret = -1, -1, -1, 0\\n for i in range(len(nums)):\\n if nums[i] > maxK or nums[i] < minK: l = i\\n if nums[i] == maxK: r1 = i\\n if nums[i] == minK: r2 = i\\n ret += max(0, min(r1, r2) - l)\\n return ret\\n
\\n","description":"定界子数组满足性质: 子数组不能包含越界的数字($nums[i] > maxK$ 或 $nums[i] < minK$);\\n子数组必须同时包含 $maxK$ 和 $minK$。\\n\\n根据上述条件,我们从左到右遍历数组,统计以 $i$ 为右端点的定界子数组数量:\\n\\n维护左侧第一个越界数字的位置 $l$,表示左端点不能等于或越过 $l$;\\n同时,分别维护 $maxK$ 和 $minK$ 在左侧第一次出现的位置 $r_1$ 和 $r_2$,表示左端点必须在 $\\\\min(r_1, r_2)$ 及其左侧,否则子数组中会缺少 $maxK$ 或 $minK$;\\n因此…","guid":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/fen-xi-ding-jie-zi-shu-zu-de-xing-zhi-yi-qusi","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-10-16T04:07:13.506Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-gag2","content":"class Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, int minK, int maxK) {\\n int n = nums.size();\\n long long ret = 0;\\n for(int i = 0, l = -1, r1 = -1, r2 = -1; i < n; ++i) {\\n if(nums[i] > maxK || nums[i] < minK) l = i;\\n if(nums[i] == maxK) r1 = i;\\n if(nums[i] == minK) r2 = i;\\n ret += max(0, min(r1, r2) - l);\\n }\\n return ret;\\n }\\n};\\n
从特殊到一般。首先考虑一个简单的情况,$\\\\textit{nums}$ 的所有元素都在 $[\\\\textit{minK},\\\\textit{maxK}]$ 范围内。
\\n在这种情况下,问题相当于:
\\n\\n
\\n- 同时包含 $\\\\textit{minK}$ 和 $\\\\textit{maxK}$ 的子数组的个数。
\\n核心思路:枚举子数组的右端点,统计有多少个合法的左端点。
\\n遍历 $\\\\textit{nums}$,记录 $\\\\textit{minK}$ 最近出现的位置 $\\\\textit{minI}$,以及 $\\\\textit{maxK}$ 最近出现的位置 $\\\\textit{maxI}$,当遍历到 $\\\\textit{nums}[i]$ 时,如果 $\\\\textit{minK}$ 和 $\\\\textit{maxK}$ 都遇到过,则左端点在 $[0,\\\\min(\\\\textit{minI},\\\\textit{maxI})]$ 中的子数组,包含 $\\\\textit{minK}$ 和 $\\\\textit{maxK}$,最小值一定是 $\\\\textit{minK}$,最大值一定是 $\\\\textit{maxK}$。
\\n以 $i$ 为右端点的合法子数组的个数为
\\n$$
\\n
\\n\\\\min(\\\\textit{minI},\\\\textit{maxI})+1
\\n$$回到原问题,由于子数组不能包含在 $[\\\\textit{minK},\\\\textit{maxK}]$ 范围之外的元素,我们需要额外记录在 $[\\\\textit{minK},\\\\textit{maxK}]$ 范围之外的最近元素位置,记作 $i_0$,则左端点在 $[i_0+1,\\\\min(\\\\textit{minI},\\\\textit{maxI})]$ 中的子数组都是合法的。
\\n以 $i$ 为右端点的合法子数组的个数为
\\n$$
\\n
\\n\\\\min(\\\\textit{minI},\\\\textit{maxI})-i_0
\\n$$例如 $\\\\textit{nums}=[1,4,3,4,2,2,3,3]$,如下图所示。
\\n\\n
{:width=700}
代码实现时:
\\n\\n
\\n- 可以初始化 $\\\\textit{minI}=\\\\textit{maxI}=i_0=-1$,兼容没有找到相应元素的情况。
\\n- 如果 $\\\\min(\\\\textit{minI},\\\\textit{maxI})-i_0 < 0$,则表示在 $i_0$ 右侧 $\\\\textit{minK}$ 和 $\\\\textit{maxK}$ 没有同时出现,此时以 $i$ 为右端点的合法子数组的个数为 $0$。所以加到答案中的是 $\\\\max(\\\\min(\\\\textit{minI},\\\\textit{maxI})-i_0, 0)$。
\\n\\nclass Solution:\\n def countSubarrays(self, nums: List[int], minK: int, maxK: int) -> int:\\n ans = 0\\n min_i = max_i = i0 = -1\\n for i, x in enumerate(nums):\\n if x == minK:\\n min_i = i # 最近的 minK 位置\\n if x == maxK:\\n max_i = i # 最近的 maxK 位置\\n if not minK <= x <= maxK:\\n i0 = i # 子数组不能包含 nums[i0]\\n ans += max(min(min_i, max_i) - i0, 0)\\n return ans\\n
\\nclass Solution:\\n def countSubarrays(self, nums: List[int], minK: int, maxK: int) -> int:\\n ans = 0\\n min_i = max_i = i0 = -1\\n for i, x in enumerate(nums):\\n if x == minK:\\n min_i = i\\n if x == maxK:\\n max_i = i\\n if not minK <= x <= maxK:\\n i0 = i\\n j = min_i if min_i < max_i else max_i\\n if j > i0:\\n ans += j - i0\\n return ans\\n
\\nclass Solution {\\n public long countSubarrays(int[] nums, int minK, int maxK) {\\n long ans = 0;\\n int minI = -1, maxI = -1, i0 = -1;\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n if (x == minK) {\\n minI = i; // 最近的 minK 位置\\n }\\n if (x == maxK) {\\n maxI = i; // 最近的 maxK 位置\\n }\\n if (x < minK || x > maxK) {\\n i0 = i; // 子数组不能包含 nums[i0]\\n }\\n ans += Math.max(Math.min(minI, maxI) - i0, 0);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, int minK, int maxK) {\\n long long ans = 0;\\n int min_i = -1, max_i = -1, i0 = -1;\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n if (x == minK) {\\n min_i = i; // 最近的 minK 位置\\n }\\n if (x == maxK) {\\n max_i = i; // 最近的 maxK 位置\\n }\\n if (x < minK || x > maxK) {\\n i0 = i; // 子数组不能包含 nums[i0]\\n }\\n ans += max(min(min_i, max_i) - i0, 0);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long countSubarrays(int* nums, int numsSize, int minK, int maxK) {\\n long long ans = 0;\\n int min_i = -1, max_i = -1, i0 = -1;\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n if (x == minK) {\\n min_i = i; // 最近的 minK 位置\\n }\\n if (x == maxK) {\\n max_i = i; // 最近的 maxK 位置\\n }\\n if (x < minK || x > maxK) {\\n i0 = i; // 子数组不能包含 nums[i0]\\n }\\n ans += MAX(MIN(min_i, max_i) - i0, 0);\\n }\\n return ans;\\n}\\n
\\nfunc countSubarrays(nums []int, minK, maxK int) (ans int64) {\\n minI, maxI, i0 := -1, -1, -1\\n for i, x := range nums {\\n if x == minK {\\n minI = i // 最近的 minK 位置\\n }\\n if x == maxK {\\n maxI = i // 最近的 maxK 位置\\n }\\n if x < minK || x > maxK {\\n i0 = i // 子数组不能包含 nums[i0]\\n }\\n ans += int64(max(min(minI, maxI)-i0, 0))\\n }\\n return\\n}\\n
\\nvar countSubarrays = function(nums, minK, maxK) {\\n let ans = 0, minI = -1, maxI = -1, i0 = -1;\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n if (x === minK) {\\n minI = i; // 最近的 minK 位置\\n }\\n if (x === maxK) {\\n maxI = i; // 最近的 maxK 位置\\n }\\n if (x < minK || x > maxK) {\\n i0 = i; // 子数组不能包含 nums[i0]\\n }\\n ans += Math.max(Math.min(minI, maxI) - i0, 0);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn count_subarrays(nums: Vec<i32>, min_k: i32, max_k: i32) -> i64 {\\n let mut ans = 0;\\n let mut min_i = -1;\\n let mut max_i = -1;\\n let mut i0 = -1;\\n for (i, x) in nums.into_iter().enumerate() {\\n let i = i as i32;\\n if x == min_k {\\n min_i = i; // 最近的 min_k 位置\\n }\\n if x == max_k {\\n max_i = i; // 最近的 max_k 位置\\n }\\n if x < min_k || x > max_k {\\n i0 = i; // 子数组不能包含 nums[i0]\\n }\\n ans += 0.max(min_i.min(max_i) - i0) as i64;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n相似题目
\\n\\n
\\n- 795. 区间子数组个数
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"从特殊到一般。首先考虑一个简单的情况,$\\\\textit{nums}$ 的所有元素都在 $[\\\\textit{minK},\\\\textit{maxK}]$ 范围内。 在这种情况下,问题相当于:\\n\\n同时包含 $\\\\textit{minK}$ 和 $\\\\textit{maxK}$ 的子数组的个数。\\n\\n核心思路:枚举子数组的右端点,统计有多少个合法的左端点。\\n\\n遍历 $\\\\textit{nums}$,记录 $\\\\textit{minK}$ 最近出现的位置 $\\\\textit{minI}$,以及 $\\\\textit{maxK}$ 最近出现的位置 $\\\\textit{maxI…","guid":"https://leetcode.cn/problems/count-subarrays-with-fixed-bounds//solution/jian-ji-xie-fa-pythonjavacgo-by-endlessc-gag2","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-10-16T04:06:23.404Z","media":[{"url":"https://pic.leetcode.cn/1744761446-ngTDfA-lc2444-c.png","type":"photo","width":2174,"height":1667,"blurhash":"LGSY{q?bt7?b~qM{MxayM{ayt7of"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"给定栈入栈序列,求出栈最小字典序的序列","url":"https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string//solution/by-xiang-yun-h5-gxj9","content":"代码
\\n###java
\\n\\n","description":"代码 ###java\\n\\nclass Solution {\\n public String robotWithString(String s) {\\n Stackclass Solution {\\n public String robotWithString(String s) {\\n Stack<Character> stack = new Stack<>();\\n //记录s中各字符的出现次数\\n int[] charCount = new int[26];\\n for(int i=0;i<s.length();i++){\\n charCount[s.charAt(i) - \'a\']++;\\n }\\n \\n StringBuilder sb = new StringBuilder();\\n for(int i=0;i<s.length();i++){\\n stack.push(s.charAt(i));\\n //每个字符一旦入栈,就要把出现次数--\\n charCount[stack.peek() - \'a\']--;\\n\\n //如果栈不空,且栈顶字符前面没有更小的字符了,就出栈\\n while(!stack.empty() && !jugde(charCount,stack.peek() - \'a\')){\\n sb.append(stack.pop());\\n } \\n }\\n return sb.toString();\\n }\\n //判断当前字符前面是否还有更小的字符\\n public boolean jugde(int[] count,int index){\\n for(int i=index-1;i>=0;i--){\\n //如果当前字符前面的字符的出现次数>1则说明有,否则说明没有\\n if(count[i] != 0)\\n return true;\\n }\\n return false;\\n }\\n}\\n
stack = new Stack<>();\\n //记录s中各字符的出现次数\\n int[] charCount = new int[26];\\n for(int i=0;i 解法:贪心\\n 本题是经典贪心:求出栈序列的最小字典序。
\\n我们首先将题目描述进行转化:有一个初始为空的栈,给定字符的入栈顺序,求字典序最小的出栈序列。
\\n当一个字符入栈后,我们持续检查栈顶元素 $c$。设还未入栈的字符中,字典序最小的字符是 $m$,有以下两种情况。
\\n\\n
\\n- $c \\\\le m$:此时弹出 $c$ 最优。如果此时按兵不动,下一个出栈的将会是大等于 $c$ 的字符,答案不会变优。
\\n- $c > m$:此时不弹出 $c$,等待后续更小的字符入栈。
\\n所有字符都入栈后,栈内的剩余字符按顺序弹出即可。复杂度 $\\\\mathcal{O}(n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:贪心 本题是经典贪心:求出栈序列的最小字典序。\\n\\n我们首先将题目描述进行转化:有一个初始为空的栈,给定字符的入栈顺序,求字典序最小的出栈序列。\\n\\n当一个字符入栈后,我们持续检查栈顶元素 $c$。设还未入栈的字符中,字典序最小的字符是 $m$,有以下两种情况。\\n\\n$c \\\\le m$:此时弹出 $c$ 最优。如果此时按兵不动,下一个出栈的将会是大等于 $c$ 的字符,答案不会变优。\\n$c > m$:此时不弹出 $c$,等待后续更小的字符入栈。\\n\\n所有字符都入栈后,栈内的剩余字符按顺序弹出即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)…","guid":"https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string//solution/by-tsreaper-sx1s","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-10-09T04:07:35.745Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 + 栈(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string//solution/tan-xin-zhan-by-endlesscheng-ldds","content":"class Solution {\\npublic:\\n string robotWithString(string s) {\\n int n = s.size();\\n vector<char> f(n + 1);\\n f[n] = \'z\' + 1;\\n for (int i = n - 1; i >= 0; i--) f[i] = min(f[i + 1], s[i]);\\n\\n string ans;\\n stack<char> stk;\\n for (int i = 0; i < n; i++) {\\n stk.push(s[i]);\\n while (!stk.empty() && stk.top() <= f[i + 1]) ans.push_back(stk.top()), stk.pop();\\n }\\n return ans;\\n }\\n};\\n\\n
根据题意,$t$ 中字符是先进后出的,是一个栈。
\\n问题相当于从左到右遍历 $s$,在允许用一个辅助栈的前提下,计算能得到的字典序最小的字符串。
\\n看示例 2,$s=\\\\texttt{bac}$:
\\n\\n
\\n- $s[0]=\\\\texttt{b}$ 入栈,能立刻出栈吗?\\n
\\n\\n
\\n- 如果立刻出栈,那么答案的第一个字母是 $\\\\texttt{b}$。
\\n- 如果不出栈,后面遍历到 $\\\\texttt{a}$ 的时候把 $\\\\texttt{a}$ 出栈,那么答案的第一个字母就会是 $\\\\texttt{a}$,字典序更小。
\\n- 因此,如果我们发现后面有更小的字母,那就不出栈。
\\n- $s[1]=\\\\texttt{a}$ 入栈,能立刻出栈吗?\\n
\\n\\n
\\n- 如果立刻出栈,那么答案的第一个字母是 $\\\\texttt{a}$。
\\n- 如果不出栈,后面也没有更小的字母了。把 $\\\\texttt{c}$ 入栈再出栈,答案的第一个字母是 $\\\\texttt{c}$,不是最优的。
\\n- 因此,如果我们发现当前字母比剩余字母(后缀 $s[i+1:]$)中的最小值还小,那么就立刻出栈。
\\n- 此外,$\\\\texttt{a}$ 出栈后,应该继续把栈顶的 $\\\\texttt{b}$ 也出栈,不然后面 $\\\\texttt{c}$ 入栈出栈,会把 $\\\\texttt{c}$ 填到答案的第二个位置上,不是最优的。
\\n- $s[2]=\\\\texttt{c}$ 入栈,再出栈。
\\n- 最终答案为 $\\\\texttt{abc}$。
\\n如果 $s$ 中有相同字母,要如何处理呢?
\\n以 $s=\\\\texttt{caba}$ 为例。对于 $s[1]=\\\\texttt{a}$,应该立刻出栈。如果不出栈,遍历到 $s[3]=\\\\texttt{a}$ 才出栈,那么结果是 $\\\\texttt{abac}$,但正确答案是 $\\\\texttt{aabc}$。在错误答案中,因为没有及时把 $\\\\texttt{a}$ 出栈,我们把更大的 $\\\\texttt{b}$ 插在了两个 $\\\\texttt{a}$ 中间。所以如果栈顶等于剩余字母(后缀 $s[i+1:]$)中的最小值,也应该立刻出栈。
\\n总结:如果栈顶 $\\\\le$ 剩余字母(后缀 $s[i+1:]$)中的最小值,就立刻出栈。
\\n\\nclass Solution:\\n def robotWithString(self, s: str) -> str:\\n n = len(s)\\n # 计算后缀最小值\\n suf_min = [\'z\'] * (n + 1)\\n for i in range(n - 1, -1, -1):\\n suf_min[i] = min(suf_min[i + 1], s[i])\\n\\n ans = []\\n st = []\\n for i, ch in enumerate(s):\\n st.append(ch)\\n while st and st[-1] <= suf_min[i + 1]:\\n ans.append(st.pop())\\n return \'\'.join(ans)\\n
\\n// 更快的写法见【Java 数组】\\nclass Solution {\\n public String robotWithString(String s) {\\n int n = s.length();\\n // 计算后缀最小值\\n char[] sufMin = new char[n + 1];\\n sufMin[n] = Character.MAX_VALUE;\\n for (int i = n - 1; i >= 0; i--) {\\n sufMin[i] = (char) Math.min(sufMin[i + 1], s.charAt(i));\\n }\\n\\n StringBuilder ans = new StringBuilder(n);\\n Deque<Character> st = new ArrayDeque<>();\\n for (int i = 0; i < n; i++) {\\n st.push(s.charAt(i));\\n while (!st.isEmpty() && st.peek() <= sufMin[i + 1]) {\\n ans.append(st.pop());\\n }\\n }\\n return ans.toString();\\n }\\n}\\n
\\nclass Solution {\\n public String robotWithString(String S) {\\n char[] s = S.toCharArray();\\n int n = s.length;\\n // 计算后缀最小值\\n char[] sufMin = new char[n + 1];\\n sufMin[n] = Character.MAX_VALUE;\\n for (int i = n - 1; i >= 0; i--) {\\n sufMin[i] = (char) Math.min(sufMin[i + 1], s[i]);\\n }\\n\\n char[] ans = s;\\n char[] st = sufMin;\\n int idx = 0;\\n int top = -1;\\n for (int i = 0; i < n; i++) {\\n st[++top] = s[i];\\n while (top >= 0 && st[top] <= sufMin[i + 1]) {\\n ans[idx++] = st[top--];\\n }\\n }\\n return new String(ans);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string robotWithString(string s) {\\n int n = s.size();\\n // 计算后缀最小值\\n vector<char> suf_min(n + 1);\\n suf_min[n] = \'z\';\\n for (int i = n - 1; i >= 0; i--) {\\n suf_min[i] = min(suf_min[i + 1], s[i]);\\n }\\n\\n string ans;\\n stack<char> st;\\n for (int i = 0; i < n; i++) {\\n st.push(s[i]);\\n while (!st.empty() && st.top() <= suf_min[i + 1]) {\\n ans += st.top();\\n st.pop();\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nchar* robotWithString(char* s) {\\n int n = strlen(s);\\n // 计算后缀最小值\\n char* suf_min = malloc((n + 1) * sizeof(char));\\n suf_min[n] = \'z\';\\n for (int i = n - 1; i >= 0; i--) {\\n suf_min[i] = MIN(suf_min[i + 1], s[i]);\\n }\\n\\n char* ans = s;\\n char* st = suf_min;\\n int idx = 0, top = -1;\\n for (int i = 0; i < n; i++) {\\n st[++top] = s[i];\\n while (top >= 0 && st[top] <= suf_min[i + 1]) {\\n ans[idx++] = st[top--];\\n }\\n }\\n ans[idx] = \'\\\\0\';\\n\\n free(suf_min);\\n return ans;\\n}\\n
\\nfunc robotWithString(s string) string {\\nn := len(s)\\n// 计算后缀最小值\\nsufMin := make([]byte, n+1)\\nsufMin[n] = math.MaxUint8\\nfor i := n - 1; i >= 0; i-- {\\nsufMin[i] = min(sufMin[i+1], s[i])\\n}\\n\\nans := make([]byte, 0, n)\\nst := sufMin[:0]\\nfor i, ch := range s {\\nst = append(st, byte(ch))\\nfor len(st) > 0 && st[len(st)-1] <= sufMin[i+1] {\\nans = append(ans, st[len(st)-1])\\nst = st[:len(st)-1]\\n}\\n}\\nreturn string(ans)\\n}\\n
\\nvar robotWithString = function(s) {\\n const n = s.length;\\n // 计算后缀最小值\\n const sufMin = Array(n + 1);\\n sufMin[n] = \'z\';\\n for (let i = n - 1; i >= 0; i--) {\\n sufMin[i] = s[i] < sufMin[i + 1] ? s[i] : sufMin[i + 1];\\n }\\n\\n const ans = [];\\n const st = [];\\n for (let i = 0; i < n; i++) {\\n st.push(s[i]);\\n while (st.length > 0 && st[st.length - 1] <= sufMin[i + 1]) {\\n ans.push(st.pop());\\n }\\n }\\n return ans.join(\'\');\\n};\\n
\\nimpl Solution {\\n pub fn robot_with_string(s: String) -> String {\\n let n = s.len();\\n // 计算后缀最小值\\n let mut suf_min = vec![u8::MAX; n + 1];\\n for (i, ch) in s.bytes().enumerate().rev() {\\n suf_min[i] = suf_min[i + 1].min(ch);\\n }\\n\\n let mut ans = Vec::with_capacity(n);\\n let mut st = vec![];\\n for (i, ch) in s.bytes().enumerate() {\\n st.push(ch);\\n while let Some(&top) = st.last() {\\n if top > suf_min[i + 1] {\\n break;\\n }\\n ans.push(st.pop().unwrap());\\n }\\n }\\n unsafe { String::from_utf8_unchecked(ans) }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。每个字母入栈出栈各恰好一次,所以二重循环的时间复杂度是 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"根据题意,$t$ 中字符是先进后出的,是一个栈。 问题相当于从左到右遍历 $s$,在允许用一个辅助栈的前提下,计算能得到的字典序最小的字符串。\\n\\n看示例 2,$s=\\\\texttt{bac}$:\\n\\n$s[0]=\\\\texttt{b}$ 入栈,能立刻出栈吗?\\n如果立刻出栈,那么答案的第一个字母是 $\\\\texttt{b}$。\\n如果不出栈,后面遍历到 $\\\\texttt{a}$ 的时候把 $\\\\texttt{a}$ 出栈,那么答案的第一个字母就会是 $\\\\texttt{a}$,字典序更小。\\n因此,如果我们发现后面有更小的字母,那就不出栈。\\n$s[1]=\\\\texttt…","guid":"https://leetcode.cn/problems/using-a-robot-to-print-the-lexicographically-smallest-string//solution/tan-xin-zhan-by-endlesscheng-ldds","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-10-09T04:07:27.885Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"c++/python3/java (1)统计","url":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/cpython3java-1-by-xinghe_xinghe-g1tj","content":"思路和心得:
\\n(一)统计
\\n\\nclass Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n n = len(s)\\n res = 1\\n winLen = 1\\n for i in range(1, n):\\n if ord(s[i]) - ord(s[i - 1]) == 1:\\n winLen += 1\\n else:\\n winLen = 1\\n res = max(res, winLen)\\n \\n return res\\n
\\nclass Solution {\\npublic:\\n int longestContinuousSubstring(string s) {\\n int n = (int)s.size();\\n int res = 1;\\n int winLen = 1;\\n for (int i = 1; i < n; i ++){\\n if (s[i] - s[i - 1] == 1){\\n winLen ++;\\n }else{\\n winLen = 1;\\n }\\n res = max(res, winLen);\\n }\\n\\n return res;\\n }\\n};\\n
\\n","description":"思路和心得: class Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n n = len(s)\\n res = 1\\n winLen = 1\\n for i in range(1, n):\\n if ord(s[i]) - ord(s[i - 1]) == 1:\\n winLen += 1\\n else:\\n winLen…","guid":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/cpython3java-1-by-xinghe_xinghe-g1tj","author":"XingHe_XingHe","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-19T01:20:27.474Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"维护连续递增长度(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/by-endlesscheng-rant","content":"class Solution {\\n public int longestContinuousSubstring(String s) {\\n int n = s.length();\\n int res = 1;\\n int winLen = 1;\\n for (int i = 1; i < n; i ++){\\n if (s.charAt(i) - s.charAt(i - 1) == 1){\\n winLen ++;\\n }else{\\n winLen = 1;\\n }\\n res = Math.max(res, winLen);\\n }\\n\\n return res;\\n }\\n}\\n
从左到右遍历 $s$,同时维护连续递增长度 $\\\\textit{cnt}$:
\\n\\n
\\n- 如果 $s[i-1]+1 = s[i]$,把 $\\\\textit{cnt}$ 加一,然后用 $\\\\textit{cnt}$ 更新答案的最大值。
\\n- 如果 $s[i-1]+1\\\\ne s[i]$,把 $\\\\textit{cnt}$ 重置为 $1$。
\\n$\\\\textit{cnt}$ 和答案的初始值均为 $1$。
\\n\\nclass Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n ans = cnt = 1\\n for x, y in pairwise(map(ord, s)):\\n cnt = cnt + 1 if x + 1 == y else 1\\n ans = max(ans, cnt)\\n return ans\\n
\\nclass Solution {\\n public int longestContinuousSubstring(String S) {\\n char[] s = S.toCharArray();\\n int ans = 1;\\n int cnt = 1;\\n for (int i = 1; i < s.length; i++) {\\n if (s[i - 1] + 1 == s[i]) {\\n ans = Math.max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int longestContinuousSubstring(string s) {\\n int ans = 1, cnt = 1;\\n for (int i = 1; i < s.length(); i++) {\\n if (s[i - 1] + 1 == s[i]) {\\n ans = max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint longestContinuousSubstring(char* s) {\\n int ans = 1, cnt = 1;\\n for (int i = 1; s[i]; i++) {\\n if (s[i - 1] + 1 == s[i]) {\\n cnt++;\\n ans = MAX(ans, cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n}\\n
\\nfunc longestContinuousSubstring(s string) int {\\n ans, cnt := 1, 1\\n for i := 1; i < len(s); i++ {\\n if s[i-1]+1 == s[i] {\\n cnt++\\n ans = max(ans, cnt)\\n } else {\\n cnt = 1\\n }\\n }\\n return ans\\n}\\n
\\nvar longestContinuousSubstring = function(s) {\\n let ans = 1, cnt = 1;\\n for (let i = 1; i < s.length; i++) {\\n if (s.charCodeAt(i - 1) + 1 === s.charCodeAt(i)) {\\n ans = Math.max(ans, ++cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn longest_continuous_substring(s: String) -> i32 {\\n let mut ans = 1;\\n let mut cnt = 1;\\n let s = s.as_bytes();\\n for i in 1..s.len() {\\n if s[i - 1] + 1 == s[i] {\\n cnt += 1;\\n ans = ans.max(cnt);\\n } else {\\n cnt = 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"从左到右遍历 $s$,同时维护连续递增长度 $\\\\textit{cnt}$: 如果 $s[i-1]+1 = s[i]$,把 $\\\\textit{cnt}$ 加一,然后用 $\\\\textit{cnt}$ 更新答案的最大值。\\n如果 $s[i-1]+1\\\\ne s[i]$,把 $\\\\textit{cnt}$ 重置为 $1$。\\n\\n$\\\\textit{cnt}$ 和答案的初始值均为 $1$。\\n\\nclass Solution:\\n def longestContinuousSubstring(self, s: str) -> int:\\n ans = cnt =…","guid":"https://leetcode.cn/problems/length-of-the-longest-alphabetical-continuous-substring//solution/by-endlesscheng-rant","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-18T04:12:18.451Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 + 一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/by-endlesscheng-lvym","content":"「任意一种交易顺序下,都能完成所有交易」意味着要考虑在最坏情况下,需要多少初始钱数 $\\\\textit{initMoney}$。
\\n什么是最坏情况?
\\n先亏钱($\\\\textit{cost}>\\\\textit{cashback}$),再赚钱($\\\\textit{cost}\\\\le\\\\textit{cashback}$),主打一个欲扬先抑。
\\n初始钱数必须满足,在最穷困潦倒的时候,也能完成交易。
\\n什么时候最穷?完成所有亏钱交易后最穷。
\\n记 $\\\\textit{totalLose}$ 为所有亏钱的 $\\\\textit{cost}-\\\\textit{cashback}$ 之和。
\\n遍历 $\\\\textit{transactions}$,分类讨论:
\\n\\n
\\n- 对于赚钱的交易,假设这是(亏钱后的)第一笔赚钱的交易,那么初始钱数是多少?为了完成这笔交易,题目要求此时的钱至少是 $\\\\textit{cost}$,即 $\\\\textit{initMoney} - \\\\textit{totalLose} \\\\ge \\\\textit{cost}$,得 $\\\\textit{initMoney}\\\\ge \\\\textit{totalLose}+\\\\textit{cost}$。
\\n- 对于亏钱的交易,假设这是最后一笔亏钱的交易,那么初始钱数是多少?由于 $\\\\textit{cost}-\\\\textit{cashback}$ 已经计入 $\\\\textit{totalLose}$ 中,需要先从 $\\\\textit{totalLose}$ 中减去 $\\\\textit{cost}-\\\\textit{cashback}$,即 $\\\\textit{initMoney} - (\\\\textit{totalLose}-(\\\\textit{cost}-\\\\textit{cashback})) \\\\ge \\\\textit{cost}$,化简得到 $\\\\textit{initMoney}\\\\ge \\\\textit{totalLose}+\\\\textit{cashback}$。
\\n所有情况取最大值,就能保证在任意一种交易顺序下,都能完成所有交易。
\\n\\n
\\n- 如果赚钱,即 $\\\\textit{cost}\\\\le\\\\textit{cashback}$,那么 $\\\\textit{totalLose}$ 加上的是二者的较小值 $\\\\textit{cost}$。
\\n- 如果亏钱,即 $\\\\textit{cost}>\\\\textit{cashback}$,那么 $\\\\textit{totalLose}$ 加上的也是二者的较小值 $\\\\textit{cashback}$。
\\n综上所述,初始钱数 $\\\\textit{initMoney}$ 等于 $\\\\textit{totalLose}$ 加上 $\\\\min(\\\\textit{cost},\\\\textit{cashback})$ 的最大值。
\\n\\nclass Solution:\\n def minimumMoney(self, transactions: List[List[int]]) -> int:\\n total_lose = mx = 0\\n for cost, cashback in transactions:\\n total_lose += max(cost - cashback, 0)\\n mx = max(mx, min(cost, cashback))\\n return total_lose + mx\\n
\\nclass Solution {\\n public long minimumMoney(int[][] transactions) {\\n long totalLose = 0;\\n int mx = 0;\\n for (int[] t : transactions) {\\n totalLose += Math.max(t[0] - t[1], 0);\\n mx = Math.max(mx, Math.min(t[0], t[1]));\\n }\\n return totalLose + mx;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long minimumMoney(vector<vector<int>>& transactions) {\\n long long total_lose = 0;\\n int mx = 0;\\n for (auto& t : transactions) {\\n total_lose += max(t[0] - t[1], 0);\\n mx = max(mx, min(t[0], t[1]));\\n }\\n return total_lose + mx;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nlong long minimumMoney(int** transactions, int transactionsSize, int* transactionsColSize) {\\n long long total_lose = 0;\\n int mx = 0;\\n for (int i = 0; i < transactionsSize; i++) {\\n int cost = transactions[i][0], cashback = transactions[i][1];\\n total_lose += MAX(cost - cashback, 0);\\n mx = MAX(mx, MIN(cost, cashback));\\n }\\n return total_lose + mx;\\n}\\n
\\nfunc minimumMoney(transactions [][]int) int64 {\\ntotalLose, mx := 0, 0\\nfor _, t := range transactions {\\ntotalLose += max(t[0]-t[1], 0)\\nmx = max(mx, min(t[0], t[1]))\\n}\\nreturn int64(totalLose + mx)\\n}\\n
\\nvar minimumMoney = function(transactions) {\\n let totalLose = 0, mx = 0;\\n for (const [cost, cashback] of transactions) {\\n totalLose += Math.max(cost - cashback, 0);\\n mx = Math.max(mx, Math.min(cost, cashback));\\n }\\n return totalLose + mx;\\n};\\n
\\nimpl Solution {\\n pub fn minimum_money(transactions: Vec<Vec<i32>>) -> i64 {\\n let mut total_lose = 0;\\n let mut mx = 0;\\n for t in transactions {\\n total_lose += 0.max(t[0] - t[1]) as i64;\\n mx = mx.max(t[0].min(t[1]));\\n }\\n total_lose + mx as i64\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{transactions}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$,仅用到若干变量。
\\n思考题
\\n如果把题干的「任意一种」改成「至少一种」要怎么做?
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"「任意一种交易顺序下,都能完成所有交易」意味着要考虑在最坏情况下,需要多少初始钱数 $\\\\textit{initMoney}$。 什么是最坏情况?\\n\\n先亏钱($\\\\textit{cost}>\\\\textit{cashback}$),再赚钱($\\\\textit{cost}\\\\le\\\\textit{cashback}$),主打一个欲扬先抑。\\n\\n初始钱数必须满足,在最穷困潦倒的时候,也能完成交易。\\n\\n什么时候最穷?完成所有亏钱交易后最穷。\\n\\n记 $\\\\textit{totalLose}$ 为所有亏钱的 $\\\\textit{cost}-\\\\textit{cashback}$ 之和。\\n\\n遍历…","guid":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/by-endlesscheng-lvym","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-17T23:45:08.278Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分别考虑每笔交易所对应的最坏情况","url":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/fen-bie-kao-lu-mei-bi-jiao-yi-suo-dui-yi-t5ry","content":"T4 的最搞的地方在于,他不是让你自己选一个顺序求最小初始,而是 所有顺序 的 最小初始 的 最大值,(也就是 最坏情况 下的最小初始)。
\\n另外很容易想到,通过贪心排序找到这个最坏情况,比如说,先贪心选择 $cost$ 最大的,或者贪心选择 $cost - cashback$ 最大的(也就是亏最多的),然后再二分求答案,但是这两个贪心都折戟在示例1 上了(示例1给的太棒了,如果没有示例1 说不定首页大片 wa)。(经@zerotrac 提醒,实际上可以有一种排序方案的,分析附后)
\\n所以要换种思路:首先对投资项目进行分类,要么最终亏了,要么不亏。那么,对于任意一个交易 $A = [cost, gain]$,最坏的情况自然是我们 把除了它自身,所有亏损的投资都完成了之后再考虑交易 $A$,这时候剩下的钱不能少于 $cost$。
\\n具体实现:首先把所有 亏损 的项目求个总亏损额 $S$,然后逐个考虑交易,对于交易 $A = [cost, back]$,如果它亏损,需要在总和 $S$ 中减去 $A$ 的亏损额,得到其他项目的总亏损额 $S\'$,此时初始钱数至少为 $cost + S\'$;如果它不亏损,那么无需在总和 $S$ 中减去,初始钱数至少为 $cost + S$。答案就是对每个交易求得的最大值。
\\n正确性证明:假设我们最终求得的结果是 $res$ 。
\\n\\n
\\n- 首先,任何一种顺序下的最小初始不会超过 $res$,因为对于任何一个交易,在他之前的亏损额都不会超过除了它自身的所有亏损额,因此按照前述算法求得的 $res$ 足以支撑该交易,因此最终答案不会大于 $res$;
\\n- 其次,存在一种排序,使得最小初始钱数就是 $res$,假设之前我们在求 $res$ 的过程中,是交易 $A$ 把 $res$ 设为最大值的,那么我们只需把所有亏钱的交易安排在交易 $A$ 之前,那么这个排序的最小初始就是 $res$,因此最终答案不会小于 $res$。
\\n综上所述,最终答案就是按照上面算法所求的 $res$。
\\n###c++
\\n\\nclass Solution {\\npublic:\\n long long minimumMoney(vector<vector<int>>& trans) {\\n long long sumd = 0;\\n for(auto & v : trans) {\\n if(v[0] > v[1]) sumd += v[0] - v[1];\\n }\\n long long res = 0;\\n for(auto & v : trans) {\\n if(v[0] > v[1]) sumd -= v[0] - v[1];\\n res = max(res, sumd + v[0]);\\n if(v[0] > v[1]) sumd += v[0] - v[1];\\n }\\n return res;\\n }\\n};\\n
附:贪心排序方法分析
\\n
\\n首先根据前面的分析,在最坏情况下,我们如果把交易 $A = [cost, back]$ 放到所有亏钱交易之后交易,那么:\\n
\\n- 如果 $A$ 亏损,那么它所需的初始金钱为 $S\' + cost = (S - (cost - back)) + cost = S + back$。
\\n
\\n因此,我们应当在所有亏钱的交易中,选择 $back$ 最大的那个作为最后一次交易。- 如果 $A$ 不亏,那么它所需的初始金钱为 $S + cost$。
\\n
\\n因此,我们应当在所有赚钱的交易中,选择 $cost$ 最大的作为第一次交易(也就是放到所有亏钱的交易后的第一个)。综上所述,贪心排序的方法为:
\\n\\n
\\n","description":"T4 的最搞的地方在于,他不是让你自己选一个顺序求最小初始,而是 所有顺序 的 最小初始 的 最大值,(也就是 最坏情况 下的最小初始)。 另外很容易想到,通过贪心排序找到这个最坏情况,比如说,先贪心选择 $cost$ 最大的,或者贪心选择 $cost - cashback$ 最大的(也就是亏最多的),然后再二分求答案,但是这两个贪心都折戟在示例1 上了(示例1给的太棒了,如果没有示例1 说不定首页大片 wa)。(经@zerotrac 提醒,实际上可以有一种排序方案的,分析附后)\\n\\n所以要换种思路:首先对投资项目进行分类,要么最终亏了,要么不亏。那么…","guid":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/fen-bie-kao-lu-mei-bi-jiao-yi-suo-dui-yi-t5ry","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-17T16:07:20.346Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 贪心","url":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/by-tsreaper-esbt","content":"- 先进行亏钱交易,再进行赚钱交易;
\\n- 亏钱交易中,按 $back$ 升序排序。$back$ 最大的放到最后交易;
\\n- 赚钱交易中,按 $cost$ 降序排序。$cost$ 最大的放到最先交易。
\\n解法:枚举 & 贪心
\\n我们来思考一下,整个交易过程中,哪个环节会导致交易失败?只可能是我们需要
\\ncost
的代价,但当前金钱不足cost
才会失败。因此我们枚举哪个交易 $T$ 会让我们失败。称
\\ncashback - cost < 0
的交易为“负收益”,最差情况下,进行交易 $T$ 之前,我们会首先完成其它所有负收益交易,这样才会让我们的当前金钱变得最少。因此为了完成交易 $T$,我们初始至少需要cost(T) - sum(负收益之和)
的金钱。最终答案就是该式的最大值。复杂度 $\\\\mathcal{O}(n)$。有的朋友可能会担心,如果在进行交易 $T$ 之前,钱就不够了怎么办?其实无需担心,假设在进行交易 $T$ 之前,进行交易 $T\'$ 的时候钱就不够了,那交易 $T\'$ 导致我们失败的最差情况也会在我们枚举到交易 $T\'$ 的时候计算并更新答案。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:枚举 & 贪心 我们来思考一下,整个交易过程中,哪个环节会导致交易失败?只可能是我们需要 cost 的代价,但当前金钱不足 cost 才会失败。\\n\\n因此我们枚举哪个交易 $T$ 会让我们失败。称 cashback - cost < 0 的交易为“负收益”,最差情况下,进行交易 $T$ 之前,我们会首先完成其它所有负收益交易,这样才会让我们的当前金钱变得最少。因此为了完成交易 $T$,我们初始至少需要 cost(T) - sum(负收益之和) 的金钱。最终答案就是该式的最大值。复杂度 $\\\\mathcal{O}(n)$。\\n\\n有的朋友可能会担心…","guid":"https://leetcode.cn/problems/minimum-money-required-before-transactions//solution/by-tsreaper-esbt","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-17T16:07:12.908Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 做法:单调队列(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/by-endlesscheng-7ukp","content":"class Solution {\\npublic:\\n long long minimumMoney(vector<vector<int>>& transactions) {\\n long long ans = 0, neg = 0;\\n // 计算所有负收益之和\\n for (auto &vec : transactions) {\\n long long det = vec[1] - vec[0];\\n if (det < 0) neg += det;\\n }\\n // 枚举哪个交易会失败\\n for (auto &vec : transactions) {\\n long long det = vec[1] - vec[0];\\n // 这是一个负收益交易,计算答案之前要先把它和其它负收益交易分开\\n if (det < 0) ans = max(ans, vec[0] - (neg - det));\\n // 这是一个正收益交易,计算答案不需要特殊操作\\n else ans = max(ans, vec[0] - neg);\\n }\\n return ans;\\n }\\n};\\n
前置题目:239. 滑动窗口最大值,视频讲解请看 单调队列【基础算法精讲 27】。
\\n题目要求机器人连续运行,看成一个连续子数组,题目要求计算最长子数组长度。
\\n枚举子数组右端点 $\\\\textit{right}$,我们需要知道此时左端点 $\\\\textit{left}$ 的最小值,这样子数组尽量长。
\\n由于有 $\\\\textit{budget}$ 的限制,所以 $\\\\textit{right}$ 越大,$\\\\textit{left}$ 也越大,有单调性,可以用滑动窗口解决。
\\n本题的一种做法是二分答案,这样就转换成了固定长度的 239 题。
\\n但实际上不用二分,在 239 题的基础上,把定长滑窗改为不定长滑窗,套路如下:
\\n\\n
\\n- 入:$\\\\textit{chargeTimes}[\\\\textit{right}]$ 进入窗口时,弹出队尾的 $\\\\le \\\\textit{chargeTimes}[\\\\textit{right}]$ 的元素。
\\n- 出:如果总开销超过 $\\\\textit{budget}$,则不断移出左端点,直到总开销不超过 $\\\\textit{budget}$。特别地,如果左端点恰好等于队首,则弹出队首。
\\n- 更新答案:用窗口长度 $\\\\textit{right}-\\\\textit{left}+1$ 更新答案的最大值。
\\n⚠注意:为了方便判断队首是否要出队,单调队列中保存的是下标。
\\n\\nclass Solution:\\n def maximumRobots(self, chargeTimes: List[int], runningCosts: List[int], budget: int) -> int:\\n ans = s = left = 0\\n q = deque()\\n for right, (t, c) in enumerate(zip(chargeTimes, runningCosts)):\\n # 1. 入\\n while q and t >= chargeTimes[q[-1]]:\\n q.pop()\\n q.append(right)\\n s += c # 维护 sum(runningCosts)\\n\\n # 2. 出\\n while q and chargeTimes[q[0]] + (right - left + 1) * s > budget:\\n if q[0] == left:\\n q.popleft()\\n s -= runningCosts[left] # 维护 sum(runningCosts)\\n left += 1\\n\\n # 3. 更新答案\\n ans = max(ans, right - left + 1)\\n return ans\\n
\\nclass Solution {\\n public int maximumRobots(int[] chargeTimes, int[] runningCosts, long budget) {\\n int ans = 0;\\n int left = 0;\\n long sum = 0;\\n Deque<Integer> q = new ArrayDeque<>();\\n for (int right = 0; right < chargeTimes.length; right++) {\\n // 1. 入\\n while (!q.isEmpty() && chargeTimes[right] >= chargeTimes[q.peekLast()]) {\\n q.pollLast();\\n }\\n q.addLast(right);\\n sum += runningCosts[right];\\n\\n // 2. 出\\n while (!q.isEmpty() && chargeTimes[q.peekFirst()] + (right - left + 1) * sum > budget) {\\n if (q.peekFirst() == left) {\\n q.pollFirst();\\n }\\n sum -= runningCosts[left++];\\n }\\n\\n // 3. 更新答案\\n ans = Math.max(ans, right - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n public int maximumRobots(int[] chargeTimes, int[] runningCosts, long budget) {\\n int ans = 0;\\n int left = 0;\\n long sum = 0;\\n int[] q = new int[chargeTimes.length];\\n int head = 0; // 队头\\n int tail = 0; // 队尾+1\\n for (int right = 0; right < chargeTimes.length; right++) {\\n // 1. 入\\n while (head < tail && chargeTimes[right] >= chargeTimes[q[tail - 1]]) {\\n tail--;\\n }\\n q[tail++] = right;\\n sum += runningCosts[right];\\n\\n // 2. 出\\n while (head < tail && chargeTimes[q[head]] + (right - left + 1) * sum > budget) {\\n if (q[head] == left) {\\n head++;\\n }\\n sum -= runningCosts[left++];\\n }\\n\\n // 3. 更新答案\\n ans = Math.max(ans, right - left + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) {\\n int ans = 0, left = 0;\\n long long sum = 0;\\n deque<int> q;\\n for (int right = 0; right < chargeTimes.size(); right++) {\\n // 1. 入\\n while (!q.empty() && chargeTimes[right] >= chargeTimes[q.back()]) {\\n q.pop_back();\\n }\\n q.push_back(right);\\n sum += runningCosts[right];\\n\\n // 2. 出\\n while (!q.empty() && chargeTimes[q.front()] + (right - left + 1) * sum > budget) {\\n if (q.front() == left) {\\n q.pop_front();\\n }\\n sum -= runningCosts[left++];\\n }\\n\\n // 3. 更新答案\\n ans = max(ans, right - left + 1);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint maximumRobots(int* chargeTimes, int chargeTimesSize, int* runningCosts, int runningCostsSize, long long budget) {\\n int ans = 0, left = 0;\\n long long sum = 0;\\n int* q = malloc(chargeTimesSize * sizeof(int));\\n int head = 0, tail = 0; // 队头和队尾\\n for (int right = 0; right < chargeTimesSize; right++) {\\n // 1. 入\\n while (head < tail && chargeTimes[right] >= chargeTimes[q[tail - 1]]) {\\n tail--;\\n }\\n q[tail++] = right;\\n sum += runningCosts[right];\\n\\n // 2. 出\\n while (head < tail && chargeTimes[q[head]] + (right - left + 1) * sum > budget) {\\n if (q[head] == left) {\\n head++;\\n }\\n sum -= runningCosts[left++];\\n }\\n\\n // 3. 更新答案\\n ans = MAX(ans, right - left + 1);\\n }\\n free(q);\\n return ans;\\n}\\n
\\nfunc maximumRobots(chargeTimes, runningCosts []int, budget int64) (ans int) {\\n q := []int{}\\n sum := int64(0)\\n left := 0\\n for right, t := range chargeTimes {\\n // 1. 入\\n for len(q) > 0 && t >= chargeTimes[q[len(q)-1]] {\\n q = q[:len(q)-1]\\n }\\n q = append(q, right)\\n sum += int64(runningCosts[right])\\n\\n // 2. 出\\n for len(q) > 0 && int64(chargeTimes[q[0]])+int64(right-left+1)*sum > budget {\\n if q[0] == left {\\n q = q[1:]\\n }\\n sum -= int64(runningCosts[left])\\n left++\\n }\\n\\n // 3. 更新答案\\n ans = max(ans, right-left+1)\\n }\\n return\\n}\\n
\\nvar maximumRobots = function(chargeTimes, runningCosts, budget) {\\n let ans = 0, left = 0, sum = 0;\\n const q = Array(chargeTimes.length);\\n let head = 0, tail = 0; // 队头和队尾\\n for (let right = 0; right < chargeTimes.length; right++) {\\n // 1. 入\\n while (head < tail && chargeTimes[right] >= chargeTimes[q[tail - 1]]) {\\n tail--;\\n }\\n q[tail++] = right;\\n sum += runningCosts[right];\\n\\n // 2. 出\\n while (head < tail && chargeTimes[q[head]] + (right - left + 1) * sum > budget) {\\n if (q[head] === left) {\\n head++;\\n }\\n sum -= runningCosts[left++];\\n }\\n\\n // 3. 更新答案\\n ans = Math.max(ans, right - left + 1);\\n }\\n return ans;\\n};\\n
\\nuse std::collections::VecDeque;\\n\\nimpl Solution {\\n pub fn maximum_robots(charge_times: Vec<i32>, running_costs: Vec<i32>, budget: i64) -> i32 {\\n let mut ans = 0;\\n let mut left = 0;\\n let mut sum = 0i64;\\n let mut q = VecDeque::new();\\n for right in 0..charge_times.len() {\\n // 1. 入\\n while !q.is_empty() && charge_times[right] >= charge_times[*q.back().unwrap()] {\\n q.pop_back();\\n }\\n q.push_back(right);\\n sum += running_costs[right] as i64;\\n\\n // 2. 出\\n while !q.is_empty() && charge_times[*q.front().unwrap()] as i64 + (right - left + 1) as i64 * sum > budget {\\n if *q.front().unwrap() == left {\\n q.pop_front();\\n }\\n sum -= running_costs[left] as i64;\\n left += 1;\\n }\\n\\n // 3. 更新答案\\n ans = ans.max(right - left + 1);\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{chargeTimes}$ 的长度。虽然有二重循环,但是每个元素至多出队一次,以及 $\\\\textit{left}$ 最多增加 $n$ 次。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n思考题
\\n把「子数组」改成「子序列」要怎么做?
\\n欢迎在评论区发表的你的思路/代码。
\\n更多相似题目,见下面数据结构题单中的「§4.3 单调队列」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:239. 滑动窗口最大值,视频讲解请看 单调队列【基础算法精讲 27】。 题目要求机器人连续运行,看成一个连续子数组,题目要求计算最长子数组长度。\\n\\n枚举子数组右端点 $\\\\textit{right}$,我们需要知道此时左端点 $\\\\textit{left}$ 的最小值,这样子数组尽量长。\\n\\n由于有 $\\\\textit{budget}$ 的限制,所以 $\\\\textit{right}$ 越大,$\\\\textit{left}$ 也越大,有单调性,可以用滑动窗口解决。\\n\\n本题的一种做法是二分答案,这样就转换成了固定长度的 239 题。\\n\\n但实际上不用二分,在 239…","guid":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/by-endlesscheng-7ukp","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-04T00:04:45.206Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"typescript st表","url":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/typescript-by-981377660lmt-l205","content":"解题思路
\\n静态查询区间最值使用 $st表$,$O(nlogn)$ 预处理,$O(1)$ 查询
\\n代码
\\n###typescript
\\n\\n","description":"解题思路 静态查询区间最值使用 $st表$,$O(nlogn)$ 预处理,$O(1)$ 查询\\n\\n代码\\n\\n###typescript\\n\\nfunction maximumRobots(chargeTimes: number[], runningCosts: number[], budget: number): number {\\n const n = chargeTimes.length\\n const st = new SparseTable(chargeTimes, Math.max)\\n const preSum = Array(n + 1).fill(0)…","guid":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/typescript-by-981377660lmt-l205","author":"981377660LMT","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-03T16:20:01.469Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"two pointers & 单调队列","url":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/by-tsreaper-g9xu","content":"function maximumRobots(chargeTimes: number[], runningCosts: number[], budget: number): number {\\n const n = chargeTimes.length\\n const st = new SparseTable(chargeTimes, Math.max)\\n const preSum = Array(n + 1).fill(0)\\n for (let i = 1; i <= n; i++) preSum[i] = preSum[i - 1] + runningCosts[i - 1]\\n\\n let left = 1\\n let right = n\\n while (left <= right) {\\n const mid = Math.floor((left + right) / 2)\\n if (check(mid)) left = mid + 1\\n else right = mid - 1\\n }\\n\\n return right\\n\\n function check(mid: number): boolean {\\n for (let left = 0; left + mid - 1 < n; left++) {\\n const right = left + mid - 1\\n const max = st.query(left, right)\\n const sum = preSum[right + 1] - preSum[left]\\n if (max + mid * sum <= budget) return true\\n }\\n return false\\n }\\n}\\n\\n\\ntype MergeFunc = (a: number, b: number) => number\\n\\n/**\\n * @summary st表适用于可重复贡献问题/RMQ静态区间最值查询\\n * @description 可重复贡献问题是指对于运算`opt`,满足`x opt x = x`,则对应的区间询问就是一个可重复贡献问题。\\n *\\n * 例如,最大值有 `max(a,a) = a`,gcd有 `gcd(a,a) = a` ,所以RMQ和区间GCD就是一个可重复贡献问题。\\n * 像区间和就不具有这个性质,如果求区间和的时候采用的预处理区间重叠了,则会导致重曼部分被计算两次,这是我们所不愿意看到的。\\n * 另外,opt还必须满足结合律才能使用ST表求解。\\n *\\n * @see {@link https://oi-wiki.org/ds/sparse-table/}\\n */\\nclass SparseTable {\\n private readonly _size: number\\n private readonly _mergeFunc: MergeFunc\\n private readonly _dp: number[][]\\n\\n constructor(nums: number[], mergeFunc: MergeFunc) {\\n const n = nums.length\\n const upper = Math.ceil(Math.log2(n)) + 1\\n\\n this._size = n\\n this._mergeFunc = mergeFunc\\n this._dp = Array.from({ length: n }, () => Array(upper).fill(0))\\n for (let i = 0; i < n; i++) this._dp[i][0] = nums[i]\\n\\n for (let j = 1; j < upper; j++) {\\n for (let i = 0; i < n; i++) {\\n if (i + (1 << (j - 1)) >= n) break\\n this._dp[i][j] = this._mergeFunc(this._dp[i][j - 1], this._dp[i + (1 << (j - 1))][j - 1])\\n }\\n }\\n }\\n\\n /**\\n * @returns [`left`,`right`] 闭区间的贡献值\\n */\\n query(left: number, right: number): number {\\n // this.checkRange(left, right)\\n const k = Math.floor(Math.log2(right - left + 1))\\n return this._mergeFunc(this._dp[left][k], this._dp[right - (1 << k) + 1][k])\\n }\\n\\n private checkRange(left: number, right: number): void {\\n if (left >= 0 && left <= right && right < this._size) return\\n throw new RangeError(`invalid range [${left}, ${right}]`)\\n }\\n}\\n
解法:two pointers & 单调队列
\\n如果一个区间的机器人费用小于
\\nbudget
,那么子区间的费用肯定小于budget
(因为最大值不会变大,而k
以及sum(runningCosts)
都在变小),因此可以使用 two pointers 解决问题。用单调队列维护max(chargeTimes)
即可,复杂度 $\\\\mathcal{O}(n)$。详细实现见参考代码。参考代码(c++)
\\n###c++
\\n\\n","description":"解法:two pointers & 单调队列 如果一个区间的机器人费用小于 budget,那么子区间的费用肯定小于 budget(因为最大值不会变大,而 k 以及 sum(runningCosts) 都在变小),因此可以使用 two pointers 解决问题。用单调队列维护 max(chargeTimes) 即可,复杂度 $\\\\mathcal{O}(n)$。详细实现见参考代码。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\n typedef pairclass Solution {\\n typedef pair<int, int> pii;\\n \\npublic:\\n int maximumRobots(vector<int>& chargeTimes, vector<int>& runningCosts, long long budget) {\\n int n = chargeTimes.size();\\n int ans = 0;\\n deque<pii> q;\\n long long sm = 0;\\n // i 是 two pointers 区间的末尾,j 是 two pointers 区间的开头,区间包含 i 和 j\\n for (int i = 0, j = 0; i < n; i++) {\\n if (q.empty()) j = i;\\n // 维护一个递减的单调队列,单调队列的头即为区间内的最大值\\n while (!q.empty() && q.back().first <= chargeTimes[i]) q.pop_back();\\n q.push_back(pii(chargeTimes[i], i));\\n sm += runningCosts[i];\\n // 增加 j 直到费用符合要求\\n while (!q.empty() && q.front().first + (i - j + 1) * sm > budget) {\\n if (j == q.front().second) q.pop_front();\\n sm -= runningCosts[j];\\n j++;\\n }\\n if (!q.empty()) ans = max(ans, i - j + 1);\\n }\\n return ans;\\n }\\n};\\n
pii;\\n \\npublic:\\n int…","guid":"https://leetcode.cn/problems/maximum-number-of-robots-within-budget//solution/by-tsreaper-g9xu","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-03T16:11:54.774Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2390. 从字符串中移除星号","url":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/by-serene-i3oseese-6mcd","content":" 解题思路
\\n\\n
\\n模拟实现代码
\\n###cpp
\\n\\n","description":"解题思路 模拟实现\\n\\n代码\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n string removeStars(string s) {\\n string ret;\\n for(auto c:s)\\n {\\n if( c != \'*\' )\\n ret+=c;\\n else\\n ret.pop_back();\\n }\\n return ret;\\n }\\n};","guid":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/by-serene-i3oseese-6mcd","author":"serene-i3oseese","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-09-01T05:51:31.452Z","media":[{"url":"https://pic.leetcode-cn.com/1662011489-KihKjl-image.png","type":"photo","width":1362,"height":820}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 用栈维护(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/zhan-de-jing-dian-ying-yong-by-endlessch-ljym","content":"class Solution {\\npublic:\\n string removeStars(string s) {\\n string ret;\\n for(auto c:s)\\n {\\n if( c != \'*\' )\\n ret+=c;\\n else\\n ret.pop_back();\\n }\\n return ret;\\n }\\n};\\n
用栈维护,遇到星号
\\n*
则弹出栈顶,否则把字符入栈。最后从栈底到栈顶就是答案。\\n\\n注:题目保证生成的输入总是可以执行题面中描述的操作。
\\n如果你没有想到栈,推荐做做 数据结构题单 中的 3.1 节和 3.3 节。
\\n\\n\\n注:本题类似前段时间的每日一题 3174. 清除数字。
\\n所以为什么两题难度不一样\\nclass Solution:\\n def removeStars(self, s: str) -> str:\\n st = []\\n for c in s:\\n if c == \'*\':\\n st.pop()\\n else:\\n st.append(c)\\n return \'\'.join(st)\\n
\\nclass Solution {\\n public String removeStars(String s) {\\n StringBuilder st = new StringBuilder();\\n for (char c : s.toCharArray()) {\\n if (c == \'*\') {\\n st.deleteCharAt(st.length() - 1);\\n } else {\\n st.append(c);\\n }\\n }\\n return st.toString();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string removeStars(string s) {\\n string st;\\n for (char c : s) {\\n if (c == \'*\') {\\n st.pop_back();\\n } else {\\n st += c;\\n }\\n }\\n return st;\\n }\\n};\\n
\\nchar* removeStars(char* s) {\\n int top = 0; // 栈顶\\n for (int i = 0; s[i]; i++) {\\n if (s[i] == \'*\') {\\n top--; // 出栈\\n } else {\\n s[top++] = s[i]; // 入栈(把 s 当作栈)\\n }\\n }\\n s[top] = \'\\\\0\';\\n return s;\\n}\\n
\\nfunc removeStars(s string) string {\\nst := []rune{}\\nfor _, c := range s {\\nif c == \'*\' {\\nst = st[:len(st)-1]\\n} else {\\nst = append(st, c)\\n}\\n}\\nreturn string(st)\\n}\\n
\\nvar removeStars = function(s) {\\n const st = [];\\n for (const c of s) {\\n if (c === \'*\') {\\n st.pop();\\n } else {\\n st.push(c);\\n }\\n }\\n return st.join(\'\');\\n};\\n
\\nimpl Solution {\\n pub fn remove_stars(s: String) -> String {\\n let mut st = vec![];\\n for c in s.bytes() {\\n if c == b\'*\' {\\n st.pop();\\n } else {\\n st.push(c);\\n }\\n }\\n unsafe { String::from_utf8_unchecked(st) }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$ 或 $\\\\mathcal{O}(1)$。如果把 $s$ 当作栈,则空间复杂度为 $\\\\mathcal{O}(1)$,见 C 语言。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用栈维护,遇到星号 * 则弹出栈顶,否则把字符入栈。最后从栈底到栈顶就是答案。 注:题目保证生成的输入总是可以执行题面中描述的操作。\\n\\n如果你没有想到栈,推荐做做 数据结构题单 中的 3.1 节和 3.3 节。\\n\\n注:本题类似前段时间的每日一题 3174. 清除数字。所以为什么两题难度不一样\\n\\nclass Solution:\\n def removeStars(self, s: str) -> str:\\n st = []\\n for c in s:\\n if c == \'*\':…","guid":"https://leetcode.cn/problems/removing-stars-from-a-string//solution/zhan-de-jing-dian-ying-yong-by-endlessch-ljym","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-28T04:08:33.803Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【爪哇缪斯】图解LeetCode","url":"https://leetcode.cn/problems/transform-to-chessboard//solution/zhua-wa-mou-si-by-muse-77-pit5","content":"解题思路
\\n首先,根据题意,我们要计算出最小的可以变为“棋盘”的步数。那么,这道题的难度,其实就是如下两点:
\\n\\n\\n难点1:如何判断出某个矩阵是否可以变为棋盘?
\\n
\\n难点2:如何计算出变为棋盘的步数,并获得最小的步数作为方法的返回。那么针对如上的难点,我们也一一的对其进行攻破。那么,首先,对于如何判断某个矩阵是否可以变为棋盘的问题,其实换句话说,就是,某个矩阵是否是本题约束下的“合法”矩阵。那么,既然要变化为棋盘,我们何不先将标准的棋盘结构分析一下,看看它们具有哪些特性。
\\n1> 难点1:矩阵是否合法(判断条件一)
\\n首先,针对于棋盘布局,其实也是分为两方面,分别为长度布局和数字布局:
\\n\\n\\n长度布局:分为偶数(格子)长度和奇数(格子)长度。
\\n
\\n数字布局:以0开始进行数字布局,还是以1作为数字布局。那么,由于棋盘分为了长度布局和数字布局,那么就有如下四种情况的棋盘:
\\n\\n
我们对其进行分析,发现对于红色标注的这四个“角”的格子,要么是四个0,要么是四个1,要么就是两个0和两个1。大家也可以通过移动上面的棋盘,会发现,无论如何移动,都会满足上述三种情况之一。那么,既然棋盘具有这种规律,我们在解题时,就可以首先通过判断上面的过滤,去过滤一批不合法的矩阵。
\\n那么,我们怎么判断某一个矩阵是否满足上述三种条件呢?一种方法是,可以通过获取四个节点之后,通过
\\nif...else if
这种分支判断的方式,进行3种情况的判断。除了这种方式之外,其实,还有一种方式,就是通过按位异或来进行判断。因为按位异或的特点之一就是类似“翻牌”机制,如果两个数相同,则返回0,如果两个数不同,则返回1。那么,我们上面说的三种情况,0和1出现的次数只会是偶数次,那么,最终异或的结果也肯定为0。所以,我们反向判断一下,如果返回结果等于1,则说明是“非法”的矩阵,直接返回-1即可。\\n
2> 难点1:矩阵是否合法(判断条件二)
\\n那么,由于棋盘中的每一行和列都是0与1互相穿插排序的,并且,虽然我们可以移动矩阵,但是我们改变的只是行或者列中元素的顺序,并无法改变它们的数量。那么,我们依然可以通过0和1出现的次数得出以下结论:
\\n\\n
所以,通过上图我们可以发现,如果矩阵的长度为n,那么:
\\n\\n\\n偶数行/列,1或0出现的次数就是:
\\nn/2
。
\\n奇数行/列,1或0出现的次数就是:n/2
或(n+1)/2
。如果某个矩阵不满足上述条件的话,那么则说明是非法矩阵,直接返回-1即可。
\\n3.3> 难点2:如何计算出变为棋盘的步数
\\n关于如何移动成为一个棋盘,因为我们是移动某一行或者某一列,那么只要这个矩阵满足了可以成为棋盘的条件之后,我们其实只需要关注第一行和第一列的移动情况即可。也就是说,第一行和第一列已经满足了棋盘的条件,其他行和列,必然也会满足棋盘的条件。
\\n\\n
那么怎么移动矩阵称为棋盘,并且如何判断移动的步数呢?这里面,我们其实采用了“位差”的概念,也就是说,我们将矩阵的一行或者一列,去跟标准棋盘的一行或者一列进行对比(无论是以1开头还是以0开头,这个无所谓),他们之间出现的差值,其实就是我们应该移动的方格,而因为我们移动的时候,是任意的两行或者两列进行移动,那么每次移动,无论是针对行还是针对列,其实都是两个格子的变化,也就是说,
\\n(行的位差 + 列的位差)/2
就是我们要移动的步数了。我们还是以下图为例,用图示的方式进行说明:\\n
那么,在上面的图中,我们发现, 偶数行/列,会有偶数次格子的移动情况发生;如果是奇数行/列,会有偶数格子或奇数格子移动的情况发生。对于
\\n偶数位差
,这个我们可以通过移动有位差的格子或者无位差的格子,这个都可以的。比如:\\n
对于
\\n奇数位差
,当我们计算出位差是奇数的时候,因为每次移动的都是偶数格子,所以,我们移动(n - 位差数)
,如果是偶数位差,则跟上图一样。下图我们展示一下,当奇数位差时(n - 位差数)的示例:\\n
四、代码实现
\\n由于本题我没有做出来,确实难度超出了我的能力范围了。我是参照Kvicii大佬的解题思路做的图解分析,文章的目的并不是显示我自己算法能力有多强,而是,希望能给一些同样对这道题没有思路的同学,一个更便于理解和学习的引路小短文。这里我就不班门弄斧了。将大佬的实现代码原样展示。
\\n###java
\\n\\nclass Solution {\\n public int movesToChessboard(int[][] board) {\\n int n = board.length, rowCnt = 0, colCnt = 0, rowSwap = 0, colSwap = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n if ((board[0][0] ^ board[i][0] ^ board[0][j] ^ board[i][j]) == 1) {\\n return -1;\\n }\\n }\\n }\\n for (int i = 0; i < n; i++) {\\n rowCnt += board[0][i];\\n colCnt += board[i][0];\\n if (board[i][0] == i % 2) {\\n rowSwap++;\\n }\\n if (board[0][i] == i % 2) {\\n colSwap++;\\n }\\n }\\n if (rowCnt != n / 2 && rowCnt != (n + 1) / 2) {\\n return -1;\\n }\\n if (colCnt != n / 2 && colCnt != (n + 1) / 2) {\\n return -1;\\n }\\n if (n % 2 == 1) {\\n if (rowSwap % 2 == 1) {\\n rowSwap = n - rowSwap;\\n }\\n if (colSwap % 2 == 1) {\\n colSwap = n - colSwap;\\n }\\n } else {\\n rowSwap = Math.min(rowSwap, n - rowSwap);\\n colSwap = Math.min(colSwap, n - colSwap);\\n }\\n return (rowSwap + colSwap) / 2;\\n }\\n}\\n
\\n
今天的文章内容就这些了:
\\n\\n\\n写作不易,笔者几个小时甚至数天完成的一篇文章,只愿换来您几秒钟的 点赞 & 分享 。
\\n更多技术干货,欢迎大家关注公众号“爪哇缪斯” ~ \\\\(^o^)/ ~ 「干货分享,每天更新」
\\n","description":"解题思路 首先,根据题意,我们要计算出最小的可以变为“棋盘”的步数。那么,这道题的难度,其实就是如下两点:\\n\\n难点1:如何判断出某个矩阵是否可以变为棋盘?\\n 难点2:如何计算出变为棋盘的步数,并获得最小的步数作为方法的返回。\\n\\n那么针对如上的难点,我们也一一的对其进行攻破。那么,首先,对于如何判断某个矩阵是否可以变为棋盘的问题,其实换句话说,就是,某个矩阵是否是本题约束下的“合法”矩阵。那么,既然要变化为棋盘,我们何不先将标准的棋盘结构分析一下,看看它们具有哪些特性。\\n\\n1> 难点1:矩阵是否合法(判断条件一)\\n\\n首先,针对于棋盘布局,其实也是分为两方面…","guid":"https://leetcode.cn/problems/transform-to-chessboard//solution/zhua-wa-mou-si-by-muse-77-pit5","author":"muse-77","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-23T06:42:01.713Z","media":[{"url":"https://pic.leetcode-cn.com/1661236975-jyygnD-1.png","type":"photo","width":1572,"height":482,"blurhash":"LaP%FStS9r-p-pxuoHoH?Ko#oOV["},{"url":"https://pic.leetcode-cn.com/1661236992-CtXpsS-1.png","type":"photo","width":2096,"height":953,"blurhash":"LAR3G{yCXm^%00whxcSNDaRobvot"},{"url":"https://pic.leetcode-cn.com/1661237005-NuJvcj-1.png","type":"photo","width":1028,"height":490,"blurhash":"LWQJDo?w%hu3%2W.s;V@MykCoek7"},{"url":"https://pic.leetcode-cn.com/1661237023-qaFQOq-1.png","type":"photo","width":494,"height":432,"blurhash":"LPRMe*-=-@_3tTa*jqs}-rRjWSWV"},{"url":"https://pic.leetcode-cn.com/1661237035-GgOGvR-1.png","type":"photo","width":1210,"height":1064,"blurhash":"LIRMVg?]H?R{.8VsozRjRQbbV@WB"},{"url":"https://pic.leetcode-cn.com/1661237049-dtbcGz-1.png","type":"photo","width":1758,"height":1078,"blurhash":"LGSFz{-:S^_3_NkCaKVs?c-?n-RP"},{"url":"https://pic.leetcode-cn.com/1661237063-AyvkVr-1.png","type":"photo","width":1600,"height":490,"blurhash":"LWQl]}tjyZr_%~R4Q.kq,@x^kpnh"},{"url":"https://pic.leetcode-cn.com/1661237078-EtACCC-1.png","type":"photo","width":2096,"height":588,"blurhash":"LGRyy$%Moc-;~UWBa#of-ht7azWA"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】构造分析题","url":"https://leetcode.cn/problems/transform-to-chessboard//solution/by-ac_oier-vf1m","content":"构造分析
\\n数据范围具有一定的迷惑性,但其并不是一个棋盘搜索问题。
\\n我们需要考虑何种情况下无解,以及有解情况的最小步数。
\\n在给定棋盘大小 $n$ 的前提下,所能构造的合法棋盘只有两种情况:首个格子为 $0$ 或首个格子为 $1$,即问题转化为能否构造出合法棋盘,以及构造哪种合法棋盘所用步数更小。
\\n同时,交换行和交换列均不会影响行的种类数量和列的种类数量,因此我们可以得到第一个判断无解的条件:若起始棋盘的行/列种类数不为 $2$,必然无法构造出合法棋盘。
\\n假设起始的行分别为
\\nr1
和r2
,起始的列分别为c1
和c2
。不难发现第二性质:若能构成合法棋盘,r1
和r2
中 $0$ 和 $1$ 的数量必然相等,c1
和c2
中的 $0$ 和 $1$ 的数量必然相等。同时由于交换行和交换列具有对称性和独立性,我们可以先使用「交换列」来进行分析,交换列不会导致行种类发生变化,但会导致行的数值分布发生变化。
\\n因此第二性质可拓展为:
\\nr1
和r2
对称位置为必然不同,c1
和c2
对称位置必然不同,即两者异或结果为必然为 $(111...111)_2$,即为mask = (1 << n) - 1
,否则必然无解。若上述两性质满足,可能有解。
\\n由于
\\nr1
和r2
及c1
和c2
对称位置必然不同,因此我们调整好r1
后,r2
唯一确定(c1
和c2
同理),同时构造其中一种间隔行为t
= $(...101)_2$,根据合法棋盘定义可知要么是将首行调整为 $t$,要么是将次行调整为 $t$。我们设置函数
\\nint getCnt(int a, int b)
计算将a
变为b
所需要的最小转换次数,两状态转换所需次数为不同位个数除以 $2$(一次交换可实现消除两个不同位)。分别计算「将
\\nr1
和r2
转换为t
所需步数」和「将c1
和c2
转换为t
所需步数」,两者之和即为答案。代码:
\\n###Java
\\n\\nclass Solution {\\n int n = 0, INF = 0x3f3f3f3f;\\n int getCnt(int a, int b) {\\n return Integer.bitCount(a) != Integer.bitCount(b) ? INF : Integer.bitCount(a ^ b) / 2;\\n }\\n public int movesToChessboard(int[][] g) {\\n n = g.length;\\n int r1 = -1, r2 = -1, c1 = -1, c2 = -1, mask = (1 << n) - 1;\\n for (int i = 0; i < n; i++) {\\n int a = 0, b = 0;\\n for (int j = 0; j < n; j++) {\\n if (g[i][j] == 1) a += (1 << j);\\n if (g[j][i] == 1) b += (1 << j);\\n }\\n if (r1 == -1) r1 = a;\\n else if (r2 == -1 && a != r1) r2 = a;\\n if (c1 == -1) c1 = b;\\n else if (c2 == -1 && b != c1) c2 = b;\\n if (a != r1 && a != r2) return -1;\\n if (b != c1 && b != c2) return -1;\\n }\\n if (r2 == -1 || c2 == -1) return -1;\\n if ((r1 ^ r2) != mask || (c1 ^ c2) != mask) return -1;\\n int t = 0;\\n for (int i = 0; i < n; i += 2) t += (1 << i);\\n int ans = Math.min(getCnt(r1, t), getCnt(r2, t)) + Math.min(getCnt(c1, t), getCnt(c2, t));\\n return ans >= INF ? -1 : ans;\\n }\\n}\\n
###TypeScript
\\n\\nlet n: number = 0, INF = 0x3f3f3f3f\\nfunction bitCount(x: number): number {\\n let ans = 0\\n while (x != 0 && ++ans >= 0) x -= (x & -x)\\n return ans\\n}\\nfunction getCnt(a: number, b: number): number {\\n return bitCount(a) != bitCount(b) ? INF : bitCount(a ^ b) / 2\\n}\\nfunction movesToChessboard(g: number[][]): number {\\n n = g.length\\n let r1 = -1, r2 = -1, c1 = -1, c2 = -1, mask = (1 << n) - 1\\n for (let i = 0; i < n; i++) {\\n let a = 0, b = 0\\n for (let j = 0; j < n; j++) {\\n if (g[i][j] == 1) a += (1 << j)\\n if (g[j][i] == 1) b += (1 << j)\\n }\\n if (r1 == -1) r1 = a\\n else if (r2 == -1 && a != r1) r2 = a\\n if (c1 == -1) c1 = b\\n else if (c2 == -1 && b != c1) c2 = b\\n if (a != r1 && a != r2) return -1\\n if (b != c1 && b != c2) return -1\\n }\\n if (r2 == -1 || c2 == -1) return -1\\n if ((r1 ^ r2) != mask || (c1 ^ c2) != mask) return -1\\n let t = 0\\n for (let i = 0; i < n; i += 2) t += (1 << i)\\n const ans = Math.min(getCnt(r1, t), getCnt(r2, t)) + Math.min(getCnt(c1, t), getCnt(c2, t))\\n return ans >= INF ? -1 : ans\\n};\\n
\\n
\\n- 时间复杂度:$O(n^2)$
\\n- 空间复杂度:$O(1)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"构造分析 数据范围具有一定的迷惑性,但其并不是一个棋盘搜索问题。\\n\\n我们需要考虑何种情况下无解,以及有解情况的最小步数。\\n\\n在给定棋盘大小 $n$ 的前提下,所能构造的合法棋盘只有两种情况:首个格子为 $0$ 或首个格子为 $1$,即问题转化为能否构造出合法棋盘,以及构造哪种合法棋盘所用步数更小。\\n\\n同时,交换行和交换列均不会影响行的种类数量和列的种类数量,因此我们可以得到第一个判断无解的条件:若起始棋盘的行/列种类数不为 $2$,必然无法构造出合法棋盘。\\n\\n假设起始的行分别为 r1 和 r2,起始的列分别为 c1 和 c2。不难发现第二性质:若能构成合法棋盘,…","guid":"https://leetcode.cn/problems/transform-to-chessboard//solution/by-ac_oier-vf1m","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-23T02:39:39.648Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【儿须成名酒须醉】Python3+并查集","url":"https://leetcode.cn/problems/lexicographically-smallest-equivalent-string//solution/er-xu-cheng-ming-jiu-xu-zui-python3bing-02n0b","content":"解题思路
\\n【儿须成名酒须醉】Python3+并查集
\\n代码
\\n\\n
\\n- 执行用时:44 ms, 在所有 Python3 提交中击败了 61.76% 的用户
\\n- 内存消耗:15 MB, 在所有 Python3 提交中击败了 92.65% 的用户
\\n- 通过测试用例:116 / 116
\\n###python3
\\n\\n","description":"解题思路 【儿须成名酒须醉】Python3+并查集\\n\\n代码\\n执行用时:44 ms, 在所有 Python3 提交中击败了 61.76% 的用户\\n内存消耗:15 MB, 在所有 Python3 提交中击败了 92.65% 的用户\\n通过测试用例:116 / 116\\n\\n###python3\\n\\nclass UnionFind:\\n def __init__(self, n):\\n self.root = [i for i in range(n)]\\n\\n def find(self, x):\\n if x != self.root[x…","guid":"https://leetcode.cn/problems/lexicographically-smallest-equivalent-string//solution/er-xu-cheng-ming-jiu-xu-zui-python3bing-02n0b","author":"liupengsay","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-19T15:08:54.659Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单模拟题","url":"https://leetcode.cn/problems/design-an-ordered-stream//solution/by-ac_oier-5pe8","content":"class UnionFind:\\n def __init__(self, n):\\n self.root = [i for i in range(n)]\\n\\n def find(self, x):\\n if x != self.root[x]:\\n # 在查询的时候合并到顺带直接根节点\\n root_x = self.find(self.root[x])\\n self.root[x] = root_x\\n return root_x\\n return x\\n\\n def union(self, x, y):\\n root_x = self.find(x)\\n root_y = self.find(y)\\n if root_x == root_y:\\n return\\n if root_x < root_y:\\n root_x, root_y = root_y, root_x\\n self.root[root_x] = root_y\\n return\\n\\n\\nclass Solution:\\n def smallestEquivalentString(self, s1: str, s2: str, baseStr: str) -> str:\\n n = len(s1)\\n uf = UnionFind(26)\\n for i in range(n):\\n uf.union(ord(s1[i]) - ord(\\"a\\"), ord(s2[i]) - ord(\\"a\\"))\\n ans = \\"\\"\\n for w in baseStr:\\n ans += chr(uf.find(ord(w) - ord(\\"a\\")) + ord(\\"a\\"))\\n return ans\\n
模拟
\\n根据题意进行模拟即可。
\\n代码:
\\n###Java
\\n\\nclass OrderedStream {\\n String[] ss = new String[1010];\\n int idx, n;\\n public OrderedStream(int _n) {\\n Arrays.fill(ss, \\"\\");\\n idx = 1; n = _n;\\n }\\n public List<String> insert(int key, String value) {\\n ss[key] = value;\\n List<String> ans = new ArrayList<>();\\n while (ss[idx].length() == 5) ans.add(ss[idx++]);\\n return ans;\\n }\\n}\\n
###TypeScript
\\n\\nclass OrderedStream {\\n ss: string[]\\n idx: number; n: number;\\n constructor(_n: number) {\\n this.idx = 1; this.n = _n;\\n this.ss = new Array<string>(1010).fill(\\"\\")\\n }\\n insert(key: number, value: string): string[] {\\n this.ss[key] = value\\n const ans = new Array<string>()\\n while (this.ss[this.idx].length == 5) ans.push(this.ss[this.idx++])\\n return ans\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n加餐
\\n加餐一道近期笔试题 : 近期面试原题(简单计算几何运用)🎉 🎉
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 根据题意进行模拟即可。\\n\\n代码:\\n\\n###Java\\n\\nclass OrderedStream {\\n String[] ss = new String[1010];\\n int idx, n;\\n public OrderedStream(int _n) {\\n Arrays.fill(ss, \\"\\");\\n idx = 1; n = _n;\\n }\\n public Listinsert(int key, String value) {\\n ss[key] = value;…","guid":"https://leetcode.cn/problems/design-an-ordered-stream//solution/by-ac_oier-5pe8","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-16T01:20:58.140Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python/Java/TypeScript/Go] 模拟","url":"https://leetcode.cn/problems/design-an-ordered-stream//solution/pythonjavatypescriptgo-by-himymben-ne0f","content":" 解题思路
\\n看不懂题但是按照题目的文字写
\\n换句话说是插入位置永远固定,按坐标,但是返回值要按照当时的ptr来判断,如果插入位置刚好在ptr,才移动ptr。
\\n代码
\\n###Python3
\\n\\nclass OrderedStream:\\n\\n def __init__(self, n: int):\\n self.data, self.ptr = [None] * n, 0\\n\\n def insert(self, idKey: int, value: str) -> List[str]:\\n self.data[idKey - 1] = value\\n ans = []\\n while self.ptr < len(self.data) and self.data[self.ptr]:\\n ans.append(self.data[self.ptr])\\n self.ptr += 1\\n return ans\\n\\n# Your OrderedStream object will be instantiated and called as such:\\n# obj = OrderedStream(n)\\n# param_1 = obj.insert(idKey,value)\\n
###Java
\\n\\nclass OrderedStream {\\n private String[] data;\\n private int ptr;\\n public OrderedStream(int n) {\\n data = new String[n];\\n ptr = 0;\\n }\\n \\n public List<String> insert(int idKey, String value) {\\n data[--idKey] = value;\\n List<String> ans = new ArrayList<>();\\n while (ptr < data.length && data[ptr] != null) {\\n ans.add(data[ptr++]);\\n }\\n return ans;\\n }\\n}\\n\\n/**\\n * Your OrderedStream object will be instantiated and called as such:\\n * OrderedStream obj = new OrderedStream(n);\\n * List<String> param_1 = obj.insert(idKey,value);\\n */\\n
###TypeScript
\\n\\nclass OrderedStream {\\n ptr: number\\n data: Array<string>\\n constructor(n: number) {\\n this.ptr = 0\\n this.data = new Array<string>(n)\\n }\\n\\n insert(idKey: number, value: string): string[] {\\n this.data[--idKey] = value\\n const ans: Array<string> = new Array<string>()\\n while (this.ptr < this.data.length && this.data[this.ptr] !== undefined) {\\n ans.push(this.data[this.ptr++])\\n }\\n return ans\\n }\\n}\\n\\n/**\\n * Your OrderedStream object will be instantiated and called as such:\\n * var obj = new OrderedStream(n)\\n * var param_1 = obj.insert(idKey,value)\\n */\\n
###Go
\\n\\n","description":"解题思路 看不懂题但是按照题目的文字写\\n\\n换句话说是插入位置永远固定,按坐标,但是返回值要按照当时的ptr来判断,如果插入位置刚好在ptr,才移动ptr。\\n\\n代码\\n\\n###Python3\\n\\nclass OrderedStream:\\n\\n def __init__(self, n: int):\\n self.data, self.ptr = [None] * n, 0\\n\\n def insert(self, idKey: int, value: str) -> List[str]:\\n self.data[idKey - 1…","guid":"https://leetcode.cn/problems/design-an-ordered-stream//solution/pythonjavatypescriptgo-by-himymben-ne0f","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-15T22:57:48.713Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"设计有序流","url":"https://leetcode.cn/problems/design-an-ordered-stream//solution/she-ji-you-xu-liu-by-leetcode-solution-3imb","content":"type OrderedStream struct {\\n Data []string\\n Ptr int\\n}\\n\\n\\nfunc Constructor(n int) OrderedStream {\\n return OrderedStream{make([]string, n), 0}\\n}\\n\\n\\nfunc (this *OrderedStream) Insert(idKey int, value string) (ans []string) {\\n idKey--\\n this.Data[idKey] = value\\n for this.Ptr < len(this.Data) && this.Data[this.Ptr] != \\"\\" {\\n ans = append(ans, this.Data[this.Ptr])\\n this.Ptr++\\n }\\n return\\n}\\n\\n\\n/**\\n * Your OrderedStream object will be instantiated and called as such:\\n * obj := Constructor(n);\\n * param_1 := obj.Insert(idKey,value);\\n */\\n
方法一:使用数组存储 + 遍历
\\n思路与算法
\\n对于 $\\\\text{OrderedStream(int n)}$,我们在初始化时开辟一个长度为 $n+1$ 的数组 $\\\\textit{stream}$,用来存储后续的字符串。注意到题目中指针 $\\\\textit{ptr}$ 的初始值为 $1$,而多数语言数组的下标是从 $0$ 开始的,因此使用长度为 $n+1$ 的数组可以使得编码更加容易。
\\n对于 $\\\\text{String[] insert(int id, String value)}$,我们直接根据题目描述中的要求进行遍历即可。我们首先将 $\\\\textit{stream}[\\\\textit{id}]$ 置为 $\\\\textit{value}$。随后,如果 $\\\\textit{stream}[\\\\textit{id}]$ 不为空,我们就将其加入答案,并将 $\\\\textit{ptr}$ 增加 $1$,直到指针超出边界或 $\\\\textit{stream}[\\\\textit{id}]$ 为空时结束并返回答案。
\\n代码
\\n###C++
\\n\\nclass OrderedStream {\\npublic:\\n OrderedStream(int n) {\\n stream.resize(n + 1);\\n ptr = 1;\\n }\\n \\n vector<string> insert(int idKey, string value) {\\n stream[idKey] = value;\\n vector<string> res;\\n while (ptr < stream.size() && !stream[ptr].empty()) {\\n res.push_back(stream[ptr]);\\n ++ptr;\\n }\\n return res;\\n }\\n\\nprivate:\\n vector<string> stream;\\n int ptr;\\n};\\n
###Java
\\n\\nclass OrderedStream {\\n private String[] stream;\\n private int ptr;\\n\\n public OrderedStream(int n) {\\n stream = new String[n + 1];\\n ptr = 1;\\n }\\n\\n public List<String> insert(int idKey, String value) {\\n stream[idKey] = value;\\n List<String> res = new ArrayList<String>();\\n while (ptr < stream.length && stream[ptr] != null) {\\n res.add(stream[ptr]);\\n ++ptr;\\n }\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class OrderedStream {\\n private string[] stream;\\n private int ptr;\\n\\n public OrderedStream(int n) {\\n stream = new string[n + 1];\\n ptr = 1;\\n }\\n\\n public IList<string> Insert(int idKey, string value) {\\n stream[idKey] = value;\\n IList<string> res = new List<string>();\\n while (ptr < stream.Length && stream[ptr] != null) {\\n res.Add(stream[ptr]);\\n ++ptr;\\n }\\n return res;\\n }\\n}\\n
###Python
\\n\\nclass OrderedStream:\\n\\n def __init__(self, n: int):\\n self.stream = [\\"\\"] * (n + 1)\\n self.ptr = 1\\n\\n def insert(self, idKey: int, value: str) -> List[str]:\\n stream_ = self.stream\\n\\n stream_[idKey] = value\\n res = list()\\n while self.ptr < len(stream_) and stream_[self.ptr]:\\n res.append(stream_[self.ptr])\\n self.ptr += 1\\n \\n return res\\n
###C
\\n\\ntypedef struct {\\n char **stream;\\n int streamSize;\\n int ptr;\\n} OrderedStream;\\n\\nOrderedStream* orderedStreamCreate(int n) {\\n OrderedStream *obj = (OrderedStream *)malloc(sizeof(OrderedStream));\\n obj->stream = (char **)malloc(sizeof(char *) * (n + 1));\\n for (int i = 0; i <= n; i++) {\\n obj->stream[i] = NULL;\\n }\\n obj->streamSize = n + 1;\\n obj->ptr = 1;\\n return obj;\\n}\\n\\nchar ** orderedStreamInsert(OrderedStream* obj, int idKey, char * value, int* retSize) {\\n obj->stream[idKey] = value;\\n char **res = (char **)malloc(sizeof(char *) * obj->streamSize);\\n int pos = 0;\\n while (obj->ptr < obj->streamSize && obj->stream[obj->ptr]) {\\n res[pos++] = obj->stream[obj->ptr];\\n obj->ptr++;\\n }\\n *retSize = pos;\\n return res;\\n}\\n\\nvoid orderedStreamFree(OrderedStream* obj) {\\n free(obj->stream);\\n free(obj);\\n}\\n
###go
\\n\\ntype OrderedStream struct {\\n stream []string\\n ptr int\\n}\\n\\nfunc Constructor(n int) OrderedStream {\\n return OrderedStream{make([]string, n+1), 1}\\n}\\n\\nfunc (s *OrderedStream) Insert(idKey int, value string) []string {\\n s.stream[idKey] = value\\n start := s.ptr\\n for s.ptr < len(s.stream) && s.stream[s.ptr] != \\"\\" {\\n s.ptr++\\n }\\n return s.stream[start:s.ptr]\\n}\\n
复杂度分析
\\n注意这里我们将字符串的固定常数 $5$ 看成常数。
\\n\\n
\\n","description":"方法一:使用数组存储 + 遍历 思路与算法\\n\\n对于 $\\\\text{OrderedStream(int n)}$,我们在初始化时开辟一个长度为 $n+1$ 的数组 $\\\\textit{stream}$,用来存储后续的字符串。注意到题目中指针 $\\\\textit{ptr}$ 的初始值为 $1$,而多数语言数组的下标是从 $0$ 开始的,因此使用长度为 $n+1$ 的数组可以使得编码更加容易。\\n\\n对于 $\\\\text{String[] insert(int id, String value)}$,我们直接根据题目描述中的要求进行遍历即可。我们首先将 $\\\\textit…","guid":"https://leetcode.cn/problems/design-an-ordered-stream//solution/she-ji-you-xu-liu-by-leetcode-solution-3imb","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-15T01:45:16.379Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"6151. 统计特殊整数 谁都能看懂的简单而又朴实的模拟法","url":"https://leetcode.cn/problems/count-special-integers//solution/6151-tong-ji-te-shu-zheng-shu-po-su-de-m-yty1","content":"- \\n
\\n时间复杂度:
\\n\\n
\\n- \\n
\\n$\\\\text{OrderedStream(int n)}$ 的时间复杂度为 $O(n)$;
\\n- \\n
\\n$\\\\text{String[] insert(int id, String value)}$ 的时间复杂度为均摊 $O(1)$,这是因为我们会恰好调用该函数 $n$ 次,那么每一个字符串最多会被包含在返回数组中一次;
\\n- \\n
\\n空间复杂度:$O(n)$,即为存储 $n$ 个字符串需要的空间。
\\n解题思路
\\n请仔细阅读,跟着例子走一遍,就能发现规律了
\\n前提条件:【排列组合】指的是用0~9不重复的排列组合
\\n假设给出的num是45352
\\n
\\n首先num是个五位数
\\n意味排列组合的所有一位数、二位数、三位数、四位数都符合条件
\\n第一位数有9种可能(注意排除首位0)
\\n第二位数在剩余的9个数字中选择,即有9 * 9种可能
\\n第三位数在剩余的8个数字中选择,即有9 * 9 * 8种可能
\\n第四位数在剩余的7个数字中选择,即有9 * 9 * 8 * 7种可能上述的结果累加,放一边。
\\n对于num = 45352
\\n
\\n在五位数中,首个数字 < 4的所有五位数排列组合符合条件
\\n即所有的1XXXX、2XXXX、3XXXX(注意排除首位0,不要把0XXXX算进去了)
\\n这一步计算为 3 * 9 * 8 * 7 * 6之后固定第一位数为4
\\n
\\n在4开头的五位数中,第二个数字 < 5(来自num的第二位)的所有五位数排列组合符合条件
\\n即:40XXX、41XXX、42XXX、43XXX(注意排除44XXX,因为4已经被使用过)
\\n这一步计算为 1 * 4 * 8 * 7 * 6之后固定第二位数为5
\\n
\\n在45开头五位数中,第三个数字 < 3(来自num的第三位)的所有五位数排列组合符合条件
\\n即:450XX、451XX、452XX
\\n这一步计算为 1 * 1 * 3 * 7 * 6之后固定第三位数为3
\\n
\\n在453开头五位数中,第四个数字 < 5(来自num的第四位)的所有五位数排列组合符合条件
\\n即:4530X、4531X、4532X(注意排除4533X、4534X,3和4已经在前面被用过了)
\\n这一步计算为 1 * 1 * 1 * 3 * 6这里出现常见的特殊情况,第四位不能固定为5(因为5在前面被用了)
\\n
\\n第四位也不能固定为4、3,只能固定为2
\\n也就是要讨论4532X的可能
\\n但是4532X在上一轮讨论计算过了,所以我们可以直接跳出循环结束计算
\\n可以认为,当发现num中重复的数,即可结束这一步计算最后,因为这种计算会把num本身忽略
\\n
\\n所以需要额外判断一下num本身是否为特殊整数
\\n是的话就+1代码
\\n###java
\\n\\n","description":"解题思路 请仔细阅读,跟着例子走一遍,就能发现规律了\\n\\n前提条件:【排列组合】指的是用0~9不重复的排列组合\\n\\n假设给出的num是45352\\n 首先num是个五位数\\n 意味排列组合的所有一位数、二位数、三位数、四位数都符合条件\\n 第一位数有9种可能(注意排除首位0)\\n 第二位数在剩余的9个数字中选择,即有9 * 9种可能\\n 第三位数在剩余的8个数字中选择,即有9 * 9 * 8种可能\\n 第四位数在剩余的7个数字中选择,即有9 * 9 * 8 * 7种可能\\n\\n上述的结果累加,放一边。\\n\\n对于num = 45352\\n 在五位数中,首个数字 < 4的所有五位数排列组合符合条件\\n 即所…","guid":"https://leetcode.cn/problems/count-special-integers//solution/6151-tong-ji-te-shu-zheng-shu-po-su-de-m-yty1","author":"peterqiu-007","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-14T05:34:07.824Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"不要被图吓到了,解法 和图没有一点关系; 双百简单解法","url":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/bu-yao-bei-tu-xia-zhao-liao-by-es-7-qsof","content":"class Solution {\\n private final static int[] TABLE = {\\n 9,\\n 9 * 8,\\n 9 * 8 * 7,\\n 9 * 8 * 7 * 6,\\n 9 * 8 * 7 * 6 * 5,\\n 9 * 8 * 7 * 6 * 5 * 4,\\n 9 * 8 * 7 * 6 * 5 * 4 * 3,\\n 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2,\\n 9 * 8 * 7 * 6 * 5 * 4 * 3 * 2 * 1,\\n };\\n\\n public int countSpecialNumbers(int n) {\\n int[] nums = parseArr(n);\\n // 数位长度\\n int len = nums.length;\\n // 标记0~9是否使用\\n boolean[] visited = new boolean[10];\\n // 用于返回的统计结果\\n int ans = 0;\\n // 假设原数为4位数,那么所有的1、2、3位数都符合要求\\n // 4位数要单独拿出来讨论\\n ans += totalCountByLen(len - 1);\\n\\n for (int i = 0; i < len; i++) {\\n int num = nums[i];\\n // 假设第一位为4\\n // 那么3XXX、2XXX、1XXX都符合要求,注意0XXX要排除\\n int cnt = countLessThanMax(visited, num);\\n if (i == 0) {\\n // 首位数排除0\\n --cnt;\\n }\\n\\n // 当前剩下的数,统计有几种排列组合\\n int restTotal = cnt;\\n int rest = countLessThanMax(visited, 10) - 1;\\n for (int j = i + 1; j < len; j++) {\\n restTotal *= rest--;\\n }\\n\\n ans += restTotal;\\n\\n if (visited[num]) {\\n // 该数字在前面已被使用,无需再往后判断\\n // 比如num = 22225,已通过固定第一位的2,计算了20XXX和21XXX的所有可能性,无需再固定前两位做后续计算\\n break;\\n }\\n visited[num] = true;\\n }\\n\\n // 因为上面的计算方式会排除掉该数本身,所以对该数字本身是否特殊再做一次判断\\n if (isSpecial(nums)) {\\n ++ans;\\n }\\n\\n return ans;\\n }\\n\\n /**\\n * 统计有几个1位数+2位数+...+len位数\\n */\\n private int totalCountByLen(int len) {\\n if (len == 0) {\\n return 0;\\n }\\n // 1位数有9个(排除0)\\n int sum = 9;\\n\\n for (int i = 0; i < len - 1; i++) {\\n // 2位数有9 * 9 = 81个\\n // 3位数有9 * 9*8 = 648个\\n // 4位数有9 * 9*8*7 = 4536个\\n // 累加起来\\n sum += 9 * TABLE[i];\\n }\\n\\n return sum;\\n }\\n\\n /**\\n * 判断这个数本身是否特殊\\n */\\n private boolean isSpecial(int[] nums) {\\n boolean[] visited = new boolean[10];\\n for (int num : nums) {\\n if (visited[num]) {\\n return false;\\n }\\n visited[num] = true;\\n }\\n return true;\\n }\\n\\n /**\\n * 找出还有几个未使用的且 < max的数\\n */\\n private int countLessThanMax(boolean[] visited, int max) {\\n int cnt = 0;\\n for (int i = 0; i < max; i++) {\\n if (!visited[i]) {\\n ++cnt;\\n }\\n }\\n return cnt;\\n }\\n\\n /**\\n * 数字转数组,便于后续计算\\n */\\n private int[] parseArr(int num) {\\n char[] chars = String.valueOf(num).toCharArray();\\n int len = chars.length;\\n int[] arr = new int[len];\\n\\n for (int i = 0; i < len; i++) {\\n arr[i] = chars[i] - \'0\';\\n }\\n return arr;\\n }\\n}\\n
\\n
\\n","description":"var edgeScore = function(edges) { let t = Array(edges.length).fill(0);\\n for(let i = 0; i < edges.length; i++){\\n t[edges[i]] += i \\n }\\n let max = Math.max(...t)\\n return t.findIndex(v => v == max)\\n};","guid":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/bu-yao-bei-tu-xia-zhao-liao-by-es-7-qsof","author":"es-7","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-14T04:43:53.431Z","media":[{"url":"https://pic.leetcode-cn.com/1660453431-oYmPSd-image.png","type":"photo","width":955,"height":219}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数位 DP 通用模板(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-special-integers//solution/shu-wei-dp-mo-ban-by-endlesscheng-xtgx","content":"var edgeScore = function(edges) {\\n let t = Array(edges.length).fill(0);\\n for(let i = 0; i < edges.length; i++){\\n t[edges[i]] += i \\n }\\n let max = Math.max(...t)\\n return t.findIndex(v => v == max)\\n};\\n
本题视频讲解
\\n请看 数位 DP 通用模板。
\\n除了讲解模板,还讲了如何使用该模板秒杀相关困难题目。
\\n前置知识:位运算与集合论
\\n集合可以用二进制表示,二进制从低到高第 $i$ 位为 $1$ 表示 $i$ 在集合中,为 $0$ 表示 $i$ 不在集合中。例如集合 ${0,2,3}$ 对应的二进制数为 $1101_{(2)}$。
\\n设集合对应的二进制数为 $x$。本题需要用到两个位运算操作:
\\n\\n
\\n- 判断元素 $d$ 是否在集合中:
\\nx >> d & 1
可以取出 $x$ 的第 $d$ 个比特位,如果是 $1$ 就说明 $d$ 在集合中。- 把元素 $d$ 添加到集合中:将
\\nx
更新为x | (1 << d)
。更多位运算的知识点,请看 从集合论到位运算,常见位运算技巧分类总结!
\\n思路
\\n将 $n$ 转换成字符串 $s$,定义 $\\\\textit{dfs}(i,\\\\textit{mask}, \\\\textit{isLimit},\\\\textit{isNum})$ 表示构造第 $i$ 位及其之后数位的合法方案数,其余参数的含义为:
\\n\\n
\\n- $\\\\textit{mask}$ 表示前面选过的数字集合,换句话说,第 $i$ 位要选的数字不能在 $\\\\textit{mask}$ 中。
\\n- $\\\\textit{isLimit}$ 表示当前是否受到了 $n$ 的约束(注意要构造的数字不能超过 $n$)。若为真,则第 $i$ 位填入的数字至多为 $s[i]$,否则可以是 $9$。如果在受到约束的情况下填了 $s[i]$,那么后续填入的数字仍会受到 $n$ 的约束。例如 $n=123$,如果 $i=0$ 填的是 $1$ 的话,$i=1$ 的这一位至多填 $2$。如果 $i=0$ 填的是 $1$,$i=1$ 填的是 $2$,那么 $i=2$ 的这一位至多填 $3$。
\\n- $\\\\textit{isNum}$ 表示 $i$ 前面的数位是否填了数字。若为假,则当前位可以跳过(不填数字),或者要填入的数字至少为 $1$;若为真,则要填入的数字可以从 $0$ 开始。例如 $n=123$,在 $i=0$ 时跳过的话,相当于后面要构造的是一个 $99$ 以内的数字了,如果 $i=1$ 不跳过,那么相当于构造一个 $10$ 到 $99$ 的两位数,如果 $i=1$ 跳过,相当于构造的是一个 $9$ 以内的数字。
\\n- 为什么要定义 $\\\\textit{isNum}$?因为 $010$ 和 $10$ 都是 $10$,如果认为第一个 $0$ 和第三个 $0$ 都是我们填入的数字,这就不符合题目要求了,但 $10$ 显然是符合题目要求的。
\\n实现细节
\\n递归入口:$\\\\textit{dfs}(0,0,\\\\texttt{true},\\\\texttt{false})$,表示:
\\n\\n
\\n- 从 $s[0]$ 开始枚举;
\\n- 一开始集合中没有数字(空集);
\\n- 一开始要受到 $n$ 的约束(否则就可以随意填了,这肯定不行);
\\n- 一开始没有填数字。
\\n递归中:
\\n\\n
\\n- 如果 $\\\\textit{isNum}$ 为假,说明前面没有填数字,那么当前也可以不填数字。一旦从这里递归下去,$\\\\textit{isLimit}$ 就可以置为
\\nfalse
了,这是因为 $s[0]$ 必然是大于 $0$ 的,后面就不受到 $n$ 的约束了。或者说,最高位不填数字,后面无论怎么填都比 $n$ 小。- 如果 $\\\\textit{isNum}$ 为真,那么当前必须填一个数字。枚举填入的数字,根据 $\\\\textit{isNum}$ 和 $\\\\textit{isLimit}$ 来决定填入数字的范围。
\\n递归终点:当 $i$ 等于 $s$ 长度时,如果 $\\\\textit{isNum}$ 为真,则表示得到了一个合法数字(因为不合法的数字不会递归到终点),返回 $1$,否则返回 $0$。
\\n答疑
\\n问:$\\\\textit{isNum}$ 这个参数可以去掉吗?
\\n答:本题由于 $\\\\textit{mask}$ 中记录了数字,可以通过判断 $\\\\textit{mask}$ 是否为 $0$(空集)来判断前面是否填了数字,所以对于本题来说,$\\\\textit{isNum}$ 可以省略。
\\n下面的代码保留了 $\\\\textit{isNum}$,主要是为了方便大家掌握这个模板。因为有些题目不需要 $\\\\textit{mask}$,但需要 $\\\\textit{isNum}$。
\\n问:记忆化四个状态有点麻烦,能不能只记忆化 $(i,\\\\textit{mask})$ 这两个状态?
\\n答:是可以的。比如 $n=234$,第一位填 $2$,第二位填 $3$,后面无论怎么递归,都不会再次递归到第一位填 $2$,第二位填 $3$ 的情况,所以不需要记录。又比如,第一位不填,第二位也不填,后面无论怎么递归也不会再次递归到这种情况,所以也不需要记录。
\\n根据这个例子,我们可以只记录不受到 $\\\\textit{isLimit}$ 或 $\\\\textit{isNum}$ 约束时的状态 $(i,\\\\textit{mask})$。比如 $n=234$,第一位(最高位)填的 $1$,那么继续递归,后面就可以随便填,所以状态 $(1,2)$ 就表示前面填了一个 $1$(对应的 $\\\\textit{mask}=2$),从第二位往后随便填的方案数。
\\n相当于我们记忆化的是 $(i,\\\\textit{mask},\\\\texttt{false},\\\\texttt{true})$。
\\n问:能不能只记忆化 $i$?
\\n答:这是不行的。想一想,我们为什么要用记忆化?如果递归到同一个状态时,计算出的结果是一样的,那么第二次递归到同一个状态,就可以直接返回第一次计算的结果了。通过保存第一次计算的结果,来优化时间复杂度。
\\n由于前面选的数字会影响后面选的数字,两次递归到相同的 $i$,如果前面选的数字不一样,计算出的结果就可能是不一样的。如果只记忆化 $i$,就可能会算出错误的结果。
\\n\\nclass Solution:\\n def countSpecialNumbers(self, n: int) -> int:\\n s = str(n)\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, mask: int, is_limit: bool, is_num: bool) -> int:\\n if i == len(s):\\n return 1 if is_num else 0 # is_num 为 True 表示得到了一个合法数字\\n res = 0\\n if not is_num: # 可以跳过当前数位\\n res = dfs(i + 1, mask, False, False)\\n # 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n low = 0 if is_num else 1\\n # 如果前面填的数字都和 n 的一样,那么这一位至多填 s[i](否则就超过 n 啦)\\n up = int(s[i]) if is_limit else 9\\n for d in range(low, up + 1): # 枚举要填入的数字 d\\n if mask >> d & 1 == 0: # d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i + 1, mask | (1 << d), is_limit and d == up, True)\\n return res\\n return dfs(0, 0, True, False)\\n
\\nclass Solution {\\n public int countSpecialNumbers(int n) {\\n char[] s = Integer.toString(n).toCharArray();\\n int[][] memo = new int[s.length][1 << 10];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(0, 0, true, false, s, memo);\\n }\\n\\n private int dfs(int i, int mask, boolean isLimit, boolean isNum, char[] s, int[][] memo) {\\n if (i == s.length) {\\n return isNum ? 1 : 0; // isNum 为 true 表示得到了一个合法数字\\n }\\n if (!isLimit && isNum && memo[i][mask] != -1) {\\n return memo[i][mask]; // 之前计算过\\n }\\n int res = 0;\\n if (!isNum) { // 可以跳过当前数位\\n res = dfs(i + 1, mask, false, false, s, memo);\\n }\\n // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n int up = isLimit ? s[i] - \'0\' : 9;\\n // 枚举要填入的数字 d\\n // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n for (int d = isNum ? 0 : 1; d <= up; d++) {\\n if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i + 1, mask | (1 << d), isLimit && d == up, true, s, memo);\\n }\\n }\\n if (!isLimit && isNum) {\\n memo[i][mask] = res; // 记忆化\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int countSpecialNumbers(int n) {\\n string s = to_string(n);\\n int m = s.length();\\n vector<vector<int>> memo(m, vector<int>(1 << 10, -1)); // -1 表示没有计算过\\n auto dfs = [&](auto&& dfs, int i, int mask, bool is_limit, bool is_num) -> int {\\n if (i == m) {\\n return is_num; // is_num 为 true 表示得到了一个合法数字\\n }\\n if (!is_limit && is_num && memo[i][mask] != -1) {\\n return memo[i][mask]; // 之前计算过\\n }\\n int res = 0;\\n if (!is_num) { // 可以跳过当前数位\\n res = dfs(dfs, i + 1, mask, false, false);\\n }\\n // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n int up = is_limit ? s[i] - \'0\' : 9;\\n // 枚举要填入的数字 d\\n // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n for (int d = is_num ? 0 : 1; d <= up; d++) {\\n if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(dfs, i + 1, mask | (1 << d), is_limit && d == up, true);\\n }\\n }\\n if (!is_limit && is_num) {\\n memo[i][mask] = res; // 记忆化\\n }\\n return res;\\n };\\n return dfs(dfs, 0, 0, true, false);\\n }\\n};\\n
\\n#define MAX_M 11\\nint memo[MAX_M][1 << 10];\\n\\nint dfs(int i, int mask, bool is_limit, bool is_num, const char* s) {\\n if (s[i] == \'\\\\0\') {\\n return is_num; // is_num 为 true 表示得到了一个合法数字\\n }\\n if (!is_limit && is_num && memo[i][mask] != -1) {\\n return memo[i][mask]; // 之前计算过\\n }\\n int res = 0;\\n if (!is_num) { // 可以跳过当前数位\\n res = dfs(i + 1, mask, false, false, s);\\n }\\n // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n int up = is_limit ? s[i] - \'0\' : 9;\\n // 枚举要填入的数字 d\\n // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n for (int d = is_num ? 0 : 1; d <= up; d++) {\\n if ((mask >> d & 1) == 0) { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i + 1, mask | (1 << d), is_limit && d == up, 1, s);\\n }\\n }\\n if (!is_limit && is_num) {\\n memo[i][mask] = res; // 记忆化\\n }\\n return res;\\n}\\n\\nint countSpecialNumbers(int n) {\\n char s[MAX_M];\\n sprintf(s, \\"%d\\", n); // 将数字 n 转换为字符串\\n int m = strlen(s);\\n memset(memo, -1, m * (1 << 10) * sizeof(int)); // -1 表示没有计算过\\n return dfs(0, 0, true, false, s);\\n}\\n
\\nfunc countSpecialNumbers(n int) int {\\n s := strconv.Itoa(n)\\n m := len(s)\\n memo := make([][1 << 10]int, m)\\n for i := range memo {\\n for j := range memo[i] {\\n memo[i][j] = -1 // -1 表示没有计算过\\n }\\n }\\n var dfs func(int, int, bool, bool) int\\n dfs = func(i, mask int, isLimit, isNum bool) (res int) {\\n if i == m {\\n if isNum {\\n return 1 // 得到了一个合法数字\\n }\\n return\\n }\\n if !isLimit && isNum {\\n p := &memo[i][mask]\\n if *p >= 0 { // 之前计算过\\n return *p\\n }\\n defer func() { *p = res }() // 记忆化\\n }\\n if !isNum { // 可以跳过当前数位\\n res += dfs(i+1, mask, false, false)\\n }\\n d := 0\\n if !isNum {\\n d = 1 // 如果前面没有填数字,必须从 1 开始(因为不能有前导零)\\n }\\n up := 9\\n if isLimit {\\n up = int(s[i] - \'0\') // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n }\\n for ; d <= up; d++ { // 枚举要填入的数字 d\\n if mask>>d&1 == 0 { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i+1, mask|1<<d, isLimit && d == up, true)\\n }\\n }\\n return\\n }\\n return dfs(0, 0, true, false)\\n}\\n
\\nvar countSpecialNumbers = function(n) {\\n const s = n.toString();\\n const m = s.length;\\n const memo = Array.from({ length: m }, () => Array(1 << 10).fill(-1)); // -1 表示没有计算过\\n function dfs(i, mask, isLimit, isNum) {\\n if (i === m) {\\n return isNum ? 1 : 0; // is_num 为 true 表示得到了一个合法数字\\n }\\n if (!isLimit && isNum && memo[i][mask] !== -1) {\\n return memo[i][mask]; // 之前计算过\\n }\\n let res = 0;\\n if (!isNum) { // 可以跳过当前数位\\n res += dfs(i + 1, mask, false, false);\\n }\\n // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n const up = isLimit ? +s[i] : 9;\\n // 枚举要填入的数字 d\\n // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n for (let d = isNum ? 0 : 1; d <= up; d++) {\\n if ((mask >> d & 1) === 0) { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i + 1, mask | (1 << d), isLimit && d === up, true);\\n }\\n }\\n if (!isLimit && isNum) {\\n memo[i][mask] = res; // 记忆化\\n }\\n return res;\\n }\\n return dfs(0, 0, true, false);\\n};\\n
\\nimpl Solution {\\n pub fn count_special_numbers(n: i32) -> i32 {\\n fn dfs(i: usize, mask: usize, is_limit: bool, is_num: bool, s: &[u8], memo: &mut Vec<Vec<i32>>) -> i32 {\\n if i == s.len() {\\n return if is_num { 1 } else { 0 }; // is_num 为 true 表示得到了一个合法数字\\n }\\n if !is_limit && is_num && memo[i][mask] != -1 {\\n return memo[i][mask]; // 之前计算过\\n }\\n let mut res = 0;\\n if !is_num { // 可以跳过当前数位\\n res = dfs(i + 1, mask, false, false, s, memo);\\n }\\n // 如果前面填的数字都和 n 的一样,那么这一位至多填数字 s[i](否则就超过 n 啦)\\n let up = if is_limit { s[i] - b\'0\' } else { 9 };\\n // 枚举要填入的数字 d\\n // 如果前面没有填数字,则必须从 1 开始(因为不能有前导零)\\n let low = if is_num { 0 } else { 1 };\\n for d in low..=up {\\n if (mask >> d & 1) == 0 { // d 不在 mask 中,说明之前没有填过 d\\n res += dfs(i + 1, mask | (1 << d), is_limit && d == up, true, s, memo);\\n }\\n }\\n if !is_limit && is_num {\\n memo[i][mask] = res; // 记忆化\\n }\\n return res;\\n }\\n\\n let s = n.to_string();\\n let s = s.as_bytes();\\n let mut memo = vec![vec![-1; 1 << 10]; s.len()]; // -1 表示没有计算过\\n return dfs(0, 0, true, false, &s, &mut memo);\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(mD2^D)$,其中 $m$ 为 $s$ 的长度,即 $\\\\mathcal{O}(\\\\log n)$,$D=10$。由于每个状态只会计算一次,因此动态规划的时间复杂度 $=$ 状态个数 $\\\\times$ 单个状态的计算时间。本题状态个数为 $\\\\mathcal{O}(m2^D)$,单个状态的计算时间为 $\\\\mathcal{O}(D)$,因此时间复杂度为 $\\\\mathcal{O}(mD2^D)$。
\\n- 空间复杂度:$\\\\mathcal{O}(m2^D)$。
\\n更多相似题目,见下面动态规划题单中的「数位 DP」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本题视频讲解 请看 数位 DP 通用模板。\\n\\n除了讲解模板,还讲了如何使用该模板秒杀相关困难题目。\\n\\n前置知识:位运算与集合论\\n\\n集合可以用二进制表示,二进制从低到高第 $i$ 位为 $1$ 表示 $i$ 在集合中,为 $0$ 表示 $i$ 不在集合中。例如集合 ${0,2,3}$ 对应的二进制数为 $1101_{(2)}$。\\n\\n设集合对应的二进制数为 $x$。本题需要用到两个位运算操作:\\n\\n判断元素 $d$ 是否在集合中:x >> d & 1 可以取出 $x$ 的第 $d$ 个比特位,如果是 $1$ 就说明 $d$ 在集合中。\\n把元素 $d$ 添加到集合中:将…","guid":"https://leetcode.cn/problems/count-special-integers//solution/shu-wei-dp-mo-ban-by-endlesscheng-xtgx","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-14T04:21:05.272Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"呱呱呱","url":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/gu-gu-gu-by-fan_of_emptyhope-tqb5","content":"简单模拟,按照题意模拟即可。
\\n开一个数组
\\ncnt
表示到 $i$ 的和,取最大值。然后找第一个是最大值的
\\ncnt[i]
就是答案。时间复杂度 $\\\\mathcal O(n)$,空间复杂度 $\\\\mathcal O(n)$。
\\n代码如下。
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int edgeScore(vector<int>& edge) {\\n vector<long long>cnt(edge.size(),0);\\n for(int i=0;i<edge.size();i++)cnt[edge[i]]+=i;\\n long long mx=0;for(int i=0;i<edge.size();i++)mx=max(mx,cnt[i]);\\n for(int i=0;i<edge.size();i++)if(cnt[i]==mx)return i;\\n return 0;\\n }\\n};\\n
呱呱呱
\\n","description":"简单模拟,按照题意模拟即可。 开一个数组 cnt 表示到 $i$ 的和,取最大值。\\n\\n然后找第一个是最大值的 cnt[i] 就是答案。\\n\\n时间复杂度 $\\\\mathcal O(n)$,空间复杂度 $\\\\mathcal O(n)$。\\n\\n代码如下。\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int edgeScore(vector& edge) {\\n vector cnt(edge.size(),0);\\n for(int i=0;i 用一个长为 $n$ 的数组 $\\\\textit{score}$ 记录每个节点的得分。\\n 遍历 $\\\\textit{edges}$,根据题意,把 $i$ 加到 $\\\\textit{score}[\\\\textit{edges}[i]]$ 中。
\\n返回 $\\\\textit{score}[i]$ 最大且(在积分相同时)$i$ 最小的 $i$。
\\n\\nclass Solution:\\n def edgeScore(self, edges: List[int]) -> int:\\n ans = 0\\n score = [0] * len(edges)\\n for i, to in enumerate(edges):\\n score[to] += i\\n if score[to] > score[ans] or score[to] == score[ans] and to < ans:\\n ans = to\\n return ans\\n
\\n# 虽然简洁,但不是一次遍历\\nclass Solution:\\n def edgeScore(self, edges: List[int]) -> int:\\n score = [0] * len(edges)\\n for i, to in enumerate(edges):\\n score[to] += i\\n return score.index(max(score))\\n
\\nclass Solution {\\n public int edgeScore(int[] edges) {\\n int ans = 0;\\n long[] score = new long[edges.length];\\n for (int i = 0; i < edges.length; i++) {\\n int to = edges[i];\\n score[to] += i;\\n if (score[to] > score[ans] || score[to] == score[ans] && to < ans) {\\n ans = to;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int edgeScore(vector<int>& edges) {\\n int n = edges.size(), ans = 0;\\n vector<long long> score(n);\\n for (int i = 0; i < n; i++) {\\n int to = edges[i];\\n score[to] += i;\\n if (score[to] > score[ans] || score[to] == score[ans] && to < ans) {\\n ans = to;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint edgeScore(int* edges, int edgesSize) {\\n int ans = 0;\\n long long* score = calloc(edgesSize, sizeof(long long));\\n for (int i = 0; i < edgesSize; i++) {\\n int to = edges[i];\\n score[to] += i;\\n if (score[to] > score[ans] || score[to] == score[ans] && to < ans) {\\n ans = to;\\n }\\n }\\n free(score);\\n return ans;\\n}\\n
\\nfunc edgeScore(edges []int) (ans int) {\\n score := make([]int, len(edges))\\n for i, to := range edges {\\n score[to] += i\\n if score[to] > score[ans] || score[to] == score[ans] && to < ans {\\n ans = to\\n }\\n }\\n return\\n}\\n
\\nvar edgeScore = function(edges) {\\n const score = Array(edges.length).fill(0);\\n let ans = 0;\\n for (let i = 0; i < edges.length; i++) {\\n const to = edges[i];\\n score[to] += i;\\n if (score[to] > score[ans] || score[to] === score[ans] && to < ans) {\\n ans = to;\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn edge_score(edges: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut score = vec![0i64; edges.len()];\\n for (i, &to) in edges.iter().enumerate() {\\n let to = to as usize;\\n score[to] += i as i64;\\n if score[to] > score[ans] || score[to] == score[ans] && to < ans {\\n ans = to;\\n }\\n }\\n ans as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{edges}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用一个长为 $n$ 的数组 $\\\\textit{score}$ 记录每个节点的得分。 遍历 $\\\\textit{edges}$,根据题意,把 $i$ 加到 $\\\\textit{score}[\\\\textit{edges}[i]]$ 中。\\n\\n返回 $\\\\textit{score}[i]$ 最大且(在积分相同时)$i$ 最小的 $i$。\\n\\nclass Solution:\\n def edgeScore(self, edges: List[int]) -> int:\\n ans = 0\\n score = [0] * len(edges)…","guid":"https://leetcode.cn/problems/node-with-highest-edge-score//solution/by-endlesscheng-z1sm","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-14T04:08:56.798Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【C++】分类讨论 + 迭代 O(logn) O(1)","url":"https://leetcode.cn/problems/count-special-integers//solution/c-fen-lei-tao-lun-ji-ke-by-muriyatensei-hvi3","content":"什么垃圾题出原题 1012. 至少有 1 位重复的数字
\\n
\\n最气的是我还没做过(这要是知道原题随便就前25了(摔)(怎么别人都知道原题的)分类讨论+迭代(数位DP+组合数学)
\\n对于位数等于n的位数的情况
\\n从高位开始考虑。
\\n\\n
\\n- 如果比n的对应位大,必然无解
\\n- 如果比n的对应位小,后续位可以任意填写(不重复即可),可以直接全排列(这里预处理阶乘 $O(1)$ 求出。而比n小的位有多少种情况,简单可以遍历 $[0,s[i])$ 统计出,这样需要 $O(|\\\\Sigma|)$。 采用位运算,用标记位代表已经选择的数字,可以通过 $__builtin_popcount(x)$ $O(1)$ 求出。
\\n- 如果相等
\\n
\\n3.1. 如果这个数字未被使用,标记这个数字为已使用,考虑次高位,重复1~3步。
\\n3.2. 如果这个数字已被使用,那么证明不存在这种情况,并且不存在等于n的解,可以直接返回答案(另一种情况提前计算完毕)注意到,这个过程中我们是没有考虑完全等于 $n$ 的情况的,最后要把这个情况加上(+1)
\\n
\\n如果相等不可行,在3.2退出时就是这种情况,不加一直接返回即可(或者减1后break,回到整体再return,一样的)对于位数小于n的位数的情况
\\n对于每种长度分别计算全排列即可
\\n注意最高位不能为0,因此最高位有9种可能,
\\n次高位至末尾为 $98... = f[9]/f[10-i]$ 其中 $i$ 为总位数, $f[i]$ 为 $i! = i * (i-1) * ... * 1$
\\n注意到,这部分同样可以预处理求出
\\n###c++
\\n\\nint f[11]{1}, fas[11]{0};//f[i] 阶乘; fas[i] i位数的可行全排列\\nauto init = []{\\n for (int i = 1; i < 11; ++i) f[i] = f[i - 1] * i;\\n for (int i = 1; i < 10; ++i) fas[i+1] = fas[i] + 9 * f[9] / f[10-i];\\n return 1;\\n}();\\nclass Solution {\\npublic:\\n int countSpecialNumbers(int n) {\\n auto s = to_string(n);//ans 初始化为 【对于位数小于n的情况数】 减 【0开头的全排列数】\\n int mask = 0, k = s.size(), ans = fas[k] - f[9] / f[10 - k]; \\n for (int q = 0; q < k; ++q) {\\n ans += (f[9 - q] / f[10 - k]) * __builtin_popcount(((1 << (s[q] - \'0\')) - 1) & ~mask);\\n if (mask >> (s[q] - \'0\') & 1) return ans;\\n mask |= 1 << (s[q] - \'0\');\\n }\\n return ans + 1;\\n }\\n};\\n
\\n
\\n- 时间复杂度:$O(\\\\log_{10}{n})$ 遍历十进制的每一位
\\n- 空间复杂度:$O(\\\\log_{10}{n})$ 储存十进制的每一位
\\n
\\n理论上空间这里可以优化到 $O(1)$ ?
\\n(不过事实上存在最大的表示 $9876543210$, 空间可以认为是 $O(\\\\log_{10}{n}) < O(10) = O(1)$吧)迭代空间优化
\\n通过迭代计算全排列,空间降到 $O(1)$
\\n
\\n此外,位数小于n的所有全排列用了一种特殊的迭代方式计算。###c++
\\n\\n","description":"什么垃圾题出原题 1012. 至少有 1 位重复的数字 最气的是我还没做过(这要是知道原题随便就前25了(摔)(怎么别人都知道原题的)\\n\\n对于位数等于n的位数的情况\\n\\n从高位开始考虑。\\n\\n如果比n的对应位大,必然无解\\n如果比n的对应位小,后续位可以任意填写(不重复即可),可以直接全排列(这里预处理阶乘 $O(1)$ 求出。而比n小的位有多少种情况,简单可以遍历 $[0,s[i])$ 统计出,这样需要 $O(|\\\\Sigma|)$。 采用位运算,用标记位代表已经选择的数字,可以通过 $__builtin_popcount(x)$ $O(1)$ 求出。\\n如果相等…","guid":"https://leetcode.cn/problems/count-special-integers//solution/c-fen-lei-tao-lun-ji-ke-by-muriyatensei-hvi3","author":"MuriyaTensei","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-14T04:08:03.012Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Java】6142. 统计坏数对的数目 91.71% 附类似等式交换问题","url":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/-by-wa-pian-d-uhi5","content":"class Solution {\\npublic:\\n int countSpecialNumbers(int n) {\\n int mask = 0, k = log10(n) + 1, w = pow(10, k-1); \\n int bf = 1, ans = 0;\\n for(int i = 9; i > 10 - k; --i) bf *= i;\\n for(int i = k; i > 2; --i) ans = (ans + 1) * (12 - i);\\n ans = (k > 1 ? 9 : 0) * (ans + 1) - bf;\\n for (int q = 0; q < k; ++q) {\\n int x = n / w;\\n n -= x * w;\\n w /= 10;\\n ans += (bf) * __builtin_popcount(((1 << x) - 1) & ~mask);\\n bf /= 9 - q;\\n if (mask >> x & 1) return ans;\\n mask |= 1 << x;\\n }\\n return ans + 1;\\n }\\n};\\n
解题思路
\\n\\n
j - i != nums[j] - nums[i]
\\n交换一下
\\nj - nums[j] != i - nums[i]
\\n就解了把
\\n向前找的问题
变成当前 + 哈希
问题。避免了向前找的动作。类似的
\\n[中等] 1814. 统计一个数组中好对子的数目【数组】【哈希表】【数学】【计数】 [哈希表] [1814. 统计一个数组中好对子的数目]
\\n代码
\\n###java
\\n\\n","description":"解题思路 j - i != nums[j] - nums[i]\\n 交换一下\\n j - nums[j] != i - nums[i]\\n 就解了\\n\\n把向前找的问题变成当前 + 哈希问题。避免了向前找的动作。\\n\\n类似的\\n\\n[中等] 1814. 统计一个数组中好对子的数目【数组】【哈希表】【数学】【计数】 [哈希表] [1814. 统计一个数组中好对子的数目]\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\npublic long countBadPairs(int[] nums) {\\nlong ans = 0;\\nMapclass Solution {\\npublic long countBadPairs(int[] nums) {\\nlong ans = 0;\\nMap<Integer, Integer> map = new HashMap<>();\\nfor (int i = 0; i < nums.length; i++) {\\nint val = i - nums[i];\\nint same = map.getOrDefault(val, 0);\\nans += i - same;\\nmap.put(val, same + 1);\\n}\\nreturn ans;\\n}\\n}\\n
map…","guid":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/-by-wa-pian-d-uhi5","author":"wa-pian-d","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-08T00:17:04.770Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"式子变形 + 枚举右维护左(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/by-endlesscheng-uam3","content":" 前置题目:1512. 好数对的数目
\\n题目要求
\\n$$
\\n
\\nj - i \\\\ne \\\\textit{nums}[j] - \\\\textit{nums}[i]
\\n$$移项得
\\n$$
\\n
\\n\\\\textit{nums}[i]-i \\\\ne \\\\textit{nums}[j]-j
\\n$$正难则反,用总数对个数 $\\\\dfrac{n(n-1)}{2}$ 减去满足
\\n$$
\\n
\\n\\\\textit{nums}[i]-i = \\\\textit{nums}[j]-j
\\n$$的数对个数,即为答案。
\\n设 $a[i] = \\\\textit{nums}[i]-i$,就和 1512 题完全一样了。
\\n为什么要先更新 $\\\\textit{ans}$,再更新 $\\\\textit{cnt}$?理由见 1512 题 我的题解。
\\n\\nclass Solution:\\n def countBadPairs(self, nums: List[int]) -> int:\\n ans = comb(len(nums), 2)\\n cnt = defaultdict(int)\\n for i, x in enumerate(nums):\\n ans -= cnt[x - i]\\n cnt[x - i] += 1\\n return ans\\n
\\nclass Solution {\\n public long countBadPairs(int[] nums) {\\n int n = nums.length;\\n long ans = (long) n * (n - 1) / 2;\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int i = 0; i < n; i++) {\\n int x = nums[i] - i;\\n int c = cnt.getOrDefault(x, 0);\\n ans -= c;\\n cnt.put(x, c + 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n public long countBadPairs(int[] nums) {\\n int n = nums.length;\\n long ans = (long) n * (n - 1) / 2;\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int i = 0; i < n; i++) {\\n ans -= cnt.merge(nums[i] - i, 1, Integer::sum) - 1;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countBadPairs(vector<int>& nums) {\\n int n = nums.size();\\n long long ans = 1LL * n * (n - 1) / 2;\\n unordered_map<int, int> cnt;\\n for (int i = 0; i < n; i++) {\\n ans -= cnt[nums[i] - i]++;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc countBadPairs(nums []int) int64 {\\n n := len(nums)\\n ans := n * (n - 1) / 2\\n cnt := map[int]int{}\\n for i, x := range nums {\\n ans -= cnt[x-i]\\n cnt[x-i]++\\n }\\n return int64(ans)\\n}\\n
\\nvar countBadPairs = function(nums) {\\n const n = nums.length;\\n let ans = n * (n - 1) / 2;\\n const cnt = new Map();\\n for (let i = 0; i < n; i++) {\\n const x = nums[i] - i;\\n const c = cnt.get(x) ?? 0;\\n ans -= c;\\n cnt.set(x, c + 1);\\n }\\n return ans;\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn count_bad_pairs(nums: Vec<i32>) -> i64 {\\n let n = nums.len() as i64;\\n let mut ans = n * (n - 1) / 2;\\n let mut cnt = HashMap::new();\\n for (i, x) in nums.into_iter().enumerate() {\\n let e = cnt.entry(x - i as i32).or_insert(0);\\n ans -= *e as i64;\\n *e += 1;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n更多相似题目,见下面数据结构题单中的「§0.1 枚举右,维护左」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置题目:1512. 好数对的数目 题目要求\\n\\n$$\\n j - i \\\\ne \\\\textit{nums}[j] - \\\\textit{nums}[i]\\n $$\\n\\n移项得\\n\\n$$\\n \\\\textit{nums}[i]-i \\\\ne \\\\textit{nums}[j]-j\\n $$\\n\\n正难则反,用总数对个数 $\\\\dfrac{n(n-1)}{2}$ 减去满足\\n\\n$$\\n \\\\textit{nums}[i]-i = \\\\textit{nums}[j]-j\\n $$\\n\\n的数对个数,即为答案。\\n\\n设 $a[i] = \\\\textit{nums}[i]-i$,就和 1512 题完全一样了。\\n\\n为什么要先更新…","guid":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/by-endlesscheng-uam3","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-06T23:57:38.395Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java36毫秒 计数+求和","url":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/java-by-zhy-6y7zp","content":"解题思路
\\n1.差值(数值-下标)相同的数可以组成好数对,按相同差值聚合计数。
\\n
\\n2.设数组长度为n,数组总共数对个数为(n-1)+(n-2)+...+1累加之和,形成等差数列,公式求和(1+n-1)*(n-1)/2,得到总数对个数。相同差值的个数也可以视为数组长度,同理得到好数对个数。
\\n3.坏数对=总数对-好数对。\\n
代码
\\n###java
\\n\\n","description":"解题思路 1.差值(数值-下标)相同的数可以组成好数对,按相同差值聚合计数。\\n 2.设数组长度为n,数组总共数对个数为(n-1)+(n-2)+...+1累加之和,形成等差数列,公式求和(1+n-1)*(n-1)/2,得到总数对个数。相同差值的个数也可以视为数组长度,同理得到好数对个数。\\n 3.坏数对=总数对-好数对。\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public long countBadPairs(int[] nums) {\\n int n=nums.length;\\n long cnt=(long…","guid":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/java-by-zhy-6y7zp","author":"zhy-","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-06T16:31:17.756Z","media":[{"url":"https://pic.leetcode-cn.com/1659803768-UrqBAA-%E7%BB%9F%E8%AE%A1%E5%9D%8F%E6%95%B0%E5%AF%B9%E7%9A%84%E6%95%B0%E7%9B%AE.png","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/by-tsreaper-3dgs","content":"class Solution {\\n public long countBadPairs(int[] nums) {\\n int n=nums.length;\\n long cnt=(long)(1.0d*(1+n-1)*(n-1)/2);\\n Map<Integer,Integer> map=new HashMap<>();\\n for(int i=0;i<n;i++){\\n int d=nums[i]-i;\\n map.put(d,map.getOrDefault(d,0)+1);\\n }\\n for(int count:map.values()){\\n cnt-=(long)(1.0d*(1+count-1)*(count-1)/2);\\n }\\n return cnt;\\n }\\n}\\n
解法:枚举
\\n坏数对的数目,就是所有数对的数目,减去满足
\\ni < j
且j - i == nums[j] - nums[i]
的数对数目。把等式两边移项,变为
\\nnums[i] - i == nums[j] - j
。因此我们只需要维护一个unordered_map<int, int> mp
,mp[x]
表示目前为止满足nums[i] - i == x
的i
有几个。我们枚举j
,并从答案中减去mp[nums[j] - j]
的值即可。复杂度 $\\\\mathcal{O}(n)$。参考代码(c++)
\\n###c++
\\n\\n","description":"解法:枚举 坏数对的数目,就是所有数对的数目,减去满足 i < j 且 j - i == nums[j] - nums[i] 的数对数目。\\n\\n把等式两边移项,变为 nums[i] - i == nums[j] - j。因此我们只需要维护一个 unordered_mapclass Solution {\\npublic:\\n long long countBadPairs(vector<int>& nums) {\\n int n = nums.size();\\n long long ans = 1LL * n * (n - 1) / 2;\\n unordered_map<int, int> mp;\\n for (int i = 0; i < n; i++) {\\n int t = nums[i] - i;\\n ans -= mp[t];\\n mp[t]++;\\n }\\n return ans;\\n }\\n};\\n
mp,mp[x] 表示目前为止满足 nums[i] - i == x 的 i 有几个。我们枚举 j,并从答案中减去 mp[nums[j] - j] 的值即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/count-number-of-bad-pairs//solution/by-tsreaper-3dgs","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-08-06T16:19:39.715Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"🥹 含泪总结周赛中的两道「图」问题","url":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/by-lfool-t4y7","content":" 如果想要查看作者更多文章,可以点击此处!!!🔥🔥🔥
\\n为了本篇文章更好的观感,可以点击此处!!!
\\n\\n\\n
\\n本周周赛后两题都是和「图」有关的,其中一个是「最短路径」问题,另一个是「环」问题
\\n找到离给定两个节点最近的节点
\\n题目详情可见 找到离给定两个节点最近的节点
\\n首先看到这个题目,可以想到求出
\\nnode1
和node2
到其它所有点的最短距离,然后遍历可选择的「中间点」。所以现在的问题就是如何求出其它点到起点的最短路径「最短路径」算法有 最短路径-Dijkstra 和 无权值最短路径算法(BFS)
\\n\\n
\\n- \\n
\\n「Dijkstra」适用于加权有向图,没有负权重边,且无环,一般是求起点到其它所有点的最短路径,也可以改进为求两点的最短路径
\\n- \\n
\\n「无权值最短路径算法(BFS)」适用于无权有向图,可以有环,一般是求两点的最短路径,也可以改进为求起点到其它所有点的最短路径
\\n对于本题,每条边的权重均为 1,所以可以看作为无权值,我们使用上述的第二种方法「无权值最短路径算法(BFS)」
\\n由于点与点之间最多只有一条边,所以可以简化「无权值最短路径算法(BFS)」
\\n下面给出详细代码:
\\n###java
\\n\\npublic int closestMeetingNode(int[] edges, int node1, int node2) {\\n int n = edges.length;\\n // 点 node1 和 node2 到其它所有点的最短路径\\n int[] dist1 = getDist(edges, node1);\\n int[] dist2 = getDist(edges, node2);\\n int ans = -1, min = (int) 1e5 + 7;\\n // 遍历可选择的「中间点」\\n for (int i = 0; i < n; i++) {\\n // 分别为 node1 和 node2 到 i 的最短路径\\n int d1 = dist1[i], d2 = dist2[i];\\n // 如果有一方到不了,则跳过\\n if (d1 == -1 || d2 == -1) continue;\\n int max = Math.max(d1, d2);\\n if (max < min) {\\n min = max;\\n ans = i;\\n }\\n }\\n return ans;\\n}\\n// 求点 s 到其它所有点的最短路径\\nprivate int[] getDist(int[] edges, int s) {\\n int d = 0;\\n int[] dist = new int[edges.length];\\n Arrays.fill(dist, -1);\\n while (s != -1) {\\n // 已经访问,考虑「环」的存在\\n if (dist[s] != -1) break;\\n dist[s] = d++;\\n // 下一个点\\n s = edges[s];\\n }\\n return dist;\\n}\\n
图中的最长环
\\n题目详情可见 图中的最长环
\\n关于「拓扑排序」的详细介绍可见 环检测 & 拓扑排序
\\n先利用「拓扑排序」,找出所有不在环中的节点,那么剩余的节点全在环中
\\n\\n
如图,会把点
\\n1
和2
删去,留下两个环中的点下面给出详细代码:
\\n###java
\\n\\n","description":"6134. 找到离给定两个节点最近的节点 6135. 图中的最长环\\n\\n本周周赛后两题都是和「图」有关的,其中一个是「最短路径」问题,另一个是「环」问题\\n\\n找到离给定两个节点最近的节点\\n\\n题目详情可见 找到离给定两个节点最近的节点\\n\\n首先看到这个题目,可以想到求出 node1 和 node2 到其它所有点的最短距离,然后遍历可选择的「中间点」。所以现在的问题就是如何求出其它点到起点的最短路径\\n\\n「最短路径」算法有 最短路径-Dijkstra 和 无权值最短路径算法(BFS)\\n\\n「Dijkstra」适用于加权有向图,没有负权重边,且无环,一般是求起点到其它所有…","guid":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/by-lfool-t4y7","author":"lfool","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T06:10:19.957Z","media":[{"url":"https://pic.leetcode-cn.com/1659267611-OFeQfO-2.svg","type":"photo","width":0,"height":0,"blurhash":""}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java 拓扑排序 DFS","url":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/by-darkbin-35bs","content":"public int longestCycle(int[] edges) {\\n int n = edges.length;\\n // 计算每个节点的入度\\n int[] indegree = new int[n];\\n for (int i = 0; i < n; i++) {\\n if (edges[i] == -1) continue;\\n indegree[edges[i]]++;\\n }\\n boolean[] notInCycle = new boolean[n];\\n Queue<Integer> q = new LinkedList<>();\\n // 入度为 0 的节点加入队列\\n for (int i = 0; i < n; i++) {\\n if (indegree[i] == 0) q.offer(i);\\n }\\n // 找出所有不在环中的节点\\n while (!q.isEmpty()) {\\n int cur = q.poll();\\n // 标记点是否在环中\\n notInCycle[cur] = true;\\n if (edges[cur] == -1) continue;\\n if (--indegree[edges[cur]] == 0) q.offer(edges[cur]);\\n }\\n int ans = -1;\\n for (int i = 0; i < n; i++) {\\n // 不在环中,跳过\\n if (notInCycle[i]) continue;\\n indegree[i] = 0;\\n int cnt = 0;\\n q.offer(i);\\n // 求一个环的长度\\n while(!q.isEmpty()) {\\n int cur = q.poll();\\n cnt++;\\n notInCycle[i] = true;\\n if (--indegree[edges[cur]] == 0) q.offer(edges[cur]);\\n }\\n // 更新 ans\\n ans = Math.max(ans, cnt);\\n }\\n return ans;\\n}\\n
思路
\\n先做一遍拓扑排序,将不在环中的节点排除;随后通过dfs求得每个环中的节点数。
\\n代码
\\n###java
\\n\\n","description":"思路 先做一遍拓扑排序,将不在环中的节点排除;随后通过dfs求得每个环中的节点数。\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n \\n private int ans = 0;\\n private boolean[] vis;\\n \\n // dfs求的环中节点个数\\n public void dfs(int p, int len, int n, int[] edges){\\n ans = Math.max(ans, len);\\n if (edges[p] != -1 && !vis…","guid":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/by-darkbin-35bs","author":"darkbin","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T04:09:42.170Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"『 拓扑排序 + BFS 』分离出环,并计算有向环的长度","url":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/by-flix-gcit","content":"class Solution {\\n \\n private int ans = 0;\\n private boolean[] vis;\\n \\n // dfs求的环中节点个数\\n public void dfs(int p, int len, int n, int[] edges){\\n ans = Math.max(ans, len);\\n if (edges[p] != -1 && !vis[edges[p]]){\\n vis[edges[p]] = true;\\n dfs(edges[p], len + 1, n, edges);\\n }\\n }\\n \\n public int longestCycle(int[] edges) {\\n int n = edges.length, cnt = 0;\\n vis = new boolean[n];\\n int[] indegree = new int[n];\\n Deque<Integer> q = new LinkedList<>();\\n for (int i = 0; i < n; ++i){\\n if (edges[i] != -1) ++indegree[edges[i]];\\n }\\n for (int i = 0; i < n; ++i){\\n if (indegree[i] == 0) q.addLast(i);\\n }\\n // 拓扑排序将非环中节点排除\\n while (!q.isEmpty()){\\n int cur = q.pollFirst();\\n vis[cur] = true;\\n ++cnt;\\n if (edges[cur] != -1) {\\n int next = edges[cur];\\n --indegree[next];\\n if (indegree[next] == 0) q.addLast(next);\\n }\\n }\\n // 无环返回-1\\n if (cnt == n) return -1;\\n // 遍历每个环求节点个数取最大值\\n for (int i = 0; i < n; ++i){\\n if (!vis[i]){\\n vis[i] = true;\\n dfs(i, 1, n, edges);\\n }\\n }\\n return ans;\\n }\\n}\\n
解题思路
\\n大致步骤可分为两步:
\\n1. 拓扑排序,分离出环:
\\n
\\n根据构建出的有向图,依次删除入度为 0 的节点,得到图中所有的环。
\\n如下图所示:\\n
{:width=500}
\\n
2. 求出每个环的大小:
\\n可采用深度优先(DFS)或广度优先(BFS)遍历,求出每个环的大小。\\n
\\n- 每个节点至多有一条出边,因此每个节点至多存在于一个环中。
\\n- 在环中,每个节点有且只有一个子代节点,因此实际操作中采用 广度优先搜索(BFS) 有着较大的优势。
\\n最终记录下 最长环 的长度即可。
\\n
\\n类似题目:
\\n\\n\\n
\\n\\n \\n\\n\\n题号 \\n题解 \\n难度 \\n\\n \\n\\n2127. 参加会议的最多员工数 \\n『三步解决困难题』:拓扑排序+有向环的长度+N叉树的最大深度 \\n困难 \\n
\\n代码
\\n###Python
\\n\\n","description":"解题思路 大致步骤可分为两步:\\n\\n\\n1. 拓扑排序,分离出环:\\n 根据构建出的有向图,依次删除入度为 0 的节点,得到图中所有的环。\\n 如下图所示:\\n\\n{:width=500}\\n\\n\\n2. 求出每个环的大小:\\n 可采用深度优先(DFS)或广度优先(BFS)遍历,求出每个环的大小。\\n\\n每个节点至多有一条出边,因此每个节点至多存在于一个环中。\\n在环中,每个节点有且只有一个子代节点,因此实际操作中采用 广度优先搜索(BFS) 有着较大的优势。\\n\\n最终记录下 最长环 的长度即可。\\n\\n类似题目:\\n\\n题号\\t 题解\\t 难度 2127. 参加会议的最多员工数\\t 『三步解决困难题…","guid":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/by-flix-gcit","author":"flix","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T04:08:42.712Z","media":[{"url":"https://pic.leetcode-cn.com/1641124850-INAdMu-huiyi-2.svg","type":"photo","width":565,"height":346,"blurhash":"LFSr},?btk?v_NtRMdjIRPoLafkC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java可达性分析","url":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/-by-mu-xin-31-xdb5","content":"class Solution:\\n def longestCycle(self, edges: List[int]) -> int:\\n \\n n = len(edges)\\n \\n # 记录节点入度\\n indeg = [0] * n\\n for u in edges:\\n if u == -1:\\n continue\\n indeg[u] += 1\\n \\n # 拓扑排序【得到有向环中的全部节点】\\n queue = [u for u in range(n) if indeg[u]==0]\\n while queue:\\n u = queue.pop()\\n v = edges[u] # 节点u指向节点v:u -> v\\n if v == -1:\\n continue\\n indeg[v] -= 1\\n if indeg[v] == 0: # 入度为 0 的节点入队列\\n queue.append(v)\\n \\n # 无环,直接返回-1\\n if max(indeg) == 0:\\n return -1\\n \\n # BFS计算环的大小\\n def bfs(u):\\n step = 0\\n while True:\\n if u in visited: # 完成闭环,返回环的长度\\n return step\\n visited.add(u)\\n u = edges[u] # 环中的下一个节点\\n step += 1\\n \\n # 遍历有向环中的所有节点\\n ans = 0 # 最长环\\n visited = set() # 防止重复访问\\n for u in range(n):\\n if indeg[u] == 0 or u in visited: # 每个节点至多存在于一个环中,且若已访问过则无需再次访问\\n continue\\n circle_len = bfs(u)\\n ans = max(ans, circle_len) # 尝试更新最长环\\n \\n return ans\\n\\n
执行用时:13 ms, 在所有 Java 提交中击败了100.00%的用户
\\n解题思路
\\n分析给定的两个节点的可达节点,然后记录下来对应的边数。
\\n如果对于某个节点,这两个节点都可达,则记录下来对应的较大值;对于所有的节点,依次更新这个较大值,并记录当前值所对应的节点即可。
\\n代码
\\n###java
\\n\\n","description":"执行用时:13 ms, 在所有 Java 提交中击败了100.00%的用户 解题思路\\n\\n分析给定的两个节点的可达节点,然后记录下来对应的边数。\\n\\n如果对于某个节点,这两个节点都可达,则记录下来对应的较大值;对于所有的节点,依次更新这个较大值,并记录当前值所对应的节点即可。\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int closestMeetingNode(int[] edges, int node1, int node2) {\\n int[] r1 = reach(node1, edges);…","guid":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/-by-mu-xin-31-xdb5","author":"mu-xin-31","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T04:06:17.423Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:最短路 / 我吹过你吹过的晚风(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/ji-suan-dao-mei-ge-dian-de-ju-chi-python-gr2u","content":"class Solution {\\n public int closestMeetingNode(int[] edges, int node1, int node2) {\\n int[] r1 = reach(node1, edges);\\n int[] r2 = reach(node2, edges);\\n int min = Integer.MAX_VALUE;\\n int ans=-1;\\n for (int i = 0; i < edges.length; i++) {\\n if (r1[i] == -1 || r2[i] == -1) continue;\\n int t = Math.max(r1[i], r2[i]);\\n if (t<min){\\n min=t;\\n ans=i;\\n }\\n }\\n return ans;\\n }\\n\\n //返回的数组记录了这个节点可以到达的元素的边数\\n public int[] reach(int node, int[] edges) {\\n int[] r = new int[edges.length];\\n int cur=node;\\n int next=edges[node];\\n int len=0;\\n //如果某个节点已经遍历过,就退出,防止死循环\\n while (next!=-1 && r[next]==0){\\n len++;\\n node=next;\\n next=edges[next];\\n r[node]=len;\\n }\\n for (int i=0;i<edges.length;i++){\\n //针对自身可能会有环\\n if (i==cur) r[i]=0;\\n else if (r[i]==0) r[i]=-1;\\n }\\n return r;\\n }\\n}\\n
方法一:计算最短路
\\n我们需要知道 $\\\\textit{node}_1$ 到每个点的最短路长度 $\\\\textit{dis}_1[i]$,以及 $\\\\textit{node}_2$ 到每个点的最短路长度 $\\\\textit{dis}_2[i]$。
\\n题目要我们计算的,是 $\\\\max(\\\\textit{dis}_1[i],\\\\textit{dis}_2[i])$ 的最小值对应的节点编号 $i$。若没有这样的节点,返回 $-1$。
\\n求最短路可以用 BFS 做。不过,由于本题输入的是 内向基环树(森林),每个连通块至多有一个环,我们可以用一个简单的循环求出 $\\\\textit{dis}_i$。
\\n\\nclass Solution:\\n def closestMeetingNode(self, edges: List[int], node1: int, node2: int) -> int:\\n n = len(edges)\\n def calc_dis(x: int) -> List[int]:\\n dis = [n] * n # 初始化成 n,表示无法到达或者尚未访问的节点\\n d = 0\\n # 从 x 出发,直到无路可走(x=-1)或者重复访问节点(dis[x]<n)\\n while x >= 0 and dis[x] == n:\\n dis[x] = d\\n d += 1\\n x = edges[x]\\n return dis\\n\\n dis1 = calc_dis(node1)\\n dis2 = calc_dis(node2)\\n\\n min_dis, ans = n, -1\\n for i, (d1, d2) in enumerate(zip(dis1, dis2)):\\n d = max(d1, d2)\\n if d < min_dis:\\n min_dis, ans = d, i\\n return ans\\n
\\nclass Solution {\\n public int closestMeetingNode(int[] edges, int node1, int node2) {\\n int[] dis1 = calcDis(edges, node1);\\n int[] dis2 = calcDis(edges, node2);\\n\\n int n = edges.length;\\n int minDis = n;\\n int ans = -1;\\n for (int i = 0; i < n; i++) {\\n int d = Math.max(dis1[i], dis2[i]);\\n if (d < minDis) {\\n minDis = d;\\n ans = i;\\n }\\n }\\n return ans;\\n }\\n\\n private int[] calcDis(int[] edges, int x) {\\n int n = edges.length;\\n int[] dis = new int[n];\\n Arrays.fill(dis, n); // n 表示无法到达或者尚未访问的节点\\n // 从 x 出发,直到无路可走(x=-1)或者重复访问节点(dis[x]<n)\\n for (int d = 0; x >= 0 && dis[x] == n; x = edges[x]) {\\n dis[x] = d++;\\n }\\n return dis;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int closestMeetingNode(vector<int>& edges, int node1, int node2) {\\n int n = edges.size();\\n auto calc_dis = [&](int x) {\\n vector<int> dis(n, n); // 初始化成 n,表示无法到达或者尚未访问的节点\\n // 从 x 出发,直到无路可走(x=-1)或者重复访问节点(dis[x]<n)\\n for (int d = 0; x >= 0 && dis[x] == n; x = edges[x]) {\\n dis[x] = d++;\\n }\\n return dis;\\n };\\n\\n vector<int> dis1 = calc_dis(node1);\\n vector<int> dis2 = calc_dis(node2);\\n\\n int min_dis = n, ans = -1;\\n for (int i = 0; i < n; i++) {\\n int d = max(dis1[i], dis2[i]);\\n if (d < min_dis) {\\n min_dis = d;\\n ans = i;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc closestMeetingNode(edges []int, node1, node2 int) int {\\nn := len(edges)\\ncalcDis := func(x int) []int {\\ndis := make([]int, n)\\nfor i := range dis {\\ndis[i] = n // n 表示无法到达或者尚未访问的节点\\n}\\n// 从 x 出发,直到无路可走(x=-1)或者重复访问节点(dis[x]<n)\\nfor d := 0; x >= 0 && dis[x] == n; x = edges[x] {\\ndis[x] = d\\nd++\\n}\\nreturn dis\\n}\\n\\ndis1 := calcDis(node1)\\ndis2 := calcDis(node2)\\n\\nminDis, ans := n, -1\\nfor i, d1 := range dis1 {\\nd := max(d1, dis2[i])\\nif d < minDis {\\nminDis, ans = d, i\\n}\\n}\\nreturn ans\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{edges}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:「我吹过你吹过的晚风」
\\n为方便描述,把 $\\\\textit{node}_1$ 和 $\\\\textit{node}_2$ 分别视作两个人 $x$ 和 $y$。
\\n两个人顺着 $\\\\textit{edges}$ 移动,标记访问过的节点,即更新 $\\\\textit{visX}[x]=\\\\texttt{true}$ 以及 $\\\\textit{visY}[y]=\\\\texttt{true}$。
\\n如果某个时刻,$x$ 到达 $y$ 走过的路,即 $\\\\textit{visY}[x]$ 为 $\\\\texttt{true}$,那么当前 $x$ 所在节点,就是两人都可以到达的节点,且距离最近。
\\n如果某个时刻,$y$ 到达 $x$ 走过的路,即 $\\\\textit{visX}[y]$ 为 $\\\\texttt{true}$,那么当前 $y$ 所在节点,就是两人都可以到达的节点,且距离最近。
\\n如果某个时刻上面两种情况同时发生,返回二者的最小值。
\\n如果两个人都走到死路或者各自走过的路,返回 $-1$。
\\n\\nclass Solution:\\n def closestMeetingNode(self, edges: List[int], x: int, y: int) -> int:\\n ans = n = len(edges)\\n vis_x = [False] * n\\n vis_y = [False] * n\\n\\n while not vis_x[x] or not vis_y[y]: # x 或 y 没有访问过\\n vis_x[x] = vis_y[y] = True # 标记访问过\\n\\n if vis_y[x]: # 我吹过你吹过的晚风\\n ans = x\\n if vis_x[y]:\\n ans = min(ans, y) # 如果有多个答案,返回最小的节点编号\\n if ans < n:\\n return ans\\n\\n if edges[x] >= 0:\\n x = edges[x] # 继续走\\n if edges[y] >= 0:\\n y = edges[y] # 继续走\\n\\n return -1\\n
\\nclass Solution {\\n public int closestMeetingNode(int[] edges, int x, int y) {\\n int n = edges.length;\\n int ans = n;\\n boolean[] visX = new boolean[n];\\n boolean[] visY = new boolean[n];\\n\\n while (!visX[x] || !visY[y]) { // x 或 y 没有访问过\\n visX[x] = visY[y] = true; // 标记访问过\\n\\n if (visY[x]) { // 我吹过你吹过的晚风\\n ans = x;\\n }\\n if (visX[y]) {\\n ans = Math.min(ans, y); // 如果有多个答案,返回最小的节点编号\\n }\\n if (ans < n) {\\n return ans;\\n }\\n\\n if (edges[x] >= 0) {\\n x = edges[x]; // 继续走\\n }\\n if (edges[y] >= 0) {\\n y = edges[y]; // 继续走\\n }\\n }\\n\\n return -1;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int closestMeetingNode(vector<int>& edges, int x, int y) {\\n int n = edges.size();\\n int ans = n;\\n vector<int8_t> vis_x(n), vis_y(n);\\n\\n while (!vis_x[x] || !vis_y[y]) { // x 或 y 没有访问过\\n vis_x[x] = vis_y[y] = true; // 标记访问过\\n\\n if (vis_y[x]) { // 我吹过你吹过的晚风\\n ans = x;\\n }\\n if (vis_x[y]) {\\n ans = min(ans, y); // 如果有多个答案,返回最小的节点编号\\n }\\n if (ans < n) {\\n return ans;\\n }\\n\\n if (edges[x] >= 0) {\\n x = edges[x]; // 继续走\\n }\\n if (edges[y] >= 0) {\\n y = edges[y]; // 继续走\\n }\\n }\\n\\n return -1;\\n }\\n};\\n
\\nfunc closestMeetingNode(edges []int, x, y int) int {\\nn := len(edges)\\nans := n\\nvisX := make([]bool, n)\\nvisY := make([]bool, n)\\n\\nfor !visX[x] || !visY[y] { // x 或 y 没有访问过\\nvisX[x] = true // 标记访问过\\nvisY[y] = true\\n\\nif visY[x] { // 我吹过你吹过的晚风\\nans = x\\n}\\nif visX[y] {\\nans = min(ans, y) // 如果有多个答案,返回最小的节点编号\\n}\\nif ans < n {\\nreturn ans\\n}\\n\\nif edges[x] >= 0 {\\nx = edges[x] // 继续走\\n}\\nif edges[y] >= 0 {\\ny = edges[y] // 继续走\\n}\\n}\\n\\nreturn -1\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{edges}$ 的长度。单看 $x$ 或 $y$,每次循环会标记一个没有访问过的节点,一共有 $n$ 个节点,所以至多循环 $n$ 次。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n思考题
\\n\\n
\\n- 如果输入的不止两个节点 $\\\\textit{node}_1$ 和 $\\\\textit{node}_2$,而是一个很长的 $\\\\textit{nodes}$ 列表,要怎么做呢?
\\n- 如果输入的是 $\\\\textit{queries}$ 询问数组,每个询问包含两个节点 $\\\\textit{node}_1$ 和 $\\\\textit{node}_2$,你需要快速计算
\\nclosestMeetingNode(edges, node1, node2)
,要怎么做呢?解答:见 视频讲解 第三题。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:计算最短路 我们需要知道 $\\\\textit{node}_1$ 到每个点的最短路长度 $\\\\textit{dis}_1[i]$,以及 $\\\\textit{node}_2$ 到每个点的最短路长度 $\\\\textit{dis}_2[i]$。\\n\\n题目要我们计算的,是 $\\\\max(\\\\textit{dis}_1[i],\\\\textit{dis}_2[i])$ 的最小值对应的节点编号 $i$。若没有这样的节点,返回 $-1$。\\n\\n求最短路可以用 BFS 做。不过,由于本题输入的是 内向基环树(森林),每个连通块至多有一个环,我们可以用一个简单的循环求出 $\\\\textit…","guid":"https://leetcode.cn/problems/find-closest-node-to-given-two-nodes//solution/ji-suan-dao-mei-ge-dian-de-ju-chi-python-gr2u","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T04:06:12.391Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"在操场上跑步,跑一圈要多久(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/nei-xiang-ji-huan-shu-zhao-huan-li-yong-pmqmr","content":"前言:关于图的性质
\\n「每个节点至多有一条出边」意味着,对于图中任意一个大小为 $m$ 的连通块,有 $m$ 个点,每个点至多出去一条边,所以连通块至多有 $m$ 条边。
\\n我们知道,$m$ 个点 $m-1$ 条边的连通图是一棵树,在树上增加一条有向边,至多会形成一个环。(这样的图叫做内向基环树)
\\n思路
\\n\\n
假设你在跑步,操场跑道是 $3\\\\to 2\\\\to 4\\\\to 3$(上图红线)。
\\n你想知道跑一圈要多久。你从节点 $0$ 开始跑,跑到节点 $3$ 的时候,记录当前时间为 $t_1=2$,再次跑到节点 $3$ 的时候,记录当前时间为 $t_2=5$,那么跑一圈就需要 $t_2-t_1=5-2=3$ 个单位时间。
\\n如果每访问一个节点,计时器就加一,那么 $t_2-t_1$ 就是跑道长度,即环长。
\\n算法
\\n初始时间为 $\\\\textit{curTime}=1$。遍历图,每访问到一个新的节点 $x$,就记录首次访问时间 $\\\\textit{visTime}[x]=\\\\textit{curTime}$,然后将 $\\\\textit{curTime}$ 加一。
\\n假设我们从节点 $i$ 开始。首先记录开始时间 $\\\\textit{startTime}=\\\\textit{curTime}$,然后继续走,如果走到死路,或者找到了一个之前访问过的点 $x$,则退出循环。
\\n退出循环后,分类讨论:
\\n\\n
\\n- 如果 $\\\\textit{visTime}[x] < \\\\textit{startTime}$,说明 $x$ 不是在本轮循环中访问的。例如上图从节点 $0$ 开始,访问节点 $0,3,2,4$。然后接着从节点 $1$ 开始,访问节点 $3$,发现 $\\\\textit{visTime}[3]$ 比访问节点 $1$ 的时间还要早,那么包含节点 $3$ 的环长我们之前已经计算过了,无需再次计算。
\\n- 如果 $\\\\textit{visTime}[x] \\\\ge \\\\textit{startTime}$,说明 $x$ 是在本轮循环中访问的,且被访问了两次。这只有一种可能,就是 $x$ 在环上。根据前后两次访问 $x$ 的时间差,就能算出环长,即 $\\\\textit{curTime}-\\\\textit{visTime}[x]$。
\\n\\n\\n注:本题保证每个连通块至多有一个环,所以可以根据时间差算出环长。如果没有这个保证,时间差算出的可能不是最长环。一般图的最长环是 NP-hard 问题。
\\n取所有环长的最大值作为答案。如果图中无环,则返回 $-1$。
\\n\\nclass Solution:\\n def longestCycle(self, edges: List[int]) -> int:\\n n = len(edges)\\n ans = -1\\n cur_time = 1 # 当前时间\\n vis_time = [0] * n # 首次访问 x 的时间\\n for x in range(n):\\n start_time = cur_time # 本轮循环的开始时间\\n while x != -1 and vis_time[x] == 0: # 没有访问过 x\\n vis_time[x] = cur_time # 记录访问 x 的时间\\n cur_time += 1\\n x = edges[x] # 访问下一个节点\\n if x != -1 and vis_time[x] >= start_time: # x 在本轮循环中访问了两次,说明 x 在环上\\n ans = max(ans, cur_time - vis_time[x]) # 前后两次访问 x 的时间差,即为环长\\n return ans # 如果没有找到环,返回的是 ans 的初始值 -1\\n
\\nclass Solution {\\n public int longestCycle(int[] edges) {\\n int n = edges.length;\\n int ans = -1;\\n int curTime = 1; // 当前时间\\n int[] visTime = new int[n]; // 首次访问 x 的时间\\n for (int i = 0; i < n; i++) {\\n int x = i;\\n int startTime = curTime; // 本轮循环的开始时间\\n while (x != -1 && visTime[x] == 0) { // 没有访问过 x\\n visTime[x] = curTime++; // 记录访问 x 的时间\\n x = edges[x]; // 访问下一个节点\\n }\\n if (x != -1 && visTime[x] >= startTime) { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = Math.max(ans, curTime - visTime[x]); // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n return ans; // 如果没有找到环,返回的是 ans 的初始值 -1\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int longestCycle(vector<int>& edges) {\\n int n = edges.size();\\n int ans = -1;\\n int cur_time = 1; // 当前时间\\n vector<int> vis_time(n); // 首次访问 x 的时间\\n for (int i = 0; i < n; i++) {\\n int x = i;\\n int start_time = cur_time; // 本轮循环的开始时间\\n while (x != -1 && vis_time[x] == 0) { // 没有访问过 x\\n vis_time[x] = cur_time++; // 记录访问 x 的时间\\n x = edges[x]; // 访问下一个节点\\n }\\n if (x != -1 && vis_time[x] >= start_time) { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = max(ans, cur_time - vis_time[x]); // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n return ans; // 如果没有找到环,返回的是 ans 的初始值 -1\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint longestCycle(int* edges, int edgesSize) {\\n int ans = -1;\\n int cur_time = 1; // 当前时间\\n int* vis_time = calloc(edgesSize, sizeof(int)); // 首次访问 x 的时间\\n for (int i = 0; i < edgesSize; i++) {\\n int x = i;\\n int start_time = cur_time; // 本轮循环的开始时间\\n while (x != -1 && vis_time[x] == 0) { // 没有访问过 x\\n vis_time[x] = cur_time++; // 记录访问 x 的时间\\n x = edges[x]; // 访问下一个节点\\n }\\n if (x != -1 && vis_time[x] >= start_time) { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = MAX(ans, cur_time - vis_time[x]); // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n free(vis_time);\\n return ans; // 如果没有找到环,返回的是 ans 的初始值 -1\\n}\\n
\\nfunc longestCycle(edges []int) int {\\n ans := -1\\n curTime := 1 // 当前时间\\n visTime := make([]int, len(edges)) // 首次访问 x 的时间\\n for x := range edges {\\n startTime := curTime // 本轮循环的开始时间\\n for x != -1 && visTime[x] == 0 { // 没有访问过 x\\n visTime[x] = curTime // 记录访问 x 的时间\\n curTime++\\n x = edges[x] // 访问下一个节点\\n }\\n if x != -1 && visTime[x] >= startTime { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = max(ans, curTime-visTime[x]) // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n return ans // 如果没有找到环,返回的是 ans 的初始值 -1\\n}\\n
\\nvar longestCycle = function(edges) {\\n const n = edges.length; \\n const visTime = Array(n).fill(0); // 首次访问 x 的时间\\n let curTime = 1; // 当前时间\\n let ans = -1;\\n for (let i = 0; i < n; i++) {\\n let x = i;\\n let startTime = curTime; // 本轮循环的开始时间\\n while (x !== -1 && visTime[x] === 0) { // 没有访问过 x\\n visTime[x] = curTime++; // 记录访问 x 的时间\\n x = edges[x]; // 访问下一个节点\\n }\\n if (x !== -1 && visTime[x] >= startTime) { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = Math.max(ans, curTime - visTime[x]); // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n return ans; // 如果没有找到环,返回的是 ans 的初始值 -1\\n};\\n
\\nimpl Solution {\\n pub fn longest_cycle(edges: Vec<i32>) -> i32 {\\n let n = edges.len();\\n let mut ans = -1;\\n let mut cur_time = 1; // 当前时间\\n let mut vis_time = vec![0; n]; // 首次访问 x 的时间\\n for mut x in 0..n {\\n let start_time = cur_time; // 本轮循环的开始时间\\n while x < n && vis_time[x] == 0 { // 没有访问过 x\\n vis_time[x] = cur_time; // 记录访问 x 的时间\\n cur_time += 1;\\n x = edges[x] as usize; // 访问下一个节点,如果是 -1 则转换成 MAX\\n }\\n if x < n && vis_time[x] >= start_time { // x 在本轮循环中访问了两次,说明 x 在环上\\n ans = ans.max(cur_time - vis_time[x]); // 前后两次访问 x 的时间差,即为环长\\n }\\n }\\n ans // 如果没有找到环,返回的是 ans 的初始值 -1\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{edges}$ 的长度。虽然写了个二重循环,但每个节点只会记录一次访问时间,所以二重循环的总循环次数是 $\\\\mathcal{O}(n)$ 的。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n思考题
\\n\\n
\\n- 改成返回最长环上的所有节点(返回一个列表),要怎么做?欢迎在评论区分享你的思路/代码。
\\n- 改成最短环呢?对于一般图,如何计算最短环?见 2608. 图中的最短环。
\\n更多相似题目,见下面图论题单中的「§2.3 基环树」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前言:关于图的性质 「每个节点至多有一条出边」意味着,对于图中任意一个大小为 $m$ 的连通块,有 $m$ 个点,每个点至多出去一条边,所以连通块至多有 $m$ 条边。\\n\\n我们知道,$m$ 个点 $m-1$ 条边的连通图是一棵树,在树上增加一条有向边,至多会形成一个环。(这样的图叫做内向基环树)\\n\\n思路\\n\\n假设你在跑步,操场跑道是 $3\\\\to 2\\\\to 4\\\\to 3$(上图红线)。\\n\\n你想知道跑一圈要多久。你从节点 $0$ 开始跑,跑到节点 $3$ 的时候,记录当前时间为 $t_1=2$,再次跑到节点 $3$ 的时候,记录当前时间为 $t_2=5…","guid":"https://leetcode.cn/problems/longest-cycle-in-a-graph//solution/nei-xiang-ji-huan-shu-zhao-huan-li-yong-pmqmr","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-31T04:05:51.590Z","media":[{"url":"https://pic.leetcode.cn/1742870244-vqHncL-lc2360.png","type":"photo","width":335,"height":191,"blurhash":"LASidI?^M{~W_3R*~qV@Io-px]t7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Python: 贪心,尝试解释清楚每一步","url":"https://leetcode.cn/problems/smallest-range-ii//solution/by-gg_boy-vnio","content":"910. 最小差值 II
\\n一、题目概述
\\n给你一个整数数组
\\nnums
,和一个整数k
。对于每个下标
\\ni
(0 <= i < nums.length
),将nums[i]
变成nums[i] + k
或nums[i] - k
。\\n
nums
的 分数 是nums
中最大元素和最小元素的差值。在更改每个下标对应的值之后,返回
\\nnums
的最小 分数 。示例 1:
\\n\\n输入:nums = [1], k = 0\\n输出:0\\n解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。\\n
示例 2:
\\n\\n输入:nums = [0,10], k = 2\\n输出:6\\n解释:将数组变为 [2, 8] 。分数 = max(nums) - min(nums) = 8 - 2 = 6 。\\n
示例 3:
\\n\\n输入:nums = [1,3,6], k = 3\\n输出:3\\n解释:将数组变为 [4, 6, 3] 。分数 = max(nums) - min(nums) = 6 - 3 = 3 。\\n
提示:
\\n\\n
\\n- 1 <= nums.length <= $10^4$
\\n- 0 <= nums[i] <= $10^4$
\\n- 0 <= k <= $10^4$
\\n二、解题思路
\\n原问题:对每个元素可以进行$+k,-k$得操作,请你返回操作之后$nums$的最小分数(最大值和最小值的差最小)
\\n换句话说,我们需要让$nums$的所有元素,尽可能地靠拢,较小的就要变大,较大的就要变小。
\\n我认为难点在这里,什么叫较小,什么叫较大。
\\n考虑这么一个问题:
\\n假设我们已经知道这个区间,也就是说,我告诉你,你需要把这些元素值,放在区间$[x,y]$内
\\n那么,你应该如何对元素值进行操作呢?
\\n小于$x$的,我就增大。大于$y$的,我就减小。这个很好理解。
\\n那反过来,我不知道这个区间,该如何对元素进行操作,最终找到这个最小分数?
\\n
\\n请再考虑两个问题:
\\n1.如果一个数$x$变大,那么比它小的数,是不是一定也会变大?
\\n2.如果一个数$x$变小,那么比它大的数,是不是一定也会变小?
\\n用通俗的语言来描述上面两个问题
\\n小明拿到了钱,比他穷的人,应不应该拿到钱?
\\n小明纳了税,比他富有的人,应不应该纳税?
\\n答案是显然的,因此我们得到了一个结论:
\\n如果$x$变大,小于$x$的数,一定也会变大
\\n如果$x$变小,大于$x$的数,一定也会变小
\\n故我们可以知道,所有的元素分为两个部分,一部分增大,一部分减小。
\\n因此就一定存在一个分界点,大于分界点的元素减小,小于分界点的元素增大
\\n那至于哪一点是分界点呢,我们就遍历试试看吧
\\n完整代码:
\\n###python
\\n\\nclass Solution:\\n def smallestRangeII(self, nums: List[int], k: int) -> int:\\nn = len(nums)\\n res = inf\\n res = max(nums) - min(nums) # 不存在分界点的时候\\n for i in range(n): # 枚举分界点\\n temp = nums[:]\\n for j in range(n):\\n if nums[j] <= nums[i]:\\n temp[j] = nums[j] + k\\n else:\\n temp[j] = nums[j] - k\\n \\n res = min(res, max(temp) - min(temp))\\n return res\\n
上述代码会超时,因为我们枚举每一次分界点,都进行了一次数组遍历,复杂度是$O(n^2)$
\\n考虑优化:
\\n事先对数组进行升序排序,如果分界点是$i$
\\n则$[0...i]$的所有元素都需要增大,$[i+1...n-1]$的所有元素都需要减小
\\n由于我们只需要求元素最大值减去最小值,其实没必要遍历所有的元素
\\n最大值只可能存在于$nums[n-1]-k, nums[i]+k$
\\n最小值只能存在于$nums[i+1]-k, nums[0]+k$
\\n因为元素分为变大和变小的两个群体,因此最大值存在于两个群体的最大值
\\n最小值存在于两个群体的最小值。
\\n又因为数组是升序的,故很容易求得最大值和最小值
\\n优化代码如下:
\\n###python
\\n\\n","description":"一、题目概述 给你一个整数数组 nums,和一个整数 k 。\\n\\n对于每个下标 i(0 <= i < nums.length),将 nums[i] 变成 nums[i] + k 或 nums[i] - k 。\\n\\nnums 的 分数 是 nums 中最大元素和最小元素的差值。\\n\\n在更改每个下标对应的值之后,返回 nums 的最小 分数 。\\n\\n示例 1:\\n\\n输入:nums = [1], k = 0\\n输出:0\\n解释:分数 = max(nums) - min(nums) = 1 - 1 = 0 。\\n\\n\\n示例 2:\\n\\n输入:nums = [0,10], k = 2\\n输出:6…","guid":"https://leetcode.cn/problems/smallest-range-ii//solution/by-gg_boy-vnio","author":"GG_boy","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-28T09:54:51.244Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】数据结构实现题","url":"https://leetcode.cn/problems/design-skiplist//solution/by-ac_oier-38rd","content":"class Solution:\\n def smallestRangeII(self, nums: List[int], k: int) -> int:\\nn = len(nums)\\n res = inf\\n nums.sort()\\n res = nums[-1] - nums[0] # 所有人都要失去或者得到钱得情况\\n for i in range(n-1):\\n rich = max(nums[-1]-k, nums[i]+k)\\n poor = min(nums[i+1]-k, nums[0]+k)\\n res = min(res, rich-poor)\\n return res\\n
数据结构
\\n对于单链表而言,所有的操作(增删改查)都遵循「先查找,再操作」的步骤,这导致在单链表上所有操作复杂度均为 $O(n)$(瓶颈在于查找过程)。
\\n跳表相对于单链表,则是通过引入「多层」链表来优化查找过程,其中每层链表均是「有序」链表:
\\n\\n
\\n- \\n
\\n对于单链表的
\\nNode
设计而言,我们只需存储对应的节点值val
,以及当前节点的下一节点的指针ne
即可(ne
为一指针变量)- \\n
\\n对于跳表来说,除了存储对应的节点值
\\nval
以外,我们需要存储当前节点在「每一层」的下一节点指针ne
(ne
为指针数组)跳表的
\\nlevel
编号从下往上递增,最下层的链表为元素最全的有序单链表,而查找过程则是按照level
从上往下进行。\\n
操作次数的数据范围为 $n = 5 \\\\times 10^4$,因此设计最大的
\\nlevel
为 $\\\\log{n}$ 即可确保复杂度,但由于操作次数 $n = 5 \\\\times 10^4$ 不可能全是add
操作,因此这里直接取level
为 $10$。同时为了简化,建立一个哨兵节点
\\nhe
,哨兵值的值应当足够小(根据数据范围,设定为 $-1$ 即可),所有的操作(假设当前操作的传入值为t
),先进行统一化的查找:查找出每一层比t
严格小的最后一个节点,将其存成ns
数组。即 $ns[i]$ 为 $level = i$ 层严格比 $t$ 小的最后一个节点。再根据不同的操作进行下一步动作:
\\n\\n
\\n- \\n
search
操作:由于最后一层必然是元素最全的单链表,因此可以直接访问ns[0].ne[0]
即是所有元素中满足大于等于t
的第一个元素,通过判断其值与传入值t
的大小关系来决定结果;- \\n
add
操作:由于最后一层必然是元素最全的单链表,因此我们「从下往上」进行插入,最底下一层必然要插入,然后以一半的概率往上传递;- \\n
erase
操作:与add
操作互逆,按照「从下往上」的顺序进行删除。需要注意的是,由于相同的值在跳表中可能存在多个,因此我们在「从下往上」删除过程中需要判断待删除的元素与ns[0].ne[0]
是否为同一元素(即要判断地址是否相同,而不是值相同)。代码:
\\n###Java
\\n\\nclass Skiplist {\\n int level = 10;\\n class Node {\\n int val;\\n Node[] ne = new Node[level];\\n Node (int _val) {\\n val = _val;\\n }\\n }\\n Random random = new Random();\\n Node he = new Node(-1);\\n void find(int t, Node[] ns) {\\n Node cur = he;\\n for (int i = level - 1; i >= 0; i--) {\\n while (cur.ne[i] != null && cur.ne[i].val < t) cur = cur.ne[i];\\n ns[i] = cur;\\n }\\n }\\n public boolean search(int t) {\\n Node[] ns = new Node[level];\\n find(t, ns);\\n return ns[0].ne[0] != null && ns[0].ne[0].val == t;\\n }\\n public void add(int t) {\\n Node[] ns = new Node[level];\\n find(t, ns);\\n Node node = new Node(t);\\n for (int i = 0; i < level; i++) {\\n node.ne[i] = ns[i].ne[i];\\n ns[i].ne[i] = node;\\n if (random.nextInt(2) == 0) break;\\n }\\n }\\n public boolean erase(int t) {\\n Node[] ns = new Node[level];\\n find(t, ns);\\n Node node = ns[0].ne[0];\\n if (node == null || node.val != t) return false;\\n for (int i = 0; i < level && ns[i].ne[i] == node; i++) ns[i].ne[i] = ns[i].ne[i].ne[i];\\n return true;\\n }\\n}\\n
###TypeScript
\\n\\nconst level: number = 10\\nclass TNode {\\n val: number\\n ne: TNode[] = new Array<TNode>(level)\\n constructor(_val: number) {\\n this.val = _val\\n } \\n}\\nclass Skiplist {\\n he: TNode = new TNode(-1)\\n find(t: number, ns: TNode[]): void {\\n let cur = this.he\\n for (let i = level - 1; i >= 0; i--) {\\n while (cur.ne[i] != null && cur.ne[i].val < t) cur = cur.ne[i]\\n ns[i] = cur\\n }\\n }\\n search(t: number): boolean {\\n let ns: TNode[] = new Array<TNode>(level)\\n this.find(t, ns)\\n return ns[0].ne[0] != null && ns[0].ne[0].val == t\\n }\\n add(t: number): void {\\n let ns: TNode[] = new Array<TNode>(level)\\n this.find(t, ns)\\n const node = new TNode(t)\\n for (let i = 0; i < level; i++) {\\n node.ne[i] = ns[i].ne[i]\\n ns[i].ne[i] = node\\n if (Math.round(Math.random()) == 0) break\\n }\\n }\\n erase(t: number): boolean {\\n let ns: TNode[] = new Array<TNode>(level)\\n this.find(t, ns)\\n const node = ns[0].ne[0]\\n if (node == null || node.val != t) return false\\n for (let i = 0; i < level && ns[i].ne[i] == node; i++) ns[i].ne[i] = ns[i].ne[i].ne[i]\\n return true\\n }\\n}\\n
\\n
\\n- 时间复杂度:所有操作的复杂度瓶颈在于
\\nfind
查找,find
查找期望复杂度为 $O(\\\\log{n})$- 空间复杂度:$O(n)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"数据结构 对于单链表而言,所有的操作(增删改查)都遵循「先查找,再操作」的步骤,这导致在单链表上所有操作复杂度均为 $O(n)$(瓶颈在于查找过程)。\\n\\n跳表相对于单链表,则是通过引入「多层」链表来优化查找过程,其中每层链表均是「有序」链表:\\n\\n对于单链表的 Node 设计而言,我们只需存储对应的节点值 val,以及当前节点的下一节点的指针 ne 即可(ne 为一指针变量)\\n\\n对于跳表来说,除了存储对应的节点值 val 以外,我们需要存储当前节点在「每一层」的下一节点指针 ne(ne 为指针数组)\\n\\n跳表的 level 编号从下往上递增…","guid":"https://leetcode.cn/problems/design-skiplist//solution/by-ac_oier-38rd","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-26T01:36:29.043Z","media":[{"url":"https://pic.leetcode-cn.com/1658799970-fukqFh-image.png","type":"photo","width":1078,"height":356,"blurhash":"LIRp8=%M$|?a~pRlazof-;t6Rka$"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"设计跳表【跳跃表详解】","url":"https://leetcode.cn/problems/design-skiplist//solution/she-ji-tiao-biao-by-capital-worker-3vqk","content":"1、什么是跳表?
\\n
\\n在传统的单链表中,每个元素都存放这下一个元素的引用,我们查找元素时,需要从链表头开始从前向后遍历,查找的时间复杂度为O(n)。
\\n
\\n传统链表的查询效率非常低。那有没有什么办法提高效率呢?我们可以采用空间换时间的办法,将上面的每两个元素抽出来做一个类似于索引的链表。
\\n
\\n
\\n假设我们要查询6,我们从上层链表开始遍历,当遍历到7时,大于目标值,我们需要到下一层接着找目标值。在上述遍历中,我们只需要遍历1、3、5、5、6即可找到目标结点,在普通的链表中需要1、2、3、4、5、6才能找到。
\\n
\\n
\\n也许有人会说,这才节约了1次,其实只是我们的数据量比较少且层数比较少,假如我们有1000万条数据,减少一半就是500万条数据。随着我们层数的增加,查询的效率接近二分查找的O(logn),在空间复杂度上,假如我们每层都是原来的1/2,上层元素的结点和为:n/2 + n/4 + n/8 +...,会无限接近n,所以空间复杂度为O(n)。
\\n2、跳表如何插入数据?
\\n插入数据也很简单,跳表的原始链表需要保持有序,所以我们会向查找元素一样,找到元素应该插入的位置。
\\n
\\n
\\n但是这样插入会有问题,如下图所示假如一直往原始列表中添加数据,但是不更新上层元素,就可能出现两个节点之间数据非常多的情况,极端情况,跳表退化为单链表,从而使得查找效率退化为O(n)
\\n
\\n我们如何去维护上层元素呢?比较容易理解的做法就是完全重建上层元素,我们每次插入数据后,都把这个跳表的上层元素删掉全部重建,重建的时间复杂度是多少呢?因为上层元素的空间复杂度是O(n),即:上层元素节点的个数是O(n)级别,时间复杂度是O(n)。导致每次插入的时间复杂度也变为了O(n),而不是O(logn)。
\\n由于我们是均匀的选取n/2个元素作为上一层的元素,我们也可以采用随机的方式,也就是在链表中随机的选取n/2个元素作为他的上一层元素,并且当原始链表中元素数量足够大,且抽取足够随机的话,我们得到的上层元素是均匀的。于是我们可以在每次新插入元素的时候,一定要插入第一层,有1/2的概率插入第二层、1/4的概率插入第三层、1/8的概率插入第四层。当每次有数据要插入时,先通过概率算法告诉我们这个元素需要插入到几层中。(SKIPLIST_P配置0.5 实际代码参考了zset配置了0.25 也就是1/4的概率插入第二层、1/16的概率插入第三层依次类推)
\\n###java
\\n\\n/**\\n * 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且\\n * 1/2 的概率返回 2\\n * 1/4 的概率返回 3\\n * 1/8 的概率返回 4 以此类推\\n */\\n private int randomLevel() {\\n int level = 1;\\n // 当 level < MAX_LEVEL,且随机数小于设定的晋升概率时,level + 1\\n while (Math.random() < SKIPLIST_P && level < MAX_LEVEL) {\\n level++;\\n }\\n return level;\\n }\\n
上述代码可以实现我们的功能,而且,我们的例子中概率 SKIPLIST_P 设置的 1/2,即:每两个结点抽出一个结点作为上一层的结点。如果我们想节省空间利用率,可以适当的降低代码中的 SKIPLIST_P,从而减少上层元素个数,Redis 的 zset 中 SKIPLIST_P 设定的 0.25,MAX_LEVEL为32。
\\n假设插入5,随机了2层,则给第一层和第二层都插入元素
\\n
\\n
\\n代码如下###java
\\n\\npublic void add(int num) {\\n //存放更新的位置\\n Node[] update = new Node[MAX_LEVEL];\\n Arrays.fill(update, head);\\n Node cur = this.head;\\n for (int i = curLevel - 1; i >= 0; i--) {\\n //找到所有层的前驱结点\\n while (cur.next[i] != null && cur.next[i].val < num) {\\n cur = cur.next[i];\\n }\\n update[i] = cur;\\n }\\n int randomLevel = randomLevel();\\n //更新最高的层数\\n this.curLevel = Math.max(this.curLevel, randomLevel);\\n Node newNode = new Node(num, randomLevel);\\n //插入随机出来的所有level\\n for (int i = 0; i < randomLevel; i++) {\\n newNode.next[i] = update[i].next[i];\\n update[i].next[i] = newNode;\\n }\\n }\\n
3、跳表如何删除数据?
\\n删除元素的过程跟查找元素的过程类似,只不过在查找的路径上如果发现了要删除的元素 ,则执行删除操作。我们把每一层的元素删除即可,如果顶层数据没有,则需要降低层数。
\\n###java
\\n\\npublic boolean erase(int num) {\\n Node[] update = new Node[MAX_LEVEL];\\n Node cur = this.head;\\n for (int i = curLevel - 1; i >= 0; i--) {\\n //找到第i层最大的小于target的元素\\n while (cur.next[i] != null && cur.next[i].val < num) {\\n cur = cur.next[i];\\n }\\n update[i] = cur;\\n }\\n cur = cur.next[0];\\n //判断num是否存在\\n if (cur == null || cur.val != num) {\\n return false;\\n }\\n for (int i = 0; i < curLevel; i++) {\\n if (update[i].next[i] != cur) {\\n break;\\n }\\n //删除第i层的值和num相等的元素\\n update[i].next[i] = cur.next[i];\\n }\\n //有可能最上层只有一个元素,缩短层数\\n while (curLevel > 1 && head.next[curLevel - 1] == null) {\\n curLevel--;\\n }\\n return true;\\n }\\n
在具体的实现上采用数组的形式,可以进一步节省空间
\\n
\\n整体代码
\\n###java
\\n\\nclass Skiplist {\\n private static final int MAX_LEVEL = 32;\\n private static final double SKIPLIST_P = 0.25;\\n private Node head;\\n private int curLevel;\\n\\n public Skiplist() {\\n this.head = new Node(-1, MAX_LEVEL);\\n this.curLevel = 0;\\n }\\n\\n public boolean search(int target) {\\n Node cur = this.head;\\n for (int i = curLevel - 1; i >= 0; i--) {\\n //找到第i层最大的小于target的元素\\n while (cur.next[i] != null && cur.next[i].val < target) {\\n cur = cur.next[i];\\n }\\n }\\n //已经在第一层\\n cur = cur.next[0];\\n //当前元素的值是否等于 target\\n return cur != null && cur.val == target;\\n }\\n\\n public void add(int num) {\\n //存放更新的位置\\n Node[] update = new Node[MAX_LEVEL];\\n Arrays.fill(update, head);\\n Node cur = this.head;\\n for (int i = curLevel - 1; i >= 0; i--) {\\n //找到所有层的前驱结点\\n while (cur.next[i] != null && cur.next[i].val < num) {\\n cur = cur.next[i];\\n }\\n update[i] = cur;\\n }\\n int randomLevel = randomLevel();\\n //更新最高的层数\\n this.curLevel = Math.max(this.curLevel, randomLevel);\\n Node newNode = new Node(num, randomLevel);\\n //插入随机出来的所有level\\n for (int i = 0; i < randomLevel; i++) {\\n newNode.next[i] = update[i].next[i];\\n update[i].next[i] = newNode;\\n }\\n }\\n\\n public boolean erase(int num) {\\n Node[] update = new Node[MAX_LEVEL];\\n Node cur = this.head;\\n for (int i = curLevel - 1; i >= 0; i--) {\\n //找到第i层最大的小于target的元素\\n while (cur.next[i] != null && cur.next[i].val < num) {\\n cur = cur.next[i];\\n }\\n update[i] = cur;\\n }\\n cur = cur.next[0];\\n //判断num是否存在\\n if (cur == null || cur.val != num) {\\n return false;\\n }\\n for (int i = 0; i < curLevel; i++) {\\n if (update[i].next[i] != cur) {\\n break;\\n }\\n //删除第i层的值和num相等的元素\\n update[i].next[i] = cur.next[i];\\n }\\n //有可能最上层只有一个元素,缩短层数\\n while (curLevel > 1 && head.next[curLevel - 1] == null) {\\n curLevel--;\\n }\\n return true;\\n }\\n\\n /**\\n * 该 randomLevel 方法会随机生成 1~MAX_LEVEL 之间的数,且\\n * 1/2 的概率返回 2\\n * 1/4 的概率返回 3\\n * 1/8 的概率返回 4 以此类推\\n */\\n private int randomLevel() {\\n int level = 1;\\n // 当 level < MAX_LEVEL,且随机数小于设定的晋升概率时,level + 1\\n while (Math.random() < SKIPLIST_P && level < MAX_LEVEL) {\\n level++;\\n }\\n return level;\\n }\\n}\\n\\nclass Node {\\n int val;\\n Node[] next;\\n\\n public Node(int val, int maxLevel) {\\n this.val = val;\\n this.next = new Node[maxLevel];\\n }\\n}\\n
参考:redis zset实现 https://github.com/redis/redis/blob/unstable/src/t_zset.c
\\n","description":"1、什么是跳表? 在传统的单链表中,每个元素都存放这下一个元素的引用,我们查找元素时,需要从链表头开始从前向后遍历,查找的时间复杂度为O(n)。\\n \\n 传统链表的查询效率非常低。那有没有什么办法提高效率呢?\\n\\n我们可以采用空间换时间的办法,将上面的每两个元素抽出来做一个类似于索引的链表。\\n \\n 假设我们要查询6,我们从上层链表开始遍历,当遍历到7时,大于目标值,我们需要到下一层接着找目标值。\\n\\n在上述遍历中,我们只需要遍历1、3、5、5、6即可找到目标结点,在普通的链表中需要1、2、3、4、5、6才能找到。\\n \\n 也许有人会说,这才节约了1次…","guid":"https://leetcode.cn/problems/design-skiplist//solution/she-ji-tiao-biao-by-capital-worker-3vqk","author":"capital-worker","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-26T00:15:23.888Z","media":[{"url":"https://pic.leetcode-cn.com/1658794651-QBLKPM-2022-07-25-22-50-32-image.png","type":"photo","width":685,"height":91,"blurhash":"LYS=;f$#Xo%N%Lj@fkj[yZf,nha$"},{"url":"https://pic.leetcode-cn.com/1658794672-ThwCrc-2022-07-25-22-51-05-image.png","type":"photo","width":699,"height":203,"blurhash":"LFS$P;?vQ--B?Ko}VYVYtSa|oLof"},{"url":"https://pic.leetcode-cn.com/1658794690-ZllCio-2022-07-25-22-51-12-image.png","type":"photo","width":693,"height":256,"blurhash":"LIS=^$?HpI?b?dkCkCogx^j]Vsae"},{"url":"https://pic.leetcode-cn.com/1658794725-jVAdZM-2022-07-25-22-51-20-image.png","type":"photo","width":825,"height":316,"blurhash":"LKS$ZE%fWU-p.TRiWAj?kpX8WVo}"},{"url":"https://pic.leetcode-cn.com/1658794739-OFbgEw-2022-07-25-22-51-25-image.png","type":"photo","width":938,"height":314,"blurhash":"LKS$cM%gbH?H.TR%WAoLkoW.bHoz"},{"url":"https://pic.leetcode-cn.com/1658794844-ddWFFY-2022-07-26-07-59-22-image.png","type":"photo","width":816,"height":304,"blurhash":"LJSiX0yEXB?w?wngxZs;tie.s;n5"},{"url":"https://pic.leetcode-cn.com/1658794904-iNwlpl-2022-07-26-08-14-18-image.png","type":"photo","width":878,"height":247,"blurhash":"LPS$cM%Mj@-p%Mj[ayj[%%j]azkC"},{"url":"https://pic.leetcode-cn.com/1658795474-JDYAWX-image.png","type":"photo","width":483,"height":48,"blurhash":"LESiHk.Spc~q?bj[j[jZ%#niaKV@"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"设计跳表","url":"https://leetcode.cn/problems/design-skiplist//solution/she-ji-tiao-biao-by-leetcode-solution-e8yh","content":"
\\nserver.h
\\n
\\n写题解不易,如果对您有帮助,记得关注 + 点赞 + 收藏呦!!!
\\n每天都会更新每日一题题解,大家加油!!方法一:直接构造
\\n跳表这种数据结构是由 $\\\\text{William Pugh}$ 发明的,关于跳表的详细介绍可以参考论文:「Skip Lists: A Probabilistic Alternative to Balanced Trees」,论文中详细阐述了关于 $\\\\texttt{skiplist}$ 查找元素、删除元素、插入元素的算法伪代码,以及时间复杂度的分析。跳表是一种随机化的数据结构,可以被看做二叉树的一个变种,它在性能上和红黑树、$\\\\texttt{AVL}$ 树不相上下,但是跳表的原理非常简单,目前在 $\\\\texttt{Redis}$ 和 $\\\\texttt{LevelDB}$ 中都有用到。跳表的期望空间复杂度为 $O(n)$,跳表的查询,插入和删除操作的期望时间复杂度均为 $O(\\\\log n)$。跳表实际为一种多层的有序链表,跳表的每一层都为一个有序链表,且满足每个位于第 $i$ 层的节点有 $p$ 的概率出现在第 $i+1$ 层,其中 $p$ 为常数。
\\n它的结构类似于如下图所示:
\\n跳表在进行查找时,首先从当前的最高层 $L(n)$ 层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第一层。此时,若下一个节点是目标节点,则成功查找;反之,则元素不存在。由于从高层往低层开始查找,由于低层出现的元素可能不会出现在高层,因此跳表在进行查找的过程中会跳过一些元素,相比于有序链表的查询,跳表的查询速度会更快。
\\n
\\n跳表的初始化、查找、添加、删除操作详细描述如下:\\n
\\n
\\n- $\\\\texttt{search}$:从跳表的当前的最大层数 $\\\\textit{level}$ 层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第 $1$ 层。此时,若第 $1$ 层的下一个节点的值等于 $\\\\textit{target}$,则返回 $\\\\texttt{true}$;反之,则返回 $\\\\texttt{false}$。如图所示:
\\n\\n
\\n
\\n- $\\\\texttt{add}$:从跳表的当前的最大层数 $\\\\textit{level}$ 层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第 $1$ 层。设新加入的节点为 $\\\\textit{newNode}$,我们需要计算出此次节点插入的层数 $\\\\textit{lv}$,如果 $\\\\textit{level}$ 小于 $\\\\textit{lv}$,则同时需要更新 $\\\\textit{level}$。我们用数组 $\\\\textit{update}$ 保存每一层查找的最后一个节点,第 $i$ 层最后的节点为 $\\\\textit{update}[i]$。我们将 $\\\\textit{newNode}$ 的后续节点指向 $update[i]$ 的下一个节点,同时更新 $update[i]$ 的后续节点为 $\\\\textit{newNode}$。如图所示:
\\n<
\\n,
,
,
,
>
\\n
\\n- $\\\\texttt{erase}$:首先我们需要查找当前元素是否存在跳表中。从跳表的当前的最大层数 $\\\\textit{level}$ 层开始查找,在当前层水平地逐个比较直至当前节点的下一个节点大于等于目标节点,然后移动至下一层进行查找,重复这个过程直至到达第 $1$ 层。如果第 $1$ 层的下一个节点不等于 $\\\\textit{num}$ 时,则表示当前元素不存在直接返回。我们用数组 $\\\\textit{update}$ 保存每一层查找的最后一个节点,第 $i$ 层最后的节点为 $\\\\textit{update}[i]$。此时第 $i$ 层的下一个节点的值为 $\\\\textit{num}$,则我们需要将其从跳表中将其删除。由于第 $i$ 层的以 $p$ 的概率出现在第 $i+1$ 层,因此我们应当从第 $1$ 层开始往上进行更新,将 $\\\\textit{num}$ 从 $update[i]$ 的下一跳中删除,同时更新 $update[i]$ 的后续节点,直到当前层的链表中没有出现 $\\\\textit{num}$ 的节点为止。最后我们还需要更新跳表中当前的最大层数 $\\\\textit{level}$。如图所示:
\\n<
\\n,
,
,
,
,
>
关于跳表的复杂度的分析如下:
\\n\\n
\\n- 空间复杂度分析:我们知道每次添加节点时,节点出现在第 $i$ 层的概率为 $(1-p)\\\\times p^{i-1}$,跳表插入时的期望层数为:
\\n$$
\\n
\\n\\\\begin{aligned}
\\nE(L) &= 1 \\\\times (1-p) + 2 \\\\times (1-p)\\\\times p + 3 \\\\times (1-p) \\\\times p^2 + \\\\cdots \\\\
\\n&= \\\\sum_{i=1}^{\\\\infty} i \\\\times (1-p) \\\\times p^{i-1} \\\\
\\n&= (1-p) \\\\times \\\\sum_{i=1}^{\\\\infty} i \\\\times p^{i-1} \\\\
\\n&= (1-p) \\\\times \\\\dfrac{1}{(1-p)^2} \\\\
\\n&= \\\\dfrac{1}{1-p}
\\n\\\\end{aligned}
\\n$$如果节点的目标层数为 $L$,则此时需要的空间为 $O(L)$,因此总的空间复杂度为 $O(n \\\\times E(L)) = O(n \\\\times \\\\dfrac{1}{1-p}) = O(n)$。
\\n\\n
\\n- 时间复杂度分析: 在含有 $n$ 个节点的跳表中,当前最大层数 $L(n)$ 包含的元素个数期望为 $\\\\dfrac{1}{p}$,根据跳表的定义可以知道第 $1$ 层的每个元素出现在 $L(n)$ 的概率为 $p^{L(n)-1}$,则此时我们可以推出如下:
\\n$$
\\n
\\n\\\\begin{aligned}
\\n\\\\dfrac{1}{p} &= np^{L(n)-1} \\\\
\\np^{L(n)} &= \\\\dfrac{1}{n} \\\\
\\nL(n) &= \\\\log_p {\\\\dfrac{1}{n}}
\\n\\\\end{aligned}
\\n$$根据以上结论可以知道在含有 $n$ 个节点的跳表中,当前最大层数期望 $L(n) = \\\\log_p {\\\\dfrac{1}{n}}$。
\\n我们首先思考一下搜索目标节点 $x$ 的过程,每次我们搜索第 $i$ 层时,如果第 $i$ 层的当前节点小于 $x$ 时,则我们会在第 $i$ 层向右进行搜索,直到下一个节点的值大于等于 $x$;如果第 $i$ 层的节点值大于等于 $x$,则我们则会下降到 $i-1$ 层。根据之前的定义,如果节点 $x$ 在第 $i$ 层出现,则节点 $x$ 一定会出现在第 $i-1$ 层。现在假设我们从 $L(n)$ 的第一个节点搜索到第 $1$ 层的目标节点 $x$ 的路径为 $S$,现在我们将路径 $S$ 反过来,即从第 $1$ 的节点 $x$ 回到 $L(n)$ 层的第一个节点,我们可以观察到从第 $1$ 层的节点 $x$ 一直往上一层,直到 $x$ 的最大层数,然后再向左走一步到达节点 $y$,再向上走,再重复上述步骤,实际搜索时如果在上一层可以到访问到节点 $x$,则在下一层遍历时一定不会访问所有小于 $x$ 的节点。假设当前我们处于一个第 $i$ 层的节点 $x$,此时并不知道 $x$ 的最大层数和 $x$ 左侧节点的最大层数,只知道 $x$ 的最大层数至少为 $i$。我们可以知道 $x$ 的最大层数大于 $i$,那么下一步按照最优选择应该是向上一层,这种情况的概率为 $p$;如果 $x$ 的最大层数等于 $i$,那么下一步应该是同一层向左侧后退一个节点,这种情况概率为 $1-p$。令 $C(i)$ 为在一个无限长度的跳表中向上爬 $i$ 层的期望代价,则知道:
\\n$$
\\n
\\n\\\\begin{aligned}
\\nC(0) &= 0 \\\\
\\nC(i) &= (1-p)(1 + C(i)) + p(1 + C(i-1)) \\\\
\\nC(i) &= \\\\dfrac{1}{p} + C(i-1) \\\\
\\nC(i) &= \\\\dfrac{i}{p}
\\n\\\\end{aligned}
\\n$$在含有 $n$ 个元素的跳表中,从第 $1$ 层爬到第 $L(n)$ 层的期望步数存在上界 $\\\\dfrac{L(n) - 1}{p}$。现在只需要分析爬到第 $L(n)$ 层后还要再走多少步。当达到第 $L(n)$ 层后,我们需要向左走。我们已知 $L(n)$ 层的节点总数的期望存在上界为 $\\\\dfrac{1}{p}$。所以我们知道搜索的总的代价为:
\\n$$
\\n
\\n\\\\dfrac{L(n) - 1}{p} + \\\\dfrac{1}{p} = \\\\dfrac{\\\\log_{\\\\frac{1}{p}}n -1}{p} + \\\\dfrac{1}{p} = \\\\dfrac{\\\\log_{\\\\frac{1}{p}}n}{p}
\\n$$根据以上推理可以得到查询的平均时间复杂度为 $O(\\\\log n)$。
\\n上述的推理过程与原本的论文相比还是有所忽略细节,如果对复杂度分析的详细细节感兴趣的可以参考原始论文:「Skip Lists: A Probabilistic Alternative to Balanced Trees」。
\\n###Python
\\n\\nMAX_LEVEL = 32\\nP_FACTOR = 0.25\\n\\ndef random_level() -> int:\\n lv = 1\\n while lv < MAX_LEVEL and random.random() < P_FACTOR:\\n lv += 1\\n return lv\\n\\nclass SkiplistNode:\\n __slots__ = \'val\', \'forward\'\\n\\n def __init__(self, val: int, max_level=MAX_LEVEL):\\n self.val = val\\n self.forward = [None] * max_level\\n\\nclass Skiplist:\\n def __init__(self):\\n self.head = SkiplistNode(-1)\\n self.level = 0\\n\\n def search(self, target: int) -> bool:\\n curr = self.head\\n for i in range(self.level - 1, -1, -1):\\n # 找到第 i 层小于且最接近 target 的元素\\n while curr.forward[i] and curr.forward[i].val < target:\\n curr = curr.forward[i]\\n curr = curr.forward[0]\\n # 检测当前元素的值是否等于 target\\n return curr is not None and curr.val == target\\n\\n def add(self, num: int) -> None:\\n update = [self.head] * MAX_LEVEL\\n curr = self.head\\n for i in range(self.level - 1, -1, -1):\\n # 找到第 i 层小于且最接近 num 的元素\\n while curr.forward[i] and curr.forward[i].val < num:\\n curr = curr.forward[i]\\n update[i] = curr\\n lv = random_level()\\n self.level = max(self.level, lv)\\n new_node = SkiplistNode(num, lv)\\n for i in range(lv):\\n # 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点\\n new_node.forward[i] = update[i].forward[i]\\n update[i].forward[i] = new_node\\n\\n def erase(self, num: int) -> bool:\\n update = [None] * MAX_LEVEL\\n curr = self.head\\n for i in range(self.level - 1, -1, -1):\\n # 找到第 i 层小于且最接近 num 的元素\\n while curr.forward[i] and curr.forward[i].val < num:\\n curr = curr.forward[i]\\n update[i] = curr\\n curr = curr.forward[0]\\n if curr is None or curr.val != num: # 值不存在\\n return False\\n for i in range(self.level):\\n if update[i].forward[i] != curr:\\n break\\n # 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳\\n update[i].forward[i] = curr.forward[i]\\n # 更新当前的 level\\n while self.level > 1 and self.head.forward[self.level - 1] is None:\\n self.level -= 1\\n return True\\n
###C++
\\n\\nconstexpr int MAX_LEVEL = 32;\\nconstexpr double P_FACTOR = 0.25;\\n\\nstruct SkiplistNode {\\n int val;\\n vector<SkiplistNode *> forward;\\n SkiplistNode(int _val, int _maxLevel = MAX_LEVEL) : val(_val), forward(_maxLevel, nullptr) {\\n \\n }\\n};\\n\\nclass Skiplist {\\nprivate:\\n SkiplistNode * head;\\n int level;\\n mt19937 gen{random_device{}()};\\n uniform_real_distribution<double> dis;\\n\\npublic:\\n Skiplist(): head(new SkiplistNode(-1)), level(0), dis(0, 1) {\\n\\n }\\n\\n bool search(int target) {\\n SkiplistNode *curr = this->head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 target 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < target) {\\n curr = curr->forward[i];\\n }\\n }\\n curr = curr->forward[0];\\n /* 检测当前元素的值是否等于 target */\\n if (curr && curr->val == target) {\\n return true;\\n } \\n return false;\\n }\\n\\n void add(int num) {\\n vector<SkiplistNode *> update(MAX_LEVEL, head);\\n SkiplistNode *curr = this->head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < num) {\\n curr = curr->forward[i];\\n }\\n update[i] = curr;\\n }\\n int lv = randomLevel();\\n level = max(level, lv);\\n SkiplistNode *newNode = new SkiplistNode(num, lv);\\n for (int i = 0; i < lv; i++) {\\n /* 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点 */\\n newNode->forward[i] = update[i]->forward[i];\\n update[i]->forward[i] = newNode;\\n }\\n }\\n\\n bool erase(int num) {\\n vector<SkiplistNode *> update(MAX_LEVEL, nullptr);\\n SkiplistNode *curr = this->head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < num) {\\n curr = curr->forward[i];\\n }\\n update[i] = curr;\\n }\\n curr = curr->forward[0];\\n /* 如果值不存在则返回 false */\\n if (!curr || curr->val != num) {\\n return false;\\n }\\n for (int i = 0; i < level; i++) {\\n if (update[i]->forward[i] != curr) {\\n break;\\n }\\n /* 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳 */\\n update[i]->forward[i] = curr->forward[i];\\n }\\n delete curr;\\n /* 更新当前的 level */\\n while (level > 1 && head->forward[level - 1] == nullptr) {\\n level--;\\n }\\n return true;\\n }\\n\\n int randomLevel() {\\n int lv = 1;\\n /* 随机生成 lv */\\n while (dis(gen) < P_FACTOR && lv < MAX_LEVEL) {\\n lv++;\\n }\\n return lv;\\n }\\n};\\n
###Java
\\n\\nclass Skiplist {\\n static final int MAX_LEVEL = 32;\\n static final double P_FACTOR = 0.25;\\n private SkiplistNode head;\\n private int level;\\n private Random random;\\n\\n public Skiplist() {\\n this.head = new SkiplistNode(-1, MAX_LEVEL);\\n this.level = 0;\\n this.random = new Random();\\n }\\n\\n public boolean search(int target) {\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 target 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < target) {\\n curr = curr.forward[i];\\n }\\n }\\n curr = curr.forward[0];\\n /* 检测当前元素的值是否等于 target */\\n if (curr != null && curr.val == target) {\\n return true;\\n } \\n return false;\\n }\\n\\n public void add(int num) {\\n SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];\\n Arrays.fill(update, head);\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n int lv = randomLevel();\\n level = Math.max(level, lv);\\n SkiplistNode newNode = new SkiplistNode(num, lv);\\n for (int i = 0; i < lv; i++) {\\n /* 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点 */\\n newNode.forward[i] = update[i].forward[i];\\n update[i].forward[i] = newNode;\\n }\\n }\\n\\n public boolean erase(int num) {\\n SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n curr = curr.forward[0];\\n /* 如果值不存在则返回 false */\\n if (curr == null || curr.val != num) {\\n return false;\\n }\\n for (int i = 0; i < level; i++) {\\n if (update[i].forward[i] != curr) {\\n break;\\n }\\n /* 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳 */\\n update[i].forward[i] = curr.forward[i];\\n }\\n /* 更新当前的 level */\\n while (level > 1 && head.forward[level - 1] == null) {\\n level--;\\n }\\n return true;\\n }\\n\\n private int randomLevel() {\\n int lv = 1;\\n /* 随机生成 lv */\\n while (random.nextDouble() < P_FACTOR && lv < MAX_LEVEL) {\\n lv++;\\n }\\n return lv;\\n }\\n}\\n\\nclass SkiplistNode {\\n int val;\\n SkiplistNode[] forward;\\n\\n public SkiplistNode(int val, int maxLevel) {\\n this.val = val;\\n this.forward = new SkiplistNode[maxLevel];\\n }\\n}\\n
###C#
\\n\\npublic class Skiplist {\\n const int MAX_LEVEL = 32;\\n const double P_FACTOR = 0.25;\\n private SkiplistNode head;\\n private int level;\\n private Random random;\\n\\n public Skiplist() {\\n this.head = new SkiplistNode(-1, MAX_LEVEL);\\n this.level = 0;\\n this.random = new Random();\\n }\\n \\n public bool Search(int target) {\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 target 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < target) {\\n curr = curr.forward[i];\\n }\\n }\\n curr = curr.forward[0];\\n /* 检测当前元素的值是否等于 target */\\n if (curr != null && curr.val == target) {\\n return true;\\n } \\n return false;\\n }\\n \\n public void Add(int num) {\\n SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];\\n Array.Fill(update, head);\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n int lv = RandomLevel();\\n level = Math.Max(level, lv);\\n SkiplistNode newNode = new SkiplistNode(num, lv);\\n for (int i = 0; i < lv; i++) {\\n /* 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点 */\\n newNode.forward[i] = update[i].forward[i];\\n update[i].forward[i] = newNode;\\n }\\n }\\n \\n public bool Erase(int num) {\\n SkiplistNode[] update = new SkiplistNode[MAX_LEVEL];\\n SkiplistNode curr = this.head;\\n for (int i = level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] != null && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n curr = curr.forward[0];\\n /* 如果值不存在则返回 false */\\n if (curr == null || curr.val != num) {\\n return false;\\n }\\n for (int i = 0; i < level; i++) {\\n if (update[i].forward[i] != curr) {\\n break;\\n }\\n /* 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳 */\\n update[i].forward[i] = curr.forward[i];\\n }\\n /* 更新当前的 level */\\n while (level > 1 && head.forward[level - 1] == null) {\\n level--;\\n }\\n return true;\\n }\\n\\n private int RandomLevel() {\\n int lv = 1;\\n /* 随机生成 lv */\\n while (random.NextDouble() < P_FACTOR && lv < MAX_LEVEL) {\\n lv++;\\n }\\n return lv;\\n }\\n}\\n\\npublic class SkiplistNode {\\n public int val;\\n public SkiplistNode[] forward;\\n\\n public SkiplistNode(int val, int maxLevel) {\\n this.val = val;\\n this.forward = new SkiplistNode[maxLevel];\\n }\\n}\\n
###C
\\n\\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\\nconst int MAX_LEVEL = 32;\\nconst int P_FACTOR = RAND_MAX >> 2;\\n\\ntypedef struct SkiplistNode {\\n int val;\\n int maxLevel;\\n struct SkiplistNode **forward;\\n} SkiplistNode;\\n\\ntypedef struct {\\n SkiplistNode *head;\\n int level;\\n} Skiplist;\\n\\nSkiplistNode *skiplistNodeCreat(int val, int maxLevel) {\\n SkiplistNode *obj = (SkiplistNode *)malloc(sizeof(SkiplistNode));\\n obj->val = val;\\n obj->maxLevel = maxLevel;\\n obj->forward = (SkiplistNode **)malloc(sizeof(SkiplistNode *) * maxLevel);\\n for (int i = 0; i < maxLevel; i++) {\\n obj->forward[i] = NULL;\\n }\\n return obj;\\n}\\n\\nvoid skiplistNodeFree(SkiplistNode* obj) {\\n if (obj->forward) {\\n free(obj->forward);\\n obj->forward = NULL;\\n obj->maxLevel = 0;\\n }\\n free(obj);\\n}\\n\\nSkiplist* skiplistCreate() {\\n Skiplist *obj = (Skiplist *)malloc(sizeof(Skiplist));\\n obj->head = skiplistNodeCreat(-1, MAX_LEVEL);\\n obj->level = 0;\\n srand(time(NULL));\\n return obj;\\n}\\n\\nstatic inline int randomLevel() {\\n int lv = 1;\\n /* 随机生成 lv */\\n while (rand() < P_FACTOR && lv < MAX_LEVEL) {\\n lv++;\\n }\\n return lv;\\n}\\n\\nbool skiplistSearch(Skiplist* obj, int target) {\\n SkiplistNode *curr = obj->head;\\n for (int i = obj->level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 target 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < target) {\\n curr = curr->forward[i];\\n }\\n }\\n curr = curr->forward[0];\\n /* 检测当前元素的值是否等于 target */\\n if (curr && curr->val == target) {\\n return true;\\n } \\n return false;\\n}\\n\\nvoid skiplistAdd(Skiplist* obj, int num) {\\n SkiplistNode *update[MAX_LEVEL];\\n SkiplistNode *curr = obj->head;\\n for (int i = obj->level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < num) {\\n curr = curr->forward[i];\\n }\\n update[i] = curr;\\n }\\n int lv = randomLevel();\\n if (lv > obj->level) {\\n for (int i = obj->level; i < lv; i++) {\\n update[i] = obj->head;\\n }\\n obj->level = lv;\\n }\\n SkiplistNode *newNode = skiplistNodeCreat(num, lv);\\n for (int i = 0; i < lv; i++) {\\n /* 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点 */\\n newNode->forward[i] = update[i]->forward[i];\\n update[i]->forward[i] = newNode;\\n }\\n}\\n\\nbool skiplistErase(Skiplist* obj, int num) {\\n SkiplistNode *update[MAX_LEVEL];\\n SkiplistNode *curr = obj->head;\\n for (int i = obj->level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr->forward[i] && curr->forward[i]->val < num) {\\n curr = curr->forward[i];\\n }\\n update[i] = curr;\\n }\\n curr = curr->forward[0];\\n /* 如果值不存在则返回 false */\\n if (!curr || curr->val != num) {\\n return false;\\n }\\n for (int i = 0; i < obj->level; i++) {\\n if (update[i]->forward[i] != curr) {\\n break;\\n } \\n /* 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳 */\\n update[i]->forward[i] = curr->forward[i];\\n }\\n skiplistNodeFree(curr);\\n /* 更新当前的 level */\\n while (obj->level > 1 && obj->head->forward[obj->level - 1] == NULL) {\\n obj->level--;\\n }\\n return true;\\n}\\n\\nvoid skiplistFree(Skiplist* obj) {\\n for (SkiplistNode * curr = obj->head; curr; ) {\\n SkiplistNode *prev = curr;\\n curr = curr->forward[0];\\n skiplistNodeFree(prev);\\n }\\n free(obj);\\n}\\n
###JavaScript
\\n\\nconst MAX_LEVEL = 32;\\nconst P_FACTOR = 0.25;\\nvar Skiplist = function() {\\n this.head = new SkiplistNode(-1, MAX_LEVEL);\\n this.level = 0;\\n};\\n\\nSkiplist.prototype.search = function(target) {\\n let curr = this.head;\\n for (let i = this.level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 target 的元素*/\\n while (curr.forward[i] && curr.forward[i].val < target) {\\n curr = curr.forward[i];\\n }\\n }\\n curr = curr.forward[0];\\n /* 检测当前元素的值是否等于 target */\\n if (curr && curr.val === target) {\\n return true;\\n } \\n return false;\\n};\\n\\nSkiplist.prototype.add = function(num) {\\n const update = new Array(MAX_LEVEL).fill(this.head);\\n let curr = this.head;\\n for (let i = this.level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n const lv = randomLevel();\\n this.level = Math.max(this.level, lv);\\n const newNode = new SkiplistNode(num, lv);\\n for (let i = 0; i < lv; i++) {\\n /* 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点 */\\n newNode.forward[i] = update[i].forward[i];\\n update[i].forward[i] = newNode;\\n }\\n};\\n\\nSkiplist.prototype.erase = function(num) {\\n const update = new Array(MAX_LEVEL).fill(0);\\n let curr = this.head;\\n for (let i = this.level - 1; i >= 0; i--) {\\n /* 找到第 i 层小于且最接近 num 的元素*/\\n while (curr.forward[i] && curr.forward[i].val < num) {\\n curr = curr.forward[i];\\n }\\n update[i] = curr;\\n }\\n curr = curr.forward[0];\\n /* 如果值不在存则返回 false */\\n if (!curr || curr.val !== num) {\\n return false;\\n }\\n for (let i = 0; i < this.level; i++) {\\n if (update[i].forward[i] !== curr) {\\n break;\\n }\\n /* 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳 */\\n update[i].forward[i] = curr.forward[i];\\n }\\n /* 更新当前的 level */\\n while (this.level > 1 && !this.head.forward[this.level - 1]) {\\n this.level--;\\n }\\n return true;\\n};\\n\\nconst randomLevel = () => {\\n let lv = 1;\\n /* 随机生成 lv */\\n while (Math.random() < P_FACTOR && lv < MAX_LEVEL) {\\n lv++;\\n }\\n return lv;\\n}\\n\\nclass SkiplistNode {\\n constructor(val, maxLevel) {\\n this.val = val;\\n this.forward = new Array(maxLevel).fill(0);\\n }\\n}\\n
###go
\\n\\nconst maxLevel = 32\\nconst pFactor = 0.25\\n\\ntype SkiplistNode struct {\\n val int\\n forward []*SkiplistNode\\n}\\n\\ntype Skiplist struct {\\n head *SkiplistNode\\n level int\\n}\\n\\nfunc Constructor() Skiplist {\\n return Skiplist{&SkiplistNode{-1, make([]*SkiplistNode, maxLevel)}, 0}\\n}\\n\\nfunc (Skiplist) randomLevel() int {\\n lv := 1\\n for lv < maxLevel && rand.Float64() < pFactor {\\n lv++\\n }\\n return lv\\n}\\n\\nfunc (s *Skiplist) Search(target int) bool {\\n curr := s.head\\n for i := s.level - 1; i >= 0; i-- {\\n // 找到第 i 层小于且最接近 target 的元素\\n for curr.forward[i] != nil && curr.forward[i].val < target {\\n curr = curr.forward[i]\\n }\\n }\\n curr = curr.forward[0]\\n // 检测当前元素的值是否等于 target\\n return curr != nil && curr.val == target\\n}\\n\\nfunc (s *Skiplist) Add(num int) {\\n update := make([]*SkiplistNode, maxLevel)\\n for i := range update {\\n update[i] = s.head\\n }\\n curr := s.head\\n for i := s.level - 1; i >= 0; i-- {\\n // 找到第 i 层小于且最接近 num 的元素\\n for curr.forward[i] != nil && curr.forward[i].val < num {\\n curr = curr.forward[i]\\n }\\n update[i] = curr\\n }\\n lv := s.randomLevel()\\n s.level = max(s.level, lv)\\n newNode := &SkiplistNode{num, make([]*SkiplistNode, lv)}\\n for i, node := range update[:lv] {\\n // 对第 i 层的状态进行更新,将当前元素的 forward 指向新的节点\\n newNode.forward[i] = node.forward[i]\\n node.forward[i] = newNode\\n }\\n}\\n\\nfunc (s *Skiplist) Erase(num int) bool {\\n update := make([]*SkiplistNode, maxLevel)\\n curr := s.head\\n for i := s.level - 1; i >= 0; i-- {\\n // 找到第 i 层小于且最接近 num 的元素\\n for curr.forward[i] != nil && curr.forward[i].val < num {\\n curr = curr.forward[i]\\n }\\n update[i] = curr\\n }\\n curr = curr.forward[0]\\n // 如果值不存在则返回 false\\n if curr == nil || curr.val != num {\\n return false\\n }\\n for i := 0; i < s.level && update[i].forward[i] == curr; i++ {\\n // 对第 i 层的状态进行更新,将 forward 指向被删除节点的下一跳\\n update[i].forward[i] = curr.forward[i]\\n }\\n // 更新当前的 level\\n for s.level > 1 && s.head.forward[s.level-1] == nil {\\n s.level--\\n }\\n return true\\n}\\n\\nfunc max(a, b int) int {\\n if b > a {\\n return b\\n }\\n return a\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:直接构造 跳表这种数据结构是由 $\\\\text{William Pugh}$ 发明的,关于跳表的详细介绍可以参考论文:「Skip Lists: A Probabilistic Alternative to Balanced Trees」,论文中详细阐述了关于 $\\\\texttt{skiplist}$ 查找元素、删除元素、插入元素的算法伪代码,以及时间复杂度的分析。跳表是一种随机化的数据结构,可以被看做二叉树的一个变种,它在性能上和红黑树、$\\\\texttt{AVL}$ 树不相上下,但是跳表的原理非常简单,目前在 $\\\\texttt{Redis}$ 和 $…","guid":"https://leetcode.cn/problems/design-skiplist//solution/she-ji-tiao-biao-by-leetcode-solution-e8yh","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-25T03:08:34.188Z","media":[{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_1.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs4~?ZR$?u?bj]ayoJ~XWas=nT"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_2.PNG","type":"photo","width":1280,"height":720,"blurhash":"LMS?45--XM-:-;ayaxj@.Tb2j1j^"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_3.PNG","type":"photo","width":1280,"height":720,"blurhash":"LDSs51?bxs?b_3ofbHbI~pozNHo}"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_4.PNG","type":"photo","width":1280,"height":720,"blurhash":"LESs51?bxZ?b_3ofbHbI~pozNHt7"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_5.PNG","type":"photo","width":1280,"height":720,"blurhash":"LKS$id-.XR-=?bj[jZj[?wa*nPj;"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_6.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs1]?HxZ?H?bofbHbI_MkqNHoz"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_7.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs1[?bXT-=_2kCbHof_NjcnNt2"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_9.PNG","type":"photo","width":1280,"height":720,"blurhash":"LCS$ov^*xt?v_3oMayoL~qo$M|s+"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_10.PNG","type":"photo","width":1280,"height":720,"blurhash":"LDS$ln^*xa?c_3oLf7oe~qkXRks*"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_11.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs1]^ks;.8^+kCa}of_NT0WAs+"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_12.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs1[?bbc?b_3ofa#j[_NR.n#n+"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_13.PNG","type":"photo","width":1280,"height":720,"blurhash":"LGSs1[?bbc?b?cofa}j[_NWFn#oL"},{"url":"https://assets.leetcode-cn.com/solution-static/1206/1206_14.PNG","type":"photo","width":1280,"height":720,"blurhash":"LDS?AN^+xu.9_3kCa}of_NXTNFxo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「哈希+TreeSet」","url":"https://leetcode.cn/problems/design-a-food-rating-system//solution/by-hu-li-hu-wai-h8xy","content":"- \\n
\\n时间复杂度:$O(\\\\log n)$,其中 $n$ 为 $\\\\texttt{add}$ 的调用次数。详细分析参考题解描述。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 为 $\\\\texttt{add}$ 的调用次数。详细分析参考题解描述。
\\n方法:「哈希+TreeSet」
\\n思路及算法
\\n与昨天的双周赛T3如出一辙
\\n\\n
\\n- 根据烹饪方式分类, 使用 HashMap 映射保存索引信息及所有food,具体定义及逻辑见代码
\\n- 使用 TreeSet 排序所有food, 传入自定义排序器
\\n(o1, o2) -> o1.rate != o2.rate ? o2.rate - o1.rate : o1.food.compareTo(o2.food))
- 更新时先删除再重新插入, 触发红黑树中节点的旋转
\\n代码
\\n###java
\\n\\nclass Solution {\\n class FoodRatings {\\n\\n class FoodWithRate {\\n String food;\\n int rate;\\n String cuisine;//保存烹饪方式的映射\\n\\n public FoodWithRate(String f, String cuisine, int rate) {\\n food = f;\\n this.cuisine = cuisine;\\n this.rate = rate;\\n }\\n }\\n\\n HashMap<String, TreeSet<FoodWithRate>> map = new HashMap<>();//保存各个烹饪方式中所有food的排序结果\\n HashMap<String, FoodWithRate> foodIndexMap = new HashMap<>();//对象索引\\n\\n public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {\\n int n = foods.length;\\n for (int i = 0; i < n; i++) {\\n String f = foods[i], c = cuisines[i];\\n int rate = ratings[i];\\n TreeSet<FoodWithRate> set = map.getOrDefault(c, new TreeSet<>((o1, o2) -> o1.rate != o2.rate ? o2.rate - o1.rate : o1.food.compareTo(o2.food)));\\n FoodWithRate foodWithRate = new FoodWithRate(f, c, rate);\\n foodIndexMap.put(f, foodWithRate);//保存对象索引\\n set.add(foodWithRate);//加入新food对象, 由TreeSet比较器来排序\\n map.put(c, set);\\n }\\n }\\n\\n public void changeRating(String food, int newRating) {\\n FoodWithRate legacyFood = foodIndexMap.get(food);\\n TreeSet<FoodWithRate> foodWithRates = map.get(legacyFood.cuisine);//找烹饪方式\\n foodWithRates.remove(legacyFood);//根据索引删除老节点\\n FoodWithRate foodWithRate = new FoodWithRate(food, legacyFood.cuisine, newRating);\\n foodIndexMap.put(food, foodWithRate);\\n foodWithRates.add(foodWithRate);//加入新节点,由TreeSet比较器来排序\\n }\\n\\n public String highestRated(String c) {\\n TreeSet<FoodWithRate> foodWithRates = map.get(c);\\n return foodWithRates.first().food;//找到最大值\\n }\\n }\\n}\\n
结语
\\n","description":"方法:「哈希+TreeSet」 思路及算法\\n\\n与昨天的双周赛T3如出一辙\\n\\n根据烹饪方式分类, 使用 HashMap 映射保存索引信息及所有food,具体定义及逻辑见代码\\n使用 TreeSet 排序所有food, 传入自定义排序器(o1, o2) -> o1.rate != o2.rate ? o2.rate - o1.rate : o1.food.compareTo(o2.food))\\n更新时先删除再重新插入, 触发红黑树中节点的旋转\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n class FoodRatings…","guid":"https://leetcode.cn/problems/design-a-food-rating-system//solution/by-hu-li-hu-wai-h8xy","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-24T04:11:02.481Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:有序集合 / 懒删除堆(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/design-a-food-rating-system//solution/ha-xi-biao-tao-ping-heng-shu-by-endlessc-hzct","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!方法一:有序集合
\\n用一个哈希表 $\\\\textit{foodMap}$ 记录每个食物名称对应的食物评分和烹饪方式,同时一个哈希表套有序集合 $\\\\textit{cuisineMap}$ 记录每个烹饪方式对应的食物评分和食物名字。
\\n\\n
\\n- $\\\\texttt{changeRating}$:先从 $\\\\textit{cuisineMap}$ 中删掉旧数据,然后将 $\\\\textit{newRating}$ 和 $\\\\textit{food}$ 记录到 $\\\\textit{cuisineMap}$ 和 $\\\\textit{foodMap}$ 中。
\\n- $\\\\texttt{highestRated}$:从有序集合 $\\\\textit{cuisineMap}[\\\\textit{cuisine}]$ 中找到最优数据。
\\n\\nclass FoodRatings:\\n def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):\\n self.food_map = {}\\n self.cuisine_map = defaultdict(SortedList) # sortedcontainers\\n for food, cuisine, rating in zip(foods, cuisines, ratings):\\n self.food_map[food] = [rating, cuisine]\\n # 取负号,保证 rating 相同时,字典序更小的 food 排在前面\\n self.cuisine_map[cuisine].add((-rating, food))\\n\\n def changeRating(self, food: str, newRating: int) -> None:\\n rating, cuisine = self.food_map[food]\\n sl = self.cuisine_map[cuisine]\\n sl.discard((-rating, food)) # 移除旧数据\\n sl.add((-newRating, food)) # 添加新数据\\n self.food_map[food][0] = newRating # 更新 food 的 rating\\n\\n def highestRated(self, cuisine: str) -> str:\\n return self.cuisine_map[cuisine][0][1]\\n
\\nclass FoodRatings {\\n private final Map<String, Pair<Integer, String>> foodMap = new HashMap<>();\\n private final Map<String, TreeSet<Pair<Integer, String>>> cuisineMap = new HashMap<>();\\n\\n public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {\\n for (int i = 0; i < foods.length; i++) {\\n String food = foods[i];\\n String cuisine = cuisines[i];\\n int rating = ratings[i];\\n foodMap.put(food, new Pair<>(rating, cuisine));\\n cuisineMap.computeIfAbsent(cuisine, k -> new TreeSet<>((a, b) ->\\n !Objects.equals(a.getKey(), b.getKey()) ? b.getKey() - a.getKey() : a.getValue().compareTo(b.getValue())\\n )).add(new Pair<>(rating, food));\\n }\\n }\\n\\n public void changeRating(String food, int newRating) {\\n Pair<Integer, String> p = foodMap.get(food);\\n TreeSet<Pair<Integer, String>> st = cuisineMap.get(p.getValue());\\n st.remove(new Pair<>(p.getKey(), food)); // 移除旧数据\\n st.add(new Pair<>(newRating, food)); // 添加新数据\\n foodMap.put(food, new Pair<>(newRating, p.getValue()));\\n }\\n\\n public String highestRated(String cuisine) {\\n return cuisineMap.get(cuisine).first().getValue();\\n }\\n}\\n
\\nclass FoodRatings {\\n unordered_map<string, pair<int, string>> food_map;\\n unordered_map<string, set<pair<int, string>>> cuisine_map;\\n\\npublic:\\n FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {\\n for (int i = 0; i < foods.size(); i++) {\\n auto& food = foods[i];\\n auto& cuisine = cuisines[i];\\n int rating = ratings[i];\\n food_map[food] = {rating, cuisine};\\n // 取负号,保证 rating 相同时,字典序更小的 food 排在前面\\n cuisine_map[cuisine].emplace(-rating, food);\\n }\\n }\\n\\n void changeRating(string food, int newRating) {\\n auto& [rating, cuisine] = food_map[food];\\n auto& st = cuisine_map[cuisine];\\n st.erase({-rating, food}); // 移除旧数据\\n st.emplace(-newRating, food); // 添加新数据\\n rating = newRating;\\n }\\n\\n string highestRated(string cuisine) {\\n return cuisine_map[cuisine].begin()->second;\\n }\\n};\\n
\\nimport \\"github.com/emirpasic/gods/v2/trees/redblacktree\\"\\n\\ntype pair struct {\\nrating int\\nfood string\\n}\\n\\ntype FoodRatings struct {\\nfoodMap map[string]pair\\ncuisineMap map[string]*redblacktree.Tree[pair, struct{}]\\n}\\n\\nfunc Constructor(foods []string, cuisines []string, ratings []int) FoodRatings {\\nfoodMap := map[string]pair{}\\ncuisineMap := map[string]*redblacktree.Tree[pair, struct{}]{}\\nfor i, food := range foods {\\nrating, cuisine := ratings[i], cuisines[i]\\nfoodMap[food] = pair{rating, cuisine}\\nif cuisineMap[cuisine] == nil {\\ncuisineMap[cuisine] = redblacktree.NewWith[pair, struct{}](func(a, b pair) int {\\nreturn cmp.Or(b.rating-a.rating, strings.Compare(a.food, b.food))\\n})\\n}\\ncuisineMap[cuisine].Put(pair{rating, food}, struct{}{})\\n}\\nreturn FoodRatings{foodMap, cuisineMap}\\n}\\n\\nfunc (r FoodRatings) ChangeRating(food string, newRating int) {\\np := r.foodMap[food]\\nt := r.cuisineMap[p.food]\\nt.Remove(pair{p.rating, food}) // 移除旧数据\\nt.Put(pair{newRating, food}, struct{}{}) // 添加新数据\\np.rating = newRating\\nr.foodMap[food] = p\\n}\\n\\nfunc (r FoodRatings) HighestRated(cuisine string) string {\\nreturn r.cuisineMap[cuisine].Left().Key.food\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(n\\\\log n)$,其余 $\\\\mathcal{O}(\\\\log n)$。其中 $n$ 为 $\\\\textit{foods}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:懒删除堆
\\n另一种做法是用懒删除堆:
\\n\\n
\\n- $\\\\texttt{changeRating}$:直接往 $\\\\textit{cuisineMap}$ 中记录,不做任何删除操作。
\\n- $\\\\texttt{highestRated}$:查看堆顶的食物评分是否等于其实际值,若不相同则意味着对应的元素已被替换成了其他值,堆顶存的是旧数据,直接弹出堆顶;否则堆顶就是答案。
\\n\\nclass FoodRatings:\\n def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):\\n self.food_map = {}\\n self.cuisine_map = defaultdict(list)\\n for food, cuisine, rating in zip(foods, cuisines, ratings):\\n self.food_map[food] = [rating, cuisine]\\n self.cuisine_map[cuisine].append((-rating, food))\\n # 这样可以保证初始化是线性复杂度\\n for h in self.cuisine_map.values():\\n heapify(h)\\n\\n def changeRating(self, food: str, newRating: int) -> None:\\n p = self.food_map[food]\\n # 直接添加新数据,后面 highestRated 再删除旧的\\n heappush(self.cuisine_map[p[1]], (-newRating, food))\\n p[0] = newRating\\n\\n def highestRated(self, cuisine: str) -> str:\\n h = self.cuisine_map[cuisine]\\n # 堆顶的食物评分不等于其实际值\\n while -h[0][0] != self.food_map[h[0][1]][0]:\\n heappop(h)\\n return h[0][1]\\n
\\nclass FoodRatings {\\n private final Map<String, Pair<Integer, String>> foodMap = new HashMap<>();\\n private final Map<String, PriorityQueue<Pair<Integer, String>>> cuisineMap = new HashMap<>();\\n\\n public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {\\n for (int i = 0; i < foods.length; i++) {\\n String food = foods[i];\\n String cuisine = cuisines[i];\\n int rating = ratings[i];\\n foodMap.put(food, new Pair<>(rating, cuisine));\\n cuisineMap.computeIfAbsent(cuisine, k -> new PriorityQueue<>((a, b) ->\\n !Objects.equals(a.getKey(), b.getKey()) ? b.getKey() - a.getKey() : a.getValue().compareTo(b.getValue())\\n )).offer(new Pair<>(rating, food));\\n }\\n }\\n\\n public void changeRating(String food, int newRating) {\\n String cuisine = foodMap.get(food).getValue();\\n // 直接添加新数据,后面 highestRated 再删除旧的\\n cuisineMap.get(cuisine).offer(new Pair<>(newRating, food));\\n foodMap.put(food, new Pair<>(newRating, cuisine));\\n }\\n\\n public String highestRated(String cuisine) {\\n PriorityQueue<Pair<Integer, String>> pq = cuisineMap.get(cuisine);\\n // 堆顶的食物评分不等于其实际值\\n while (!Objects.equals(pq.peek().getKey(), foodMap.get(pq.peek().getValue()).getKey())) {\\n pq.poll();\\n }\\n return pq.peek().getValue();\\n }\\n}\\n
\\nclass FoodRatings {\\n using pis = pair<int, string>;\\n\\n unordered_map<string, pair<int, string>> food_map;\\n unordered_map<string, priority_queue<pis, vector<pis>, greater<>>> cuisine_map;\\n\\npublic:\\n FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {\\n for (int i = 0; i < foods.size(); i++) {\\n auto& food = foods[i];\\n auto& cuisine = cuisines[i];\\n int rating = ratings[i];\\n food_map[food] = {rating, cuisine};\\n cuisine_map[cuisine].emplace(-rating, food);\\n }\\n }\\n\\n void changeRating(string food, int newRating) {\\n auto& [rating, cuisine] = food_map[food];\\n // 直接添加新数据,后面 highestRated 再删除旧的\\n cuisine_map[cuisine].emplace(-newRating, food);\\n rating = newRating;\\n }\\n\\n string highestRated(string cuisine) {\\n auto& pq = cuisine_map[cuisine];\\n // 堆顶的食物评分不等于其实际值\\n while (-pq.top().first != food_map[pq.top().second].first) {\\n pq.pop();\\n }\\n return pq.top().second;\\n }\\n};\\n
\\ntype pair struct {\\nrating int\\ns string\\n}\\n\\ntype FoodRatings struct {\\nfoodMap map[string]pair\\ncuisineMap map[string]*hp\\n}\\n\\nfunc Constructor(foods, cuisines []string, ratings []int) FoodRatings {\\nfoodMap := map[string]pair{}\\ncuisineMap := map[string]*hp{}\\nfor i, food := range foods {\\nrating, cuisine := ratings[i], cuisines[i]\\nfoodMap[food] = pair{rating, cuisine}\\nif cuisineMap[cuisine] == nil {\\ncuisineMap[cuisine] = &hp{}\\n}\\nheap.Push(cuisineMap[cuisine], pair{rating, food})\\n}\\nreturn FoodRatings{foodMap, cuisineMap}\\n}\\n\\nfunc (r FoodRatings) ChangeRating(food string, newRating int) {\\np := r.foodMap[food]\\n// 直接添加新数据,后面 HighestRated 再删除旧的\\nheap.Push(r.cuisineMap[p.s], pair{newRating, food})\\np.rating = newRating\\nr.foodMap[food] = p\\n}\\n\\nfunc (r FoodRatings) HighestRated(cuisine string) string {\\nh := r.cuisineMap[cuisine]\\n// 堆顶的食物评分不等于其实际值\\nfor h.Len() > 0 && (*h)[0].rating != r.foodMap[(*h)[0].s].rating {\\nheap.Pop(h)\\n}\\nreturn (*h)[0].s\\n}\\n\\ntype hp []pair\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool {\\na, b := h[i], h[j]\\nreturn a.rating > b.rating || a.rating == b.rating && a.s < b.s\\n}\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v any) { *h = append(*h, v.(pair)) }\\nfunc (h *hp) Pop() any { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(n)$ 或 $\\\\mathcal{O}(n\\\\log n)$,$\\\\texttt{changeRating}$ 为 $\\\\mathcal{O}(\\\\log (n+q))$,$\\\\texttt{highestRated}$ 为均摊 $\\\\mathcal{O}(\\\\log (n+q))$。其中 $n$ 为 $\\\\textit{foods}$ 的长度,$q$ 为 $\\\\texttt{changeRating}$ 的调用次数。
\\n- 空间复杂度:$\\\\mathcal{O}(n+q)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:有序集合 用一个哈希表 $\\\\textit{foodMap}$ 记录每个食物名称对应的食物评分和烹饪方式,同时一个哈希表套有序集合 $\\\\textit{cuisineMap}$ 记录每个烹饪方式对应的食物评分和食物名字。\\n\\n$\\\\texttt{changeRating}$:先从 $\\\\textit{cuisineMap}$ 中删掉旧数据,然后将 $\\\\textit{newRating}$ 和 $\\\\textit{food}$ 记录到 $\\\\textit{cuisineMap}$ 和 $\\\\textit{foodMap}$ 中。\\n$\\\\texttt…","guid":"https://leetcode.cn/problems/design-a-food-rating-system//solution/ha-xi-biao-tao-ping-heng-shu-by-endlessc-hzct","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-24T04:10:32.842Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟","url":"https://leetcode.cn/problems/design-a-food-rating-system//solution/by-tsreaper-j1dz","content":"解法:模拟
\\n维护一个
\\nmap<string, pair<int, string>> mp1
,mp1[food]
表示food
的评分的类型。维护一个map<string, set<pair<int, string>>> mp2
,mp2[cuisine]
表示类型为cuisine
的所有食物和评分。因为set
是有序的,所以每次查询只要输出set
的第一个元素即可。每次操作复杂度 $\\\\mathcal{O}(\\\\log n)$。参考代码(c++)
\\n###c++
\\n\\n","description":"解法:模拟 维护一个 mapclass FoodRatings {\\n typedef pair<int, string> pis;\\n map<string, pis> mp1;\\n map<string, set<pis>> mp2;\\n\\npublic:\\n FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {\\n for (int i = 0; i < foods.size(); i++) {\\n mp1[foods[i]] = pis(ratings[i], cuisines[i]);\\n // 每天一个偷懒小技巧\\n // 这里放入“负的评分”和字符串,这样“负的评分”小的,也就是评分大的食物会排在前面\\n mp2[cuisines[i]].insert(pis(-ratings[i], foods[i]));\\n }\\n }\\n \\n void changeRating(string food, int newRating) {\\n pis &p = mp1[food];\\n mp2[p.second].erase(pis(-p.first, food));\\n p.first = newRating;\\n mp2[p.second].insert(pis(-p.first, food));\\n }\\n \\n string highestRated(string cuisine) {\\n return mp2[cuisine].begin()->second;\\n }\\n};\\n\\n/**\\n * Your FoodRatings object will be instantiated and called as such:\\n * FoodRatings* obj = new FoodRatings(foods, cuisines, ratings);\\n * obj->changeRating(food,newRating);\\n * string param_2 = obj->highestRated(cuisine);\\n */\\n
> mp1,mp1[food] 表示 food 的评分的类型。维护一个 map >> mp2,mp2[cuisine] 表示类型为 cuisine 的所有食物和评分。因为 set 是有序的,所以每次查询只要输出 set 的第一个元素即可。每次操作复杂度 $\\\\mathcal{O}(\\\\log n)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass FoodRatings {\\n typedef pair 模拟\\n 根据题意进行模拟即可。
\\n用
\\ni
和j
分别代表往前和往后走的指针,a
和b
分别统计两种走法的总成本。代码:
\\n###Java
\\n\\nclass Solution {\\n public int distanceBetweenBusStops(int[] dist, int s, int t) {\\n int n = dist.length, i = s, j = s, a = 0, b = 0;\\n while (i != t) {\\n a += dist[i];\\n if (++i == n) i = 0;\\n }\\n while (j != t) {\\n if (--j < 0) j = n - 1; \\n b += dist[j];\\n }\\n return Math.min(a, b);\\n }\\n}\\n
###TypeScript
\\n\\nfunction distanceBetweenBusStops(dist: number[], s: number, t: number): number {\\n let n = dist.length, i = s, j = s, a = 0, b = 0\\n while (i != t) {\\n a += dist[i]\\n if (++i == n) i = 0\\n }\\n while (j != t) {\\n if (--j < 0) j = n - 1\\n b += dist[j]\\n }\\n return Math.min(a, b)\\n};\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(1)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 根据题意进行模拟即可。\\n\\n用 i 和 j 分别代表往前和往后走的指针,a 和 b 分别统计两种走法的总成本。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int distanceBetweenBusStops(int[] dist, int s, int t) {\\n int n = dist.length, i = s, j = s, a = 0, b = 0;\\n while (i != t) {\\n a += dist[i];\\n if (++i…","guid":"https://leetcode.cn/problems/distance-between-bus-stops//solution/by-ac_oier-fow3","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-24T01:36:15.184Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"公交站间的距离","url":"https://leetcode.cn/problems/distance-between-bus-stops//solution/gong-jiao-zhan-jian-de-ju-chi-by-leetcod-o737","content":"方法一:一次遍历
\\n记数组 $\\\\textit{distance}$ 的长度为 $n$。假设 $\\\\textit{start} \\\\le \\\\textit{destination}$,那么我们可以:
\\n\\n
\\n- 从 $\\\\textit{start}$ 到 $\\\\textit{destination}$,距离为 $\\\\sum\\\\limits_{i=\\\\textit{start}}^{\\\\textit{destination}-1}\\\\textit{distance}[i]$;
\\n- 从 $\\\\textit{start}$ 到 $0$,再从 $0$ 到 $\\\\textit{destination}$,距离为 $\\\\sum\\\\limits_{i=0}^{\\\\textit{start}-1}\\\\textit{distance}[i]+\\\\sum\\\\limits_{i=\\\\textit{destination}}^{n-1}\\\\textit{distance}[i]$。
\\n答案为这两个距离的最小值。
\\n###Python
\\n\\nclass Solution:\\n def distanceBetweenBusStops(self, distance: List[int], start: int, destination: int) -> int:\\n if start > destination:\\n start, destination = destination, start\\n return min(sum(distance[start:destination]), sum(distance[:start]) + sum(distance[destination:]))\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int distanceBetweenBusStops(vector<int>& distance, int start, int destination) {\\n if (start > destination) {\\n swap(start, destination);\\n }\\n return min(accumulate(distance.begin() + start, distance.begin() + destination, 0),\\n accumulate(distance.begin(), distance.begin() + start, 0) +\\n accumulate(distance.begin() + destination, distance.end(), 0));\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int distanceBetweenBusStops(int[] distance, int start, int destination) {\\n if (start > destination) {\\n int temp = start;\\n start = destination;\\n destination = temp;\\n }\\n int sum1 = 0, sum2 = 0;\\n for (int i = 0; i < distance.length; i++) {\\n if (i >= start && i < destination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return Math.min(sum1, sum2);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int DistanceBetweenBusStops(int[] distance, int start, int destination) {\\n if (start > destination) {\\n int temp = start;\\n start = destination;\\n destination = temp;\\n }\\n int sum1 = 0, sum2 = 0;\\n for (int i = 0; i < distance.Length; i++) {\\n if (i >= start && i < destination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return Math.Min(sum1, sum2);\\n }\\n}\\n
###go
\\n\\nfunc distanceBetweenBusStops(distance []int, start, destination int) int {\\n if start > destination {\\n start, destination = destination, start\\n }\\n sum1, sum2 := 0, 0\\n for i, d := range distance {\\n if start <= i && i < destination {\\n sum1 += d\\n } else {\\n sum2 += d\\n }\\n }\\n return min(sum1, sum2)\\n}\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n
###C
\\n\\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\\n\\nint distanceBetweenBusStops(int* distance, int distanceSize, int start, int destination){\\n if (start > destination) {\\n int temp = start;\\n start = destination;\\n destination = temp;\\n }\\n int sum1 = 0, sum2 = 0;\\n for (int i = 0; i < distanceSize; i++) {\\n if (i >= start && i < destination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return MIN(sum1, sum2);\\n}\\n
###JavaScript
\\n\\nvar distanceBetweenBusStops = function(distance, start, destination) {\\n if (start > destination) {\\n [start, destination] = [destination, start]\\n }\\n let sum1 = 0, sum2 = 0;\\n for (let i = 0; i < distance.length; i++) {\\n if (i >= start && i < destination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return Math.min(sum1, sum2);\\n};\\n
###TypeScript
\\n\\nfunction distanceBetweenBusStops(distance: number[], start: number, destination: number): number {\\n if (start > destination) {\\n [start, destination] = [destination, start]\\n }\\n let sum1 = 0, sum2 = 0;\\n for (let i = 0; i < distance.length; i++) {\\n if (i >= start && i < destination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return Math.min(sum1, sum2);\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn distance_between_bus_stops(distance: Vec<i32>, start: i32, destination: i32) -> i32 {\\n let mut start = start;\\n let mut destination = destination;\\n if (start > destination) {\\n let temp = start;\\n start = destination;\\n destination = temp;\\n }\\n let mut sum1 = 0;\\n let mut sum2 = 0;\\n for (i, x) in distance.iter().enumerate() {\\n if (i >= start as usize && i < destination as usize) {\\n sum1 += x;\\n } else {\\n sum2 += x;\\n }\\n }\\n sum1.min(sum2)\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func distanceBetweenBusStops(distance: Array<Int64>, start: Int64, destination: Int64): Int64 {\\n var currStart = start\\n var currDestination = destination\\n if (currStart > currDestination) {\\n (currStart, currDestination) = (currDestination, currStart)\\n }\\n var sum1 = 0\\n var sum2 = 0\\n for (i in 0..distance.size) {\\n if (i >= currStart && i < currDestination) {\\n sum1 += distance[i];\\n } else {\\n sum2 += distance[i];\\n }\\n }\\n return min(sum1, sum2)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:一次遍历 记数组 $\\\\textit{distance}$ 的长度为 $n$。假设 $\\\\textit{start} \\\\le \\\\textit{destination}$,那么我们可以:\\n\\n从 $\\\\textit{start}$ 到 $\\\\textit{destination}$,距离为 $\\\\sum\\\\limits_{i=\\\\textit{start}}^{\\\\textit{destination}-1}\\\\textit{distance}[i]$;\\n从 $\\\\textit{start}$ 到 $0$,再从 $0$ 到 $\\\\textit{destination…","guid":"https://leetcode.cn/problems/distance-between-bus-stops//solution/gong-jiao-zhan-jian-de-ju-chi-by-leetcod-o737","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-22T04:02:21.805Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线段树详解「汇总级别整理 🔥🔥🔥」","url":"https://leetcode.cn/problems/my-calendar-ii//solution/by-lfool-nodi","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{distance}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$,只需要额外的常数级别的空间。
\\n如果想要查看作者更多文章,可以点击此处!!!🔥🔥🔥
\\n为了本篇文章更好的观感,可以点击此处!!!
\\n\\n\\n\\n\\n\\n\\n\\n\\n
\\n线段树引入
\\n遇到过好多次线段树的题目,要么就是用其他的方法去解决,要么就是不会写!!今天痛定思痛,决定好好归纳整理一下线段树
\\n线段树解决的是「区间和」的问题,且该「区间」会被修改
\\n什么意思呢?举个简单的例子,对于
\\nnums = [1, 2, 3, 4, 5]
如果我们需要多次求某些区间的和,是不是首先想到了利用「前缀和」。关于前缀和的详细介绍可见 前缀和数组
\\n但是如果
\\nnums
会被修改呢?比如:\\n
\\n- 把第
\\ni
个元素修改成x
- 把第
\\ni
个元素增加x
- 把区间
\\n[i, j]
内的元素都增加x
此时,如果我们再使用「前缀和」,就没那么高效了。因为每一次更新,前缀和数组必须也随之更新,时间复杂度为
\\nO(n)
既然「前缀和」在这种场景下没那么高效了,所以就有了今天要介绍的「线段树」
\\n线段树原理及实现
\\n上面提到过:线段树解决的是「区间和」的问题,且该「区间」会被修改
\\n所以线段树主要实现两个方法:「求区间和」&&「修改区间」,且时间复杂度均为
\\nO(logn)
始终记住一句话:线段树的每个节点代表一个区间
\\n\\n
nums = [1, 2, 3, 4, 5]
对应的线段树如下所示:\\n
从图中可以看到,每个节点代表一个区间,而节点的值就是该区间的和 (其实还可以根据题目问题,改变表示的含义!!)
\\n\\n
\\n- 数字之和「总数字之和 = 左区间数字之和 + 右区间数字之和」
\\n- 最大公因数 (GCD)「总 GCD = gcd(左区间 GCD, 右区间 GCD)」
\\n- 最大值「总最大值 = max(左区间最大值,右区间最大值)」
\\n不符合区间加法的例子:
\\n\\n
\\n- 众数「只知道左右区间的众数,没法求总区间的众数」
\\n- 01 序列的最长连续零「只知道左右区间的最长连续零,没法知道总的最长连续零」
\\n根节点代表的区间是问题的总区间,如这个例子,问题的总区间就是数组的长度
\\n[0, 4]
其实线段树是一棵近似的完全二叉树,该例子就是一棵完全二叉树,但是有些情况不是完全二叉树
\\n所以对于给定的一个问题,如果该问题的范围是确定的,那么该问题的线段树也是确定的,因为建立线段树的过程就是不断把区间「平分」的过程,直到区间长度为 1
\\n注意:下面的所有实现均基于求「区间和」以及对区间进行「加减」的更新操作
\\n线段树的数据结构
\\n我们可以使用数组来表示一棵线段树,假如根节点为
\\ni
,那么左孩子的节点就为2 * i
,右孩子的节点就为2 * i + 1
(前提:i
从 1 开始)我们可以使用链表来表示一棵线段树,其节点的数据结构如下:
\\n###java
\\n\\nclass Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值\\n int val;\\n}\\n
个人比较倾向使用链表,因为比较节约内存,下面的实现均基于链表
\\n线段树的建立
\\n如果题目中给了具体的区间范围,我们根据该范围建立线段树。见题目 区域和检索 - 数组可修改
\\n###java
\\n\\npublic void buildTree(Node node, int start, int end) {\\n // 到达叶子节点\\n if (start == end) {\\n node.val = arr[start];\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n buildTree(node.left, start, mid);\\n buildTree(node.right, mid + 1, end);\\n // 向上更新\\n pushUp(node);\\n}\\n// 向上更新\\nprivate void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n}\\n
但是很多时候,题目中都没有给出很具体的范围,只有数据的取值范围,一般都很大,所以我们更常用的是「动态开点」
\\n下面我们手动模拟一下「动态开点」的过程。同样的,也是基于上面的例子
\\nnums = [1, 2, 3, 4, 5]
假设一种情况,最开始只知道数组的长度
\\n5
,而不知道数组内每个元素的大小,元素都是后面添加进去的。所以线段树的初始状态如下图所示:(只有一个节点,很孤独!!)\\n
假设此时,我们添加了一个元素
\\n[2, 2]; val = 3
。现在线段树的结构如下图所示:\\n
这里需要解释一下,如果一个节点没有左右孩子,会一下子把左右孩子节点都给创建出来,如上图橙色节点所示,具体代码可见方法
\\npushDown()
两个橙色的叶子节点仅仅只是被创建出来了,并无实际的值,均为 0;而另外一个橙色的非叶子节点,值为 3 的原因是下面的孩子节点的值向上更新得到的
\\n下面给出依次添加剩余节点的过程:(注意观察值的变化!!)
\\n\\n
「动态开点」一般是在「更新」或「查询」的时候动态的建立节点,具体可见下面的更新和查询操作
\\n线段树的更新
\\n我看大多数教程都是把更新分为两种:「点更新」和「区间更新」。其实这两种可以合并成一种,「点更新」不就是更新长度为 1 的区间嘛!!
\\n更新区间的前提是找到需要更新的区间,所以和查询的思路很相似
\\n如果我们要把区间
\\n[2, 4]
内的元素都「➕1」\\n
我们会发现一个很有意思的现象,我们只把
\\n[2,2]
和[3,4]
这两个区间对应的节点更新了,而区间[3, 3]
和[4,4]
并没有更新按道理来说,
\\n[3, 3]
和[4,4]
也是需要更新的,不然当我们查询区间[3, 3]
和[4,4]
的值,就会出现错误!!这是因为我们使用了「懒惰标记」的方法,我们只需要更新到满足条件的区间即可,然后再给该区间对应的节点加一个懒惰标记,表示该节点所有对应的孩子节点都应该有此更新
\\n当我们向孩子节点遍历的时候会把「懒惰标记」下推给孩子节点
\\n我们需要稍微修改一下
\\nNode
的数据结构###java
\\n\\nclass Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值\\n int val;\\n // 懒惰标记\\n int add;\\n}\\n
基于「动态开点」的前提,我们下推懒惰标记的时候,如果节点不存在左右孩子节点,那么我们就创建左右孩子节点
\\n先来实现下推懒惰标记的函数:
\\n###java
\\n\\n// leftNum 和 rightNum 表示左右孩子区间的叶子节点数量\\n// 因为如果是「加减」更新操作的话,需要用懒惰标记的值✖️叶子节点的数量\\nprivate void pushDown(Node node, int leftNum, int rightNum) {\\n // 动态开点\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n // 如果 add 为 0,表示没有标记\\n if (node.add == 0) return ;\\n // 注意:当前节点加上标记值✖️该子树所有叶子节点的数量\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 把标记下推给孩子节点\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n // 取消当前节点标记\\n node.add = 0;\\n}\\n
下面来实现更新的函数:
\\n###java
\\n\\n// 在区间 [start, end] 中更新区间 [l, r] 的值,将区间 [l, r] ➕ val\\n// 对于上面的例子,应该这样调用该函数:update(root, 0, 4, 2, 4, 1)\\npublic void update(Node node, int start, int end, int l, int r, int val) {\\n // 找到满足要求的区间\\n if (l <= start && end <= r) {\\n // 区间节点加上更新值\\n // 注意:需要✖️该子树所有叶子节点\\n node.val += (end - start + 1) * val;\\n // 添加懒惰标记\\n // 对区间进行「加减」的更新操作,懒惰标记需要累加,不能直接覆盖\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n // 下推标记\\n // mid - start + 1:表示左孩子区间叶子节点数量\\n // end - mid:表示右孩子区间叶子节点数量\\n pushDown(node, mid - start + 1, end - mid);\\n // [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n // [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n // 向上更新\\n pushUp(node);\\n}\\n
线段树的查询
\\n如果我们要查询区间
\\n[2, 4]
的结果,如下图红色标记所示:\\n
下面给出代码实现:
\\n###java
\\n\\n// 在区间 [start, end] 中查询区间 [l, r] 的结果,即 [l ,r] 保持不变\\n// 对于上面的例子,应该这样调用该函数:query(root, 0, 4, 2, 4)\\npublic int query(Node node, int start, int end, int l, int r) {\\n // 区间 [l ,r] 完全包含区间 [start, end]\\n // 例如:[2, 4] = [2, 2] + [3, 4],当 [start, end] = [2, 2] 或者 [start, end] = [3, 4],直接返回\\n if (l <= start && end <= r) return node.val;\\n // 把当前区间 [start, end] 均分得到左右孩子的区间范围\\n // node 左孩子区间 [start, mid]\\n // node 左孩子区间 [mid + 1, end]\\n int mid = (start + end) >> 1, ans = 0;\\n // 下推标记\\n pushDown(node, mid - start + 1, end - mid);\\n // [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n // [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n // ans 把左右子树的结果都累加起来了,与树的后续遍历同理\\n return ans;\\n}\\n
线段树完整模版
\\n注意:下面模版基于求「区间和」以及对区间进行「加减」的更新操作,且为「动态开点」
\\n###java
\\n\\n/**\\n * @Description: 线段树(动态开点)\\n * @Author: LFool\\n * @Date 2022/6/7 09:15\\n **/\\npublic class SegmentTreeDynamic {\\n class Node {\\n Node left, right;\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += (end - start + 1) * val;\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
再次强调一遍:上面给出的模版基于求「区间和」以及对区间进行「加减」的更新操作,且为「动态开点」
\\n但是下面给出的题目实战中,有些题目需要对模版进行小小的修改 (很多人问这个问题,这里统一整理汇总一下!!)
\\n\\n
\\n- 对于表示为「区间和」且对区间进行「加减」的更新操作的情况,我们在更新节点值的时候『需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『需要累加』!!(这种情况和模版一致!!) 如题目 最近的请求次数
\\n- 对于表示为「区间和」且对区间进行「覆盖」的更新操作的情况,我们在更新节点值的时候『需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『不需要累加』!!(因为是覆盖操作!!) 如题目 区域和检索 - 数组可修改
\\n- 对于表示为「区间最值」且对区间进行「加减」的更新操作的情况,我们在更新节点值的时候『不需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『需要累加』!! 如题目 我的日程安排表 I、我的日程安排表 III
\\n注意:对于题目 最近的请求次数 和 区域和检索 - 数组可修改 可以「不用✖️左右孩子区间叶子节点的数量」
\\n为什么??因为这两个题目是「点更新」,在介绍线段树更新的时候,我们说过:「点更新」和「区间更新」可以合并成一种,「点更新」不就是更新长度为 1 的区间嘛!!
\\n上面两个题目调用更新函数的方式为:
\\nupdate(root, 1, N, t, t, 1);
和update(root, 0, N, i, i, nums[i]);
由于区间是一个点,所以一定会更新到叶子节点,故可以不用✖️左右孩子区间叶子节点的数量!!
\\n题目实战
\\n我的日程安排表 I
\\n题目详情可见 我的日程安排表 I
\\n该题目基于下面「我的日程安排表 III」的思路!!!
\\n###java
\\n\\nclass MyCalendar {\\n\\n public MyCalendar() {\\n\\n }\\n \\n public boolean book(int start, int end) {\\n // 先查询该区间是否为 0\\n if (query(root, 0, N, start, end - 1) != 0) return false;\\n // 更新该区间\\n update(root, 0, N, start, end - 1, 1);\\n return true;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
我的日程安排表 II
\\n题目详情可见 我的日程安排表 II
\\n该题目基于下面「我的日程安排表 III」的思路!!!
\\n###java
\\n\\nclass MyCalendarTwo {\\n\\n public MyCalendarTwo() {\\n\\n }\\n \\n public boolean book(int start, int end) {\\n if (query(root, 0, N, start, end - 1) == 2) return false;\\n update(root, 0, N, start, end - 1, 1);\\n return true;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
我的日程安排表 III
\\n题目详情可见 我的日程安排表 III
\\n上面说过「节点的值不止可以表示该区间的和」,还可以「表示为当前区间的最值」,该题目存储的就是区间的最大值
\\n###java
\\n\\nclass MyCalendarThree {\\n\\n public MyCalendarThree() {\\n\\n }\\n \\n public int book(int start, int end) {\\n // 只用到了 update\\n update(root, 0, N, start, end - 1, 1);\\n // 最大值即为根节点的值\\n return root.val;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
Range 模块
\\n题目详情可见 Range 模块
\\n每个节点的值表示当前区间是否被覆盖
\\n###java
\\n\\nclass RangeModule {\\n\\n public RangeModule() {\\n\\n }\\n \\n public void addRange(int left, int right) {\\n // 1 表示复盖;-1 表示取消覆盖\\n update(root, 1, N, left, right - 1, 1);\\n }\\n \\n public boolean queryRange(int left, int right) {\\n return query(root, 1, N, left, right - 1);\\n }\\n \\n public void removeRange(int left, int right) {\\n // 1 表示复盖;-1 表示取消覆盖\\n update(root, 1, N, left, right - 1, -1);\\n }\\n\\n // *************** 下面是模版 ***************\\n class Node {\\n Node left, right;\\n // 表示当前区间是否被覆盖\\n boolean cover;\\n int add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n // 1 表示复盖;-1 表示取消覆盖\\n node.cover = val == 1;\\n node.add = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public boolean query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.cover;\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n // 查询左右子树是否被覆盖\\n boolean ans = true;\\n if (l <= mid) ans = ans && query(node.left, start, mid, l, r);\\n if (r > mid) ans = ans && query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 向上更新\\n node.cover = node.left.cover && node.right.cover;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.cover = node.add == 1;\\n node.right.cover = node.add == 1;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
区域和检索 - 数组可修改
\\n题目详情可见 区域和检索 - 数组可修改
\\n方法一:
\\n###java
\\n\\nclass NumArray {\\n\\n private final int n;\\n private final int[] arr;\\n private final int[] tree;\\n private final int[] add;\\n\\n public NumArray(int[] nums) {\\n this.n = nums.length;\\n this.arr = nums;\\n // 必须开 4 倍长度的数组\\n this.tree = new int[n << 2];\\n this.add = new int[n << 2];\\n buildTree(0, n - 1, 1);\\n }\\n \\n public void update(int index, int val) {\\n update(0, n - 1, 1, index, val);\\n }\\n \\n public int sumRange(int left, int right) {\\n return query(0, n - 1, left, right, 1);\\n }\\n\\n public void updateRange(int l, int r, int val) {\\n update(0, n - 1, 1, l, r, val);\\n }\\n\\n private void buildTree(int start, int end, int rootId) {\\n if (start == end) {\\n tree[rootId] = arr[start];\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n buildTree(start, mid, rootId << 1);\\n buildTree(mid + 1, end, rootId << 1 | 1);\\n pushUp(rootId);\\n }\\n private void update(int start, int end, int rootId, int updateIndex, int val) {\\n if (start == end) {\\n arr[start] = tree[rootId] = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n if (updateIndex > mid) {\\n update(mid + 1, end, rootId << 1 | 1, updateIndex, val);\\n } else {\\n update(start, mid, rootId << 1, updateIndex, val);\\n }\\n pushUp(rootId);\\n }\\n private void update(int start, int end, int rootId, int l, int r, int val) {\\n // 无交集\\n if (r < start || l > end) return ;\\n // 如果本区间完全在操作区间 [l, r] 以内\\n else if (l <= start && end <= r) {\\n // 更新数字和,向上保持正确\\n tree[rootId] += val * (end - start + 1);\\n // 增加 add 标记,表示本区间的 Sum 正确,子区间的 Sum 仍需要根据 add 的值来调整\\n add[rootId] += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n // 下推标记\\n pushDown(rootId, mid - start + 1, end - mid);\\n update(start, mid, rootId << 1, l, r, val);\\n update(mid + 1, end, rootId << 1 | 1, l, r, val);\\n pushUp(rootId);\\n }\\n private int query(int start, int end, int l, int r, int rootId) {\\n if (r < start || l > end) return 0;\\n else if (l <= start && end <= r) return tree[rootId];\\n int mid = (start + end) >> 1;\\n // 下推标记\\n pushDown(rootId, mid - start + 1, end - mid);\\n int lSum = query(start, mid, l, r, rootId << 1);\\n int rSum = query(mid + 1, end, l, r, rootId << 1 | 1);\\n return lSum + rSum;\\n }\\n\\n private void pushUp(int rootId) {\\n tree[rootId] = tree[rootId << 1] + tree[rootId << 1 | 1];\\n }\\n private void pushDown(int rootId, int leftNum, int rightNum) {\\n if (add[rootId] == 0) return ;\\n // 下推标记\\n add[rootId << 1] += add[rootId];\\n add[rootId << 1 | 1] += add[rootId];\\n // 修改子节点的 Sum 使之与对应的 add 相对应\\n tree[rootId << 1] += leftNum * add[rootId];\\n tree[rootId << 1 | 1] += rightNum * add[rootId];\\n add[rootId] = 0;\\n }\\n}\\n
方法二:基于动态开点,完全是为了加深对动态开点的理解
\\n###java
\\n\\nclass NumArray {\\n\\n public NumArray(int[] nums) {\\n N = nums.length - 1;\\n for (int i = 0; i <= N; i++) {\\n update(root, 0, N, i, i, nums[i]);\\n }\\n }\\n \\n public void update(int index, int val) {\\n update(root, 0, N, index, index, val);\\n }\\n \\n public int sumRange(int left, int right) {\\n return query(root, 0, N, left, right);\\n }\\n\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = (end - start + 1) * val;\\n node.add = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add * leftNum;\\n node.right.val = node.add * rightNum;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
最近的请求次数
\\n题目详情可见 最近的请求次数
\\n方法一:线段树 (动态开点);完全是为了加深对线段树的理解
\\n###java
\\n\\nclass RecentCounter {\\n\\n public RecentCounter() {\\n\\n }\\n \\n public int ping(int t) {\\n update(root, 1, N, t, t, 1);\\n return query(root, 1, N, Math.max(0, t - 3000), t);\\n }\\n\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
方法二:队列
\\n###java
\\n\\nclass RecentCounter {\\n\\n private Deque<Integer> deque;\\n\\n public RecentCounter() {\\n deque = new ArrayDeque<>();\\n }\\n \\n public int ping(int t) {\\n int past = t - 3000;\\n deque.addLast(t);\\n while (deque.getFirst() < past) deque.removeFirst();\\n return deque.size();\\n }\\n}\\n
掉落的方块
\\n题目详情可见 掉落的方块
\\n上面说过「节点的值不止可以表示该区间的和」,还可以「表示为当前区间的最值」,该题目存储的就是区间的最大值
\\n###java
\\n\\nclass Solution {\\n public List<Integer> fallingSquares(int[][] positions) {\\n List<Integer> ans = new ArrayList<>();\\n for (int[] position : positions) {\\n int x = position[0], h = position[1];\\n // 先查询出 [x, x + h] 的值\\n int cur = query(root, 0, N, x, x + h - 1);\\n // 更新 [x, x + h - 1] 为 cur + h (直接覆盖)\\n update(root, 0, N, x, x + h - 1, cur + h);\\n ans.add(root.val);\\n }\\n return ans;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = val;\\n node.add = val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add;\\n node.right.val = node.add;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
最长递增子序列 II
\\n题目详情可见 最长递增子序列 II
\\n这里是「区间最值」且对区间进行「覆盖」的更新操作类型
\\n下面给出代码:
\\n###java
\\n\\n","description":"729. 我的日程安排表 I 731. 我的日程安排表 II\\n\\n732. 我的日程安排表 III\\n\\n715. Range 模块\\n\\n307. 区域和检索 - 数组可修改\\n\\n933. 最近的请求次数\\n\\n699. 掉落的方块\\n\\n6206. 最长递增子序列 II\\n\\n线段树引入\\n\\n遇到过好多次线段树的题目,要么就是用其他的方法去解决,要么就是不会写!!今天痛定思痛,决定好好归纳整理一下线段树\\n\\n线段树解决的是「区间和」的问题,且该「区间」会被修改\\n\\n什么意思呢?举个简单的例子,对于 nums = [1, 2, 3, 4, 5]\\n\\n如果我们需要多次求某些区间的和…","guid":"https://leetcode.cn/problems/my-calendar-ii//solution/by-lfool-nodi","author":"lfool","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-19T01:40:50.043Z","media":[{"url":"https://pic.leetcode-cn.com/1654588271-zbOpBr-1.svg","type":"photo","width":621,"height":311,"blurhash":"L34Vs*re0cT:uwMyIU+S01PK=$.j"},{"url":"https://pic.leetcode-cn.com/1655808248-EfPSRV-1.svg","type":"photo","width":62,"height":71,"blurhash":"LoGSDht700Rj_3ofD%WBD%WBxuj["},{"url":"https://pic.leetcode-cn.com/1655808231-VTJvAM-2.svg","type":"photo","width":462,"height":232,"blurhash":"L64L?nYG54xb^TQ;4Ux[_3WB9Fxu"},{"url":"https://pic.leetcode-cn.com/1655808224-kMYiyq-3.svg","type":"photo","width":1374,"height":804,"blurhash":"LKRpE{_2My_4?JkCofj]Myt7NFt7"},{"url":"https://pic.leetcode-cn.com/1654588378-Bhkpkc-3.svg","type":"photo","width":641,"height":341,"blurhash":"L455RWK}DiSaZWQ:Sdi*0_nN-=cW"},{"url":"https://pic.leetcode-cn.com/1654588328-LNnVpz-2.svg","type":"photo","width":621,"height":311,"blurhash":"L55E~RP8DiTqmbR7M{#H0_r=x]%y"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"我的日程安排表 II","url":"https://leetcode.cn/problems/my-calendar-ii//solution/wo-de-ri-cheng-an-pai-biao-ii-by-leetcod-wo6n","content":"class Solution {\\n public int lengthOfLIS(int[] nums, int k) {\\n int ans = 0;\\n Map<Integer, Integer> map = new HashMap<>();\\n for (int i = 0; i < nums.length; i++) {\\n // 查询区间 [nums[i] - k, nums[i] - 1] 的最值\\n int cnt = query(root, 0, N, Math.max(0, nums[i] - k), nums[i] - 1) + 1;\\n // 更新,注意这里是覆盖更新,对应的模版中覆盖更新不需要累加,已在下方代码中标注\\n update(root, 0, N, nums[i], nums[i], cnt);\\n ans = Math.max(ans, cnt);\\n }\\n return ans;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n Node left, right;\\n int val, add;\\n }\\n private int N = (int) 1e5;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = val; // 不需要累加\\n node.add = val; // 不需要累加\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add; // 不需要累加\\n node.right.val = node.add; // 不需要累加\\n node.left.add = node.add; // 不需要累加\\n node.right.add = node.add; // 不需要累加\\n node.add = 0;\\n }\\n}\\n
方法一:直接遍历
\\n记录下所有已经预定的课程安排区间与已经预定过两次的课程安排区间,当我们预定新的区间 $[\\\\textit{start}, \\\\textit{end})$ 时,此时检查当前已经预定过两次的每个日程安排是否与新日程安排冲突。若不冲突,则可以添加新的日程安排。
\\n\\n
\\n- 对于两个区间 $[s_1,e_1)$ 和 $[s_2,e_2)$,如果二者没有交集,则此时应当满足 $s_1 \\\\ge e_2$ 或者 $s_2 \\\\ge e_1$,这就意味着如果满足 $s_1 < e_2$ 并且 $s_2 < e_1$ 时,则两者会产生差集。
\\n- 首先检测新加入的区间 $[\\\\textit{start}, \\\\textit{end})$ 是否与已经预定过两次的区间有交集,如果没有冲突,则将新加入的区间与已经预定的区间进行检查,求出新增的预定两次的区间。对于两个区间 $[s_1,e_1)$ 和 $[s_2,e_2)$,则他们之间的交集为 $[\\\\max(s_1,s_2), \\\\min(e_1,e_2))$。
\\n###Python
\\n\\nclass MyCalendarTwo:\\n def __init__(self):\\n self.booked = []\\n self.overlaps = []\\n\\n def book(self, start: int, end: int) -> bool:\\n if any(s < end and start < e for s, e in self.overlaps):\\n return False\\n for s, e in self.booked:\\n if s < end and start < e:\\n self.overlaps.append((max(s, start), min(e, end)))\\n self.booked.append((start, end))\\n return True\\n
###C++
\\n\\nclass MyCalendarTwo {\\npublic:\\n MyCalendarTwo() {\\n\\n }\\n\\n bool book(int start, int end) {\\n for (auto &[l, r] : overlaps) {\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n for (auto &[l, r] : booked) {\\n if (l < end && start < r) {\\n overlaps.emplace_back(max(l, start), min(r, end));\\n }\\n }\\n booked.emplace_back(start, end);\\n return true;\\n }\\nprivate:\\n vector<pair<int, int>> booked;\\n vector<pair<int, int>> overlaps;\\n};\\n
###Java
\\n\\nclass MyCalendarTwo {\\n List<int[]> booked;\\n List<int[]> overlaps;\\n\\n public MyCalendarTwo() {\\n booked = new ArrayList<int[]>();\\n overlaps = new ArrayList<int[]>();\\n }\\n\\n public boolean book(int start, int end) {\\n for (int[] arr : overlaps) {\\n int l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n for (int[] arr : booked) {\\n int l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n overlaps.add(new int[]{Math.max(l, start), Math.min(r, end)});\\n }\\n }\\n booked.add(new int[]{start, end});\\n return true;\\n }\\n}\\n
###C#
\\n\\npublic class MyCalendarTwo {\\n List<Tuple<int, int>> booked;\\n List<Tuple<int, int>> overlaps;\\n\\n public MyCalendarTwo() {\\n booked = new List<Tuple<int, int>>();\\n overlaps = new List<Tuple<int, int>>();\\n }\\n\\n public bool Book(int start, int end) {\\n foreach (Tuple<int, int> tuple in overlaps) {\\n int l = tuple.Item1, r = tuple.Item2;\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n foreach (Tuple<int, int> tuple in booked) {\\n int l = tuple.Item1, r = tuple.Item2;\\n if (l < end && start < r) {\\n overlaps.Add(new Tuple<int, int>(Math.Max(l, start), Math.Min(r, end)));\\n }\\n }\\n booked.Add(new Tuple<int, int>(start, end));\\n return true;\\n }\\n}\\n
###C
\\n\\ntypedef struct {\\n int *booked;\\n int bookedSize;\\n int *overlaps;\\n int overlapsSize;\\n} MyCalendarTwo;\\n\\n#define MAX_BOOK_SIZE 1001\\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\\n\\nMyCalendarTwo* myCalendarTwoCreate() {\\n MyCalendarTwo *obj = (MyCalendarTwo *)malloc(sizeof(MyCalendarTwo));\\n obj->booked = (int *)malloc(sizeof(int) * 2 * MAX_BOOK_SIZE);\\n obj->overlaps = (int *)malloc(sizeof(int) * 2 * MAX_BOOK_SIZE);\\n obj->bookedSize = 0;\\n obj->overlapsSize = 0;\\n return obj;\\n}\\n\\nbool myCalendarTwoBook(MyCalendarTwo* obj, int start, int end) {\\n for (int i = 0; i < obj->overlapsSize; i++) {\\n int l = obj->overlaps[2 * i];\\n int r = obj->overlaps[2 * i + 1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n for (int i = 0; i < obj->bookedSize; i++) {\\n int l = obj->booked[2 * i];\\n int r = obj->booked[2 * i + 1];\\n if (l < end && start < r) {\\n obj->overlaps[obj->overlapsSize * 2] = MAX(l, start);\\n obj->overlaps[obj->overlapsSize * 2 + 1] = MIN(r, end);\\n obj->overlapsSize++;\\n }\\n }\\n obj->booked[obj->bookedSize * 2] = start;\\n obj->booked[obj->bookedSize * 2 + 1] = end;\\n obj->bookedSize++;\\n return true;\\n}\\n\\nvoid myCalendarTwoFree(MyCalendarTwo* obj) {\\n free(obj->booked);\\n free(obj->overlaps);\\n}\\n
###JavaScript
\\n\\nvar MyCalendarTwo = function() {\\n this.booked = [];\\n this.overlaps = [];\\n};\\n\\nMyCalendarTwo.prototype.book = function(start, end) {\\n for (const arr of this.overlaps) {\\n let l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n for (const arr of this.booked) {\\n let l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n this.overlaps.push([Math.max(l, start), Math.min(r, end)]);\\n }\\n }\\n this.booked.push([start, end]);\\n return true;\\n};\\n
###go
\\n\\ntype pair struct{ start, end int }\\ntype MyCalendarTwo struct{ booked, overlaps []pair }\\n\\nfunc Constructor() MyCalendarTwo {\\n return MyCalendarTwo{}\\n}\\n\\nfunc (c *MyCalendarTwo) Book(start, end int) bool {\\n for _, p := range c.overlaps {\\n if p.start < end && start < p.end {\\n return false\\n }\\n }\\n for _, p := range c.booked {\\n if p.start < end && start < p.end {\\n c.overlaps = append(c.overlaps, pair{max(p.start, start), min(p.end, end)})\\n }\\n }\\n c.booked = append(c.booked, pair{start, end})\\n return true\\n}\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n\\nfunc max(a, b int) int {\\n if b > a {\\n return b\\n }\\n return a\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$, 其中 $n$ 表示日程安排的数量。由于每次在进行预定时,都需要遍历所有已经预定的行程安排。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 表示日程安排的数量。需要保存所有已经预定的行程。
\\n方法二:差分数组
\\n利用差分数组的思想,每当我们预定一个新的日程安排 $[\\\\textit{start}, \\\\textit{end})$,在 $\\\\textit{start}$ 计数 $\\\\textit{cnt}[\\\\textit{start}]$ 加 $1$,表示从 $\\\\textit{start}$ 预定的数目加 $1$;从 $\\\\textit{end}$ 计数 $\\\\textit{cnt}[\\\\textit{end}]$ 减 $1$,表示从 $\\\\textit{end}$ 开始预定的数目减 $1$。此时以起点 $x$ 开始的预定的数目 $\\\\textit{book}x = \\\\sum{y \\\\le x}\\\\textit{cnt}[y]$,当我们将 $[\\\\textit{start}, \\\\textit{end})$ 加入后,如果发现存在区间的预定数目大于 $2$ 时,此时为非法应去除新加入的区间 $[\\\\textit{start}, \\\\textit{end})$。由于本题中 $\\\\textit{start}, \\\\textit{end}$ 数量较大,我们利用 $\\\\texttt{TreeMap}$ 计数即可。
\\n###Python
\\n\\nfrom sortedcontainers import SortedDict\\n\\nclass MyCalendarTwo:\\n def __init__(self):\\n self.cnt = SortedDict()\\n\\n def book(self, start: int, end: int) -> bool:\\n self.cnt[start] = self.cnt.get(start, 0) + 1\\n self.cnt[end] = self.cnt.get(end, 0) - 1\\n maxBook = 0\\n for c in self.cnt.values():\\n maxBook += c\\n if maxBook > 2:\\n self.cnt[start] = self.cnt.get(start, 0) - 1\\n self.cnt[end] = self.cnt.get(end, 0) + 1\\n return False\\n return True\\n
###C++
\\n\\nclass MyCalendarTwo {\\npublic:\\n MyCalendarTwo() {\\n\\n }\\n\\n bool book(int start, int end) {\\n int ans = 0;\\n int maxBook = 0;\\n cnt[start]++;\\n cnt[end]--;\\n for (auto &[_, freq] : cnt) {\\n maxBook += freq;\\n ans = max(maxBook, ans);\\n if (maxBook > 2) {\\n cnt[start]--;\\n cnt[end]++;\\n return false;\\n }\\n }\\n return true;\\n }\\nprivate:\\n map<int, int> cnt;\\n};\\n
###Java
\\n\\nclass MyCalendarTwo {\\n TreeMap<Integer, Integer> cnt;\\n\\n public MyCalendarTwo() {\\n cnt = new TreeMap<Integer, Integer>();\\n }\\n\\n public boolean book(int start, int end) {\\n int ans = 0;\\n int maxBook = 0;\\n cnt.put(start, cnt.getOrDefault(start, 0) + 1);\\n cnt.put(end, cnt.getOrDefault(end, 0) - 1);\\n for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {\\n int freq = entry.getValue();\\n maxBook += freq;\\n ans = Math.max(maxBook, ans);\\n if (maxBook > 2) {\\n cnt.put(start, cnt.getOrDefault(start, 0) - 1);\\n cnt.put(end, cnt.getOrDefault(end, 0) + 1);\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
###go
\\n\\ntype MyCalendarTwo struct {\\n *redblacktree.Tree\\n}\\n\\nfunc Constructor() MyCalendarTwo {\\n return MyCalendarTwo{redblacktree.NewWithIntComparator()}\\n}\\n\\nfunc (c MyCalendarTwo) add(key, value int) {\\n if v, ok := c.Get(key); ok {\\n c.Put(key, v.(int)+value)\\n } else {\\n c.Put(key, value)\\n }\\n}\\n\\nfunc (c MyCalendarTwo) Book(start, end int) bool {\\n c.add(start, 1)\\n c.add(end, -1)\\n maxBook := 0\\n it := c.Iterator()\\n for it.Next() {\\n maxBook += it.Value().(int)\\n if maxBook > 2 {\\n c.add(start, -1)\\n c.add(end, 1)\\n return false\\n }\\n }\\n return true\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 为日程安排的数量。每次求的最大的预定需要遍历所有的日程安排。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 为日程安排的数量。需要空间存储所有的日程安排计数,需要的空间为 $O(n)$。
\\n方法三:线段树
\\n利用线段树,假设我们开辟了数组 $\\\\textit{arr}[0,\\\\cdots, 10^9]$,初始时每个元素的值都为 $0$,对于每次行程预定的区间 $[\\\\textit{start}, \\\\textit{end})$ ,则我们将区间中的元素 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 中的每个元素加 $1$,如果数组 $arr$ 的最大元素大于 $2$ 时,此时则出现某个区间被安排了 $2$ 次上,此时返回 $\\\\texttt{false}$,同时将数组区间 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 进行减 $1$ 即可恢复。实际我们不必实际开辟数组 $\\\\textit{arr}$,可采用动态线段树,懒标记 $\\\\textit{lazy}$ 标记区间 $[l,r]$ 进行累加的次数,$\\\\textit{tree}$ 记录区间 $[l,r]$ 的最大值,每次动态更新线段树即可。
\\n###Python
\\n\\nclass MyCalendarTwo:\\n def __init__(self):\\n self.tree = {}\\n\\n def update(self, start: int, end: int, val: int, l: int, r: int, idx: int) -> None:\\n if r < start or end < l:\\n return\\n if start <= l and r <= end:\\n p = self.tree.get(idx, [0, 0])\\n p[0] += val\\n p[1] += val\\n self.tree[idx] = p\\n return\\n mid = (l + r) // 2\\n self.update(start, end, val, l, mid, 2 * idx)\\n self.update(start, end, val, mid + 1, r, 2 * idx + 1)\\n p = self.tree.get(idx, [0, 0])\\n p[0] = p[1] + max(self.tree.get(2 * idx, (0,))[0], self.tree.get(2 * idx + 1, (0,))[0])\\n self.tree[idx] = p\\n\\n def book(self, start: int, end: int) -> bool:\\n self.update(start, end - 1, 1, 0, 10 ** 9, 1)\\n if self.tree[1][0] > 2:\\n self.update(start, end - 1, -1, 0, 10 ** 9, 1)\\n return False\\n return True\\n
###C++
\\n\\nclass MyCalendarTwo {\\npublic:\\n MyCalendarTwo() {\\n\\n }\\n\\n void update(int start, int end, int val, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n tree[idx].first += val;\\n tree[idx].second += val;\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, val, l, mid, 2 * idx);\\n update(start, end, val, mid + 1, r, 2 * idx + 1);\\n tree[idx].first = tree[idx].second + max(tree[2 * idx].first, tree[2 * idx + 1].first);\\n }\\n }\\n\\n bool book(int start, int end) { \\n update(start, end - 1, 1, 0, 1e9, 1);\\n if (tree[1].first > 2) {\\n update(start, end - 1, -1, 0, 1e9, 1);\\n return false;\\n }\\n return true;\\n }\\nprivate:\\n unordered_map<int, pair<int, int>> tree;\\n};\\n
###Java
\\n\\nclass MyCalendarTwo {\\n Map<Integer, int[]> tree;\\n\\n public MyCalendarTwo() {\\n tree = new HashMap<Integer, int[]>();\\n }\\n\\n public boolean book(int start, int end) {\\n update(start, end - 1, 1, 0, 1000000000, 1);\\n tree.putIfAbsent(1, new int[2]);\\n if (tree.get(1)[0] > 2) {\\n update(start, end - 1, -1, 0, 1000000000, 1);\\n return false;\\n }\\n return true;\\n }\\n\\n public void update(int start, int end, int val, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n tree.putIfAbsent(idx, new int[2]);\\n if (start <= l && r <= end) {\\n tree.get(idx)[0] += val;\\n tree.get(idx)[1] += val;\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, val, l, mid, 2 * idx);\\n update(start, end, val, mid + 1, r, 2 * idx + 1);\\n tree.putIfAbsent(2 * idx, new int[2]);\\n tree.putIfAbsent(2 * idx + 1, new int[2]);\\n tree.get(idx)[0] = tree.get(idx)[1] + Math.max(tree.get(2 * idx)[0], tree.get(2 * idx + 1)[0]);\\n }\\n }\\n}\\n
###C#
\\n\\npublic class MyCalendarTwo {\\n Dictionary<int, int[]> tree;\\n\\n public MyCalendarTwo() {\\n tree = new Dictionary<int, int[]>();\\n }\\n\\n public bool Book(int start, int end) {\\n Update(start, end - 1, 1, 0, 1000000000, 1);\\n if (!tree.ContainsKey(1)) {\\n tree.Add(1, new int[2]);\\n }\\n if (tree[1][0] > 2) {\\n Update(start, end - 1, -1, 0, 1000000000, 1);\\n return false;\\n }\\n return true;\\n }\\n\\n public void Update(int start, int end, int val, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (!tree.ContainsKey(idx)) {\\n tree.Add(idx, new int[2]);\\n }\\n if (start <= l && r <= end) {\\n tree[idx][0] += val;\\n tree[idx][1] += val;\\n } else {\\n int mid = (l + r) >> 1;\\n Update(start, end, val, l, mid, 2 * idx);\\n Update(start, end, val, mid + 1, r, 2 * idx + 1);\\n if (!tree.ContainsKey(2 * idx)) {\\n tree.Add(2 * idx, new int[2]);\\n }\\n if (!tree.ContainsKey(2 * idx + 1)) {\\n tree.Add(2 * idx + 1, new int[2]);\\n }\\n tree[idx][0] = tree[idx][1] + Math.Max(tree[2 * idx][0], tree[2 * idx + 1][0]);\\n }\\n }\\n}\\n
###C
\\n\\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\\n\\ntypedef struct HashItem {\\n int key;\\n int maxBook;\\n int lazy;\\n UT_hash_handle hh;\\n} HashItem;\\n\\ntypedef struct {\\n HashItem *tree;\\n} MyCalendarTwo;\\n\\nMyCalendarTwo* myCalendarTwoCreate() {\\n MyCalendarTwo *obj = (MyCalendarTwo *)malloc(sizeof(MyCalendarTwo));\\n obj->tree = NULL;\\n return obj; \\n}\\n\\nvoid update(MyCalendarTwo* obj, int start, int end, int val, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry == NULL) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n pEntry->maxBook = val;\\n pEntry->lazy = val;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n } else {\\n pEntry->maxBook += val;\\n pEntry->lazy += val;\\n }\\n } else {\\n int mid = (l + r) >> 1;\\n update(obj, start, end, val, l, mid, 2 * idx);\\n update(obj, start, end, val, mid + 1, r, 2 * idx + 1);\\n int lchid = idx * 2, rchid = idx * 2 + 1;\\n int lmax = 0, rmax = 0;\\n HashItem *pEntry1 = NULL, *pEntry2 = NULL;\\n HASH_FIND_INT(obj->tree, &lchid, pEntry1);\\n HASH_FIND_INT(obj->tree, &rchid, pEntry2);\\n if (pEntry1) {\\n lmax = pEntry1->maxBook;\\n }\\n if (pEntry2) {\\n rmax = pEntry2->maxBook;\\n }\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry) {\\n pEntry->maxBook = pEntry->lazy + MAX(lmax, rmax);\\n } else {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n pEntry->maxBook = 1;\\n pEntry->lazy = 0;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n }\\n }\\n}\\n\\nbool myCalendarTwoBook(MyCalendarTwo* obj, int start, int end) {\\n update(obj, start, end - 1, 1, 0, 1e9, 1);\\n int idx = 1;\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry->maxBook > 2) {\\n update(obj, start, end - 1, -1, 0, 1e9, 1);\\n return false;\\n }\\n return true;\\n}\\n\\nvoid myCalendarTwoFree(MyCalendarTwo* obj) {\\n struct HashItem *curr,*tmp;\\n HASH_ITER(hh, obj->tree, curr, tmp) {\\n HASH_DEL(obj->tree, curr); \\n free(curr); \\n } \\n free(obj); \\n}\\n
###JavaScript
\\n\\nvar MyCalendarTwo = function() {\\n this.tree = new Map();\\n};\\n\\nMyCalendarTwo.prototype.book = function(start, end) {\\n const update = (start, end, val, l, r, idx) => {\\n if (r < start || end < l) {\\n return;\\n } \\n if (!this.tree.has(idx)) {\\n this.tree.set(idx, [0, 0]);\\n }\\n if (start <= l && r <= end) {\\n this.tree.get(idx)[0] += val;\\n this.tree.get(idx)[1] += val;\\n } else {\\n const mid = (l + r) >> 1;\\n update(start, end, val, l, mid, 2 * idx);\\n update(start, end, val, mid + 1, r, 2 * idx + 1);\\n if (!this.tree.has(2 * idx)) {\\n this.tree.set(2 * idx, [0, 0]);\\n }\\n if (!this.tree.has(2 * idx + 1)) {\\n this.tree.set(2 * idx + 1, [0, 0]);\\n }\\n this.tree.get(idx)[0] = this.tree.get(idx)[1] + Math.max(this.tree.get(2 * idx)[0], this.tree.get(2 * idx + 1)[0]);\\n }\\n }\\n update(start, end - 1, 1, 0, 1000000000, 1);\\n if (!this.tree.has(1)) {\\n this.tree.set(1, [0, 0])\\n }\\n if (this.tree.get(1)[0] > 2) {\\n update(start, end - 1, -1, 0, 1000000000, 1);\\n return false;\\n }\\n return true;\\n};\\n
###go
\\n\\ntype pair struct{ first, second int }\\ntype MyCalendarTwo map[int]pair\\n\\nfunc Constructor() MyCalendarTwo {\\n return MyCalendarTwo{}\\n}\\n\\nfunc (tree MyCalendarTwo) update(start, end, val, l, r, idx int) {\\n if r < start || end < l {\\n return\\n }\\n if start <= l && r <= end {\\n p := tree[idx]\\n p.first += val\\n p.second += val\\n tree[idx] = p\\n return\\n }\\n mid := (l + r) >> 1\\n tree.update(start, end, val, l, mid, 2*idx)\\n tree.update(start, end, val, mid+1, r, 2*idx+1)\\n p := tree[idx]\\n p.first = p.second + max(tree[2*idx].first, tree[2*idx+1].first)\\n tree[idx] = p\\n}\\n\\nfunc (tree MyCalendarTwo) Book(start, end int) bool {\\n tree.update(start, end-1, 1, 0, 1e9, 1)\\n if tree[1].first > 2 {\\n tree.update(start, end-1, -1, 0, 1e9, 1)\\n return false\\n }\\n return true\\n}\\n\\nfunc max(a, b int) int {\\n if b > a {\\n return b\\n }\\n return a\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:直接遍历 记录下所有已经预定的课程安排区间与已经预定过两次的课程安排区间,当我们预定新的区间 $[\\\\textit{start}, \\\\textit{end})$ 时,此时检查当前已经预定过两次的每个日程安排是否与新日程安排冲突。若不冲突,则可以添加新的日程安排。\\n\\n对于两个区间 $[s_1,e_1)$ 和 $[s_2,e_2)$,如果二者没有交集,则此时应当满足 $s_1 \\\\ge e_2$ 或者 $s_2 \\\\ge e_1$,这就意味着如果满足 $s_1 < e_2$ 并且 $s_2 < e_1$ 时,则两者会产生差集。\\n首先检测新加入的区间…","guid":"https://leetcode.cn/problems/my-calendar-ii//solution/wo-de-ri-cheng-an-pai-biao-ii-by-leetcod-wo6n","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-18T02:00:31.833Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数据范围格局小了","url":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/shu-ju-fan-wei-ge-ju-xiao-liao-by-johnkr-dl63","content":"- \\n
\\n时间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于使用了线段树查询,线段树的最大深度为 $\\\\log C$, 每次最多会查询 $\\\\log C$ 个节点,每次求最大的预定需的时间复杂度为 $O(\\\\log C + \\\\log C)$,因此时间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值即为 $10^9$。
\\n- \\n
\\n空间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于该解法采用的为动态线段树,线段树的最大深度为 $\\\\log C$,每次预定最多会在线段树上增加 $\\\\log C$ 个节点,因此空间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值即为 $10^9$。
\\n线性时间解法
\\n设$f[x]$为长度为$n$,$arr[0]=1$,$arr[n-1]=x$的不同理想数组的数目。
\\n考虑一个长度为$n$,$arr[0]=1$,$arr[n-1]=x$的理想数组,$\\\\forall 1\\\\le i\\\\le \\\\lfloor\\\\frac{maxValue }{x}\\\\rfloor$,将每一项均乘上$i$,可得一个$arr[0]=i,arr[n-1]=i*x$的理想数组。因此,枚举$x$和$i$可得原问题答案为$\\\\sum_{x=1}^{maxValue}\\\\lfloor\\\\frac{maxValue}{x}\\\\rfloor f[x]$。
\\n接下来考虑如何求$f[x]$。设$x=\\\\prod_{i=1}^kp_i^{a_i}$,则每一个满足条件的理想数组均对应唯一一组$b_{i,j}(1\\\\le i\\\\le k,0\\\\le j\\\\le n-1)$,使得$arr[j]=\\\\prod_{i=1}^kp_i^{b_{i,j}}$。由$f[x]$定义得$b_{i,j}$的限制为$\\\\forall 1\\\\le i\\\\le k,0=b_{i,0}\\\\le b_{i,1}\\\\le...\\\\le b_{i,n-2}\\\\le b_{i,n-1}=a_i$。
\\n设$c_j=b_{i,j+1}-b_{i,j}(0\\\\le j\\\\le n-2)$,则$c_j$的限制为$c_j\\\\ge0$与$\\\\sum_{j=0}^{n-2}c_j=a_i$。使用隔板法得对于$\\\\forall 1\\\\le i\\\\le k$,所有满足条件的$c_{j}$的个数(即$b_{i,j}$的个数)为${a_i+n-2}\\\\choose{n-2}$。不同的$i$之间是独立的,因此$f[x]=\\\\prod_{i=1}^k{{a_i+n-2}\\\\choose{n-2}}$。
\\n注意到这是一个积性函数,因此可以用线性筛在$\\\\Theta(maxValue)$的时间求出所有的$f[x]$。求出所有$f[x]$之后就可以在$\\\\Theta(maxValue)$的时间内得到原问题的答案。总时间复杂度为$\\\\Theta(maxValue)$。
\\n具体来说,${{a+1+n-2}\\\\choose{n-2}}=\\\\frac{a+n-1}{a+1}{{a+n-2}\\\\choose{n-2}}$,因此可以预处理出所有可能的$\\\\frac{1}{a+1}(1\\\\le a\\\\le \\\\log maxValue)$,然后再用一个数组$a[x]$维护每个$x$的$a_1$,就可以在$\\\\Theta(1)$的时间内使用$f[x]$和$a[x]$得到$f[xp]$和$a[xp]$。
\\nC++代码如下(代码中的$a$和$d$对应题解中的$f$和$a$):
\\n\\n","description":"设$f[x]$为长度为$n$,$arr[0]=1$,$arr[n-1]=x$的不同理想数组的数目。 考虑一个长度为$n$,$arr[0]=1$,$arr[n-1]=x$的理想数组,$\\\\forall 1\\\\le i\\\\le \\\\lfloor\\\\frac{maxValue }{x}\\\\rfloor$,将每一项均乘上$i$,可得一个$arr[0]=i,arr[n-1]=i*x$的理想数组。因此,枚举$x$和$i$可得原问题答案为$\\\\sum_{x=1}^{maxValue}\\\\lfloor\\\\frac{maxValue}{x}\\\\rfloor f[x]$。\\n\\n接下来考虑如何求…","guid":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/shu-ju-fan-wei-ge-ju-xiao-liao-by-johnkr-dl63","author":"JOHNKRAM","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-10T04:47:43.837Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【图解】转化成放球问题(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/shu-lun-zu-he-shu-xue-zuo-fa-by-endlessc-iouh","content":"class Solution {\\n const int P=1000000007;\\n typedef long long ll;\\n int p[10005],a[10005],d[10005],I[20];\\n bool b[10005];\\npublic:\\n int idealArrays(int n, int maxValue) {\\n int m=0,i,j,ans=maxValue;\\n for(I[1]=1,i=2;i<20;i++)I[i]=(ll)(P-P/i)*I[P%i]%P;\\n for(a[1]=1,i=2;i<=maxValue;i++)\\n {\\n if(!b[i])\\n {\\n a[p[m++]=i]=n-1;\\n d[i]=1;\\n }\\n ans=(ans+(ll)maxValue/i*a[i])%P;\\n for(j=0;j<m&&i*p[j]<=maxValue;j++)\\n {\\n b[i*p[j]]=1;\\n if(i%p[j])\\n {\\n a[i*p[j]]=(ll)a[i]*(n-1)%P;\\n d[i*p[j]]=1;\\n }\\n else\\n {\\n a[i*p[j]]=(ll)a[i]*(d[i]+n-1)%P*I[d[i]+1]%P;\\n d[i*p[j]]=d[i]+1;\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
分析
\\n下文把 $\\\\textit{arr}$ 简记为 $a$。
\\n题目说,每个 $a_i$ 都可以被 $a_{i-1}$ 整除,即 $\\\\dfrac{a_i}{a_{i-1}}$ 是整数。
\\n比如 $a=[1,1,2,4,4,8,8,8]$ 是符合题目要求的。看上去,$a$ 中有很多重复元素,或者说,不同元素个数并不会很多。
\\n想一想,如果 $n=10^4$,$\\\\textit{maxValue}=8$,那么 $a$ 中至多有多少个不同的元素?
\\n如果 $a_{i-1}\\\\ne a_i$,那么 $a_i$ 至少是 $2\\\\cdot a_{i-1}$。假设 $a_0=1$,至多乘三次 $2$,得到 $8$,就不能再变大了,所以 $a$ 中至多有 $4$ 个不同的元素,即 $1,2,4,8$。
\\n这个例子说明,即使 $n$ 很大,$a$ 中也至多有 $\\\\left\\\\lfloor\\\\log_2 \\\\textit{maxValue}\\\\right\\\\rfloor + 1$ 个不同的元素。
\\n这启发我们重点考虑 $a_{i-1}\\\\ne a_i$ 的情况,也就是 $\\\\dfrac{a_i}{a_{i-1}} > 1$ 的情况。
\\n商分
\\n类似 差分,我们来计算 $a$ 的「商分」,即相邻两数的商。
\\n定义 $q_0 = a_0$,$q_i = \\\\dfrac{a_i}{a_{i-1}}\\\\ (i\\\\ge 1)$。
\\n例如:
\\n\\n
\\n- $a=[2,2,4,8,8]$ 的商分数组为 $q=[2,1,2,2,1]$。
\\n- $a=[1,4,4,8,8]$ 的商分数组为 $q=[1,4,1,2,1]$。
\\n不同的 $a$,唯一对应着不同的 $q$。计算理想数组 $a$ 的个数,可以转化成计算商分数组 $q$ 的个数。
\\n根据 $q$ 的定义,所有 $q_i$ 的乘积等于
\\n$$
\\n
\\n\\\\prod_{i=0}^{n-1}q_i = a_0\\\\cdot \\\\dfrac{a_1}{a_0} \\\\cdot \\\\dfrac{a_2}{a_1} \\\\cdots \\\\dfrac{a_{n-1}}{a_{n-2}} = a_{n-1}
\\n$$现在假设 $a_{n-1}=8$,也就是所有 $q_i$ 的乘积等于 $8$,有多少个不同的 $q$?
\\n放球问题
\\n这个问题等价于,有 $n$ 个位置,把 $3$ 个 $2$ 分配到 $n$ 个位置的方案数。注:分配到同一个位置就乘起来,比如 $2$ 个 $2$ 分配到同一个位置就是 $4$。没分配 $2$ 的位置是 $1$。
\\n这等价于如下放球问题:
\\n\\n
\\n- 把 $k=3$ 个无区别的小球放到 $n$ 个有区别的盒子中,允许盒子为空,一个盒子也可以放多个小球,有多少种不同的放法?
\\n\\n
思路
\\n枚举 $a_{n-1}=1,2,3,\\\\ldots,\\\\textit{maxValue}$,根据上图最后的公式,计算方案数,加到答案中。
\\n如何分解质因子
\\n方法一:枚举(适用于本题)
\\n计算 $x$ 每个质因子的个数。从 $i=2$ 开始枚举,如果 $x$ 能被 $i$ 整除,就不断地除 $i$,直到 $x$ 不能被 $i$ 整除为止,统计除 $i$ 的次数,即为 $x$ 中的质因子 $i$ 的出现次数。
\\n什么时候停止枚举呢?如果 $i^2 > x$,继续向后枚举是不会出现 $x$ 被 $i$ 整除的情况的。这可以用反证法证明:假设存在 $i$,满足 $i^2>x$ 且 $x$ 能被 $i$ 整除,那么 $x$ 也能被 $\\\\dfrac{x}{i}$ 整除,注意到 $\\\\dfrac{x}{i}<i$,但我们已经处理完小于 $i$ 的质因子了,不会出现 $x$ 仍可以被一个小于 $i$ 的质因子整除的情况,矛盾。所以当 $i^2 > x$ 时可以停止枚举。
\\n循环结束后,如果 $x>1$,说明还有一个质因子为 $x$。
\\n方法二:预处理 LPF(适用于更大的数据范围)
\\n利用埃氏筛或者欧拉筛,用质数 $p$ 标记 $p$ 的倍数(跳过已经标记的数),我们可以预处理每个数 $x$ 的最小质因子 $\\\\text{LPF}[x]$。不断地更新 $x$ 为 $\\\\dfrac{x}{\\\\text{LPF}[x]}$,直到 $x=1$,在这个过程中统计每个质因子的出现次数。
\\n如何计算组合数
\\n方法一:递推(适用于本题)
\\n对于从 $n$ 个物品中选择 $k$ 个物品的方案数 $C(n,k)$,可以用「选或不选」来思考,对于第 $n$ 个物品:
\\n\\n
\\n- 不选:问题变成从 $n-1$ 个物品中选择 $k$ 个物品的方案数 $C(n-1,k)$。
\\n- 选:问题变成从 $n-1$ 个物品中选择 $k-1$ 个物品的方案数 $C(n-1,k-1)$。
\\n所以 $C(n,k) = C(n-1,k) + C(n-1,k-1)$。
\\n初始值:$C(n,0) = 1$。
\\n对于本题,由于 $2^{13} < 10^4 < 2^{14}$,我们可以预处理 $n\\\\le 10^4 + 13-1$ 和 $k \\\\le 13$ 的组合数。
\\n方法二:预处理阶乘及其逆元(适用于更大的数据范围)
\\n\\n\\nMOD = 1_000_000_007\\nMAX_N = 10_000\\nMAX_E = 13\\n\\n# EXP[x] 为 x 分解质因数后,每个质因数的指数\\nEXP = [[] for _ in range(MAX_N + 1)]\\nfor x in range(2, len(EXP)):\\n t = x\\n i = 2\\n while i * i <= t:\\n e = 0\\n while t % i == 0:\\n e += 1\\n t //= i\\n if e:\\n EXP[x].append(e)\\n i += 1\\n if t > 1:\\n EXP[x].append(1)\\n\\n# 预处理组合数\\nC = [[0] * (MAX_E + 1) for _ in range(MAX_N + MAX_E)]\\nfor i in range(len(C)):\\n C[i][0] = 1\\n for j in range(1, min(i, MAX_E) + 1):\\n C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD\\n\\nclass Solution:\\n def idealArrays(self, n: int, maxValue: int) -> int:\\n ans = 0\\n for x in range(1, maxValue + 1):\\n res = 1\\n for e in EXP[x]:\\n res = res * C[n + e - 1][e] % MOD\\n ans += res\\n return ans % MOD\\n
\\nMOD = 1_000_000_007\\nMAX_N = 10_000\\n\\n# EXP[x] 为 x 分解质因数后,每个质因数的指数\\nEXP = [[] for _ in range(MAX_N + 1)]\\nfor x in range(2, len(EXP)):\\n t = x\\n i = 2\\n while i * i <= t:\\n e = 0\\n while t % i == 0:\\n e += 1\\n t //= i\\n if e:\\n EXP[x].append(e)\\n i += 1\\n if t > 1:\\n EXP[x].append(1)\\n\\nclass Solution:\\n def idealArrays(self, n: int, maxValue: int) -> int:\\n ans = 0\\n for x in range(1, maxValue + 1):\\n res = 1\\n for e in EXP[x]:\\n res = res * comb(n + e - 1, e) % MOD\\n ans += res\\n return ans % MOD\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MAX_N = 10_000;\\n private static final int MAX_E = 13;\\n\\n private static final List<Integer>[] EXP = new ArrayList[MAX_N + 1];\\n private static final int[][] C = new int[MAX_N + MAX_E][MAX_E + 1];\\n\\n private static boolean done = false;\\n\\n private void init() {\\n // 这样写比 static block 更快\\n if (done) {\\n return;\\n }\\n done = true;\\n\\n // EXP[x] 为 x 分解质因数后,每个质因数的指数\\n for (int x = 1; x < EXP.length; x++) {\\n EXP[x] = new ArrayList<>();\\n int t = x;\\n for (int i = 2; i * i <= t; i++) {\\n int e = 0;\\n for (; t % i == 0; t /= i) {\\n e++;\\n }\\n if (e > 0) {\\n EXP[x].add(e);\\n }\\n }\\n if (t > 1) {\\n EXP[x].add(1);\\n }\\n }\\n\\n // 预处理组合数\\n for (int i = 0; i < MAX_N + MAX_E; i++) {\\n C[i][0] = 1;\\n for (int j = 1; j <= Math.min(i, MAX_E); j++) {\\n C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;\\n }\\n }\\n }\\n\\n public int idealArrays(int n, int maxValue) {\\n init();\\n long ans = 0;\\n for (int x = 1; x <= maxValue; x++) {\\n long mul = 1;\\n for (int e : EXP[x]) {\\n mul = mul * C[n + e - 1][e] % MOD;\\n }\\n ans += mul;\\n }\\n return (int) (ans % MOD);\\n }\\n}\\n
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MAX_N = 10\'000;\\nconst int MAX_E = 13;\\n\\nvector<int> EXP[MAX_N + 1]; \\nint C[MAX_N + MAX_E][MAX_E + 1];\\n\\nint init = []() {\\n // EXP[x] 为 x 分解质因数后,每个质因数的指数\\n for (int x = 2; x <= MAX_N; x++) {\\n int t = x;\\n for (int i = 2; i * i <= t; i++) {\\n int e = 0;\\n for (; t % i == 0; t /= i) {\\n e++;\\n }\\n if (e) {\\n EXP[x].push_back(e);\\n }\\n }\\n if (t > 1) {\\n EXP[x].push_back(1);\\n }\\n }\\n\\n // 预处理组合数\\n for (int i = 0; i < MAX_N + MAX_E; i++) {\\n C[i][0] = 1;\\n for (int j = 1; j <= min(i, MAX_E); j++) {\\n C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int idealArrays(int n, int maxValue) {\\n long long ans = 0;\\n for (int x = 1; x <= maxValue; x++) {\\n long long res = 1;\\n for (int e : EXP[x]) {\\n res = res * C[n + e - 1][e] % MOD;\\n }\\n ans += res;\\n }\\n return ans % MOD;\\n }\\n};\\n
\\nconst mod = 1_000_000_007\\nconst maxN = 10_000\\nconst maxE = 13\\n\\nvar exp [maxN + 1][]int\\nvar c [maxN + maxE][maxE + 1]int\\n\\nfunc init() {\\n// exp[x] 为 x 分解质因数后,每个质因数的指数\\nfor x := 2; x <= maxN; x++ {\\nt := x\\nfor i := 2; i*i <= t; i++ {\\ne := 0\\nfor ; t%i == 0; t /= i {\\ne++\\n}\\nif e > 0 {\\nexp[x] = append(exp[x], e)\\n}\\n}\\nif t > 1 {\\nexp[x] = append(exp[x], 1)\\n}\\n}\\n\\n// 预处理组合数\\nfor i := range c {\\nc[i][0] = 1\\nfor j := 1; j <= min(i, maxE); j++ {\\nc[i][j] = (c[i-1][j] + c[i-1][j-1]) % mod\\n}\\n}\\n}\\n\\nfunc idealArrays(n, maxValue int) (ans int) {\\nfor x := 1; x <= maxValue; x++ {\\nres := 1\\nfor _, e := range exp[x] {\\nres = res * c[n+e-1][e] % mod\\n}\\nans += res\\n}\\nreturn ans % mod\\n}\\n
复杂度分析
\\n忽略预处理的时间和空间。
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(m\\\\log\\\\log m)$,其中 $m$ 表示 $\\\\textit{maxValue}$。循环次数等同于 $[1,m]$ 中的每个数的不同质因子个数之和。由于每个质数 $p$ 的贡献不超过 $\\\\dfrac{m}{p}$,累加得 $m\\\\displaystyle\\\\sum\\\\limits_{p\\\\le m}\\\\dfrac{1}{p} = \\\\mathcal{O}(m\\\\log\\\\log m)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n相似题目
\\n\\n更多相似题目,见下面数学题单的「§2.3 放球问题」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 下文把 $\\\\textit{arr}$ 简记为 $a$。\\n\\n题目说,每个 $a_i$ 都可以被 $a_{i-1}$ 整除,即 $\\\\dfrac{a_i}{a_{i-1}}$ 是整数。\\n\\n比如 $a=[1,1,2,4,4,8,8,8]$ 是符合题目要求的。看上去,$a$ 中有很多重复元素,或者说,不同元素个数并不会很多。\\n\\n想一想,如果 $n=10^4$,$\\\\textit{maxValue}=8$,那么 $a$ 中至多有多少个不同的元素?\\n\\n如果 $a_{i-1}\\\\ne a_i$,那么 $a_i$ 至少是 $2\\\\cdot a_{i-1}$。假设 $a_0=1…","guid":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/shu-lun-zu-he-shu-xue-zuo-fa-by-endlessc-iouh","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-10T04:43:41.275Z","media":[{"url":"https://pic.leetcode.cn/1744685351-GGrXfu-lc2338-c.png","type":"photo","width":2594,"height":7389,"blurhash":"LMRfd?-;xvt7?bWBayj]I7f6Rjs:"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数学 & 递推,附复杂度与 n 无关(klogk)的做法","url":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/by-tsreaper-bzt9","content":"解法:数学 & 递推
\\n子问题
\\n\\n\\n求长度为 $n$ 的理想数组,但有一个附加条件:
\\narr[i]
与arr[i - 1]
不能相等。根据题意,
\\narr[i]
是arr[i - 1]
的倍数,也就是说arr[i]
至少是2 * arr[i - 1]
。设元素最大值为 $k$,那么这个数组的长度最多只有 $\\\\log_2{k} + 1$。因此我们维护 $f(i, j)$ 表示以 $i$ 为最后一个元素,且长度为 $j$ 的数组的方案数。有递推式
\\n$$f(i, j) = \\\\sum\\\\limits_{k \\\\text{ 是 } i \\\\text{ 的因数且小于 } i} f(k, j - 1)$$
\\n初值 $f(i, 1) = 1$。
\\n每个数的因数可以用 $\\\\mathcal{O}(n\\\\ln n)$ 的方式预先求出来。设元素最大值为 $k$,子问题的复杂度为 $\\\\mathcal{O}(k\\\\log^2 k)$。
\\n原问题
\\n原问题中允许相邻元素相等。这里我们需要观察到:如果我们定好了每种元素第一次出现的位置,那么整个数组也就确定下来了。
\\n因此问题转化为:现在有 $t$ 种元素($t$ 是 $\\\\mathcal{O}(\\\\log k)$ 级的),我们要按顺序从 $n$ 个位置里选出每种元素第一次出现的位置,而且每次选出的位置必须在上一次选择的位置之后,求方案数。
\\n我们只要从 $(n - 1)$ 个位置里选 $(t - 1)$ 个位置(因为第一个位置上的元素必然是第一次出现的,也就是说第一个位置必选),然后把所有元素按顺序放进我们选择的位置即可。那么方案数就是组合数 $C_{n - 1}^{t - 1}$。
\\n因此最终的答案就是 $\\\\sum\\\\limits_{i=1}^k\\\\sum\\\\limits_{j=1}^{\\\\log_2 k + 1}f(i, j) \\\\times C_{n - 1}^{j - 1}$。计算组合数的复杂度是 $\\\\mathcal{O}(n\\\\log k)$,统计答案的复杂度是 $\\\\mathcal{O}(k\\\\log k)$ 的。总体复杂度 $\\\\mathcal{O}(n\\\\log k + k\\\\log^2 k)$。
\\n参考代码(c++,332ms)
\\n###c++
\\n\\nclass Solution {\\n const int MOD = 1000000007;\\n const int MAXP = 16;\\n\\npublic:\\n int idealArrays(int n, int K) {\\n // nlnn 求因数\\n vector<vector<int>> fac(K + 1);\\n for (int i = 1; i <= K; i++) for (int j = i << 1; j <= K; j += i) fac[j].push_back(i);\\n\\n // 计算子问题的答案\\n vector<vector<long long>> f; \\n f.resize(K + 1, vector<long long>(20));\\n for (int i = 1; i <= K; i++) {\\n f[i][1] = 1;\\n for (int j = 2; j <= MAXP; j++) for (int t : fac[i]) f[i][j] = (f[i][j] + f[t][j - 1]) % MOD;\\n }\\n\\n // 求组合数\\n vector<vector<long long>> C;\\n C.resize(n + 1, vector<long long>(20));\\n C[0][0] = C[1][0] = C[1][1] = 1;\\n for (int i = 2; i <= n; i++) {\\n C[i][0] = 1;\\n for (int j = 1; j <= i && j <= MAXP; j++) C[i][j] = (C[i - 1][j] + C[i - 1][j - 1]) % MOD;\\n }\\n\\n // 统计最终答案\\n long long ans = 0;\\n for (int i = 1; i <= K; i++) for (int j = 1; j <= MAXP; j++) ans = (ans + C[n - 1][j - 1] * f[i][j]) % MOD;\\n return ans;\\n }\\n};\\n
补充
\\n另外这里推荐大家看一下 @Yawn_Sean 的 题解。通过不同的思路处理问题,最终复杂度可以做到 $\\\\mathcal{O}((n + k)\\\\log k)$,比本题解优。
\\n他的题解里要计算组合数 $C_{n - 1 + t}^{n - 1} = C_{n - 1 + t}^{t}$,这个其实可以 $\\\\mathcal{O}(t)$ 算出来,所以还可以把复杂度优化到 $\\\\mathcal{O}(k\\\\log k)$(先 $\\\\mathcal{O}(\\\\log k)$ 预处理 $[1, \\\\log k]$ 的逆元),直接和 $n$ 无关。
\\n参考代码(c++,44ms)
\\n###c++
\\n\\n","description":"解法:数学 & 递推 子问题\\n\\n求长度为 $n$ 的理想数组,但有一个附加条件:arr[i] 与 arr[i - 1] 不能相等。\\n\\n根据题意,arr[i] 是 arr[i - 1] 的倍数,也就是说 arr[i] 至少是 2 * arr[i - 1]。设元素最大值为 $k$,那么这个数组的长度最多只有 $\\\\log_2{k} + 1$。\\n\\n因此我们维护 $f(i, j)$ 表示以 $i$ 为最后一个元素,且长度为 $j$ 的数组的方案数。有递推式\\n\\n$$f(i, j) = \\\\sum\\\\limits_{k \\\\text{ 是 } i \\\\text{ 的因数且小于 }…","guid":"https://leetcode.cn/problems/count-the-number-of-ideal-arrays//solution/by-tsreaper-bzt9","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-10T04:10:13.494Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"插队(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/pai-xu-by-endlesscheng-h9w9","content":"class Solution {\\n const int MOD = 1000000007;\\n vector<long long> inv;\\n\\n // O(b) 求组合数\\n long long C(int a, int b) {\\n if (b > a) return 0;\\n long long ret = 1;\\n for (int i = 1; i <= b; i++) ret = (ret * (a - i + 1) % MOD * inv[i]) % MOD;\\n return ret;\\n }\\n\\npublic:\\n int idealArrays(int n, int K) {\\n // nlnn 进行质因数分解,f[i] 的元素表示 i 的每种质因数的指数\\n vector<vector<int>> f(K + 1);\\n int mx = 0;\\n for (int i = 2; i <= K; i++) if (f[i].empty()) for (int j = i; j <= K; j += i) {\\n int x = j, y = 0;\\n for (; x % i == 0; x /= i) y++;\\n f[j].push_back(y);\\n mx = max(mx, y);\\n }\\n\\n // 线性求逆元\\n inv.resize(mx + 5);\\n inv[1] = 1;\\n for (int i = 2; i <= mx; i++) inv[i] = (MOD - MOD / i) * inv[MOD % i] % MOD;\\n\\n // 统计答案\\n long long ans = 0;\\n for (int i = 1; i <= K; i++) {\\n long long t = 1;\\n for (int x : f[i]) t = (t * C(n + x - 1, x)) % MOD;\\n ans = (ans + t) % MOD;\\n }\\n return ans;\\n }\\n};\\n
分类讨论
\\n\\n
\\n- 如果最后一班公交还有空位:\\n
\\n\\n
\\n- 如果最后一班公交发车时,没有乘客到达公交站,我们可以在发车时到达公交站。
\\n极限操作- 如果发车时恰好有乘客到达公交站,由于题目要求不能跟别的乘客同时到达,我们可以顺着这位乘客往前找没人到达的时刻,在这个时刻「插队」。
\\n- 如果最后一班公交没有空位:\\n
\\n\\n
\\n- 找最后一个上车的乘客 A,然后往前找没人到达的时刻,在这个时刻「插队」,把 A 挤下去。(可怜的 A)
\\n为什么可以插队?万一前面的乘客没有上车,我们不就也没法上车了吗?
\\n这是不会的,因为先来先上车,如果一个乘客上了车,那么他前面的乘客也肯定上了车。
\\n思路
\\n至此,本题的算法分为两个过程:
\\n\\n
\\n- 不考虑自己,模拟乘客上车的过程。
\\n- 根据上面的分类讨论,寻找合适的插队时机。
\\n模拟乘客上车
\\n\\n
\\n- 为方便模拟,把 $\\\\textit{buses}$ 和 $\\\\textit{passengers}$ 都从小到大排序。
\\n- 双指针遍历 $\\\\textit{buses}$ 和 $\\\\textit{passengers}$。
\\n- 对于 $\\\\textit{buses}[i]$,初始化 $c = \\\\textit{capacity}$。
\\n- 不断循环,如果 $c> 0$ 且 $\\\\textit{passengers}[j]\\\\le \\\\textit{buses}[i]$,那么第 $j$ 位乘客可以上车,把 $c$ 减一,$j$ 加一。如果没法上车,只能等下一班车。
\\n- 双指针遍历结束后,$j-1$ 就是最后一个上车的乘客。这里减一是因为第 $j$ 位乘客上车后我们把 $j$ 加一了。
\\n\\n\\n注:双指针遍历结束时,有可能所有乘客都上车了,最后几班公交没有任何乘客。这不会影响我们的结论,仍然选最后一班公交到达的时刻上车。
\\n寻找插队时机
\\n\\n
\\n\\n- 为方便写代码,先把 $j$ 减一。
\\n- 如果双指针遍历结束时的 $c>0$,那么最后一班公交一定是有空位的,初始化答案 $\\\\textit{ans}= \\\\textit{buses}[n-1]$,否则初始化 $\\\\textit{ans}=\\\\textit{passengers}[j]$。
\\n- 如果 $\\\\textit{ans} = \\\\textit{passengers}[j]$,就往前找插队的时机,把 $\\\\textit{ans}$ 和 $j$ 都减一,直到 $\\\\textit{ans}\\\\ne\\\\textit{passengers}[j]$,我们在 $\\\\textit{ans}$ 这一时刻插队。
\\n\\nclass Solution:\\n def latestTimeCatchTheBus(self, buses: List[int], passengers: List[int], capacity: int) -> int:\\n buses.sort()\\n passengers.sort()\\n\\n # 模拟乘客上车\\n j = 0\\n for t in buses:\\n c = capacity\\n while c and j < len(passengers) and passengers[j] <= t:\\n j += 1\\n c -= 1\\n\\n # 寻找插队时机\\n j -= 1\\n ans = buses[-1] if c else passengers[j]\\n while j >= 0 and ans == passengers[j]: # 往前找没人到达的时刻\\n ans -= 1\\n j -= 1\\n return ans\\n
\\nclass Solution {\\n public int latestTimeCatchTheBus(int[] buses, int[] passengers, int capacity) {\\n Arrays.sort(buses);\\n Arrays.sort(passengers);\\n\\n // 模拟乘客上车\\n int j = 0;\\n int c = 0;\\n for (int t : buses) {\\n for (c = capacity; c > 0 && j < passengers.length && passengers[j] <= t; c--) {\\n j++;\\n }\\n }\\n\\n // 寻找插队时机\\n j--;\\n int ans = c > 0 ? buses[buses.length - 1] : passengers[j];\\n while (j >= 0 && ans == passengers[j]) {\\n ans--; // 往前找没人到达的时刻\\n j--;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int latestTimeCatchTheBus(vector<int>& buses, vector<int>& passengers, int capacity) {\\n ranges::sort(buses);\\n ranges::sort(passengers);\\n\\n // 模拟乘客上车\\n int j = 0, c;\\n for (int t : buses) {\\n for (c = capacity; c && j < passengers.size() && passengers[j] <= t; c--) {\\n j++;\\n }\\n }\\n\\n // 寻找插队时机\\n j--;\\n int ans = c ? buses.back() : passengers[j];\\n while (j >= 0 && ans == passengers[j]) {\\n ans--; // 往前找没人到达的时刻\\n j--;\\n }\\n return ans;\\n }\\n};\\n
\\nint cmp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nint latestTimeCatchTheBus(int* buses, int busesSize, int* passengers, int passengersSize, int capacity) {\\n qsort(buses, busesSize, sizeof(int), cmp);\\n qsort(passengers, passengersSize, sizeof(int), cmp);\\n\\n // 模拟乘客上车\\n int j = 0, c;\\n for (int i = 0; i < busesSize; i++) {\\n for (c = capacity; c && j < passengersSize && passengers[j] <= buses[i]; c--) {\\n j++;\\n }\\n }\\n\\n // 寻找插队时机\\n j--;\\n int ans = c ? buses[busesSize - 1] : passengers[j];\\n while (j >= 0 && ans == passengers[j]) {\\n ans--; // 往前找没人到达的时刻\\n j--;\\n }\\n return ans;\\n}\\n
\\nfunc latestTimeCatchTheBus(buses, passengers []int, capacity int) (ans int) {\\n slices.Sort(buses)\\n slices.Sort(passengers)\\n\\n // 模拟乘客上车\\n j, c := 0, 0\\n for _, t := range buses {\\n for c = capacity; c > 0 && j < len(passengers) && passengers[j] <= t; c-- {\\n j++\\n }\\n }\\n \\n // 插队\\n if c > 0 {\\n ans = buses[len(buses)-1] // 最后一班公交还有空位,在发车时到达\\n } else {\\n ans = passengers[j-1] // 上一个上车的乘客\\n }\\n for j--; j >= 0 && ans == passengers[j]; j-- { // 往前找没人到达的时刻\\n ans--\\n }\\n return\\n}\\n
\\nvar latestTimeCatchTheBus = function(buses, passengers, capacity) {\\n buses.sort((a, b) => a - b);\\n passengers.sort((a, b) => a - b);\\n\\n // 模拟乘客上车\\n let j = 0, c;\\n for (const t of buses) {\\n for (c = capacity; c > 0 && j < passengers.length && passengers[j] <= t; c--) {\\n j++;\\n }\\n }\\n\\n // 寻找插队时机\\n j--;\\n let ans = c > 0 ? buses[buses.length - 1] : passengers[j];\\n while (j >= 0 && ans === passengers[j]) {\\n ans--; // 往前找没人到达的时刻\\n j--;\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn latest_time_catch_the_bus(mut buses: Vec<i32>, mut passengers: Vec<i32>, capacity: i32) -> i32 {\\n buses.sort_unstable();\\n passengers.sort_unstable();\\n\\n // 模拟乘客上车\\n let mut j = 0;\\n let mut c = 0;\\n for &t in &buses {\\n c = capacity;\\n while c > 0 && j < passengers.len() && passengers[j] <= t {\\n j += 1;\\n c -= 1;\\n }\\n }\\n\\n // 寻找插队时机\\n j -= 1;\\n let mut ans = if c > 0 { *buses.last().unwrap() } else { passengers[j] };\\n while j < passengers.len() && ans == passengers[j] {\\n ans -= 1; // 往前找没人到达的时刻\\n j -= 1;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n+m\\\\log m)$,其中 $n$ 是 $\\\\textit{buses}$ 的长度,$m$ 是 $\\\\textit{passengers}$ 的长度。瓶颈在排序上。请注意,虽然代码写了二重循环,但 $j$ 最多增加 $\\\\mathcal{O}(m)$ 次,再算上遍历 $\\\\textit{buses}$ 的时间,所以二重循环的总循环次数是 $\\\\mathcal{O}(n+m)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分类讨论 如果最后一班公交还有空位:\\n如果最后一班公交发车时,没有乘客到达公交站,我们可以在发车时到达公交站。极限操作\\n如果发车时恰好有乘客到达公交站,由于题目要求不能跟别的乘客同时到达,我们可以顺着这位乘客往前找没人到达的时刻,在这个时刻「插队」。\\n如果最后一班公交没有空位:\\n找最后一个上车的乘客 A,然后往前找没人到达的时刻,在这个时刻「插队」,把 A 挤下去。(可怜的 A)\\n\\n为什么可以插队?万一前面的乘客没有上车,我们不就也没法上车了吗?\\n\\n这是不会的,因为先来先上车,如果一个乘客上了车,那么他前面的乘客也肯定上了车。\\n\\n思路\\n\\n至此…","guid":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/pai-xu-by-endlesscheng-h9w9","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-09T23:14:36.807Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 & two pointers","url":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/by-tsreaper-qcto","content":"解法:贪心 & two pointers
\\n本题的难点在于“答案不能和已有乘客到达的时间相同”。如果直接二分答案可能需要进行一些判断,比较繁琐。
\\n由于求的是最晚到达公交站的时间,根据贪心容易得到,这个时间要么是某个公交的发车时间,要么比某个乘客早到 $1$ 单位时间。因此我们通过 two pointers 的方式模拟上车过程,并枚举所有可能的答案:
\\n\\n
\\n- 当一个乘客在 $t$ 时刻到达时,我们尝试抢先在它之前上车。只要不存在 $(t - 1)$ 时刻到达的乘客即可;
\\n- 当公交发车时,若当前公交没有坐满,且不存在发车时到达的乘客,我们可以在这个时刻上车。
\\n在所有可以上车的时刻中取最大值即可。复杂度 $\\\\mathcal{O}(n\\\\log n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:贪心 & two pointers 本题的难点在于“答案不能和已有乘客到达的时间相同”。如果直接二分答案可能需要进行一些判断,比较繁琐。\\n\\n由于求的是最晚到达公交站的时间,根据贪心容易得到,这个时间要么是某个公交的发车时间,要么比某个乘客早到 $1$ 单位时间。因此我们通过 two pointers 的方式模拟上车过程,并枚举所有可能的答案:\\n\\n当一个乘客在 $t$ 时刻到达时,我们尝试抢先在它之前上车。只要不存在 $(t - 1)$ 时刻到达的乘客即可;\\n当公交发车时,若当前公交没有坐满,且不存在发车时到达的乘客,我们可以在这个时刻上车。\\n\\n在所有…","guid":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/by-tsreaper-qcto","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-09T16:23:34.043Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Py] 插队; p2>p4","url":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/by-migeater-5a6b","content":"class Solution {\\npublic:\\n int latestTimeCatchTheBus(vector<int>& buses, vector<int>& passengers, int capacity) {\\n int n = buses.size(), m = passengers.size();\\n sort(buses.begin(), buses.end());\\n set<int> P;\\n for (int x : passengers) P.insert(x);\\n\\n int ans = 1, cnt = 0;\\n auto it = P.begin();\\n // 利用 two pointers 的方式模拟上车过程\\n for (int i = 0; i < n; i++) {\\n while (cnt < capacity && it != P.end() && *it <= buses[i]) {\\n // 乘客上车,抢先在它之前上车\\n // 由于 set 是有序的,如果存在 t - 1 时刻到达的乘客,那么一定是上一个乘客\\n if (it == P.begin() || *prev(it) != *it - 1) ans = *it - 1;\\n it++; cnt++;\\n }\\n // 公交没有坐满,准备发车\\n if (cnt < capacity && P.count(buses[i]) == 0) ans = buses[i];\\n cnt = 0;\\n }\\n\\n return ans;\\n }\\n};\\n
思路
\\n按照没有\\"你\\"的情况模拟一下事件(按时间顺序), 然后考虑取代其中一个乘客
\\n为什么要按时间顺序模拟事件呢? 因为这种类型题(有明显时间线和事件)都是这么处理的
\\n
\\n这题难点在哪?\\n
\\n- 用二分做会导致很不好思考, 因为你不得不把\\"你自己\\"加入乘客中, 而做题经验又会让我(们)第一时间去用二分
\\n- \\n
你 不能 跟别的乘客同时刻到达。
对这一条件的边界处理实现
\\n按没有\\"你\\"的情况, 时间顺序遍历有意义的时间点, 模拟上车开车事件
\\n\\n
\\n- 对于有空位的车, 记录最晚能坐该车的时间
\\n- 对于没空位的车, 记录最晚坐该车的乘客
\\n然后由于
\\n1 <= passengers.length <= 100000
, 为了满足你 不能 跟别的乘客同时刻到达。
的额外限制, 用hashset处理即可时间复杂度
\\n$O(Plog(P) + |buses|log(|buses|))$
\\n代码
\\n顺带一提, 欲实现一个具有 hasIterator, nextIterator 功能的迭代器 可以用 queue乃至list (比单调指针舒服点)
\\n###python
\\n\\n","description":"思路 按照没有\\"你\\"的情况模拟一下事件(按时间顺序), 然后考虑取代其中一个乘客\\n\\n为什么要按时间顺序模拟事件呢? 因为这种类型题(有明显时间线和事件)都是这么处理的\\n 这题难点在哪?\\n\\n用二分做会导致很不好思考, 因为你不得不把\\"你自己\\"加入乘客中, 而做题经验又会让我(们)第一时间去用二分\\n你 不能 跟别的乘客同时刻到达。 对这一条件的边界处理\\n实现\\n\\n按没有\\"你\\"的情况, 时间顺序遍历有意义的时间点, 模拟上车开车事件\\n\\n对于有空位的车, 记录最晚能坐该车的时间\\n对于没空位的车, 记录最晚坐该车的乘客\\n\\n然后由于 1 <= passengers…","guid":"https://leetcode.cn/problems/the-latest-time-to-catch-a-bus//solution/by-migeater-5a6b","author":"migeater","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-09T16:18:17.000Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题三解 :「模拟」&「线段树(动态开点)」&「分块 + 位运算(分桶)」","url":"https://leetcode.cn/problems/my-calendar-i//solution/by-ac_oier-hnjl","content":"class Solution:\\n def latestTimeCatchTheBus(self, buses: List[int], P: List[int], capacity: int) -> int:\\n SP = set(P)\\n P = deque(sorted(P))\\n last = 1\\n for t in sorted(buses):\\n rest = capacity\\n \\n while rest and P and P[0]<=t:\\n # 取而代之\\n last = P.popleft()\\n rest -= 1\\n\\n if rest:\\n # 有空位, 最后一刻坐上去\\n last = t\\n \\n while last in SP:\\n last -= 1\\n return last\\n
模拟
\\n利用
\\nbook
操作最多调用 $1000$ 次,我们可以使用一个数组存储所有已被预定的日期 $[start, end - 1]$,对于每次book
操作,检查当前传入的 $[start, end)$ 是否会与已有的日期冲突,冲突返回False
,否则将 $[start, end- 1]$ 插入数组并返回True
。代码:
\\n###Java
\\n\\nclass MyCalendar {\\n List<int[]> list = new ArrayList<>();\\n public boolean book(int start, int end) {\\n end--;\\n for (int[] info : list) {\\n int l = info[0], r = info[1];\\n if (start > r || end < l) continue;\\n return false;\\n }\\n list.add(new int[]{start, end});\\n return true;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $n$ 为
\\nbook
的最大调用次数,复杂度为 $O(n^2)$- 空间复杂度:$O(n)$
\\n
\\n线段树(动态开点)
\\n线段树维护的节点信息包括:
\\n\\n
\\n- \\n
ls/rs
: 分别代表当前节点的左右子节点在线段树数组tr
中的下标;- \\n
add
: 懒标记;- \\n
val
: 为当前区间的所包含的点的数量。对于常规的线段树实现来说,都是一开始就调用
\\nbuild
操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始build
空树的时候已经锁死。如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决
\\nMLE
问题。但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
\\n动态开点的优势在于,不需要事前构造空树,而是在插入操作
\\nadd
和查询操作query
时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过u << 1
和u << 1 | 1
的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在tr
数组的下标进行存储,分别记为ls
和rs
属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。由于存在「懒标记」,线段树的插入和查询都是 $\\\\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\\\\log{n}$ 的点,因此空间复杂度为 $O(m\\\\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\\\\log{n}$ 来进行,还要对常熟进行分析,才能得到准确的点数上界。
\\n动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\\\\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \\\\log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
\\n当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开(
\\nJava
的 $128M$ 可以开到 $5 * 10^6$ 以上)。代码:
\\n###Java
\\n\\nclass MyCalendar {\\n class Node {\\n // ls 和 rs 分别代表当前节点的左右子节点在 tr 的下标\\n // val 代表当前节点有多少数\\n // add 为懒标记\\n int ls, rs, add, val;\\n }\\n int N = (int)1e9, M = 120010, cnt = 1;\\n Node[] tr = new Node[M];\\n void update(int u, int lc, int rc, int l, int r, int v) {\\n if (l <= lc && rc <= r) {\\n tr[u].val += (rc - lc + 1) * v;\\n tr[u].add += v;\\n return ;\\n }\\n lazyCreate(u);\\n pushdown(u, rc - lc + 1);\\n int mid = lc + rc >> 1;\\n if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);\\n if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);\\n pushup(u);\\n }\\n int query(int u, int lc, int rc, int l, int r) {\\n if (l <= lc && rc <= r) return tr[u].val;\\n lazyCreate(u);\\n pushdown(u, rc - lc + 1);\\n int mid = lc + rc >> 1, ans = 0;\\n if (l <= mid) ans = query(tr[u].ls, lc, mid, l, r);\\n if (r > mid) ans += query(tr[u].rs, mid + 1, rc, l, r);\\n return ans;\\n }\\n void lazyCreate(int u) {\\n if (tr[u] == null) tr[u] = new Node();\\n if (tr[u].ls == 0) {\\n tr[u].ls = ++cnt;\\n tr[tr[u].ls] = new Node();\\n }\\n if (tr[u].rs == 0) {\\n tr[u].rs = ++cnt;\\n tr[tr[u].rs] = new Node();\\n }\\n }\\n void pushdown(int u, int len) {\\n tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;\\n tr[tr[u].ls].val += (len - len / 2) * tr[u].add; tr[tr[u].rs].val += len / 2 * tr[u].add;\\n tr[u].add = 0;\\n }\\n void pushup(int u) {\\n tr[u].val = tr[tr[u].ls].val + tr[tr[u].rs].val;\\n }\\n public boolean book(int start, int end) {\\n if (query(1, 1, N + 1, start + 1, end) > 0) return false;\\n update(1, 1, N + 1, start + 1, end, 1);\\n return true;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $n$ 为值域大小,本题固定为 $1e9$,线段树的查询和增加复杂度均为 $O(\\\\log{n})$
\\n- 空间复杂度:令询问数量为 $m$,复杂度为 $O(m\\\\log{n})$
\\n
\\n分块 + 位运算(分桶)
\\n另一个留有遗憾的算法是「分块」,朴素的分块做法是使用一个布尔数组
\\nregion
代表某个块是否有被占用,使用哈希表记录具体某个位置是否被占用,但可惜被奇怪的测评机制卡了。\\n
\\n
TLE
代码:###Java
\\n\\nclass MyCalendar {\\n static Boolean T = Boolean.TRUE, F = Boolean.FALSE;\\n static int n = (int)1e9, len = (int) Math.sqrt(n) + 30;\\n static boolean[] region = new boolean[len];\\n static Map<Integer, Boolean> map = new HashMap<>();\\n int getIdx(int x) {\\n return x / len;\\n }\\n void add(int l, int r) {\\n if (getIdx(l) == getIdx(r)) {\\n for (int k = l; k <= r; k++) map.put(k, T);\\n } else {\\n int j = l, i = r;\\n while (getIdx(j) == getIdx(l)) map.put(j++, T);\\n while (getIdx(i) == getIdx(r)) map.put(i--, T);\\n for (int k = getIdx(j); k <= getIdx(i); k++) region[k] = true;\\n }\\n }\\n boolean query(int l, int r) {\\n if (getIdx(l) == getIdx(r)) {\\n boolean cur = region[getIdx(l)];\\n for (int k = l; k <= r; k++) {\\n if (map.getOrDefault(k, F) || cur) return false;\\n }\\n return true;\\n } else {\\n int j = l, i = r;\\n while (getIdx(j) == getIdx(l)) {\\n if (map.getOrDefault(j, F) || region[getIdx(j)]) return false;\\n j++;\\n }\\n while (getIdx(i) == getIdx(r)) {\\n if (map.getOrDefault(i, F) || region[getIdx(i)]) return false;\\n i--;\\n }\\n for (int k = getIdx(j); k <= getIdx(i); k++) {\\n if (region[k]) return false;\\n }\\n return true;\\n }\\n }\\n public MyCalendar() {\\n Arrays.fill(region, false);\\n map.clear();\\n }\\n public boolean book(int start, int end) {\\n if (query(start, end - 1)) {\\n add(start, end - 1);\\n return true;\\n }\\n return false;\\n }\\n}\\n
但我们知道分块算法的复杂度并不糟糕,而哈希表可能是被卡常数的关键,因此我们可以使用
\\nint
数组来充当哈希表,由于只需要记录「是否被占用」,因此我们可以使用int
的每一位充当格子,通过这种「分桶 + 位运算」,有效降低常数。\\n
AC
代码(2022-07-05
可过):###Java
\\n\\nclass MyCalendar {\\n static int n = (int)1e9, len = (int) Math.sqrt(n) + 50, cnt = 32;\\n static boolean[] region = new boolean[len];\\n static int[] map = new int[n / cnt];\\n int getIdx(int x) {\\n return x / len;\\n }\\n boolean get(int x) {\\n return ((map[x / cnt] >> (x % cnt)) & 1) == 1;\\n }\\n void set(int x) {\\n map[x / cnt] |= (1 << (x % cnt));\\n }\\n void add(int l, int r) {\\n if (getIdx(l) == getIdx(r)) {\\n for (int k = l; k <= r; k++) set(k);\\n } else {\\n int j = l, i = r;\\n while (getIdx(j) == getIdx(l)) set(j++);\\n while (getIdx(i) == getIdx(r)) set(i--);\\n for (int k = getIdx(j); k <= getIdx(i); k++) region[k] = true;\\n }\\n }\\n boolean query(int l, int r) {\\n if (getIdx(l) == getIdx(r)) {\\n boolean cur = region[getIdx(l)];\\n for (int k = l; k <= r; k++) {\\n if (get(k) || cur) return false;\\n }\\n return true;\\n } else {\\n int j = l, i = r;\\n while (getIdx(j) == getIdx(l)) {\\n if (get(j) || region[getIdx(j)]) return false;\\n j++;\\n }\\n while (getIdx(i) == getIdx(r)) {\\n if (get(i) || region[getIdx(i)]) return false;\\n i--;\\n }\\n for (int k = getIdx(j); k <= getIdx(i); k++) {\\n if (region[k]) return false;\\n }\\n return true;\\n }\\n }\\n public MyCalendar() {\\n Arrays.fill(region, false);\\n Arrays.fill(map, 0);\\n }\\n public boolean book(int start, int end) {\\n if (query(start, end - 1)) {\\n add(start, end - 1);\\n return true;\\n }\\n return false;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n\\\\sqrt{m})$
\\n- 空间复杂度:$O(\\\\sqrt{m} + \\\\frac{M}{k})$,其中 $M = 1e9$ 为值域大小,$k = 32$ 为单个数字的能够存储的格子数
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 利用 book 操作最多调用 $1000$ 次,我们可以使用一个数组存储所有已被预定的日期 $[start, end - 1]$,对于每次 book 操作,检查当前传入的 $[start, end)$ 是否会与已有的日期冲突,冲突返回 False,否则将 $[start, end- 1]$ 插入数组并返回 True。\\n\\n代码:\\n\\n###Java\\n\\nclass MyCalendar {\\n Listlist = new ArrayList<>();\\n public boolean book(int start, int end)…","guid":"https://leetcode.cn/problems/my-calendar-i//solution/by-ac_oier-hnjl","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-05T01:10:28.835Z","media":[{"url":"https://pic.leetcode-cn.com/1656985091-vGvNNg-image.png","type":"photo","width":2384,"height":454,"blurhash":"LCSPX_~q_3~qt7WB-;of-;ayM{Rj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线段树详解「汇总级别整理 🔥🔥🔥」","url":"https://leetcode.cn/problems/my-calendar-i//solution/by-lfool-xvpv","content":" 如果想要查看作者更多文章,可以点击此处!!!🔥🔥🔥
\\n为了本篇文章更好的观感,可以点击此处!!!
\\n\\n\\n\\n\\n\\n\\n\\n\\n
\\n线段树引入
\\n遇到过好多次线段树的题目,要么就是用其他的方法去解决,要么就是不会写!!今天痛定思痛,决定好好归纳整理一下线段树
\\n线段树解决的是「区间和」的问题,且该「区间」会被修改
\\n什么意思呢?举个简单的例子,对于
\\nnums = [1, 2, 3, 4, 5]
如果我们需要多次求某些区间的和,是不是首先想到了利用「前缀和」。关于前缀和的详细介绍可见 前缀和数组
\\n但是如果
\\nnums
会被修改呢?比如:\\n
\\n- 把第
\\ni
个元素修改成x
- 把第
\\ni
个元素增加x
- 把区间
\\n[i, j]
内的元素都增加x
此时,如果我们再使用「前缀和」,就没那么高效了。因为每一次更新,前缀和数组必须也随之更新,时间复杂度为
\\nO(n)
既然「前缀和」在这种场景下没那么高效了,所以就有了今天要介绍的「线段树」
\\n线段树原理及实现
\\n上面提到过:线段树解决的是「区间和」的问题,且该「区间」会被修改
\\n所以线段树主要实现两个方法:「求区间和」&&「修改区间」,且时间复杂度均为
\\nO(logn)
始终记住一句话:线段树的每个节点代表一个区间
\\n\\n
nums = [1, 2, 3, 4, 5]
对应的线段树如下所示:\\n
{:align=center}
从图中可以看到,每个节点代表一个区间,而节点的值就是该区间的和 (其实还可以根据题目问题,改变表示的含义!!)
\\n\\n
\\n- 数字之和「总数字之和 = 左区间数字之和 + 右区间数字之和」
\\n- 最大公因数 (GCD)「总 GCD = gcd(左区间 GCD, 右区间 GCD)」
\\n- 最大值「总最大值 = max(左区间最大值,右区间最大值)」
\\n不符合区间加法的例子:
\\n\\n
\\n- 众数「只知道左右区间的众数,没法求总区间的众数」
\\n- 01 序列的最长连续零「只知道左右区间的最长连续零,没法知道总的最长连续零」
\\n根节点代表的区间是问题的总区间,如这个例子,问题的总区间就是数组的长度
\\n[0, 4]
其实线段树是一棵近似的完全二叉树,该例子就是一棵完全二叉树,但是有些情况不是完全二叉树
\\n所以对于给定的一个问题,如果该问题的范围是确定的,那么该问题的线段树也是确定的,因为建立线段树的过程就是不断把区间「平分」的过程,直到区间长度为 1
\\n注意:下面的所有实现均基于求「区间和」以及对区间进行「加减」的更新操作
\\n线段树的数据结构
\\n我们可以使用数组来表示一棵线段树,假如根节点为
\\ni
,那么左孩子的节点就为2 * i
,右孩子的节点就为2 * i + 1
(前提:i
从 1 开始)我们可以使用链表来表示一棵线段树,其节点的数据结构如下:
\\n###java
\\n\\nclass Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值\\n int val;\\n}\\n
个人比较倾向使用链表,因为比较节约内存,下面的实现均基于链表
\\n线段树的建立
\\n如果题目中给了具体的区间范围,我们根据该范围建立线段树。见题目 区域和检索 - 数组可修改
\\n###java
\\n\\npublic void buildTree(Node node, int start, int end) {\\n // 到达叶子节点\\n if (start == end) {\\n node.val = arr[start];\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n buildTree(node.left, start, mid);\\n buildTree(node.right, mid + 1, end);\\n // 向上更新\\n pushUp(node);\\n}\\n// 向上更新\\nprivate void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n}\\n
但是很多时候,题目中都没有给出很具体的范围,只有数据的取值范围,一般都很大,所以我们更常用的是「动态开点」
\\n下面我们手动模拟一下「动态开点」的过程。同样的,也是基于上面的例子
\\nnums = [1, 2, 3, 4, 5]
假设一种情况,最开始只知道数组的长度
\\n5
,而不知道数组内每个元素的大小,元素都是后面添加进去的。所以线段树的初始状态如下图所示:(只有一个节点,很孤独!!)\\n
{:align=center}
假设此时,我们添加了一个元素
\\n[2, 2]; val = 3
。现在线段树的结构如下图所示:\\n
{:align=center}
这里需要解释一下,如果一个节点没有左右孩子,会一下子把左右孩子节点都给创建出来,如上图橙色节点所示,具体代码可见方法
\\npushDown()
两个橙色的叶子节点仅仅只是被创建出来了,并无实际的值,均为 0;而另外一个橙色的非叶子节点,值为 3 的原因是下面的孩子节点的值向上更新得到的
\\n下面给出依次添加剩余节点的过程:(注意观察值的变化!!)
\\n\\n
{:align=center}
「动态开点」一般是在「更新」或「查询」的时候动态的建立节点,具体可见下面的更新和查询操作
\\n线段树的更新
\\n我看大多数教程都是把更新分为两种:「点更新」和「区间更新」。其实这两种可以合并成一种,「点更新」不就是更新长度为 1 的区间嘛!!
\\n更新区间的前提是找到需要更新的区间,所以和查询的思路很相似
\\n如果我们要把区间
\\n[2, 4]
内的元素都「➕1」\\n
{:align=center}
我们会发现一个很有意思的现象,我们只把
\\n[2,2]
和[3,4]
这两个区间对应的节点更新了,而区间[3, 3]
和[4,4]
并没有更新按道理来说,
\\n[3, 3]
和[4,4]
也是需要更新的,不然当我们查询区间[3, 3]
和[4,4]
的值,就会出现错误!!这是因为我们使用了「懒惰标记」的方法,我们只需要更新到满足条件的区间即可,然后再给该区间对应的节点加一个懒惰标记,表示该节点所有对应的孩子节点都应该有此更新
\\n当我们向孩子节点遍历的时候会把「懒惰标记」下推给孩子节点
\\n我们需要稍微修改一下
\\nNode
的数据结构###java
\\n\\nclass Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值\\n int val;\\n // 懒惰标记\\n int add;\\n}\\n
基于「动态开点」的前提,我们下推懒惰标记的时候,如果节点不存在左右孩子节点,那么我们就创建左右孩子节点
\\n先来实现下推懒惰标记的函数:
\\n###java
\\n\\n// leftNum 和 rightNum 表示左右孩子区间的叶子节点数量\\n// 因为如果是「加减」更新操作的话,需要用懒惰标记的值✖️叶子节点的数量\\nprivate void pushDown(Node node, int leftNum, int rightNum) {\\n // 动态开点\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n // 如果 add 为 0,表示没有标记\\n if (node.add == 0) return ;\\n // 注意:当前节点加上标记值✖️该子树所有叶子节点的数量\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 把标记下推给孩子节点\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n // 取消当前节点标记\\n node.add = 0;\\n}\\n
下面来实现更新的函数:
\\n###java
\\n\\n// 在区间 [start, end] 中更新区间 [l, r] 的值,将区间 [l, r] ➕ val\\n// 对于上面的例子,应该这样调用该函数:update(root, 0, 4, 2, 4, 1)\\npublic void update(Node node, int start, int end, int l, int r, int val) {\\n // 找到满足要求的区间\\n if (l <= start && end <= r) {\\n // 区间节点加上更新值\\n // 注意:需要✖️该子树所有叶子节点\\n node.val += (end - start + 1) * val;\\n // 添加懒惰标记\\n // 对区间进行「加减」的更新操作,懒惰标记需要累加,不能直接覆盖\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n // 下推标记\\n // mid - start + 1:表示左孩子区间叶子节点数量\\n // end - mid:表示右孩子区间叶子节点数量\\n pushDown(node, mid - start + 1, end - mid);\\n // [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n // [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n // 向上更新\\n pushUp(node);\\n}\\n
线段树的查询
\\n如果我们要查询区间
\\n[2, 4]
的结果,如下图红色标记所示:\\n
{:align=center}
下面给出代码实现:
\\n###java
\\n\\n// 在区间 [start, end] 中查询区间 [l, r] 的结果,即 [l ,r] 保持不变\\n// 对于上面的例子,应该这样调用该函数:query(root, 0, 4, 2, 4)\\npublic int query(Node node, int start, int end, int l, int r) {\\n // 区间 [l ,r] 完全包含区间 [start, end]\\n // 例如:[2, 4] = [2, 2] + [3, 4],当 [start, end] = [2, 2] 或者 [start, end] = [3, 4],直接返回\\n if (l <= start && end <= r) return node.val;\\n // 把当前区间 [start, end] 均分得到左右孩子的区间范围\\n // node 左孩子区间 [start, mid]\\n // node 左孩子区间 [mid + 1, end]\\n int mid = (start + end) >> 1, ans = 0;\\n // 下推标记\\n pushDown(node, mid - start + 1, end - mid);\\n // [start, mid] 和 [l, r] 可能有交集,遍历左孩子区间\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n // [mid + 1, end] 和 [l, r] 可能有交集,遍历右孩子区间\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n // ans 把左右子树的结果都累加起来了,与树的后续遍历同理\\n return ans;\\n}\\n
线段树完整模版
\\n注意:下面模版基于求「区间和」以及对区间进行「加减」的更新操作,且为「动态开点」
\\n###java
\\n\\n/**\\n * @Description: 线段树(动态开点)\\n * @Author: LFool\\n * @Date 2022/6/7 09:15\\n **/\\npublic class SegmentTreeDynamic {\\n class Node {\\n Node left, right;\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += (end - start + 1) * val;\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
再次强调一遍:上面给出的模版基于求「区间和」以及对区间进行「加减」的更新操作,且为「动态开点」
\\n但是下面给出的题目实战中,有些题目需要对模版进行小小的修改 (很多人问这个问题,这里统一整理汇总一下!!)
\\n\\n
\\n- 对于表示为「区间和」且对区间进行「加减」的更新操作的情况,我们在更新节点值的时候『需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『需要累加』!!(这种情况和模版一致!!) 如题目 最近的请求次数
\\n- 对于表示为「区间和」且对区间进行「覆盖」的更新操作的情况,我们在更新节点值的时候『需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『不需要累加』!!(因为是覆盖操作!!) 如题目 区域和检索 - 数组可修改
\\n- 对于表示为「区间最值」且对区间进行「加减」的更新操作的情况,我们在更新节点值的时候『不需要✖️左右孩子区间叶子节点的数量 (注意是叶子节点的数量)』;我们在下推懒惰标记的时候『需要累加』!! 如题目 我的日程安排表 I、我的日程安排表 III
\\n注意:对于题目 最近的请求次数 和 区域和检索 - 数组可修改 可以「不用✖️左右孩子区间叶子节点的数量」
\\n为什么??因为这两个题目是「点更新」,在介绍线段树更新的时候,我们说过:「点更新」和「区间更新」可以合并成一种,「点更新」不就是更新长度为 1 的区间嘛!!
\\n上面两个题目调用更新函数的方式为:
\\nupdate(root, 1, N, t, t, 1);
和update(root, 0, N, i, i, nums[i]);
由于区间是一个点,所以一定会更新到叶子节点,故可以不用✖️左右孩子区间叶子节点的数量!!
\\n题目实战
\\n我的日程安排表 I
\\n题目详情可见 我的日程安排表 I
\\n该题目基于下面「我的日程安排表 III」的思路!!!
\\n###java
\\n\\nclass MyCalendar {\\n\\n public MyCalendar() {\\n\\n }\\n \\n public boolean book(int start, int end) {\\n // 先查询该区间是否为 0\\n if (query(root, 0, N, start, end - 1) != 0) return false;\\n // 更新该区间\\n update(root, 0, N, start, end - 1, 1);\\n return true;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
我的日程安排表 II
\\n题目详情可见 我的日程安排表 II
\\n该题目基于下面「我的日程安排表 III」的思路!!!
\\n###java
\\n\\nclass MyCalendarTwo {\\n\\n public MyCalendarTwo() {\\n\\n }\\n \\n public boolean book(int start, int end) {\\n if (query(root, 0, N, start, end - 1) == 2) return false;\\n update(root, 0, N, start, end - 1, 1);\\n return true;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
我的日程安排表 III
\\n题目详情可见 我的日程安排表 III
\\n上面说过「节点的值不止可以表示该区间的和」,还可以「表示为当前区间的最值」,该题目存储的就是区间的最大值
\\n###java
\\n\\nclass MyCalendarThree {\\n\\n public MyCalendarThree() {\\n\\n }\\n \\n public int book(int start, int end) {\\n // 只用到了 update\\n update(root, 0, N, start, end - 1, 1);\\n // 最大值即为根节点的值\\n return root.val;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add;\\n node.right.val += node.add;\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
Range 模块
\\n题目详情可见 Range 模块
\\n每个节点的值表示当前区间是否被覆盖
\\n###java
\\n\\nclass RangeModule {\\n\\n public RangeModule() {\\n\\n }\\n \\n public void addRange(int left, int right) {\\n // 1 表示复盖;-1 表示取消覆盖\\n update(root, 1, N, left, right - 1, 1);\\n }\\n \\n public boolean queryRange(int left, int right) {\\n return query(root, 1, N, left, right - 1);\\n }\\n \\n public void removeRange(int left, int right) {\\n // 1 表示复盖;-1 表示取消覆盖\\n update(root, 1, N, left, right - 1, -1);\\n }\\n\\n // *************** 下面是模版 ***************\\n class Node {\\n Node left, right;\\n // 表示当前区间是否被覆盖\\n boolean cover;\\n int add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n // 1 表示复盖;-1 表示取消覆盖\\n node.cover = val == 1;\\n node.add = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public boolean query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.cover;\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n // 查询左右子树是否被覆盖\\n boolean ans = true;\\n if (l <= mid) ans = ans && query(node.left, start, mid, l, r);\\n if (r > mid) ans = ans && query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 向上更新\\n node.cover = node.left.cover && node.right.cover;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.cover = node.add == 1;\\n node.right.cover = node.add == 1;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
区域和检索 - 数组可修改
\\n题目详情可见 区域和检索 - 数组可修改
\\n方法一:
\\n###java
\\n\\nclass NumArray {\\n\\n private final int n;\\n private final int[] arr;\\n private final int[] tree;\\n private final int[] add;\\n\\n public NumArray(int[] nums) {\\n this.n = nums.length;\\n this.arr = nums;\\n // 必须开 4 倍长度的数组\\n this.tree = new int[n << 2];\\n this.add = new int[n << 2];\\n buildTree(0, n - 1, 1);\\n }\\n \\n public void update(int index, int val) {\\n update(0, n - 1, 1, index, val);\\n }\\n \\n public int sumRange(int left, int right) {\\n return query(0, n - 1, left, right, 1);\\n }\\n\\n public void updateRange(int l, int r, int val) {\\n update(0, n - 1, 1, l, r, val);\\n }\\n\\n private void buildTree(int start, int end, int rootId) {\\n if (start == end) {\\n tree[rootId] = arr[start];\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n buildTree(start, mid, rootId << 1);\\n buildTree(mid + 1, end, rootId << 1 | 1);\\n pushUp(rootId);\\n }\\n private void update(int start, int end, int rootId, int updateIndex, int val) {\\n if (start == end) {\\n arr[start] = tree[rootId] = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n if (updateIndex > mid) {\\n update(mid + 1, end, rootId << 1 | 1, updateIndex, val);\\n } else {\\n update(start, mid, rootId << 1, updateIndex, val);\\n }\\n pushUp(rootId);\\n }\\n private void update(int start, int end, int rootId, int l, int r, int val) {\\n // 无交集\\n if (r < start || l > end) return ;\\n // 如果本区间完全在操作区间 [l, r] 以内\\n else if (l <= start && end <= r) {\\n // 更新数字和,向上保持正确\\n tree[rootId] += val * (end - start + 1);\\n // 增加 add 标记,表示本区间的 Sum 正确,子区间的 Sum 仍需要根据 add 的值来调整\\n add[rootId] += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n // 下推标记\\n pushDown(rootId, mid - start + 1, end - mid);\\n update(start, mid, rootId << 1, l, r, val);\\n update(mid + 1, end, rootId << 1 | 1, l, r, val);\\n pushUp(rootId);\\n }\\n private int query(int start, int end, int l, int r, int rootId) {\\n if (r < start || l > end) return 0;\\n else if (l <= start && end <= r) return tree[rootId];\\n int mid = (start + end) >> 1;\\n // 下推标记\\n pushDown(rootId, mid - start + 1, end - mid);\\n int lSum = query(start, mid, l, r, rootId << 1);\\n int rSum = query(mid + 1, end, l, r, rootId << 1 | 1);\\n return lSum + rSum;\\n }\\n\\n private void pushUp(int rootId) {\\n tree[rootId] = tree[rootId << 1] + tree[rootId << 1 | 1];\\n }\\n private void pushDown(int rootId, int leftNum, int rightNum) {\\n if (add[rootId] == 0) return ;\\n // 下推标记\\n add[rootId << 1] += add[rootId];\\n add[rootId << 1 | 1] += add[rootId];\\n // 修改子节点的 Sum 使之与对应的 add 相对应\\n tree[rootId << 1] += leftNum * add[rootId];\\n tree[rootId << 1 | 1] += rightNum * add[rootId];\\n add[rootId] = 0;\\n }\\n}\\n
方法二:基于动态开点,完全是为了加深对动态开点的理解
\\n###java
\\n\\nclass NumArray {\\n\\n public NumArray(int[] nums) {\\n N = nums.length - 1;\\n for (int i = 0; i <= N; i++) {\\n update(root, 0, N, i, i, nums[i]);\\n }\\n }\\n \\n public void update(int index, int val) {\\n update(root, 0, N, index, index, val);\\n }\\n \\n public int sumRange(int left, int right) {\\n return query(root, 0, N, left, right);\\n }\\n\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = (end - start + 1) * val;\\n node.add = val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans += query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add * leftNum;\\n node.right.val = node.add * rightNum;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
最近的请求次数
\\n题目详情可见 最近的请求次数
\\n方法一:线段树 (动态开点);完全是为了加深对线段树的理解
\\n###java
\\n\\nclass RecentCounter {\\n\\n public RecentCounter() {\\n\\n }\\n \\n public int ping(int t) {\\n update(root, 1, N, t, t, 1);\\n return query(root, 1, N, Math.max(0, t - 3000), t);\\n }\\n\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val += val;\\n node.add += val;\\n return ;\\n }\\n int mid = (start + end) >> 1;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n int mid = (start + end) >> 1, ans = 0;\\n pushDown(node, mid - start + 1, end - mid);\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans += query(node.right, mid + 1, end, l, r);\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = node.left.val + node.right.val;\\n }\\n private void pushDown(Node node, int leftNum, int rightNum) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val += node.add * leftNum;\\n node.right.val += node.add * rightNum;\\n // 对区间进行「加减」的更新操作,下推懒惰标记时需要累加起来,不能直接覆盖\\n node.left.add += node.add;\\n node.right.add += node.add;\\n node.add = 0;\\n }\\n}\\n
方法二:队列
\\n###java
\\n\\nclass RecentCounter {\\n\\n private Deque<Integer> deque;\\n\\n public RecentCounter() {\\n deque = new ArrayDeque<>();\\n }\\n \\n public int ping(int t) {\\n int past = t - 3000;\\n deque.addLast(t);\\n while (deque.getFirst() < past) deque.removeFirst();\\n return deque.size();\\n }\\n}\\n
掉落的方块
\\n题目详情可见 掉落的方块
\\n上面说过「节点的值不止可以表示该区间的和」,还可以「表示为当前区间的最值」,该题目存储的就是区间的最大值
\\n###java
\\n\\nclass Solution {\\n public List<Integer> fallingSquares(int[][] positions) {\\n List<Integer> ans = new ArrayList<>();\\n for (int[] position : positions) {\\n int x = position[0], h = position[1];\\n // 先查询出 [x, x + h] 的值\\n int cur = query(root, 0, N, x, x + h - 1);\\n // 更新 [x, x + h - 1] 为 cur + h (直接覆盖)\\n update(root, 0, N, x, x + h - 1, cur + h);\\n ans.add(root.val);\\n }\\n return ans;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n // 左右孩子节点\\n Node left, right;\\n // 当前节点值,以及懒惰标记的值\\n int val, add;\\n }\\n private int N = (int) 1e9;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = val;\\n node.add = val;\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n // 每个节点存的是当前区间的最大值\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add;\\n node.right.val = node.add;\\n node.left.add = node.add;\\n node.right.add = node.add;\\n node.add = 0;\\n }\\n}\\n
最长递增子序列 II
\\n题目详情可见 最长递增子序列 II
\\n这里是「区间最值」且对区间进行「覆盖」的更新操作类型
\\n下面给出代码:
\\n###java
\\n\\n","description":"729. 我的日程安排表 I 731. 我的日程安排表 II\\n\\n732. 我的日程安排表 III\\n\\n715. Range 模块\\n\\n307. 区域和检索 - 数组可修改\\n\\n933. 最近的请求次数\\n\\n699. 掉落的方块\\n\\n6206. 最长递增子序列 II\\n\\n线段树引入\\n\\n遇到过好多次线段树的题目,要么就是用其他的方法去解决,要么就是不会写!!今天痛定思痛,决定好好归纳整理一下线段树\\n\\n线段树解决的是「区间和」的问题,且该「区间」会被修改\\n\\n什么意思呢?举个简单的例子,对于 nums = [1, 2, 3, 4, 5]\\n\\n如果我们需要多次求某些区间的和…","guid":"https://leetcode.cn/problems/my-calendar-i//solution/by-lfool-xvpv","author":"lfool","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-04T16:13:13.611Z","media":[{"url":"https://pic.leetcode-cn.com/1654588271-zbOpBr-1.svg","type":"photo","width":621,"height":311,"blurhash":"L34Vs*re0cT:uwMyIU+S01PK=$.j"},{"url":"https://pic.leetcode-cn.com/1655808248-EfPSRV-1.svg","type":"photo","width":62,"height":71,"blurhash":"LoGSDht700Rj_3ofD%WBD%WBxuj["},{"url":"https://pic.leetcode-cn.com/1655808231-VTJvAM-2.svg","type":"photo","width":462,"height":232,"blurhash":"L64L?nYG54xb^TQ;4Ux[_3WB9Fxu"},{"url":"https://pic.leetcode-cn.com/1655808224-kMYiyq-3.svg","type":"photo","width":1374,"height":804,"blurhash":"LKRpE{_2My_4?JkCofj]Myt7NFt7"},{"url":"https://pic.leetcode-cn.com/1654588378-Bhkpkc-3.svg","type":"photo","width":641,"height":341,"blurhash":"L455RWK}DiSaZWQ:Sdi*0_nN-=cW"},{"url":"https://pic.leetcode-cn.com/1654588328-LNnVpz-2.svg","type":"photo","width":621,"height":311,"blurhash":"L55E~RP8DiTqmbR7M{#H0_r=x]%y"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"我的日程安排表 I","url":"https://leetcode.cn/problems/my-calendar-i//solution/wo-de-ri-cheng-an-pai-biao-i-by-leetcode-nlxr","content":"class Solution {\\n public int lengthOfLIS(int[] nums, int k) {\\n int ans = 0;\\n Map<Integer, Integer> map = new HashMap<>();\\n for (int i = 0; i < nums.length; i++) {\\n // 查询区间 [nums[i] - k, nums[i] - 1] 的最值\\n int cnt = query(root, 0, N, Math.max(0, nums[i] - k), nums[i] - 1) + 1;\\n // 更新,注意这里是覆盖更新,对应的模版中覆盖更新不需要累加,已在下方代码中标注\\n update(root, 0, N, nums[i], nums[i], cnt);\\n ans = Math.max(ans, cnt);\\n }\\n return ans;\\n }\\n // *************** 下面是模版 ***************\\n class Node {\\n Node left, right;\\n int val, add;\\n }\\n private int N = (int) 1e5;\\n private Node root = new Node();\\n public void update(Node node, int start, int end, int l, int r, int val) {\\n if (l <= start && end <= r) {\\n node.val = val; // 不需要累加\\n node.add = val; // 不需要累加\\n return ;\\n }\\n pushDown(node);\\n int mid = (start + end) >> 1;\\n if (l <= mid) update(node.left, start, mid, l, r, val);\\n if (r > mid) update(node.right, mid + 1, end, l, r, val);\\n pushUp(node);\\n }\\n public int query(Node node, int start, int end, int l, int r) {\\n if (l <= start && end <= r) return node.val;\\n pushDown(node);\\n int mid = (start + end) >> 1, ans = 0;\\n if (l <= mid) ans = query(node.left, start, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(node.right, mid + 1, end, l, r));\\n return ans;\\n }\\n private void pushUp(Node node) {\\n node.val = Math.max(node.left.val, node.right.val);\\n }\\n private void pushDown(Node node) {\\n if (node.left == null) node.left = new Node();\\n if (node.right == null) node.right = new Node();\\n if (node.add == 0) return ;\\n node.left.val = node.add; // 不需要累加\\n node.right.val = node.add; // 不需要累加\\n node.left.add = node.add; // 不需要累加\\n node.right.add = node.add; // 不需要累加\\n node.add = 0;\\n }\\n}\\n
方法一:直接遍历
\\n我们记录下所有已经预订的课程安排区间,当我们预订新的区间 $[\\\\textit{start}, \\\\textit{end})$ 时,此时检查当前已经预订的每个日程安排是否与新日程安排冲突。若不冲突,则可以添加新的日程安排。
\\n\\n
\\n- 对于两个区间 $[s_1, e_1)$ 和 $[s_2, e_2)$,如果二者没有交集,则此时应当满足 $s_1 \\\\ge e_2$ 或者 $s_2 \\\\ge e_1$,这就意味着如果满足 $s_1 < e_2$ 并且 $s_2 < e_1$,则两者会产生交集。
\\n###Python
\\n\\nclass MyCalendar:\\n def __init__(self):\\n self.booked = []\\n\\n def book(self, start: int, end: int) -> bool:\\n if any(l < end and start < r for l, r in self.booked):\\n return False\\n self.booked.append((start, end))\\n return True\\n
###C++
\\n\\nclass MyCalendar {\\n vector<pair<int, int>> booked;\\n\\npublic:\\n bool book(int start, int end) {\\n for (auto &[l, r] : booked) {\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n booked.emplace_back(start, end);\\n return true;\\n }\\n};\\n
###Java
\\n\\nclass MyCalendar {\\n List<int[]> booked;\\n\\n public MyCalendar() {\\n booked = new ArrayList<int[]>();\\n }\\n\\n public boolean book(int start, int end) {\\n for (int[] arr : booked) {\\n int l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n booked.add(new int[]{start, end});\\n return true;\\n }\\n}\\n
###C#
\\n\\npublic class MyCalendar {\\n IList<Tuple<int, int>> booked;\\n\\n public MyCalendar() {\\n booked = new List<Tuple<int, int>>();\\n }\\n\\n public bool Book(int start, int end) {\\n foreach (Tuple<int, int> tuple in booked) {\\n int l = tuple.Item1, r = tuple.Item2;\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n booked.Add(new Tuple<int, int>(start, end));\\n return true;\\n }\\n}\\n
###C
\\n\\ntypedef struct {\\n int *booked;\\n int bookedSize;\\n} MyCalendar;\\n\\n#define MAX_BOOK_SIZE 1001\\n\\nMyCalendar* myCalendarCreate() {\\n MyCalendar *obj = (MyCalendar *)malloc(sizeof(MyCalendar));\\n obj->booked = (int *)malloc(sizeof(int) * 2 * MAX_BOOK_SIZE);\\n obj->bookedSize = 0;\\n return obj;\\n}\\n\\nbool myCalendarBook(MyCalendar* obj, int start, int end) {\\n for (int i = 0; i < obj->bookedSize; i++) {\\n int l = obj->booked[2 * i];\\n int r = obj->booked[2 * i + 1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n obj->booked[obj->bookedSize * 2] = start;\\n obj->booked[obj->bookedSize * 2 + 1] = end;\\n obj->bookedSize++;\\n return true;\\n}\\n\\nvoid myCalendarFree(MyCalendar* obj) {\\n free(obj->booked);\\n free(obj);\\n}\\n
###JavaScript
\\n\\nvar MyCalendar = function() {\\n this.booked = [];\\n};\\n\\nMyCalendar.prototype.book = function(start, end) {\\n for (const arr of this.booked) {\\n let l = arr[0], r = arr[1];\\n if (l < end && start < r) {\\n return false;\\n }\\n }\\n this.booked.push([start, end]);\\n return true;\\n};\\n
###go
\\n\\ntype pair struct{ start, end int }\\ntype MyCalendar []pair\\n\\nfunc Constructor() MyCalendar {\\n return MyCalendar{}\\n}\\n\\nfunc (c *MyCalendar) Book(start, end int) bool {\\n for _, p := range *c {\\n if p.start < end && start < p.end {\\n return false\\n }\\n }\\n *c = append(*c, pair{start, end})\\n return true\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$, 其中 $n$ 表示日程安排的数量。由于每次在进行预订时,都需要遍历所有已经预订的行程安排。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 表示日程安排的数量。需要保存所有已经预订的行程。
\\n方法二:二分查找
\\n如果我们按时间顺序维护日程安排,则可以通过二分查找日程安排的情况来检查新日程安排是否可以预订,若可以预订则在排序结构中更新插入日程安排。
\\n\\n
\\n- 需要一个数据结构能够保持元素排序和支持快速插入,可以用 $\\\\texttt{TreeSet}$ 来构建。对于给定的区间 $[\\\\textit{start}, \\\\textit{end})$,我们每次查找起点大于等于 $\\\\textit{end}$ 的第一个区间 $[l_1,r_1)$,同时紧挨着 $[l_1,r_1)$ 的前一个区间为 $[l_2,r_2)$,此时如果满足 $r_2 \\\\le \\\\textit{start} < \\\\textit{end} \\\\le l_1$,则该区间可以预订。
\\n###Python
\\n\\nfrom sortedcontainers import SortedDict\\n\\nclass MyCalendar:\\n def __init__(self):\\n self.booked = SortedDict()\\n\\n def book(self, start: int, end: int) -> bool:\\n i = self.booked.bisect_left(end)\\n if i == 0 or self.booked.items()[i - 1][1] <= start:\\n self.booked[start] = end\\n return True\\n return False\\n
###C++
\\n\\nclass MyCalendar {\\n set<pair<int, int>> booked;\\n\\npublic:\\n bool book(int start, int end) {\\n auto it = booked.lower_bound({end, 0});\\n if (it == booked.begin() || (--it)->second <= start) {\\n booked.emplace(start, end);\\n return true;\\n }\\n return false;\\n }\\n};\\n
###Java
\\n\\nclass MyCalendar {\\n TreeSet<int[]> booked;\\n\\n public MyCalendar() {\\n booked = new TreeSet<int[]>((a, b) -> a[0] - b[0]);\\n }\\n\\n public boolean book(int start, int end) {\\n if (booked.isEmpty()) {\\n booked.add(new int[]{start, end});\\n return true;\\n }\\n int[] tmp = {end, 0};\\n int[] arr = booked.ceiling(tmp);\\n int[] prev = arr == null ? booked.last() : booked.lower(arr);\\n if (arr == booked.first() || booked.lower(tmp)[1] <= start) {\\n booked.add(new int[]{start, end});\\n return true;\\n }\\n return false;\\n }\\n}\\n
###go
\\n\\ntype MyCalendar struct {\\n *redblacktree.Tree\\n}\\n\\nfunc Constructor() MyCalendar {\\n t := redblacktree.NewWithIntComparator()\\n t.Put(math.MaxInt32, nil) // 哨兵,简化代码\\n return MyCalendar{t}\\n}\\n\\nfunc (c MyCalendar) Book(start, end int) bool {\\n node, _ := c.Ceiling(end)\\n it := c.IteratorAt(node)\\n if !it.Prev() || it.Value().(int) <= start {\\n c.Put(start, end)\\n return true\\n }\\n return false\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n \\\\log n)$, 其中 $n$ 表示日程安排的数量。由于每次在进行预订时,都需要进行二分查找,需要的时间为 $O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 表示日程安排的数量。需要保存所有已经预订的行程。
\\n方法三:线段树
\\n利用线段树,假设我们开辟了数组 $\\\\textit{arr}[0,\\\\cdots, 10^9]$,初始时每个元素的值都为 $0$,对于每次行程预订的区间 $[\\\\textit{start}, \\\\textit{end})$ ,则我们将区间中的元素 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 中的每个元素都标记为 $1$,每次调用 $\\\\texttt{book}$ 时,我们只需要检测 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 区间内是否有元素被标记为 $1$。实际我们不必实际开辟数组 $\\\\textit{arr}$,可采用动态线段树,懒标记 $\\\\textit{lazy}$ 标记区间 $[l,r]$ 已经被预订,$\\\\textit{tree}$ 记录区间 $[l,r]$ 的是否存在标记为 $1$ 的元素。
\\n\\n
\\n- 每次进行 $\\\\texttt{book}$ 操作时,首先判断区间 $[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 是否存在元素被标记,如果存在被标记为 $1$ 的元素,则表明该区间不可预订;否则,则将可以预订。预订完成后,将 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 进行标记为 $1$,并同时更新线段树。
\\n###Python
\\n\\nclass MyCalendar:\\n def __init__(self):\\n self.tree = set()\\n self.lazy = set()\\n\\n def query(self, start: int, end: int, l: int, r: int, idx: int) -> bool:\\n if r < start or end < l:\\n return False\\n if idx in self.lazy: # 如果该区间已被预订,则直接返回\\n return True\\n if start <= l and r <= end:\\n return idx in self.tree\\n mid = (l + r) // 2\\n return self.query(start, end, l, mid, 2 * idx) or \\\\\\n self.query(start, end, mid + 1, r, 2 * idx + 1)\\n\\n def update(self, start: int, end: int, l: int, r: int, idx: int) -> None:\\n if r < start or end < l:\\n return\\n if start <= l and r <= end:\\n self.tree.add(idx)\\n self.lazy.add(idx)\\n else:\\n mid = (l + r) // 2\\n self.update(start, end, l, mid, 2 * idx)\\n self.update(start, end, mid + 1, r, 2 * idx + 1)\\n self.tree.add(idx)\\n if 2 * idx in self.lazy and 2 * idx + 1 in self.lazy:\\n self.lazy.add(idx)\\n\\n def book(self, start: int, end: int) -> bool:\\n if self.query(start, end - 1, 0, 10 ** 9, 1):\\n return False\\n self.update(start, end - 1, 0, 10 ** 9, 1)\\n return True\\n
###C++
\\n\\nclass MyCalendar {\\n unordered_set<int> tree, lazy;\\n\\npublic:\\n bool query(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return false;\\n }\\n /* 如果该区间已被预订,则直接返回 */\\n if (lazy.count(idx)) {\\n return true;\\n }\\n if (start <= l && r <= end) {\\n return tree.count(idx);\\n }\\n int mid = (l + r) >> 1;\\n return query(start, end, l, mid, 2 * idx) ||\\n query(start, end, mid + 1, r, 2 * idx + 1);\\n }\\n\\n void update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n }\\n if (start <= l && r <= end) {\\n tree.emplace(idx);\\n lazy.emplace(idx);\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, l, mid, 2 * idx);\\n update(start, end, mid + 1, r, 2 * idx + 1);\\n tree.emplace(idx);\\n if (lazy.count(2 * idx) && lazy.count(2 * idx + 1)) {\\n lazy.emplace(idx);\\n }\\n }\\n }\\n\\n bool book(int start, int end) {\\n if (query(start, end - 1, 0, 1e9, 1)) {\\n return false;\\n }\\n update(start, end - 1, 0, 1e9, 1);\\n return true;\\n }\\n};\\n
###Java
\\n\\nclass MyCalendar {\\n Set<Integer> tree;\\n Set<Integer> lazy;\\n\\n public MyCalendar() {\\n tree = new HashSet<Integer>();\\n lazy = new HashSet<Integer>();\\n }\\n\\n public boolean book(int start, int end) {\\n if (query(start, end - 1, 0, 1000000000, 1)) {\\n return false;\\n }\\n update(start, end - 1, 0, 1000000000, 1);\\n return true;\\n }\\n\\n public boolean query(int start, int end, int l, int r, int idx) {\\n if (start > r || end < l) {\\n return false;\\n }\\n /* 如果该区间已被预订,则直接返回 */\\n if (lazy.contains(idx)) {\\n return true;\\n }\\n if (start <= l && r <= end) {\\n return tree.contains(idx);\\n } else {\\n int mid = (l + r) >> 1;\\n if (end <= mid) {\\n return query(start, end, l, mid, 2 * idx);\\n } else if (start > mid) {\\n return query(start, end, mid + 1, r, 2 * idx + 1);\\n } else {\\n return query(start, end, l, mid, 2 * idx) | query(start, end, mid + 1, r, 2 * idx + 1);\\n }\\n }\\n }\\n\\n public void update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n tree.add(idx);\\n lazy.add(idx);\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, l, mid, 2 * idx);\\n update(start, end, mid + 1, r, 2 * idx + 1);\\n tree.add(idx);\\n }\\n }\\n}\\n
###C#
\\n\\npublic class MyCalendar {\\n ISet<int> tree;\\n ISet<int> lazy;\\n\\n public MyCalendar() {\\n tree = new HashSet<int>();\\n lazy = new HashSet<int>();\\n }\\n\\n public bool Book(int start, int end) {\\n if (Query(start, end - 1, 0, 1000000000, 1)) {\\n return false;\\n }\\n Update(start, end - 1, 0, 1000000000, 1);\\n return true;\\n }\\n\\n public bool Query(int start, int end, int l, int r, int idx) {\\n if (start > r || end < l) {\\n return false;\\n }\\n /* 如果该区间已被预订,则直接返回 */\\n if (lazy.Contains(idx)) {\\n return true;\\n }\\n if (start <= l && r <= end) {\\n return tree.Contains(idx);\\n } else {\\n int mid = (l + r) >> 1;\\n if (end <= mid) {\\n return Query(start, end, l, mid, 2 * idx);\\n } else if (start > mid) {\\n return Query(start, end, mid + 1, r, 2 * idx + 1);\\n } else {\\n return Query(start, end, l, mid, 2 * idx) | Query(start, end, mid + 1, r, 2 * idx + 1);\\n }\\n }\\n }\\n\\n public void Update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n tree.Add(idx);\\n lazy.Add(idx);\\n } else {\\n int mid = (l + r) >> 1;\\n Update(start, end, l, mid, 2 * idx);\\n Update(start, end, mid + 1, r, 2 * idx + 1);\\n tree.Add(idx);\\n }\\n }\\n}\\n
###C
\\n\\ntypedef struct HashItem {\\n int key;\\n bool hasBooked;\\n bool lazy;\\n UT_hash_handle hh;\\n} HashItem;\\n\\ntypedef struct {\\n HashItem *tree;\\n} MyCalendar;\\n\\nMyCalendar* myCalendarCreate() {\\n MyCalendar *obj = (MyCalendar *)malloc(sizeof(MyCalendar));\\n obj->tree = NULL;\\n return obj;\\n}\\n\\nbool query(MyCalendar* obj, int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return false;\\n } \\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n /* 如果该区间已被预订,则直接返回 */\\n if (pEntry && pEntry->lazy) {\\n return true;\\n }\\n if (start <= l && r <= end) {\\n if (pEntry) {\\n return pEntry->hasBooked;\\n } else {\\n return false;\\n }\\n } else {\\n int mid = (l + r) >> 1;\\n if (end <= mid) {\\n return query(obj, start, end, l, mid, 2 * idx);\\n } else if (start > mid) {\\n return query(obj, start, end, mid + 1, r, 2 * idx + 1);\\n } else {\\n return query(obj, start, end, l, mid, 2 * idx) | \\\\\\n query(obj, start, end, mid + 1, r, 2 * idx + 1);\\n }\\n }\\n}\\n\\nvoid update(MyCalendar* obj, int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (!pEntry) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n }\\n pEntry->hasBooked = true;\\n pEntry->lazy = true;\\n } else {\\n int mid = (l + r) >> 1;\\n update(obj, start, end, l, mid, 2 * idx);\\n update(obj, start, end, mid + 1, r, 2 * idx + 1);\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (!pEntry) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n } \\n pEntry->hasBooked = true;\\n pEntry->lazy = false;\\n HashItem *pEntry1 = NULL, *pEntry2 = NULL;\\n int lchild = 2 * idx, rchild = 2 * idx + 1;\\n HASH_FIND_INT(obj->tree, &lchild, pEntry1);\\n HASH_FIND_INT(obj->tree, &rchild, pEntry2);\\n if (pEntry1 && pEntry1->lazy && pEntry2 && pEntry2->lazy) {\\n pEntry->lazy = true;\\n }\\n }\\n}\\n\\nbool myCalendarBook(MyCalendar* obj, int start, int end) {\\n if (query(obj, start, end - 1, 0, 1e9, 1)) {\\n return false;\\n }\\n update(obj, start, end - 1, 0, 1e9, 1);\\n return true;\\n}\\n\\nvoid myCalendarFree(MyCalendar* obj) {\\n struct HashItem *curr, *tmp;\\n HASH_ITER(hh, obj->tree, curr, tmp) {\\n HASH_DEL(obj->tree, curr); \\n free(curr); \\n } \\n free(obj);\\n}\\n
###go
\\n\\ntype MyCalendar struct {\\n tree, lazy map[int]bool\\n}\\n\\nfunc Constructor() MyCalendar {\\n return MyCalendar{map[int]bool{}, map[int]bool{}}\\n}\\n\\nfunc (c MyCalendar) query(start, end, l, r, idx int) bool {\\n if r < start || end < l {\\n return false\\n }\\n if c.lazy[idx] { // 如果该区间已被预订,则直接返回\\n return true\\n }\\n if start <= l && r <= end {\\n return c.tree[idx]\\n }\\n mid := (l + r) >> 1\\n return c.query(start, end, l, mid, 2*idx) ||\\n c.query(start, end, mid+1, r, 2*idx+1)\\n}\\n\\nfunc (c MyCalendar) update(start, end, l, r, idx int) {\\n if r < start || end < l {\\n return\\n }\\n if start <= l && r <= end {\\n c.tree[idx] = true\\n c.lazy[idx] = true\\n } else {\\n mid := (l + r) >> 1\\n c.update(start, end, l, mid, 2*idx)\\n c.update(start, end, mid+1, r, 2*idx+1)\\n c.tree[idx] = true\\n if c.lazy[2*idx] && c.lazy[2*idx+1] {\\n c.lazy[idx] = true\\n }\\n }\\n}\\n\\nfunc (c MyCalendar) Book(start, end int) bool {\\n if c.query(start, end-1, 0, 1e9, 1) {\\n return false\\n }\\n c.update(start, end-1, 0, 1e9, 1)\\n return true\\n}\\n
###JavaScript
\\n\\nvar MyCalendar = function() {\\n this.tree = new Set();\\n this.lazy = new Set();\\n};\\n\\nMyCalendar.prototype.book = function(start, end) {\\n if (this.query(start, end - 1, 0, 1000000000, 1)) {\\n return false;\\n }\\n this.update(start, end - 1, 0, 1000000000, 1);\\n return true;\\n};\\n\\nMyCalendar.prototype.query = function(start, end, l, r, idx) {\\n if (start > r || end < l) {\\n return false;\\n }\\n /* 如果该区间已被预订,则直接返回 */\\n if (this.lazy.has(idx)) {\\n return true;\\n }\\n if (start <= l && r <= end) {\\n return this.tree.has(idx);\\n } else {\\n const mid = (l + r) >> 1;\\n if (end <= mid) {\\n return this.query(start, end, l, mid, 2 * idx);\\n } else if (start > mid) {\\n return this.query(start, end, mid + 1, r, 2 * idx + 1);\\n } else {\\n return this.query(start, end, l, mid, 2 * idx) | this.query(start, end, mid + 1, r, 2 * idx + 1);\\n }\\n }\\n}\\n\\nMyCalendar.prototype.update = function(start, end, l, r, idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n this.tree.add(idx);\\n this.lazy.add(idx);\\n } else {\\n const mid = (l + r) >> 1;\\n this.update(start, end, l, mid, 2 * idx);\\n this.update(start, end, mid + 1, r, 2 * idx + 1);\\n this.tree.add(idx);\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:直接遍历 我们记录下所有已经预订的课程安排区间,当我们预订新的区间 $[\\\\textit{start}, \\\\textit{end})$ 时,此时检查当前已经预订的每个日程安排是否与新日程安排冲突。若不冲突,则可以添加新的日程安排。\\n\\n对于两个区间 $[s_1, e_1)$ 和 $[s_2, e_2)$,如果二者没有交集,则此时应当满足 $s_1 \\\\ge e_2$ 或者 $s_2 \\\\ge e_1$,这就意味着如果满足 $s_1 < e_2$ 并且 $s_2 < e_1$,则两者会产生交集。\\n\\n###Python\\n\\nclass MyCalendar:…","guid":"https://leetcode.cn/problems/my-calendar-i//solution/wo-de-ri-cheng-an-pai-biao-i-by-leetcode-nlxr","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-04T03:24:18.219Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】经典优先队列(堆)贪心题","url":"https://leetcode.cn/problems/minimum-number-of-refueling-stops//solution/by-ac_oier-q2mk","content":"- \\n
\\n时间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于使用了线段树查询,线段树的最大深度为 $\\\\log C$,每次最多会查询 $\\\\log C$ 个节点,每次求最大的预订需的时间复杂度为 $O(\\\\log C + \\\\log C)$,因此时间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值 $10^9$。
\\n- \\n
\\n空间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于该解法采用的为动态线段树,线段树的最大深度为 $\\\\log C$,每次预订最多会在线段树上增加 $\\\\log C$ 个节点,因此空间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值 $10^9$。
\\n贪心 + 优先队列(堆)
\\n我们可以模拟行进过程,使用
\\nn
代表加油站总个数,idx
代表经过的加油站下标,使用remain
代表当前有多少油(起始有remain = startFuel
),loc
代表走了多远,ans
代表答案(至少需要的加油次数)。只要
\\nloc < target
,代表还没到达(经过)目标位置,我们可以继续模拟行进过程。每次将
\\nremain
累加到loc
中,含义为使用完剩余的油量,可以去到的最远距离,同时将所在位置stations[idx][0] <= loc
的加油站数量加入优先队列(大根堆,根据油量排倒序)中。再次检查是否满足
\\nloc < target
(下次循环),此时由于清空了剩余油量remain
,我们尝试从优先队列(大根堆)中取出过往油量最大的加油站并进行加油(同时对加油次数ans
进行加一操作)。使用新的剩余油量
\\nremain
重复上述过程,直到满足loc >= target
或无油可加。容易证明该做法的正确性:同样是消耗一次加油次数,始终选择油量最大的加油站进行加油,可以确保不存在更优的结果。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int minRefuelStops(int target, int startFuel, int[][] stations) {\\n PriorityQueue<Integer> q = new PriorityQueue<>((a,b)->b-a);\\n int n = stations.length, idx = 0;\\n int remain = startFuel, loc = 0, ans = 0;\\n while (loc < target) {\\n if (remain == 0) {\\n if (!q.isEmpty() && ++ans >= 0) remain += q.poll();\\n else return -1;\\n }\\n loc += remain; remain = 0;\\n while (idx < n && stations[idx][0] <= loc) q.add(stations[idx++][1]);\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {\\n priority_queue<int> q;\\n int n = stations.size(), idx = 0;\\n int remain = startFuel, loc = 0, ans = 0;\\n while (loc < target) {\\n if (remain == 0) {\\n if (!q.empty() && ++ans >= 0) {\\n remain += q.top();\\n q.pop();\\n } else {\\n return -1;\\n }\\n }\\n loc += remain; remain = 0;\\n while (idx < n && stations[idx][0] <= loc) q.push(stations[idx++][1]);\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def minRefuelStops(self, target: int, startFuel: int, stations: List[List[int]]) -> int:\\n q = []\\n n, idx = len(stations), 0\\n remain, loc, ans = startFuel, 0, 0\\n while loc < target:\\n if remain == 0:\\n if q:\\n ans += 1\\n remain += -heappop(q)\\n else:\\n return -1\\n loc, remain = loc + remain, 0\\n while idx < n and stations[idx][0] <= loc:\\n heappush(q, -stations[idx][1])\\n idx += 1\\n return ans\\n
\\n
\\n- 时间复杂度:$O(n\\\\log{n})$
\\n- 空间复杂度:$O(n)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"贪心 + 优先队列(堆) 我们可以模拟行进过程,使用 n 代表加油站总个数,idx 代表经过的加油站下标,使用 remain 代表当前有多少油(起始有 remain = startFuel),loc 代表走了多远,ans 代表答案(至少需要的加油次数)。\\n\\n只要 loc < target,代表还没到达(经过)目标位置,我们可以继续模拟行进过程。\\n\\n每次将 remain 累加到 loc 中,含义为使用完剩余的油量,可以去到的最远距离,同时将所在位置 stations[idx][0] <= loc 的加油站数量加入优先队列(大根堆,根据油量排倒序)中。\\n\\n再次检查…","guid":"https://leetcode.cn/problems/minimum-number-of-refueling-stops//solution/by-ac_oier-q2mk","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-07-02T00:49:18.105Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"JAVA 有图_双栈解法_简洁易懂","url":"https://leetcode.cn/problems/design-browser-history//solution/java-by-leo-72-60gn","content":"两个栈,一个栈存放历史页面(后退页面)和当前页面,另一个存放未来页面(前进页面)
\\n\\n
\\n","description":"两个栈,一个栈存放历史页面(后退页面)和当前页面,另一个存放未来页面(前进页面) class BrowserHistory {\\n Dequeclass BrowserHistory {\\n Deque<String> s1 = new LinkedList<>();\\n Deque<String> s2 = new LinkedList();\\n public BrowserHistory(String homepage) {\\n s1.push(homepage);\\n }\\n \\n public void visit(String url) {\\n s1.push(url);\\n s2.clear();\\n }\\n \\n public String back(int steps) {\\n for(int i = 0;i < steps && s1.size() > 1;i++){\\n s2.push(s1.pop());\\n }\\n return s1.peek();\\n }\\n \\n public String forward(int steps) {\\n for(int i = 0;i < steps && !s2.isEmpty();i++){\\n s1.push(s2.pop());\\n }\\n return s1.peek();\\n }\\n}\\n
s1 = new LinkedList<>();\\n Deque s2 = new LinkedList();\\n public BrowserHistory(String homepage) {\\n s1.push(homepage);\\n }\\n \\n public void visit(String url) {\\n s1…","guid":"https://leetcode.cn/problems/design-browser-history//solution/java-by-leo-72-60gn","author":"leo-72","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-19T07:31:19.064Z","media":[{"url":"https://pic.leetcode-cn.com/1655624539-UidPTC-%E6%8D%95%E8%8E%B7.PNG","type":"photo","width":1047,"height":695,"blurhash":"LFRpLEt#IU~X_3oft7ogofayj[oM"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:按照首字母分组/按照后缀分组(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/naming-a-company//solution/by-endlesscheng-ruz8","content":" 分析
\\n什么样的一对字符串无法交换首字母?
\\n示例 1 中的 $\\\\texttt{coffee}$ 和 $\\\\texttt{time}$,虽然这样两个字符串完全不一样,但如果交换了 $\\\\texttt{coffee}$ 和 $\\\\texttt{time}$ 的首字母,会得到字符串 $\\\\texttt{toffee}$,它在数组 $\\\\textit{ideas}$ 中,不符合题目要求。
\\n又例如 $\\\\textit{ideas}=[\\\\texttt{aa},\\\\texttt{ab},\\\\texttt{ac},\\\\texttt{bc},\\\\texttt{bd},\\\\texttt{be}]$,将其分成两组:
\\n\\n
\\n- 第一组:$\\\\texttt{aa},\\\\texttt{ab},\\\\texttt{ac}$。
\\n- 第二组:$\\\\texttt{bc},\\\\texttt{bd},\\\\texttt{be}$。
\\n其中第一组内的字符串是不能交换首字母的,因为交换后字符串不变,必然在 $\\\\textit{ideas}$ 中。第二组也同理。
\\n考虑交换第一组的字符串和第二组的字符串,哪些是可以交换首字母的,哪些是不能交换首字母的?
\\n\\n
\\n- 第一组的 $\\\\texttt{ac}$ 无法和第二组的任何字符串交换,因为交换后会得到 $\\\\texttt{bc}$,它在 $\\\\textit{ideas}$ 中。
\\n- 第二组的 $\\\\texttt{bc}$ 无法和第一组的任何字符串交换,因为交换后会得到 $\\\\texttt{ac}$,它在 $\\\\textit{ideas}$ 中。
\\n- 其余字符串对可以交换首字母。
\\n上面的分析立刻引出了如下方法。
\\n方法一:按照首字母分组
\\n按照首字母,把 $\\\\textit{ideas}$ 分成(至多)$26$ 组字符串。
\\n例如 $\\\\textit{ideas}=[\\\\texttt{aa},\\\\texttt{ab},\\\\texttt{ac},\\\\texttt{bc},\\\\texttt{bd},\\\\texttt{be}]$ 分成如下两组(只记录去掉首字母后的字符串):
\\n\\n
\\n- $A$ 组(集合):${\\\\texttt{a},\\\\texttt{b},\\\\texttt{c}}$。
\\n- $B$ 组(集合):${\\\\texttt{c},\\\\texttt{d},\\\\texttt{e}}$。
\\n分组后:
\\n\\n
\\n- 从 $A$ 中选一个不等于 $\\\\texttt{c}$ 的字符串,这有 $2$ 种选法。
\\n- 从 $B$ 中选一个不等于 $\\\\texttt{c}$ 的字符串,这有 $2$ 种选法。
\\n- 考虑两个字符串先后顺序(谁在左谁在右),有 $2$ 种方法。
\\n根据乘法原理,有 $2\\\\times 2\\\\times 2=8$ 对符合要求的字符串。
\\n由于无法选交集中的字符串,一般地,从 $A$ 和 $B$ 中可以选出
\\n$$
\\n
\\n2\\\\cdot(|A|-|A\\\\cap B|)\\\\cdot(|B|-|A\\\\cap B|)
\\n$$对符合要求的字符串。其中 $|S|$ 表示集合 $S$ 的大小。
\\n枚举所有组对,计算上式,累加到答案中。
\\n\\nclass Solution:\\n def distinctNames(self, ideas: List[str]) -> int:\\n groups = defaultdict(set)\\n for s in ideas:\\n groups[s[0]].add(s[1:]) # 按照首字母分组\\n\\n ans = 0\\n for a, b in combinations(groups.values(), 2): # 枚举所有组对\\n m = len(a & b) # 交集的大小\\n ans += (len(a) - m) * (len(b) - m)\\n return ans * 2 # 乘 2 放到最后\\n
\\nclass Solution {\\n public long distinctNames(String[] ideas) {\\n Set<String>[] groups = new HashSet[26];\\n Arrays.setAll(groups, i -> new HashSet<>());\\n for (String s : ideas) {\\n groups[s.charAt(0) - \'a\'].add(s.substring(1)); // 按照首字母分组\\n }\\n\\n long ans = 0;\\n for (int a = 1; a < 26; a++) { // 枚举所有组对\\n for (int b = 0; b < a; b++) {\\n int m = 0; // 交集的大小\\n for (String s : groups[a]) {\\n if (groups[b].contains(s)) {\\n m++;\\n }\\n }\\n ans += (long) (groups[a].size() - m) * (groups[b].size() - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long distinctNames(vector<string>& ideas) {\\n unordered_set<string> groups[26];\\n for (auto& s : ideas) {\\n groups[s[0] - \'a\'].insert(s.substr(1)); // 按照首字母分组\\n }\\n\\n long long ans = 0;\\n for (int a = 1; a < 26; a++) { // 枚举所有组对\\n for (int b = 0; b < a; b++) {\\n int m = 0; // 交集的大小\\n for (auto& s : groups[a]) {\\n m += groups[b].count(s);\\n }\\n ans += (long long) (groups[a].size() - m) * (groups[b].size() - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n }\\n};\\n
\\nfunc distinctNames(ideas []string) (ans int64) {\\n group := [26]map[string]bool{}\\n for i := range group {\\n group[i] = map[string]bool{}\\n }\\n for _, s := range ideas {\\n group[s[0]-\'a\'][s[1:]] = true // 按照首字母分组\\n }\\n\\n for i, a := range group { // 枚举所有组对\\n for _, b := range group[:i] {\\n m := 0 // 交集的大小\\n for s := range a {\\n if b[s] {\\n m++\\n }\\n }\\n ans += int64(len(a)-m) * int64(len(b)-m)\\n }\\n }\\n return ans * 2 // 乘 2 放到最后\\n}\\n
\\nvar distinctNames = function(ideas) {\\n const groups = Array.from({ length: 26 }, () => new Set());\\n for (const s of ideas) {\\n groups[s.charCodeAt(0) - \'a\'.charCodeAt(0)].add(s.slice(1)); // 按照首字母分组\\n }\\n\\n let ans = 0;\\n for (let a = 1; a < 26; a++) { // 枚举所有组对\\n for (let b = 0; b < a; b++) {\\n let m = 0; // 交集的大小\\n for (const s of groups[a]) {\\n if (groups[b].has(s)) {\\n m++;\\n }\\n }\\n ans += (groups[a].size - m) * (groups[b].size - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n};\\n
\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn distinct_names(ideas: Vec<String>) -> i64 {\\n let mut groups = vec![HashSet::new(); 26];\\n for s in ideas {\\n groups[(s.as_bytes()[0] - b\'a\') as usize].insert(s[1..].to_string()); // 按照首字母分组\\n }\\n\\n let mut ans = 0i64;\\n for a in 1..26 { // 枚举所有组对\\n for b in 0..a {\\n let m = groups[a].iter().filter(|&s| groups[b].contains(s)).count(); // 交集的大小\\n ans += (groups[a].len() - m) as i64 * (groups[b].len() - m) as i64;\\n }\\n }\\n ans * 2 // 乘 2 放到最后\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$O(nm|\\\\Sigma|)$,其中 $n$ 为 $\\\\textit{ideas}$ 的长度,$m\\\\le 10$ 为单个字符串的长度,$|\\\\Sigma|=26$ 是字符集合的大小。注意枚举组对的逻辑看上去是 $O(nm|\\\\Sigma|^2)$ 的,但去掉一层 $\\\\mathcal{O}(|\\\\Sigma|)$ 的循环后,剩余循环相当于把 $\\\\textit{ideas}$ 遍历了一遍,是 $\\\\mathcal{O}(nm)$,所以总的时间复杂度是 $O(nm|\\\\Sigma|)$。
\\n- 空间复杂度:$O(nm+|\\\\Sigma|)$。
\\n方法二:按照后缀分组
\\n下文把去掉首字母后的剩余部分称作后缀。
\\n横看成岭侧成峰,换一个角度计算交集大小 $|A\\\\cap B|$。
\\n在遍历 $\\\\textit{ideas}=[\\\\texttt{aa},\\\\texttt{ab},\\\\texttt{ac},\\\\texttt{bc},\\\\texttt{bd},\\\\texttt{be}]$ 的过程中,当我们遍历到 $\\\\texttt{bc}$ 时,发现之前遍历过一个后缀也为 $\\\\texttt{c}$ 的字符串 $\\\\texttt{ac}$,这就对交集大小 $|A\\\\cap B|$ 产生了 $1$ 的贡献,也就是交集大小 $|A\\\\cap B|$ 增加 $1$。
\\n具体来说,在遍历 $\\\\textit{ideas}$ 的过程中,维护如下信息:
\\n\\n
\\n- 集合大小 $\\\\textit{size}[a]$。遍历到 $s=\\\\textit{ideas}[i]$ 时,把 $\\\\textit{size}[s[0]]$ 加一。
\\n- 交集大小 $\\\\textit{intersection}[a][b]$。遍历到 $s=\\\\textit{ideas}[i]$ 时,设 $b=s[0]$,把 $\\\\textit{intersection}[a][b]$ 和 $\\\\textit{intersection}[b][a]$ 加一,其中 $a$ 是和 $s$ 同后缀的其他字符串的首字母。
\\n- 为了计算有哪些字符串和 $s$ 有着相同的后缀,用一个哈希表 $\\\\textit{groups}$ 维护,key 是后缀,value 是后缀对应的首字母列表。注意题目保证所有字符串互不相同。
\\n代码实现时,可以用把首字母列表压缩成一个二进制数,原理见 从集合论到位运算,常见位运算技巧分类总结!
\\n\\nclass Solution:\\n def distinctNames(self, ideas: List[str]) -> int:\\n size = [0] * 26 # 集合大小\\n intersection = [[0] * 26 for _ in range(26)] # 交集大小\\n groups = defaultdict(list) # 后缀 -> 首字母列表\\n for s in ideas:\\n b = ord(s[0]) - ord(\'a\')\\n size[b] += 1 # 增加集合大小\\n g = groups[s[1:]]\\n for a in g: # a 是和 s 有着相同后缀的字符串的首字母\\n intersection[a][b] += 1 # 增加交集大小\\n intersection[b][a] += 1\\n g.append(b)\\n\\n ans = 0\\n for a in range(1, 26): # 枚举所有组对\\n for b in range(a):\\n m = intersection[a][b]\\n ans += (size[a] - m) * (size[b] - m)\\n return ans * 2 # 乘 2 放到最后\\n
\\nclass Solution {\\n public long distinctNames(String[] ideas) {\\n int[] size = new int[26]; // 集合大小\\n int[][] intersection = new int[26][26]; // 交集大小\\n Map<String, Integer> groups = new HashMap<>(); // 后缀 -> 首字母\\n for (String s : ideas) {\\n int b = s.charAt(0) - \'a\';\\n size[b]++; // 增加集合大小\\n String suffix = s.substring(1);\\n int mask = groups.getOrDefault(suffix, 0);\\n groups.put(suffix, mask | 1 << b); // 把 b 加到 mask 中\\n for (int a = 0; a < 26; a++) { // a 是和 s 有着相同后缀的字符串的首字母\\n if ((mask >> a & 1) > 0) { // a 在 mask 中\\n intersection[b][a]++; // 增加交集大小\\n intersection[a][b]++;\\n }\\n }\\n }\\n\\n long ans = 0;\\n for (int a = 1; a < 26; a++) { // 枚举所有组对\\n for (int b = 0; b < a; b++) {\\n int m = intersection[a][b];\\n ans += (long) (size[a] - m) * (size[b] - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long distinctNames(vector<string>& ideas) {\\n int size[26]{}; // 集合大小\\n int intersection[26][26]{}; // 交集大小\\n unordered_map<string, int> groups; // 后缀 -> 首字母\\n for (auto& s : ideas) {\\n int b = s[0] - \'a\';\\n size[b]++; // 增加集合大小\\n auto suffix = s.substr(1);\\n int mask = groups[suffix];\\n groups[suffix] = mask | 1 << b; // 把 b 加到 mask 中\\n for (int a = 0; a < 26; a++) { // a 是和 s 有着相同后缀的字符串的首字母\\n if (mask >> a & 1) { // a 在 mask 中\\n intersection[b][a]++; // 增加交集大小\\n intersection[a][b]++;\\n }\\n }\\n }\\n\\n long long ans = 0;\\n for (int a = 1; a < 26; a++) { // 枚举所有组对\\n for (int b = 0; b < a; b++) {\\n int m = intersection[a][b];\\n ans += (long long) (size[a] - m) * (size[b] - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n }\\n};\\n
\\nfunc distinctNames(ideas []string) (ans int64) {\\n size := [26]int{} // 集合大小\\n intersection := [26][26]int{} // 交集大小\\n groups := map[string]int{} // 后缀 -> 首字母\\n for _, s := range ideas {\\n b := s[0] - \'a\'\\n size[b]++ // 增加集合大小\\n suffix := s[1:]\\n mask := groups[suffix]\\n groups[suffix] = mask | 1<<b // 把 b 加到 mask 中\\n for a := 0; a < 26; a++ { // a 是和 s 有着相同后缀的字符串的首字母\\n if mask>>a&1 > 0 { // a 在 mask 中\\n intersection[b][a]++ // 增加交集大小\\n intersection[a][b]++\\n }\\n }\\n }\\n\\n for a := 1; a < 26; a++ { // 枚举所有组对\\n for b := 0; b < a; b++ {\\n m := intersection[a][b]\\n ans += int64(size[a]-m) * int64(size[b]-m)\\n }\\n }\\n return ans * 2 // 乘 2 放到最后\\n}\\n
\\nvar distinctNames = function(ideas) {\\n const size = Array(26).fill(0); // 集合大小\\n const intersection = Array.from({ length: 26 }, () => Array(26).fill(0)); // 交集大小\\n const groups = {}; // 后缀 -> 首字母\\n for (let s of ideas) {\\n const b = s.charCodeAt(0) - \'a\'.charCodeAt(0);\\n size[b]++; // 增加集合大小\\n const suffix = s.slice(1);\\n const mask = groups[suffix] ?? 0;\\n groups[suffix] = mask | 1 << b; // 把 b 加到 mask 中\\n for (let a = 0; a < 26; a++) { // a 是和 s 有着相同后缀的字符串的首字母\\n if (mask >> a & 1) { // a 在 mask 中\\n intersection[b][a]++; // 增加交集大小\\n intersection[a][b]++;\\n }\\n }\\n }\\n\\n let ans = 0;\\n for (let a = 1; a < 26; a++) { // 枚举所有组对\\n for (let b = 0; b < a; b++) {\\n const m = intersection[a][b];\\n ans += (size[a] - m) * (size[b] - m);\\n }\\n }\\n return ans * 2; // 乘 2 放到最后\\n};\\n
\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn distinct_names(ideas: Vec<String>) -> i64 {\\n let mut size = vec![0; 26]; // 集合大小\\n let mut intersection = vec![vec![0; 26]; 26]; // 交集大小\\n let mut groups = HashMap::new(); // 后缀 -> 首字母\\n for s in ideas {\\n let b = (s.as_bytes()[0] - b\'a\') as usize;\\n size[b] += 1; // 增加集合大小\\n let suffix = &s[1..];\\n let mask = *groups.get(suffix).unwrap_or(&0);\\n groups.insert(suffix.to_string(), mask | 1 << b); // 把 b 加到 mask 中\\n for a in 0..26 { // a 是和 s 有着相同后缀的首字母\\n if (mask >> a & 1) > 0 { // a 在 mask 中\\n intersection[b][a] += 1; // 增加交集大小\\n intersection[a][b] += 1;\\n }\\n }\\n }\\n\\n let mut ans = 0i64;\\n for a in 1..26 { // 枚举所有组对\\n for b in 0..a {\\n let m = intersection[a][b];\\n ans += (size[a] - m) as i64 * (size[b] - m) as i64;\\n }\\n }\\n ans * 2 // 乘 2 放到最后\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$O(n(m+|\\\\Sigma|) + |\\\\Sigma|^2)$,其中 $n$ 为 $\\\\textit{ideas}$ 的长度,$m\\\\le 10$ 为单个字符串的长度,$|\\\\Sigma|=26$ 是字符集合的大小。
\\n- 空间复杂度:$O(nm+|\\\\Sigma|^2)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 什么样的一对字符串无法交换首字母?\\n\\n示例 1 中的 $\\\\texttt{coffee}$ 和 $\\\\texttt{time}$,虽然这样两个字符串完全不一样,但如果交换了 $\\\\texttt{coffee}$ 和 $\\\\texttt{time}$ 的首字母,会得到字符串 $\\\\texttt{toffee}$,它在数组 $\\\\textit{ideas}$ 中,不符合题目要求。\\n\\n又例如 $\\\\textit{ideas}=[\\\\texttt{aa},\\\\texttt{ab},\\\\texttt{ac},\\\\texttt{bc},\\\\texttt{bd},\\\\texttt{be}]$…","guid":"https://leetcode.cn/problems/naming-a-company//solution/by-endlesscheng-ruz8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-12T04:19:58.490Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"对每个 idea 找出可以配对的数量 + 枚举优化","url":"https://leetcode.cn/problems/naming-a-company//solution/dui-mei-ge-idea-zhao-chu-ke-yi-pei-dui-d-zpv5","content":"本题属于考察 阅读理解 + 逻辑思考 + 性能调优 的题目,比较接近于实际工程中的问题。遇到这种问题需要冷静分析,尽量不要让思维混乱,才能最终把答案理清楚。
\\n解法:换一种方式枚举
\\n我们考虑对于在 $\\\\text{ideas}$ 中的一个单词 $w$,它能够和多少个其他单词 $other$ 配对,以组成一个有效的公司名字。 显然直接遍历所有的 $other$ 来统计答案显然会是 $O(n^2)$,会超时。
\\n但是,如果换一个角度呢?不妨枚举字符 $c = \\\\texttt{\'a\'} \\\\sim \\\\texttt{\'z\'}$ ,含义为,我们把 $w[0]$ 变为 $c$,然后找任意一个首字母为 $c$ 的 $other$,把 $other[0]$ 变为 $w[0]$,以实现字母交换。为了公司名称合法,显然字符 $c$ 与 $w$ 的尾部($w[1:]$)拼接成的单词($c + w[1:]$)不能出现在 $\\\\text{ideas}$ 中。如果上述条件满足,那么,对字符 $c$ 而言,可供选择的 $other$ 的数量就是:其他所有单词中,原本的首字母为 $c$,修改后的首字母为 $w[0]$,且修改后的单词没有出现在 $\\\\text{ideas}$ 中 的单词数量。如果预先统计好,这一步可以 $O(1)$ 解决。
\\n因此我们需要预先统计 $cnt[x][y]$ 数组,含义为: 原本首字符为 $x$,修改后的首字符为 $y$,且修改后的单词没有出现在 $\\\\text{ideas}$ 中 的单词数量。统计的方法很简单,用一个哈希表记录所有的单词,然后再对每个单词 $w$,枚举首字母的修改,并检查修改后的单词是否在哈希表中即可。
\\n最后再遍历 $\\\\text{ideas}$ 中的单词 $w$,对于每个单词 $w$,遍历首字母的修改值 $c$,如果 $c + w[1:]$ 没有出现在 $\\\\text{ideas}$ 中,那么 $ans += cnt[c][w[0]]$。
\\n
\\n按照上面的做法已经可以通过本题,但是我们可以继续优化。注意到,统计 $cnt[x][y]$ 的时间成本稍微有些高,因为需要产生 $26$ 个单词并检查哈希表,设单词长度为 $l$,那么每次判断的时间复杂度为 $l$(需要对整个单词进行 $\\\\text{hash}$ 操作),总时间就是 $l \\\\times 26$。
\\n优化的方式为,将每个单词 $w$ 分成两部分:首字母 $w[0]$ 和尾部 $w[1:]$。在哈希表中,不是存储所有的单词,而是对所有的尾部,我们统计它对应哪些首字母。比如,$\\\\text{ideas} = [\\\\texttt{aaaaa, baaaa, caaaa, daaaa}]$,那么这些单词中,尾部只有一个,就是 $\\\\texttt{aaaa}$,而 $\\\\texttt{aaaa}$ 对应的首字母有 $4$ 个,分别为 $\\\\texttt{a,b,c,d}$。
\\n\\n\\n由于字母只有 $26$ 个,我们可以用一个整数来表示字母的存在情况(详见代码)。
\\n这样的好处是,给定单词 $w$,我们只需要访问哈希表一次,即可获取哪些首字母可用,哪些首字母不可用,而原来的办法需要访问 $26$ 次。
\\n
\\n时间复杂度:$O(n(l + \\\\Sigma))$,其中,$n=\\\\text{ideas.length}, l=$ 字符串的平均长度,$\\\\Sigma=26$ 为字符集的大小。
\\n
\\n空间复杂度:$O(nl)$。为哈希表所占用的空间。代码
\\n###c++
\\n\\n","description":"本题属于考察 阅读理解 + 逻辑思考 + 性能调优 的题目,比较接近于实际工程中的问题。遇到这种问题需要冷静分析,尽量不要让思维混乱,才能最终把答案理清楚。 解法:换一种方式枚举\\n\\n我们考虑对于在 $\\\\text{ideas}$ 中的一个单词 $w$,它能够和多少个其他单词 $other$ 配对,以组成一个有效的公司名字。 显然直接遍历所有的 $other$ 来统计答案显然会是 $O(n^2)$,会超时。\\n\\n但是,如果换一个角度呢?不妨枚举字符 $c = \\\\texttt{\'a\'} \\\\sim \\\\texttt{\'z\'}$ ,含义为,我们把 $w[0]$ 变为 $c…","guid":"https://leetcode.cn/problems/naming-a-company//solution/dui-mei-ge-idea-zhao-chu-ke-yi-pei-dui-d-zpv5","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-12T04:12:15.675Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举","url":"https://leetcode.cn/problems/naming-a-company//solution/by-tsreaper-v7eq","content":"class Solution {\\npublic:\\n long long distinctNames(vector<string>& ideas) {\\n unordered_map<string, int> m;\\n for(auto& w : ideas) {\\n m[w.substr(1)] |= (1 << (w[0] - \'a\'));\\n }\\n \\n int cnt[26][26];\\n memset(cnt, 0, sizeof(cnt));\\n for(auto& w : ideas) {\\n auto mask = m[w.substr(1)];\\n for(char c = \'a\'; c <= \'z\'; ++c) {\\n if(!(mask & (1 << (c - \'a\')))) {\\n cnt[w[0]-\'a\'][c-\'a\']++;\\n }\\n }\\n }\\n \\n long long res = 0;\\n for(auto& w : ideas) {\\n auto mask = m[w.substr(1)];\\n for(char c = \'a\'; c <= \'z\'; ++c) {\\n if(!(mask & (1 << (c - \'a\')))) {\\n res += cnt[c-\'a\'][w[0]-\'a\'];\\n }\\n }\\n }\\n \\n return res;\\n }\\n};\\n
解法:枚举
\\n假设
\\nideaA
是已知的,设ideaA
的首字母为x
。由于新名字只会交换首字母,我们枚举ideaB
的首字母y
,根据题意:\\n
\\n- 如果
\\nideaA
的首字母从x
改成y
以后不在名字列表里了,且- 如果
\\nideaB
的首字母从y
改成x
以后不在名字列表里了,
\\n则ideaA
和ideaB
是一对有效答案。那么符合要求的
\\nideaB
的数量要如何得知呢?记cnt[y][x]
表示把首字母从y
改成x
以后就不在ideas
里的字符串数量,符合要求的ideaB
数量就是cnt[y][x]
。这可以通过枚举所有字符串以及它们修改后的首字母来统计。这部分的复杂度是 $\\\\mathcal{O}(|\\\\Sigma|n)$(这里认为字符串长度是常量),其中 $|\\\\Sigma|$ 是字符集大小。有了
\\ncnt
数组,我们只要枚举ideaA
和新的首字母y
,若ideaA
首字母换成y
以后不在名字列表里,则最终答案加上cnt[y][x]
即可。有的朋友可能会担心去重问题,其实由于x != y
,ideaA
不会被统计到cnt[y][x]
里,因此不需要去重。这部分的复杂度也是 $\\\\mathcal{O}(|\\\\Sigma|n)$。因此整体复杂度是 $\\\\mathcal{O}(|\\\\Sigma|n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:枚举 假设 ideaA 是已知的,设 ideaA 的首字母为 x。由于新名字只会交换首字母,我们枚举 ideaB 的首字母 y,根据题意:\\n\\n如果 ideaA 的首字母从 x 改成 y 以后不在名字列表里了,且\\n如果 ideaB 的首字母从 y 改成 x 以后不在名字列表里了,\\n 则 ideaA 和 ideaB 是一对有效答案。\\n\\n那么符合要求的 ideaB 的数量要如何得知呢?记 cnt[y][x] 表示把首字母从 y 改成 x 以后就不在 ideas 里的字符串数量,符合要求的 ideaB 数量就是 cnt[y][x]。这可以通过枚举所有字符串以…","guid":"https://leetcode.cn/problems/naming-a-company//solution/by-tsreaper-v7eq","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-12T04:11:10.900Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「越短越合法」型滑动窗口(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-subarrays-with-score-less-than-k//solution/by-endlesscheng-b120","content":"class Solution {\\npublic:\\n long long distinctNames(vector<string>& ideas) {\\n int n = ideas.size();\\n\\n // 把 ideas 放进 unordered_set 里方便 O(1) 查找\\n unordered_set<string> st;\\n st.reserve(ideas.size());\\n for (string &s : ideas) st.insert(s);\\n\\n // 维护 cnt 表示把首字母从 y 改成 x 以后就不在 ideas 里的字符串数量\\n vector<vector<bool>> flag;\\n flag.resize(n, vector<bool>(26));\\n vector<vector<int>> cnt;\\n cnt.resize(26, vector<int>(26));\\n for (int i = 0; i < n; i++) {\\n string t = ideas[i];\\n int old = t[0] - \'a\';\\n for (int j = 0; j < 26; j++) {\\n t[0] = j + \'a\';\\n if (st.count(t) == 0) flag[i][j] = true, cnt[old][j]++;\\n }\\n }\\n\\n // 枚举 ideaA 和新的首字母 y\\n long long ans = 0;\\n for (int i = 0; i < n; i++) for (int j = 0; j < 26; j++) if (flag[i][j]) {\\n int k = ideas[i][0] - \'a\';\\n ans += cnt[j][k];\\n }\\n return ans;\\n }\\n};\\n
前置知识:滑动窗口【基础算法精讲 03】
\\n滑动窗口使用前提:
\\n\\n
\\n- 连续子数组/子串。
\\n- 有单调性。本题元素均为正数,所以子数组越长,分数越高;子数组越短,分数越低。这意味着只要某个子数组的分数小于 $k$,在该子数组内的更短的子数组,分数也小于 $k$。
\\n做法:
\\n\\n
\\n- 枚举子数组的右端点 $\\\\textit{right}$,同时维护窗口内的元素和 $\\\\textit{sum}$ 以及窗口左端点 $\\\\textit{left}$。窗口的分数为 $\\\\textit{sum}\\\\cdot (\\\\textit{right}-\\\\textit{left}+1)$。
\\n- 元素 $x=\\\\textit{nums}[i]$ 进入窗口,把 $\\\\textit{sum}$ 增加 $x$。
\\n- 如果窗口的分数 $\\\\ge k$,那么把左端点元素 $\\\\textit{nums}[\\\\textit{left}]$ 移出窗口,同时减少 $\\\\textit{sum}$,把 $\\\\textit{left}$ 加一。
\\n- 内层循环结束后,$[\\\\textit{left},\\\\textit{right}]$ 这个子数组是合法的。根据上面的使用前提 2,在这个子数组内部的子数组也是合法的,其中右端点小于 $\\\\textit{right}$ 的子数组,我们在之前的循环中已经统计过了,这里只需要统计右端点恰好等于 $\\\\textit{right}$ 的合法子数组,即
\\n
\\n$$
\\n[\\\\textit{left},\\\\textit{right}],[\\\\textit{left}+1,\\\\textit{right}],\\\\ldots,[\\\\textit{right},\\\\textit{right}]
\\n$$
\\n这一共有 $\\\\textit{right}-\\\\textit{left}+1$ 个,加入答案。\\nclass Solution:\\n def countSubarrays(self, nums: List[int], k: int) -> int:\\n ans = s = left = 0\\n for right, x in enumerate(nums):\\n s += x\\n while s * (right - left + 1) >= k:\\n s -= nums[left]\\n left += 1\\n ans += right - left + 1\\n return ans\\n
\\nclass Solution {\\n public long countSubarrays(int[] nums, long k) {\\n long ans = 0;\\n long sum = 0;\\n int left = 0;\\n for (int right = 0; right < nums.length; right++) {\\n sum += nums[right];\\n while (sum * (right - left + 1) >= k) {\\n sum -= nums[left];\\n left++;\\n }\\n ans += right - left + 1;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, long long k) {\\n long long ans = 0, sum = 0;\\n int left = 0;\\n for (int right = 0; right < nums.size(); right++) {\\n sum += nums[right];\\n while (sum * (right - left + 1) >= k) {\\n sum -= nums[left];\\n left++;\\n }\\n ans += right - left + 1;\\n }\\n return ans;\\n }\\n};\\n
\\nlong long countSubarrays(int* nums, int numsSize, long long k) {\\n long long ans = 0, sum = 0;\\n int left = 0;\\n for (int right = 0; right < numsSize; right++) {\\n sum += nums[right];\\n while (sum * (right - left + 1) >= k) {\\n sum -= nums[left];\\n left++;\\n }\\n ans += right - left + 1;\\n }\\n return ans;\\n}\\n
\\nfunc countSubarrays(nums []int, k int64) (ans int64) {\\n sum, left := int64(0), 0\\n for right, x := range nums {\\n sum += int64(x)\\n for sum*int64(right-left+1) >= k {\\n sum -= int64(nums[left])\\n left++\\n }\\n ans += int64(right - left + 1)\\n }\\n return\\n}\\n
\\nvar countSubarrays = function(nums, k) {\\n let ans = 0, sum = 0, left = 0;\\n for (let right = 0; right < nums.length; right++) {\\n sum += nums[right];\\n while (sum * (right - left + 1) >= k) {\\n sum -= nums[left];\\n left++;\\n }\\n ans += right - left + 1;\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn count_subarrays(nums: Vec<i32>, k: i64) -> i64 {\\n let mut ans = 0;\\n let mut sum = 0;\\n let mut left = 0;\\n for (right, &x) in nums.iter().enumerate() {\\n sum += x as i64;\\n while sum * ((right - left + 1) as i64) >= k {\\n sum -= nums[left] as i64;\\n left += 1;\\n }\\n ans += (right - left + 1) as i64;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。虽然写了个二重循环,但是内层循环中对 $\\\\textit{left}$ 加一的总执行次数不会超过 $n$ 次,所以总的时间复杂度为 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面滑动窗口题单的「§2.3.2 越短越合法」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前置知识:滑动窗口【基础算法精讲 03】 滑动窗口使用前提:\\n\\n连续子数组/子串。\\n有单调性。本题元素均为正数,所以子数组越长,分数越高;子数组越短,分数越低。这意味着只要某个子数组的分数小于 $k$,在该子数组内的更短的子数组,分数也小于 $k$。\\n\\n做法:\\n\\n枚举子数组的右端点 $\\\\textit{right}$,同时维护窗口内的元素和 $\\\\textit{sum}$ 以及窗口左端点 $\\\\textit{left}$。窗口的分数为 $\\\\textit{sum}\\\\cdot (\\\\textit{right}-\\\\textit{left}+1)$。\\n元素 $x…","guid":"https://leetcode.cn/problems/count-subarrays-with-score-less-than-k//solution/by-endlesscheng-b120","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-11T23:29:40.033Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"two pointers","url":"https://leetcode.cn/problems/count-subarrays-with-score-less-than-k//solution/by-tsreaper-nsdn","content":"解法:two pointers
\\n若一个子数组符合要求,则它的子数组也一定符合要求。因此可以使用 two pointers 对于每个下标求出以该下标为结尾的合法子数组共有几个。将所有下标的答案加起来即可。复杂度 $\\\\mathcal{O}(n)$。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:two pointers 若一个子数组符合要求,则它的子数组也一定符合要求。因此可以使用 two pointers 对于每个下标求出以该下标为结尾的合法子数组共有几个。将所有下标的答案加起来即可。复杂度 $\\\\mathcal{O}(n)$。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n long long countSubarrays(vectorclass Solution {\\npublic:\\n long long countSubarrays(vector<int>& nums, long long k) {\\n int n = nums.size();\\n vector<long long> sm(n + 1);\\n for (int i = 1; i <= n; i++) sm[i] = sm[i - 1] + nums[i - 1];\\n\\n long long ans = 0;\\n for (int i = 1, j = 1; i <= n; i++) {\\n while (j <= i && (sm[i] - sm[j - 1]) * (i - j + 1) >= k) j++;\\n ans += i - j + 1;\\n }\\n return ans;\\n }\\n};\\n
& nums, long long k) {\\n int n = nums.size();\\n vector 方法一:枚举\\n 思路与算法
\\n为了方便起见,我们用 $s$ 表示 $\\\\textit{num}$ 对应十进制表示的字符串。我们可以从左至右枚举 $s$ 中长度为 $k$ 的字符串,并判断该子串对应的整数能否被 $\\\\textit{num}$ 整除。与此同时,我们用 $\\\\textit{res}$ 统计能被 $\\\\textit{num}$ 整除的子串数量,如果某个子串能被 $\\\\textit{num}$ 整除,则我们将 $\\\\textit{res}$ 加上 $1$。最终,$\\\\textit{res}$ 即为 $\\\\textit{num}$ 的 $k$ 美丽值,我们返回 $\\\\textit{res}$ 作为答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int divisorSubstrings(int num, int k) {\\n string s = to_string(num); // num 十进制表示字符串\\n int n = s.size();\\n int res = 0;\\n for (int i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n int tmp = stoi(s.substr(i, k));\\n if (tmp && num % tmp == 0) {\\n ++res;\\n }\\n }\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def divisorSubstrings(self, num: int, k: int) -> int:\\n s = str(num) # num 十进制表示字符串\\n n = len(s)\\n res = 0\\n for i in range(n - k + 1):\\n # 枚举所有长度为 k 的子串\\n tmp = int(s[i:i+k])\\n if tmp != 0 and num % tmp == 0:\\n res += 1\\n return res\\n
###Java
\\n\\nclass Solution {\\n public int divisorSubstrings(int num, int k) {\\n String s = Integer.toString(num); // num 十进制表示字符串\\n int n = s.length();\\n int res = 0;\\n for (int i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n int tmp = Integer.parseInt(s.substring(i, i + k));\\n if (tmp != 0 && num % tmp == 0) {\\n ++res;\\n }\\n }\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int DivisorSubstrings(int num, int k) {\\n string s = num.ToString(); // num 十进制表示字符串\\n int n = s.Length;\\n int res = 0;\\n for (int i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n int tmp = int.Parse(s.Substring(i, k));\\n if (tmp != 0 && num % tmp == 0) {\\n ++res;\\n }\\n }\\n return res;\\n }\\n}\\n
###Go
\\n\\nfunc divisorSubstrings(num int, k int) int {\\n s := strconv.Itoa(num) // num 十进制表示字符串\\n n := len(s)\\n res := 0\\n for i := 0; i <= n-k; i++ {\\n // 枚举所有长度为 k 的子串\\n tmp, _ := strconv.Atoi(s[i:i+k])\\n if tmp != 0 && num%tmp == 0 {\\n res++\\n }\\n }\\n return res\\n}\\n
###C
\\n\\nint divisorSubstrings(int num, int k) {\\n char s[12]; // 足够容纳数字的字符串\\n sprintf(s, \\"%d\\", num); // num 十进制表示字符串\\n int n = strlen(s);\\n int res = 0;\\n for (int i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n char tmpStr[k + 1];\\n strncpy(tmpStr, s + i, k);\\n tmpStr[k] = \'\\\\0\';\\n int tmp = atoi(tmpStr);\\n if (tmp != 0 && num % tmp == 0) {\\n ++res;\\n }\\n }\\n return res;\\n}\\n
###JavaScript
\\n\\nvar divisorSubstrings = function(num, k) {\\n let s = num.toString(); // num 十进制表示字符串\\n let n = s.length;\\n let res = 0;\\n for (let i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n let tmp = parseInt(s.substring(i, i + k));\\n if (tmp !== 0 && num % tmp === 0) {\\n ++res;\\n }\\n }\\n return res;\\n};\\n
###TypeScript
\\n\\nfunction divisorSubstrings(num: number, k: number): number {\\n let s = num.toString(); // num 十进制表示字符串\\n let n = s.length;\\n let res = 0;\\n for (let i = 0; i <= n - k; ++i) {\\n // 枚举所有长度为 k 的子串\\n let tmp = parseInt(s.substring(i, i + k));\\n if (tmp !== 0 && num % tmp === 0) {\\n ++res;\\n }\\n }\\n return res;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn divisor_substrings(num: i32, k: i32) -> i32 {\\n let s = num.to_string(); // num 十进制表示字符串\\n let n = s.len();\\n let mut res = 0;\\n for i in 0..= n - k as usize {\\n // 枚举所有长度为 k 的子串\\n let tmp: i32 = s[i..i + k as usize].parse().unwrap();\\n if tmp != 0 && num % tmp == 0 {\\n res += 1;\\n }\\n }\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举 思路与算法\\n\\n为了方便起见,我们用 $s$ 表示 $\\\\textit{num}$ 对应十进制表示的字符串。我们可以从左至右枚举 $s$ 中长度为 $k$ 的字符串,并判断该子串对应的整数能否被 $\\\\textit{num}$ 整除。与此同时,我们用 $\\\\textit{res}$ 统计能被 $\\\\textit{num}$ 整除的子串数量,如果某个子串能被 $\\\\textit{num}$ 整除,则我们将 $\\\\textit{res}$ 加上 $1$。最终,$\\\\textit{res}$ 即为 $\\\\textit{num}$ 的 $k$ 美丽值,我们返回…","guid":"https://leetcode.cn/problems/find-the-k-beauty-of-a-number//solution/zhao-dao-yi-ge-shu-zi-de-k-mei-li-zhi-by-jn5i","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-06T10:50:07.934Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】带懒标记的「线段树(动态开点」与「分块」","url":"https://leetcode.cn/problems/my-calendar-iii//solution/by-ac_oier-ioyt","content":"- \\n
\\n时间复杂度:$O(nk)$,其中 $n$ 为 $\\\\textit{num}$ 的位数, $k$ 为子串的长度。我们总共需要枚举 $O(n)$ 个子串,其中判断每个子串都需要 $O(k)$ 的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(n)$,即为辅助字符串的空间开销。
\\n基本分析
\\n对于此类「区间和」问题,通常有多种方式可以进行求解。
\\n较为简单的做法是利用调用次数有限,使用模拟或者有序集合等做法(在「729. 我的日程安排表 I」中有实现),更进一步,能够应对更大调用次数的则是「分块」或「线段树」。
\\n关于 日程安排表 前置 🧀 系列题解 :
\\n\\n
\\n- \\n\\n
\\n- \\n\\n
\\n- \\n
\\n732. 我的日程安排表 III : 旧题解,本题解增加「分块」做法
\\n
\\n线段树(动态开点)
\\n和 731. 我的日程安排表 II 几乎完全一致,只需要将对「线段树」所维护的节点信息进行调整即可。
\\n线段树维护的节点信息包括:
\\n\\n
\\n- \\n
ls/rs
: 分别代表当前节点的左右子节点在线段树数组tr
中的下标;- \\n
add
: 懒标记;- \\n
max
: 为当前区间的最大值。对于常规的线段树实现来说,都是一开始就调用
\\nbuild
操作创建空树,而线段树一般以「满二叉树」的形式用数组存储,因此需要 $4 * n$ 的空间,并且这些空间在起始build
空树的时候已经锁死。如果一道题仅仅是「值域很大」的离线题(提前知晓所有的询问),我们还能通过「离散化」来进行处理,将值域映射到一个小空间去,从而解决
\\nMLE
问题。但对于本题而言,由于「强制在线」的原因,我们无法进行「离散化」,同时值域大小达到 $1e9$ 级别,因此如果我们想要使用「线段树」进行求解,只能采取「动态开点」的方式进行。
\\n动态开点的优势在于,不需要事前构造空树,而是在插入操作
\\nadd
和查询操作query
时根据访问需要进行「开点」操作。由于我们不保证查询和插入都是连续的,因此对于父节点 $u$ 而言,我们不能通过u << 1
和u << 1 | 1
的固定方式进行访问,而要将节点 $tr[u]$ 的左右节点所在tr
数组的下标进行存储,分别记为ls
和rs
属性。对于 $tr[u].ls = 0$ 和 $tr[u].rs = 0$ 则是代表子节点尚未被创建,当需要访问到它们,而又尚未创建的时候,则将其进行创建。由于存在「懒标记」,线段树的插入和查询都是 $\\\\log{n}$ 的,因此我们在单次操作的时候,最多会创建数量级为 $\\\\log{n}$ 的点,因此空间复杂度为 $O(m\\\\log{n})$,而不是 $O(4 * n)$,而开点数的预估需不能仅仅根据 $\\\\log{n}$ 来进行,还要对常熟进行分析,才能得到准确的点数上界。
\\n动态开点相比于原始的线段树实现,本质仍是使用「满二叉树」的形式进行存储,只不过是按需创建区间,如果我们是按照连续段进行查询或插入,最坏情况下仍然会占到 $4 * n$ 的空间,因此盲猜 $\\\\log{n}$ 的常数在 $4$ 左右,保守一点可以直接估算到 $6$,因此我们可以估算点数为 $6 * m * \\\\log{n}$,其中 $n = 1e9$ 和 $m = 1e3$ 分别代表值域大小和查询次数。
\\n当然一个比较实用的估点方式可以「尽可能的多开点数」,利用题目给定的空间上界和我们创建的自定义类(结构体)的大小,尽可能的多开(
\\nJava
的 $128M$ 可以开到 $5 * 10^6$ 以上)。代码:
\\n###Java
\\n\\nclass MyCalendarThree {\\n class Node {\\n int ls, rs, add, max;\\n }\\n int N = (int)1e9, M = 4 * 400 * 20, cnt = 1;\\n Node[] tr = new Node[M];\\n void update(int u, int lc, int rc, int l, int r, int v) {\\n if (l <= lc && rc <= r) {\\n tr[u].add += v;\\n tr[u].max += v;\\n return ;\\n }\\n lazyCreate(u);\\n pushdown(u);\\n int mid = lc + rc >> 1;\\n if (l <= mid) update(tr[u].ls, lc, mid, l, r, v);\\n if (r > mid) update(tr[u].rs, mid + 1, rc, l, r, v);\\n pushup(u);\\n }\\n int query(int u, int lc, int rc, int l, int r) {\\n if (l <= lc && rc <= r) return tr[u].max;\\n lazyCreate(u);\\n pushdown(u);\\n int mid = lc + rc >> 1, ans = 0;\\n if (l <= mid) ans = query(tr[u].ls, lc, mid, l, r);\\n if (r > mid) ans = Math.max(ans, query(tr[u].rs, mid + 1, rc, l, r));\\n return ans;\\n }\\n void lazyCreate(int u) {\\n if (tr[u] == null) tr[u] = new Node();\\n if (tr[u].ls == 0) {\\n tr[u].ls = ++cnt;\\n tr[tr[u].ls] = new Node();\\n }\\n if (tr[u].rs == 0) {\\n tr[u].rs = ++cnt;\\n tr[tr[u].rs] = new Node();\\n }\\n }\\n void pushdown(int u) {\\n tr[tr[u].ls].add += tr[u].add; tr[tr[u].rs].add += tr[u].add;\\n tr[tr[u].ls].max += tr[u].add; tr[tr[u].rs].max += tr[u].add;\\n tr[u].add = 0;\\n }\\n void pushup(int u) {\\n tr[u].max = Math.max(tr[tr[u].ls].max, tr[tr[u].rs].max);\\n }\\n public int book(int start, int end) {\\n update(1, 1, N + 1, start + 1, end, 1);\\n return query(1, 1, N + 1, 1, N + 1);\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $n$ 为值域大小,本题固定为 $1e9$,线段树的查询和增加复杂度均为 $O(\\\\log{n})$
\\n- 空间复杂度:令询问数量为 $m$,复杂度为 $O(m\\\\log{n})$
\\n
\\n带懒标记的分块(TLE)
\\n实际上,还存在另外一种十分值得学习的算法:分块。
\\n但该做法被奇怪的测评机制卡掉,是给每个样例都定了执行用时吗 🤣 ?
\\n旧题解没有这种做法,今天补充的,我们可以大概讲讲「分块」算法是如何解决涉及「区间修改」,也就是带懒标记的问题。
\\n对于本题,我们设定两个块数组
\\nmax
和add
,其中max[idx]
含义为块编号为idx
的连续区间的最大重复值,而add[idx]
代表块编号的懒标记,同时使用「哈希表」记录下某些具体位置的覆盖次数。然后我们考虑如何指定块大小,设定一个合理的块大小是减少运算量的关键。
\\n通常情况下,我们会设定块大小为 $\\\\sqrt{n}$,从而确保块内最多不超过 $\\\\sqrt{n}$ 个元素,同时块的总个数也不超过 $\\\\sqrt{n}$,即无论我们是块内暴力,还是操作多个块,复杂度均为 $O(\\\\sqrt{n})$。因此对于本题而言,设定块大小为 $\\\\sqrt{1e9}$(经试验,调成 $1200010$ 能够通过较多样例),较为合适。
\\n对于涉及「区间修改」的分块算法,我们需要在每次修改和查询前,先将懒标记下传到区间的每个元素中。
\\n然后考虑如何处理「块内」和「块间」操作:
\\n\\n
\\n- 块内操作:\\n
\\n\\n
\\n- 块内更新:直接操作
\\nmap
进行累加,同时使用更新后的map
来维护相应的max[idx]
;- 块内查询:直接查询
\\nmap
即可;- 块间操作:\\n
\\n\\n
\\n- 块间更新:直接更新懒标记
\\nadd[idx]
即可,同时由于我们是对整个区间进行累加操作,因此最大值必然也会同步被累加,因此同步更新max[idx]
;- 块间查询:直接查询
\\nmax[idx]
即可。代码:
\\n###Java
\\n\\nclass MyCalendarThree {\\n static int N = (int)1e9 + 10, M = 1200010, SZ = N / M + 10;\\n static int[] max = new int[M], add = new int[M];\\n static Map<Integer, Integer> map = new HashMap<>();\\n int minv = -1, maxv = -1;\\n int getIdx(int x) {\\n return x / SZ;\\n }\\n void add(int l, int r) {\\n pushdown(l); pushdown(r);\\n if (getIdx(l) == getIdx(r)) {\\n for (int k = l; k <= r; k++) {\\n map.put(k, map.getOrDefault(k, 0) + 1);\\n max[getIdx(k)] = Math.max(max[getIdx(k)], map.get(k));\\n }\\n } else {\\n int i = l, j = r;\\n while (getIdx(i) == getIdx(l)) {\\n map.put(i, map.getOrDefault(i, 0) + 1);\\n max[getIdx(i)] = Math.max(max[getIdx(i)], map.get(i));\\n i++;\\n }\\n while (getIdx(j) == getIdx(r)) {\\n map.put(j, map.getOrDefault(j, 0) + 1);\\n max[getIdx(j)] = Math.max(max[getIdx(j)], map.get(j));\\n j--;\\n }\\n for (int k = getIdx(i); k <= getIdx(j); k++) {\\n max[k]++; add[k]++;\\n }\\n }\\n }\\n int query(int l, int r) {\\n pushdown(l); pushdown(r);\\n int ans = 0;\\n if (getIdx(l) == getIdx(r)) {\\n for (int k = l; k <= r; k++) ans = Math.max(ans, map.getOrDefault(k, 0));\\n } else {\\n int i = l, j = r;\\n while (getIdx(i) == getIdx(l)) ans = Math.max(ans, map.getOrDefault(i++, 0));\\n while (getIdx(j) == getIdx(r)) ans = Math.max(ans, map.getOrDefault(j--, 0));\\n for (int k = getIdx(i); k <= getIdx(j); k++) ans = Math.max(ans, max[k]);\\n }\\n return ans;\\n }\\n void pushdown(int x) {\\n int idx = getIdx(x);\\n if (add[idx] == 0) return ;\\n int i = x, j = x + 1;\\n while (getIdx(i) == idx) map.put(i, map.getOrDefault(i--, 0) + add[idx]);\\n while (getIdx(j) == idx) map.put(j, map.getOrDefault(j++, 0) + add[idx]);\\n add[idx] = 0;\\n }\\n public MyCalendarThree() {\\n Arrays.fill(max, 0);\\n Arrays.fill(add, 0);\\n map.clear();\\n }\\n public int book(int start, int end) {\\n add(start + 1, end);\\n minv = minv == -1 ? start : Math.min(minv, start);\\n maxv = maxv == -1 ? end + 1 : Math.max(maxv, end + 1);\\n return query(minv, maxv);\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $M$ 为块大小,复杂度为 $O(\\\\sqrt{M})$
\\n- 空间复杂度:$O(C \\\\times \\\\sqrt{M})$,其中 $C = 1e3$ 为最大调用次数
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"基本分析 对于此类「区间和」问题,通常有多种方式可以进行求解。\\n\\n较为简单的做法是利用调用次数有限,使用模拟或者有序集合等做法(在「729. 我的日程安排表 I」中有实现),更进一步,能够应对更大调用次数的则是「分块」或「线段树」。\\n\\n关于 日程安排表 前置 🧀 系列题解 :\\n\\n729. 我的日程安排表 I\\n\\n731. 我的日程安排表 II\\n\\n732. 我的日程安排表 III : 旧题解,本题解增加「分块」做法\\n\\n线段树(动态开点)\\n\\n和 731. 我的日程安排表 II 几乎完全一致,只需要将对「线段树」所维护的节点信息进行调整即可。\\n\\n线段树维护的…","guid":"https://leetcode.cn/problems/my-calendar-iii//solution/by-ac_oier-ioyt","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-06T02:01:32.787Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线段树 -- 新手篇","url":"https://leetcode.cn/problems/my-calendar-iii//solution/xian-duan-shu-by-xiaohu9527-rfzj","content":"前言
\\n本人被线段树折磨已久,总是逃避不想学习,今天终于初入门道,因此来写一篇比较基础的线段树题解供大家学习参考。去网上搜索线段树学习的时候,最经典的应该是力扣第307题“区域和检索”。就是给你一个数组,会有简单的更新操作以及查询区域和的操作。查询操作指的是给你一个区间
\\n[L,R]
, 返回该区间[L,R]
内所有元素的和。更新操作指的是,给你一个下标索引和一个值,将数组中该索引值对于的元素值改为新的给定值。在讲本题的题解前我们得先从这一题开始说一下为什么要用线段树,使用线段树有什么优势,线段树的思想是什么。由于时间原因不可能太过全面,本文较长,但是读懂后读者能学到许多,我们进入线段树的探索过程。
\\n注:检索区间即为查询区间。不一样的词,相近的意思。下面只是不小心用了个检索这个生词。
\\n线段树(区域和检索问题)
\\n1.朴素的思想
\\n假设我们现在有一个数组,我们想对其一个区间查询其区间和,那么对这个数组的查询操作,及找到该区间内所有元素的和的时间复杂度为$O(n)$。如果我们想要更新其数组内的一个数的值,这个更新操作的时间复杂度便为$O(1)$,可以直接根据下标进行修改。
\\n
\\n2.前缀和
\\n提到和,尤其是对于查询区间和,我们容易想到的一个点就是使用前缀和,这样我们就可以将查询的操作提升到$O(1)$, 但是使用前缀和会有一个问题,当我们的更新次数过多时,尤其是需要更新的元素比较靠前时,每一次更新的代价都会为$O(n)$,从而没有达到优化的效果。但是对于元素不变动的数组前缀和还是有很不错的优势!
\\n
\\n3.线段树
\\n那么线段树为什么会对此有影响呢?线段树将此类问题的查询以及更新的时间复杂度都变成了$O(logn)$。当进行多次查询与更新时,线段树一定比上述两种方法更具优势。首先我们先来看一下线段树是什么结构。这种结构有点像堆,首先比较重要需要知道的一点是,我们用数组来表示树的结构,对于根树的根节点,它会在
\\nindex=1
的位置上(其实此处0也行,不过大家普遍用1,区别就是计算子节点的方式不同),然后对于其节点的左右子节点的下标分别为2*index
与2*index+1
。然后其子节点也是这样的规律,如果第一次接触建议读者用纸笔自己计算一番。这就是一棵满二叉树对于一个节点的下标计算方式。
\\n
\\n查询: 我们会根据区间从根节点
向树的两边递归查寻。假设我们现在要查找此树的[2,4]
的区间和,及[50,50,1]
的和, 那么这个过程是什么样的呢?
\\n
\\n更新:假设我们要将其数组中的1
更新为2
,结构会发生什么变化?而有那些节点会做出改变呢?
\\n
\\n到此我们已经基本了解了线段树了,我们发现线段树让更新与查询的时间复杂度都变成了$O(logn)$, 下面我们来代码层面的学习一下线段树。
\\n1. 建树
\\n\\n
\\n跑完以上代码后,我们的线段树数组如下: (可以发现根节点在索引为1
的位置的确为284,是所有元素的和,读者可以根据树的结构,以及从开头介绍的左右子节点的计算方式一一检查是否正确
\\n
\\n2. 检索
\\n检索需要讨论以下情况:
\\n
\\n情况一:
\\n
\\n情况二:
\\n情况二则是当前区间被我们的检索区间包围,及蓝色区间在绿色区间里面时,因此不必继续往下递归,可以直接返回当前节点值。这里比较容易想,读者可参考之前的线段树查询。思考一下,每一个节点表示的都是一个区间内所有元素的和,那么当整个当前区间都被我们的检索区间包围了,证明我们需要所有的元素,因此不必继续往下递归查询,可以返回其节点值。譬如之前例子中的区间[3,4]
,代码输出中依然可以观察到这一点。代码如下:
\\n
\\n
\\n示例查询区间[2,4]
的区域和结果如下: 50 + 50 + 1 = 101.为了可视化,我在其query
方法中加入了几行输出。我们可以发现当递归到区间[3,4]
时其实已经停止继续递归下去了,这正是我们想要的结果。
\\n
\\n3. 更新
\\n更新与建树很像,当其递归到出口时就说明我们已经找到了要更新元素的节点。要注意更新完元素后,其包含此元素的区间节点都需要更新,代码中已加注释。
\\n
\\n
\\n我们做一次更新,将数组从[93,90,50,50,1]
改到[93,90,50,50,2]
,然后在做一次查询看结果是否正确:
\\n
\\n本题
\\n终于来到了本题,如果不了解以上知识,我们根本无法讨论本题的解题方法。首先我们来将问题转化一下,让题意更加明显,其实本题的本质就是给你一个区间,然后让你将其
\\n[start, end)
内所有的元素值加上一,在进行了每一次的book
更新的操作后,在返回[0, 1e9]
这个区间内的最大元素值是多少。本题的解法本质上其实也是运用了线段树的思想,但是从检查区域和,变为了检索线段树中叶子节点的最大值是多少。我们很容易的想到,对于其一段区间我们需要book
时,我们可以将其区间内的所有元素都加上1
。显而易见的是,我们无法真的去建树,以及真的去更新其元素值。对于第一个问题,由于此题的数据问题,我们可能造成内存溢出。因此我们用哈希表来表示我们的线段树,这样可以省去许多内存空间。对于其第二个问题,我们不需要真的去手动更新每个节点值,这与朴素思想无异。我们选择的是官解中的懒标记
,及如果当前节点区间的被索引的区间覆盖时,我们则将表示此区间的节点值加一,表示此区间内的所有元素值都加了一位,这里很重要,读者需要多读几遍。 个人觉得懒标记最难理解的地方就在这里,详细可看思路步骤中的第二点解读。
\\n思路步骤:
\\n\\n
\\n- 需要两个哈希表,一个用于线段树,一个用于区间的懒标记使用。注意此时的线段树的节点拥有的是该区间内的所有元素中的最大值。(不要被上述区间和的例子干扰,注意本题问的是什么!区间和提供的是思路!)
\\n- 对于一个区间的更新,我们左右递归,下面分类讨论:\\n
\\n\\n
\\n- 当该区间不在检索区间内时,则
\\nstart > r || end < l
,不做更新,直接返回。- 当该区间被检索区间覆盖时,我们无需手动去更新该区间内所有元素值,只需要标记一下该区间内的所有元素都被加上了一位即可。显而易见,无论当前节点区间的最大值为多少,当该区间的所有元素值都加一时,其拥有的最大值自然也需要加一位。
\\n- 当该区间内有元素不在其检索区间时,递归左右两边去更新我们的线段树。
\\n- 返回根节点的最大值,及所有元素中的最大值。
\\n
\\n\\nclass MyCalendarThree {\\nprivate:\\n unordered_map<int, int> tree;\\n unordered_map<int, int> lazy;\\npublic:\\n MyCalendarThree() {\\n\\n }\\n\\n void update(int start, int end, int l, int r, int node)\\n {\\n if(start > r || end < l) {\\n return;\\n } else if(start <= l && r <= end) { \\n // 当前区间被检索区间覆盖, 因此区间内所有元素都加一\\n // 自然而然,无论当前节点区间的最大值为多少,当该区间的所有\\n // 元素值都加一时,其拥有的最大值自然也需要加一位\\n ++tree[node]; \\n ++lazy[node];\\n } else {\\n int left_node = node*2, right_node = node*2 + 1;\\n int mid = (l+r) >> 1;\\n update(start, end, l, mid, left_node);\\n update(start, end, mid+1, r, right_node);\\n tree[node] = lazy[node] + max(tree[left_node], tree[right_node]);\\n }\\n }\\n \\n int book(int start, int end) {\\n update(start, end-1, 0, 1e9, 1);\\n return tree[1];\\n }\\n};\\n
\\n// 线段树代码\\n#include <iostream>\\n#include <vector>\\nusing namespace std;\\n\\nvoid build_tree(vector<int>& arr, vector<int>& tree, int start, int end, int node_idx)\\n{\\n //递归的出口,也就是到了叶子节点\\n if(start == end) {\\n tree[node_idx] = arr[start];\\n } else {\\n //找到左子树的节点(2*node_idx)\\n //找到右子树的节点(2*node_idx+1)\\n int left_node = 2*node_idx, right_node = 2*node_idx + 1;\\n int mid = (start + end) / 2;\\n //将树进行分割,然后左右递归建树\\n build_tree(arr, tree, start, mid, left_node);\\n build_tree(arr, tree, mid+1, end, right_node);\\n tree[node_idx] = tree[left_node] + tree[right_node];\\n }\\n}\\n\\nint query(vector<int>& arr, vector<int>& tree, int start, int end, int l, int r, int node_idx)\\n{\\n //情况一\\n if(l > end || r < start) {\\n return 0;\\n } else if (l <= start && end <= r) { //情况二\\n return tree[node_idx];\\n } else {\\n //递归查询\\n //找到左子树的节点(2*node_idx)\\n //找到右子树的节点(2*node_idx+1)\\n int left_node = 2*node_idx, right_node = 2*node_idx + 1;\\n int mid = (start + end) / 2;\\n //将树进行分割,然后左右递归查询\\n int left_sum = query(arr, tree, start, mid, l, r, left_node);\\n int right_sum = query(arr, tree, mid+1, end, l, r, right_node);\\n return left_sum + right_sum;\\n }\\n}\\n\\nvoid update(vector<int>& arr, vector<int>& tree, int start, int end, int node_idx, int update_idx, int val)\\n{\\n //递归的出口,也就是到了叶子节点, 更新其值\\n if(start == end) {\\n tree[node_idx] = arr[start] = val;\\n } else {\\n //找到左子树的节点(2*node_idx)\\n //找到右子树的节点(2*node_idx+1)\\n int left_node = 2*node_idx, right_node = 2*node_idx + 1;\\n int mid = (start + end) / 2;\\n //如果要更新节点在右边\\n if(update_idx > mid) {\\n update(arr, tree, mid+1, end, right_node, update_idx, val);\\n } else { //要更新节点在左边\\n update(arr, tree, start, mid, left_node, update_idx, val);\\n }\\n //要注意更新当前节点!!!!!!\\n tree[node_idx] = tree[left_node] + tree[right_node];\\n }\\n}\\nint main()\\n{\\n vector<int> arr = {93, 90, 50, 50, 1};\\n int n = arr.size();\\n vector<int> tree(4*n);\\n build_tree(arr, tree, 0, n-1, 1);\\n cout << \\"更新前的树:\\";\\n for (auto& x : tree) cout << x << \\" \\";\\n cout << endl;\\n \\n //更新 idx = 4的元素值为 2\\n update(arr, tree, 0, n-1, 1, 4, 2);\\n cout << \\"更新后的树:\\";\\n for (auto& x : tree) cout << x << \\" \\";\\n cout << endl;\\n \\n cout << \\"查询区间[2,4]的和为:\\" << query(arr, tree, 0, n-1, 2, 4, 1) << endl;\\n\\n return 0;\\n}\\n
\\n时间复杂度: $O(nlogC)$
\\n","description":"前言 本人被线段树折磨已久,总是逃避不想学习,今天终于初入门道,因此来写一篇比较基础的线段树题解供大家学习参考。去网上搜索线段树学习的时候,最经典的应该是力扣第307题“区域和检索”。就是给你一个数组,会有简单的更新操作以及查询区域和的操作。查询操作指的是给你一个区间[L,R], 返回该区间[L,R]内所有元素的和。更新操作指的是,给你一个下标索引和一个值,将数组中该索引值对于的元素值改为新的给定值。在讲本题的题解前我们得先从这一题开始说一下为什么要用线段树,使用线段树有什么优势,线段树的思想是什么。由于时间原因不可能太过全面,本文较长…","guid":"https://leetcode.cn/problems/my-calendar-iii//solution/xian-duan-shu-by-xiaohu9527-rfzj","author":"Xiaohu9527","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-06T00:06:21.947Z","media":[{"url":"https://pic.leetcode.cn/1654475429-nPxEuU-7ceb305aa9aa94999a3fd9e70c14021.png","type":"photo","width":1603,"height":900,"blurhash":"LHR{#@?b-;_3~q-;D%Rkt7ogRjkA"},{"url":"https://pic.leetcode.cn/1654475963-dYHHeJ-865c2871b073173ea3913b033cbb996.png","type":"photo","width":1763,"height":983,"blurhash":"LJRMYu-p?HxH_NX99aXS%NaxV[oM"},{"url":"https://pic.leetcode.cn/1654492806-OPBLfB-image.png","type":"photo","width":1900,"height":987,"blurhash":"LEQS@].myC?bF}wIxZtRY6w_t7t7"},{"url":"https://pic.leetcode.cn/1654492870-AXElKN-image.png","type":"photo","width":1871,"height":952,"blurhash":"LGQS-y.myC=|PC$g%1%Lo~xtt6xt"},{"url":"https://pic.leetcode.cn/1654492907-RrcDkw-image.png","type":"photo","width":1884,"height":976,"blurhash":"LDQS_1?^%M^+E-%1%2x[cZ$%xatR"},{"url":"https://pic.leetcode.cn/1654502669-NYSNdm-image.png","type":"photo","width":1519,"height":774,"blurhash":"L17w.@%g4:oz=^M|Ndt7W?-paxRj"},{"url":"https://pic.leetcode.cn/1654502837-eeXbBG-image.png","type":"photo","width":1266,"height":169,"blurhash":"LkF}$ioffQof~WoLfQoeWVfQfQfQ"},{"url":"https://pic.leetcode.cn/1654478958-YzcdJk-image.png","type":"photo","width":1144,"height":684,"blurhash":"LFS5#B?dXT?]HWR7Vte.LgRRV@ix"},{"url":"https://pic.leetcode.cn/1654479351-AtmiGP-image.png","type":"photo","width":1565,"height":646,"blurhash":"L07w+*~V00bdVs?G-oxa0OR.?F-:"},{"url":"https://pic.leetcode.cn/1654503176-NPAxFI-image.png","type":"photo","width":1082,"height":577,"blurhash":"LFA0]:t8WCf+RjWBWBj[00j[j[WB"},{"url":"https://pic.leetcode.cn/1654479939-WKnwrz-image.png","type":"photo","width":1715,"height":670,"blurhash":"L17-Td%M02E2^*xut7a#D*oz?Gxa"},{"url":"https://pic.leetcode.cn/1654502577-yXkXqb-image.png","type":"photo","width":1445,"height":825,"blurhash":"L797eK%MWDof00j[WBRk%gofofof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两个栈快速解答,光标在两个栈之间","url":"https://leetcode.cn/problems/design-a-text-editor//solution/liang-ge-zhan-by-cgq_dlut-wf2j","content":"
\\n空间复杂度: $O(nlogC)$
\\n此题解参考力扣官方题解。
\\n模板不重要,这个思维最重要。如果觉得对你有帮助,帮忙点个👍吧~\\n
\\n- \\n
\\n初始化光标和两个桶
\\n
\\n- \\n
\\n添加字符串,将字符串转变为字符依次入栈a
\\n
\\n- \\n
\\n删除字符串,依次删除栈a,直至没有字符
\\n
\\n
\\n删除之后
\\n- \\n
\\n光标左移,将栈a的字符依次放入栈b
\\n
\\n- \\n
\\n光标右移,将栈b的字符依次放入栈a
\\n
\\n- \\n
\\n光标移动完成后读取字符串,从栈a读取最多10个字符(读取结果顺序是反的,记得做下顺序调转),最后读取完之后复原栈a,调用入栈函数即可,具体参考函数readLeftChar()
\\n感谢,C++由https://leetcode.cn/u/bw-zhang/ 供稿
\\n###Java
\\n\\nclass TextEditor {\\n\\n public TextEditor() {\\n a = new Stack<>();\\n b = new Stack<>();\\n }\\n\\n Stack<Character> a,b;\\n\\n public void addText(String text) {\\n char[] array = text.toCharArray();\\n for(char c : array) {\\n a.push(c);\\n }\\n }\\n\\n public int deleteText(int k) {\\n int target = k;\\n while (k > 0 && !a.isEmpty()) {\\n a.pop();\\n k--;\\n }\\n return target-k;\\n }\\n\\n public String cursorLeft(int k) {\\n while (!a.isEmpty() && k > 0){\\n b.push(a.pop());\\n k--;\\n }\\n return readLeftChar();\\n }\\n\\n private String readLeftChar(){\\n if(a.isEmpty()) {\\n return \\"\\";\\n }\\n \\n StringBuilder sb = new StringBuilder();\\n while (sb.length() < 10 && !a.isEmpty()) {\\n sb.append(a.pop());\\n }\\n String rs = sb.reverse().toString();\\n addText(rs);\\n return rs;\\n }\\n\\n public String cursorRight(int k) {\\n\\n while (!b.isEmpty() && k > 0){\\n a.push(b.pop());\\n k--;\\n }\\n return readLeftChar();\\n }\\n}\\n\\n/**\\n * Your TextEditor object will be instantiated and called as such:\\n * TextEditor obj = new TextEditor();\\n * obj.addText(text);\\n * int param_2 = obj.deleteText(k);\\n * String param_3 = obj.cursorLeft(k);\\n * String param_4 = obj.cursorRight(k);\\n */\\n
###C++
\\n\\n","description":"初始化光标和两个桶 添加字符串,将字符串转变为字符依次入栈a\\n\\n\\n删除字符串,依次删除栈a,直至没有字符\\n \\n 删除之后\\n\\n\\n光标左移,将栈a的字符依次放入栈b\\n\\n\\n光标右移,将栈b的字符依次放入栈a\\n\\n\\n光标移动完成后读取字符串,从栈a读取最多10个字符(读取结果顺序是反的,记得做下顺序调转),最后读取完之后复原栈a,调用入栈函数即可,具体参考函数readLeftChar()\\n\\n感谢,C++由https://leetcode.cn/u/bw-zhang/ 供稿\\n\\n###Java\\n\\nclass TextEditor {\\n\\n public…","guid":"https://leetcode.cn/problems/design-a-text-editor//solution/liang-ge-zhan-by-cgq_dlut-wf2j","author":"cgq_dlut","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-05T04:41:31.765Z","media":[{"url":"https://pic.leetcode-cn.com/1654404662-EzJeeI-image.png","type":"photo","width":664,"height":662,"blurhash":"Lh7yhzkCyGo$VWf6aJaxR3ayV=ae"},{"url":"https://pic.leetcode-cn.com/1654404712-dTuBtN-image.png","type":"photo","width":664,"height":662,"blurhash":"La9uHn$K.At8MvSiRiWUMcS5ROWB"},{"url":"https://pic.leetcode-cn.com/1654404745-sdSzlk-image.png","type":"photo","width":664,"height":662,"blurhash":"Lb9Rq#w[%%t8MwSiRiWUMvWXVrae"},{"url":"https://pic.leetcode-cn.com/1654404765-hwfLsK-image.png","type":"photo","width":664,"height":662,"blurhash":"Lc9IAEw[%%t8MwSiRiWUR3ayVrae"},{"url":"https://pic.leetcode-cn.com/1654404799-UGVVZh-image.png","type":"photo","width":664,"height":662,"blurhash":"Ld8$8@so%%o#MwShRiaxR2jZV=ad"},{"url":"https://pic.leetcode-cn.com/1654404865-paoxTf-image.png","type":"photo","width":664,"height":662,"blurhash":"Lc8$8@sk%%t8MwSiRiaxR3jZVrae"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数据结构小作业——两个栈对倒","url":"https://leetcode.cn/problems/design-a-text-editor//solution/by-freeyourmind-kr12","content":"class TextEditor {\\npublic: \\n stack<char> left, right;\\n TextEditor() {}\\n \\n void addText(string text) {\\n for (char c:text) left.push(c);\\n }\\n \\n int deleteText(int k) {\\n k = min(k, (int)left.size());\\n for (int i = 0; i < k; i++) left.pop();\\n return k;\\n }\\n\\n string text() {\\n string res;\\n for (int i = 0, k = min((int)left.size(), 10); i < k; i++) {\\n res.insert(res.begin(), left.top());\\n left.pop();\\n }\\n addText(res);\\n return res;\\n }\\n \\n string cursorLeft(int k) {\\n k = min(k, (int)left.size());\\n while (k--) {\\n right.push(left.top());\\n left.pop();\\n }\\n return text();\\n }\\n \\n string cursorRight(int k) {\\n k = min(k, (int)right.size());\\n while (k--) {\\n left.push(right.top());\\n right.pop();\\n }\\n return text();\\n }\\n};\\n
\\n","description":"class TextEditor: def __init__(self):\\n self.cur, self.rest = [], []\\n def addText(self, text: str) -> None:\\n self.cur.extend(list(text))\\n def deleteText(self, k: int) -> int:\\n tk = k\\n while self.cur and tk:\\n self.cur.pop()…","guid":"https://leetcode.cn/problems/design-a-text-editor//solution/by-freeyourmind-kr12","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-05T04:09:02.550Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"对顶栈 / Splay(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/design-a-text-editor//solution/lian-biao-mo-ni-pythonjavacgo-by-endless-egw4","content":"class TextEditor:\\n def __init__(self):\\n self.cur, self.rest = [], []\\n def addText(self, text: str) -> None:\\n self.cur.extend(list(text))\\n def deleteText(self, k: int) -> int:\\n tk = k\\n while self.cur and tk:\\n self.cur.pop()\\n tk -= 1\\n return k - tk\\n def cursorLeft(self, k: int) -> str:\\n while self.cur and k:\\n k -= 1\\n self.rest.append(self.cur.pop())\\n return \'\'.join(self.cur[-10:])\\n def cursorRight(self, k: int) -> str:\\n while self.rest and k:\\n k -= 1\\n self.cur.append(self.rest.pop())\\n return \'\'.join(self.cur[-10:])\\n
方法一:对顶栈
\\n创建左右两个栈,头对头(栈顶对栈顶),光标的左右移动就相当于两个栈中的数据来回倒(左手倒右手,右手倒左手)。
\\n对于插入和删除操作,直接在左边那个栈上入栈出栈。
\\n\\nclass TextEditor:\\n def __init__(self):\\n self.left = [] # 光标左侧字符\\n self.right = [] # 光标右侧字符\\n\\n def addText(self, text: str) -> None:\\n self.left.extend(text) # 入栈\\n\\n def deleteText(self, k: int) -> int:\\n pre = len(self.left) # 删除之前的栈大小\\n del self.left[-k:] # 出栈\\n return pre - len(self.left) # 减去删除之后的栈大小\\n\\n def text(self) -> str:\\n return \'\'.join(self.left[-10:]) # 光标左边至多 10 个字符\\n\\n def cursorLeft(self, k: int) -> str:\\n while k and self.left:\\n self.right.append(self.left.pop()) # 左手倒右手\\n k -= 1\\n return self.text()\\n\\n def cursorRight(self, k: int) -> str:\\n while k and self.right:\\n self.left.append(self.right.pop()) # 右手倒左手\\n k -= 1\\n return self.text()\\n
\\nclass TextEditor {\\n private final StringBuilder left = new StringBuilder(); // 光标左侧字符\\n private final StringBuilder right = new StringBuilder(); // 光标右侧字符\\n\\n public void addText(String text) {\\n left.append(text); // 入栈\\n }\\n\\n public int deleteText(int k) {\\n k = Math.min(k, left.length());\\n left.setLength(left.length() - k); // 出栈\\n return k;\\n }\\n\\n public String cursorLeft(int k) {\\n while (k > 0 && !left.isEmpty()) {\\n right.append(left.charAt(left.length() - 1)); // 左手倒右手\\n left.setLength(left.length() - 1);\\n k--;\\n }\\n return text();\\n }\\n\\n public String cursorRight(int k) {\\n while (k > 0 && !right.isEmpty()) {\\n left.append(right.charAt(right.length() - 1)); // 右手倒左手\\n right.setLength(right.length() - 1);\\n k--;\\n }\\n return text();\\n }\\n\\n private String text() {\\n // 光标左边至多 10 个字符\\n return left.substring(Math.max(left.length() - 10, 0));\\n }\\n}\\n
\\nclass TextEditor {\\n string left, right; // 光标左侧、右侧字符\\n\\n string text() {\\n // 光标左边至多 10 个字符\\n return left.substr(max((int) left.size() - 10, 0));\\n }\\n\\npublic:\\n void addText(string text) {\\n left += text; // 入栈\\n }\\n\\n int deleteText(int k) {\\n k = min(k, (int) left.length());\\n left.resize(left.length() - k); // 出栈\\n return k;\\n }\\n\\n string cursorLeft(int k) {\\n while (k && !left.empty()) {\\n right += left.back(); // 左手倒右手\\n left.pop_back();\\n k--;\\n }\\n return text();\\n }\\n\\n string cursorRight(int k) {\\n while (k && !right.empty()) {\\n left += right.back(); // 右手倒左手\\n right.pop_back();\\n k--;\\n }\\n return text();\\n }\\n};\\n
\\ntype TextEditor struct {\\n left, right []byte // 光标左侧、右侧字符\\n}\\n\\nfunc Constructor() TextEditor {\\n return TextEditor{}\\n}\\n\\nfunc (t *TextEditor) AddText(text string) {\\n t.left = append(t.left, text...) // 入栈\\n}\\n\\nfunc (t *TextEditor) DeleteText(k int) int {\\n k = min(k, len(t.left))\\n t.left = t.left[:len(t.left)-k] // 出栈\\n return k\\n}\\n\\nfunc (t *TextEditor) text() string {\\n // 光标左边至多 10 个字符\\n return string(t.left[max(len(t.left)-10, 0):])\\n}\\n\\nfunc (t *TextEditor) CursorLeft(k int) string {\\n for k > 0 && len(t.left) > 0 {\\n t.right = append(t.right, t.left[len(t.left)-1]) // 左手倒右手\\n t.left = t.left[:len(t.left)-1]\\n k--\\n }\\n return t.text()\\n}\\n\\nfunc (t *TextEditor) CursorRight(k int) string {\\n for k > 0 && len(t.right) > 0 {\\n t.left = append(t.left, t.right[len(t.right)-1]) // 右手倒左手\\n t.right = t.right[:len(t.right)-1]\\n k--\\n }\\n return t.text()\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(1)$,$\\\\texttt{addText}$ 为 $\\\\mathcal{O}(|\\\\textit{text}|)$,其余 $\\\\mathcal{O}(k)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。其中 $n$ 为所有 $\\\\textit{text}$ 的长度之和。
\\n方法二:Splay(选读)
\\n如果 $k$ 很大,要怎么做?有没有复杂度和 $k$ 无关的算法?
\\n可以用 Splay 模拟文本的添加和删除。感兴趣的同学可以查阅相关资料。
\\n\\ntype node struct {\\n ch [2]*node\\n sz int\\n key byte\\n}\\n\\n// 设置如下返回值是为了方便使用 node 中的 ch 数组\\nfunc (o *node) cmpKth(k int) int {\\n d := k - o.ch[0].size() - 1\\n switch {\\n case d < 0:\\n return 0 // 左儿子\\n case d > 0:\\n return 1 // 右儿子\\n default:\\n return -1\\n }\\n}\\n\\nfunc (o *node) size() int {\\n if o != nil {\\n return o.sz\\n }\\n return 0\\n}\\n\\nfunc (o *node) maintain() {\\n o.sz = 1 + o.ch[0].size() + o.ch[1].size()\\n}\\n\\n// 构建一棵中序遍历为 [l,r] 的 splay 树\\n// 比如,给你一个序列和一些修改操作,每次取出一段子区间,cut 掉然后 append 到末尾,输出完成所有操作后的最终序列:\\n// 我们可以 buildSplay(1,n),每次操作调用两次 split 来 cut 区间,得到三棵子树 a b c\\n// append 之后应该是 a c b,那么我们可以 a.merge(c.merge(b)) 来完成这一操作\\n// 注意 merge 后可能就不满足搜索树的性质了,但是没有关系,中序遍历的结果仍然是正确的,我们只要保证这一点成立,就能正确得到完成所有操作后的最终序列\\nfunc buildSplay(s string) *node {\\n if s == \\"\\" {\\n return nil\\n }\\n m := len(s) / 2\\n o := &node{key: s[m]}\\n o.ch[0] = buildSplay(s[:m])\\n o.ch[1] = buildSplay(s[m+1:])\\n o.maintain()\\n return o\\n}\\n\\n// 旋转,并维护子树大小\\n// d=0:左旋,返回 o 的右儿子\\n// d=1:右旋,返回 o 的左儿子\\nfunc (o *node) rotate(d int) *node {\\n x := o.ch[d^1]\\n o.ch[d^1] = x.ch[d]\\n x.ch[d] = o\\n o.maintain()\\n x.maintain()\\n return x\\n}\\n\\n// 将子树 o(中序遍历)的第 k 个节点伸展到 o,并返回该节点\\n// 1 <= k <= o.size()\\nfunc (o *node) splay(k int) (kth *node) {\\n d := o.cmpKth(k)\\n if d < 0 {\\n return o\\n }\\n k -= d * (o.ch[0].size() + 1)\\n c := o.ch[d]\\n if d2 := c.cmpKth(k); d2 >= 0 {\\n c.ch[d2] = c.ch[d2].splay(k - d2*(c.ch[0].size()+1))\\n if d2 == d {\\n o = o.rotate(d ^ 1)\\n } else {\\n o.ch[d] = c.rotate(d)\\n }\\n }\\n return o.rotate(d ^ 1)\\n}\\n\\n// 分裂子树 o,把 o(中序遍历)的前 k 个节点放在 lo 子树,其余放在 ro 子树\\n// 返回的 lo 节点为 o(中序遍历)的第 k 个节点\\n// 1 <= k <= o.size()\\n// 特别地,k = o.size() 时 ro 为 nil\\nfunc (o *node) split(k int) (lo, ro *node) {\\n lo = o.splay(k)\\n ro = lo.ch[1]\\n lo.ch[1] = nil\\n lo.maintain()\\n return\\n}\\n\\n// 把子树 ro 合并进子树 o,返回合并前 o(中序遍历)的最后一个节点\\n// 相当于把 ro 的中序遍历 append 到 o 的中序遍历之后\\n// ro 可以为 nil,但 o 不能为 nil\\nfunc (o *node) merge(ro *node) *node {\\n // 把最大节点伸展上来,这样会空出一个右儿子用来合并 ro\\n o = o.splay(o.size())\\n o.ch[1] = ro\\n o.maintain()\\n return o\\n}\\n\\ntype TextEditor struct {\\n root *node\\n cur int\\n}\\n\\nfunc Constructor() TextEditor { return TextEditor{} }\\n\\nfunc (t *TextEditor) AddText(text string) {\\n if t.cur == 0 {\\n t.root = buildSplay(text).merge(t.root)\\n } else {\\n lo, ro := t.root.split(t.cur)\\n t.root = lo.merge(buildSplay(text)).merge(ro)\\n }\\n t.cur += len(text)\\n}\\n\\nfunc (t *TextEditor) DeleteText(k int) int {\\n if t.cur == 0 {\\n return 0\\n }\\n if t.cur <= k { // 左边全部删除\\n _, t.root = t.root.split(t.cur)\\n ans := t.cur\\n t.cur = 0\\n return ans\\n } else {\\n lo, ro := t.root.split(t.cur)\\n t.cur -= k\\n lo, _ = lo.split(t.cur) // 删除中间 k 个\\n t.root = lo.merge(ro)\\n return k\\n }\\n}\\n\\nfunc (t *TextEditor) text() string {\\n if t.cur == 0 {\\n return \\"\\"\\n }\\n k := max(t.cur-10, 0)\\n t.root = t.root.splay(k + 1)\\n ans := make([]byte, 1, t.cur-k)\\n ans[0] = t.root.key\\n var inorder func(*node) bool\\n inorder = func(o *node) bool {\\n if o == nil {\\n return false\\n }\\n if inorder(o.ch[0]) || len(ans) == cap(ans) {\\n return true\\n }\\n ans = append(ans, o.key)\\n return inorder(o.ch[1])\\n }\\n inorder(t.root.ch[1])\\n return string(ans)\\n}\\n\\nfunc (t *TextEditor) CursorLeft(k int) string {\\n t.cur = max(t.cur-k, 0)\\n return t.text()\\n}\\n\\nfunc (t *TextEditor) CursorRight(k int) string {\\n t.cur = min(t.cur+k, t.root.size())\\n return t.text()\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(1)$,$\\\\texttt{addText}$ 均摊 $\\\\mathcal{O}(|\\\\textit{text}| + \\\\log n)$,其余均摊 $\\\\mathcal{O}(\\\\log n)$,其中 $n$ 是当前文本的长度之和。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n相似题目(对顶栈)
\\n\\n
\\n- HDU 4699. Editor
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:对顶栈 创建左右两个栈,头对头(栈顶对栈顶),光标的左右移动就相当于两个栈中的数据来回倒(左手倒右手,右手倒左手)。\\n\\n对于插入和删除操作,直接在左边那个栈上入栈出栈。\\n\\nclass TextEditor:\\n def __init__(self):\\n self.left = [] # 光标左侧字符\\n self.right = [] # 光标右侧字符\\n\\n def addText(self, text: str) -> None:\\n self.left.extend(text) # 入栈…","guid":"https://leetcode.cn/problems/design-a-text-editor//solution/lian-biao-mo-ni-pythonjavacgo-by-endless-egw4","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-05T04:07:24.405Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"我的日程安排表 III","url":"https://leetcode.cn/problems/my-calendar-iii//solution/wo-de-ri-cheng-an-pai-biao-iii-by-leetco-9rif","content":"方法一:差分数组
\\n思路与算法
\\n可以参考「731. 我的日程安排表 II」的解法二,我们可以采用同样的思路即可。利用差分数组的思想,每当我们预定一个新的日程安排 $[\\\\textit{start}, \\\\textit{end})$,在 $\\\\textit{start}$ 计数 $\\\\textit{cnt}[\\\\textit{start}]$ 加 $1$,表示从 $\\\\textit{start}$ 预定的数目加 $1$;从 $\\\\textit{end}$ 计数 $\\\\textit{cnt}[\\\\textit{end}]$ 减 $1$,表示从 $\\\\textit{end}$ 开始预定的数目减 $1$。此时以起点 $x$ 开始的预定的数目 $\\\\textit{book}x = \\\\sum{y \\\\le x}\\\\textit{cnt}[y]$,我们对计数进行累加依次求出最大的预定数目即可。由于本题中 $\\\\textit{start}, \\\\textit{end}$ 数量较大,我们利用 $\\\\texttt{TreeMap}$ 计数即可。
\\n代码
\\n###Python
\\n\\nfrom sortedcontainers import SortedDict\\n\\nclass MyCalendarThree:\\n def __init__(self):\\n self.d = SortedDict()\\n\\n def book(self, start: int, end: int) -> int:\\n self.d[start] = self.d.setdefault(start, 0) + 1\\n self.d[end] = self.d.setdefault(end, 0) - 1\\n\\n ans = maxBook = 0\\n for freq in self.d.values():\\n maxBook += freq\\n ans = max(ans, maxBook)\\n return ans\\n
###Java
\\n\\nclass MyCalendarThree {\\n private TreeMap<Integer, Integer> cnt;\\n\\n public MyCalendarThree() {\\n cnt = new TreeMap<Integer, Integer>();\\n }\\n \\n public int book(int start, int end) {\\n int ans = 0;\\n int maxBook = 0;\\n cnt.put(start, cnt.getOrDefault(start, 0) + 1);\\n cnt.put(end, cnt.getOrDefault(end, 0) - 1);\\n for (Map.Entry<Integer, Integer> entry : cnt.entrySet()) {\\n int freq = entry.getValue();\\n maxBook += freq;\\n ans = Math.max(maxBook, ans);\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\nclass MyCalendarThree {\\npublic:\\n MyCalendarThree() {\\n \\n }\\n \\n int book(int start, int end) {\\n int ans = 0;\\n int maxBook = 0;\\n cnt[start]++;\\n cnt[end]--;\\n for (auto &[_, freq] : cnt) {\\n maxBook += freq;\\n ans = max(maxBook, ans);\\n }\\n return ans;\\n }\\nprivate:\\n map<int, int> cnt;\\n};\\n
###go
\\n\\ntype MyCalendarThree struct {\\n *redblacktree.Tree\\n}\\n\\nfunc Constructor() MyCalendarThree {\\n return MyCalendarThree{redblacktree.NewWithIntComparator()}\\n}\\n\\nfunc (t MyCalendarThree) add(x, delta int) {\\n if val, ok := t.Get(x); ok {\\n delta += val.(int)\\n }\\n t.Put(x, delta)\\n}\\n\\nfunc (t MyCalendarThree) Book(start, end int) (ans int) {\\n t.add(start, 1)\\n t.add(end, -1)\\n\\n maxBook := 0\\n for it := t.Iterator(); it.Next(); {\\n maxBook += it.Value().(int)\\n if maxBook > ans {\\n ans = maxBook\\n }\\n }\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 为日程安排的数量。每次求的最大的预定需要遍历所有的日程安排。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 为日程安排的数量。需要空间存储所有的日程安排计数,需要的空间为 $O(n)$。
\\n方法二:线段树
\\n思路与算法
\\n利用线段树,假设我们开辟了数组 $\\\\textit{arr}[0,\\\\cdots, 10^9]$,初始时每个元素的值都为 $0$,对于每次行程预定的区间 $[\\\\textit{start}, \\\\textit{end})$ ,则我们将区间中的元素 $\\\\textit{arr}[\\\\textit{start},\\\\cdots,\\\\textit{end}-1]$ 中的每个元素加 $1$,最终只需要求出数组 $arr$ 的最大元素即可。实际我们不必实际开辟数组 $\\\\textit{arr}$,可采用动态线段树,懒标记 $\\\\textit{lazy}$ 标记区间 $[l,r]$ 进行累加的次数,$\\\\textit{tree}$ 记录区间 $[l,r]$ 的最大值,最终返回区间 $[0,10^9]$ 中的最大元素即可。
\\n代码
\\n###Python
\\n\\nclass MyCalendarThree:\\n def __init__(self):\\n self.tree = defaultdict(int)\\n self.lazy = defaultdict(int)\\n\\n def update(self, start: int, end: int, l: int, r: int, idx: int):\\n if r < start or end < l:\\n return\\n if start <= l and r <= end:\\n self.tree[idx] += 1\\n self.lazy[idx] += 1\\n else:\\n mid = (l + r) // 2\\n self.update(start, end, l, mid, idx * 2)\\n self.update(start, end, mid + 1, r, idx * 2 + 1)\\n self.tree[idx] = self.lazy[idx] + max(self.tree[idx * 2], self.tree[idx * 2 + 1])\\n\\n def book(self, start: int, end: int) -> int:\\n self.update(start, end - 1, 0, 10 ** 9, 1)\\n return self.tree[1]\\n
###Java
\\n\\nclass MyCalendarThree {\\n private Map<Integer, Integer> tree;\\n private Map<Integer, Integer> lazy;\\n\\n public MyCalendarThree() {\\n tree = new HashMap<Integer, Integer>();\\n lazy = new HashMap<Integer, Integer>();\\n }\\n \\n public int book(int start, int end) {\\n update(start, end - 1, 0, 1000000000, 1);\\n return tree.getOrDefault(1, 0);\\n }\\n\\n public void update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n tree.put(idx, tree.getOrDefault(idx, 0) + 1);\\n lazy.put(idx, lazy.getOrDefault(idx, 0) + 1);\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, l, mid, 2 * idx);\\n update(start, end, mid + 1, r, 2 * idx + 1);\\n tree.put(idx, lazy.getOrDefault(idx, 0) + Math.max(tree.getOrDefault(2 * idx, 0), tree.getOrDefault(2 * idx + 1, 0)));\\n }\\n }\\n}\\n
###C++
\\n\\nclass MyCalendarThree {\\npublic:\\n unordered_map<int, pair<int, int>> tree;\\n\\n MyCalendarThree() {\\n\\n }\\n \\n void update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n tree[idx].first++;\\n tree[idx].second++;\\n } else {\\n int mid = (l + r) >> 1;\\n update(start, end, l, mid, 2 * idx);\\n update(start, end, mid + 1, r, 2 * idx + 1);\\n tree[idx].first = tree[idx].second + max(tree[2 * idx].first, tree[2 * idx + 1].first);\\n }\\n }\\n\\n int book(int start, int end) { \\n update(start, end - 1, 0, 1e9, 1);\\n return tree[1].first;\\n }\\n};\\n
###C#
\\n\\npublic class MyCalendarThree {\\n private Dictionary<int, int[]> tree;\\n\\n public MyCalendarThree() {\\n tree = new Dictionary<int, int[]>();\\n }\\n \\n public int Book(int start, int end) {\\n Update(start, end - 1, 0, 1000000000, 1);\\n return tree[1][0];\\n }\\n\\n public void Update(int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n if (!tree.ContainsKey(idx)) {\\n tree.Add(idx, new int[2]);\\n }\\n tree[idx][0]++;\\n tree[idx][1]++;\\n } else {\\n int mid = (l + r) >> 1;\\n Update(start, end, l, mid, 2 * idx);\\n Update(start, end, mid + 1, r, 2 * idx + 1);\\n if (!tree.ContainsKey(idx)) {\\n tree.Add(idx, new int[2]);\\n }\\n if (!tree.ContainsKey(2 * idx)) {\\n tree.Add(2 * idx, new int[2]);\\n }\\n if (!tree.ContainsKey(2 * idx + 1)) {\\n tree.Add(2 * idx + 1, new int[2]);\\n }\\n tree[idx][0] = tree[idx][1] + Math.Max(tree[2 * idx][0], tree[2 * idx + 1][0]);\\n }\\n }\\n}\\n
###C
\\n\\n#define MAX(a, b) ((a) > (b) ? (a) : (b))\\n\\ntypedef struct HashItem {\\n int key;\\n int maxBook;\\n int lazy;\\n UT_hash_handle hh;\\n} HashItem;\\n\\ntypedef struct {\\n HashItem *tree;\\n} MyCalendarThree;\\n\\n\\nMyCalendarThree* myCalendarThreeCreate() {\\n MyCalendarThree *obj = (MyCalendarThree *)malloc(sizeof(MyCalendarThree));\\n obj->tree = NULL;\\n return obj;\\n}\\n\\nvoid update(MyCalendarThree* obj, int start, int end, int l, int r, int idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry == NULL) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n pEntry->maxBook = 1;\\n pEntry->lazy = 1;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n } else {\\n pEntry->maxBook++;\\n pEntry->lazy++;\\n }\\n } else {\\n int mid = (l + r) >> 1;\\n update(obj, start, end, l, mid, 2 * idx);\\n update(obj, start, end, mid + 1, r, 2 * idx + 1);\\n int lchid = idx * 2, rchid = idx * 2 + 1;\\n int lmax = 0, rmax = 0;\\n HashItem *pEntry1 = NULL, *pEntry2 = NULL;\\n HASH_FIND_INT(obj->tree, &lchid, pEntry1);\\n HASH_FIND_INT(obj->tree, &rchid, pEntry2);\\n if (pEntry1) {\\n lmax = pEntry1->maxBook;\\n }\\n if (pEntry2) {\\n rmax = pEntry2->maxBook;\\n }\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry) {\\n pEntry->maxBook = pEntry->lazy + MAX(lmax, rmax);\\n } else {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = idx;\\n pEntry->maxBook = 1;\\n pEntry->lazy = 0;\\n HASH_ADD_INT(obj->tree, key, pEntry);\\n }\\n }\\n}\\n\\nint myCalendarThreeBook(MyCalendarThree* obj, int start, int end) {\\n update(obj, start, end - 1, 0, 1e9, 1);\\n int idx = 1;\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(obj->tree, &idx, pEntry);\\n if (pEntry) {\\n return pEntry->maxBook;\\n }\\n return 0;\\n}\\n\\nvoid myCalendarThreeFree(MyCalendarThree* obj) {\\n struct HashItem *curr,*tmp;\\n HASH_ITER(hh, obj->tree, curr, tmp) {\\n HASH_DEL(obj->tree, curr); \\n free(curr); \\n } \\n free(obj); \\n}\\n
###go
\\n\\ntype pair struct{ num, lazy int }\\n\\ntype MyCalendarThree map[int]pair\\n\\nfunc Constructor() MyCalendarThree {\\n return MyCalendarThree{}\\n}\\n\\nfunc (t MyCalendarThree) update(start, end, l, r, idx int) {\\n if r < start || end < l {\\n return\\n }\\n if start <= l && r <= end {\\n p := t[idx]\\n p.num++\\n p.lazy++\\n t[idx] = p\\n } else {\\n mid := (l + r) / 2\\n t.update(start, end, l, mid, idx*2)\\n t.update(start, end, mid+1, r, idx*2+1)\\n p := t[idx]\\n p.num = p.lazy + max(t[idx*2].num, t[idx*2+1].num)\\n t[idx] = p\\n }\\n}\\n\\nfunc (t MyCalendarThree) Book(start, end int) int {\\n t.update(start, end-1, 0, 1e9, 1)\\n return t[1].num\\n}\\n\\nfunc max(a, b int) int {\\n if b > a {\\n return b\\n }\\n return a\\n}\\n
###JavaScript
\\n\\nvar MyCalendarThree = function() {\\n this.tree = new Map();\\n this.lazy = new Map();\\n};\\n\\nMyCalendarThree.prototype.book = function(start, end) {\\n this.update(start, end - 1, 0, 1000000000, 1);\\n return this.tree.get(1) || 0;\\n};\\n\\nMyCalendarThree.prototype.update = function(start, end, l, r, idx) {\\n if (r < start || end < l) {\\n return;\\n } \\n if (start <= l && r <= end) {\\n this.tree.set(idx, (this.tree.get(idx) || 0) + 1);\\n this.lazy.set(idx, (this.lazy.get(idx) || 0) + 1);\\n } else {\\n const mid = (l + r) >> 1;\\n this.update(start, end, l, mid, 2 * idx);\\n this.update(start, end, mid + 1, r, 2 * idx + 1);\\n this.tree.set(idx, (this.lazy.get(idx) || 0) + Math.max((this.tree.get(2 * idx) || 0), (this.tree.get(2 * idx + 1) || 0)));\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:差分数组 思路与算法\\n\\n可以参考「731. 我的日程安排表 II」的解法二,我们可以采用同样的思路即可。利用差分数组的思想,每当我们预定一个新的日程安排 $[\\\\textit{start}, \\\\textit{end})$,在 $\\\\textit{start}$ 计数 $\\\\textit{cnt}[\\\\textit{start}]$ 加 $1$,表示从 $\\\\textit{start}$ 预定的数目加 $1$;从 $\\\\textit{end}$ 计数 $\\\\textit{cnt}[\\\\textit{end}]$ 减 $1$,表示从 $\\\\textit{end…","guid":"https://leetcode.cn/problems/my-calendar-iii//solution/wo-de-ri-cheng-an-pai-biao-iii-by-leetco-9rif","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-06-04T00:44:23.231Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线段树二分(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-endlesscheng-okcu","content":"- \\n
\\n时间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于使用了线段树查询,线段树的最大深度为 $\\\\log C$,每次最多会查询 $\\\\log C$ 个节点,每次求最大的预定需的时间复杂度为 $O(\\\\log C + \\\\log C)$,因此时间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值即为 $10^9$。
\\n- \\n
\\n空间复杂度:$O(n \\\\log C)$,其中 $n$ 为日程安排的数量。由于该解法采用的为动态线段树,线段树的最大深度为 $\\\\log C$,每次预定最多会在线段树上增加 $\\\\log C$ 个节点,因此空间复杂度为 $O(n \\\\log C)$,在此 $C$ 取固定值即为 $10^9$。
\\n题意(换一个场景)
\\n一开始有 $n$ 个空水桶,每个水桶的容量都是 $m$ 升。水桶编号从 $0$ 到 $n-1$。
\\n\\n
\\n- $\\\\texttt{gather}$:在前 $\\\\textit{maxRow}$ 个水桶中,找第一个还能装至少 $k$ 升水的水桶,往里面倒入 $k$ 升水。如果有这样的水桶,返回水桶编号,以及在倒水前,水桶有多少升水;如果没有这样的水桶,返回空列表。
\\n- $\\\\texttt{scatter}$:往前 $\\\\textit{maxRow}$ 个水桶中倒入总量为 $k$ 升的水。从左到右选择没有装满的水桶依次倒入。如果无法倒入总量为 $k$ 升的水,则不执行操作,并返回 $\\\\texttt{false}$;否则执行操作,并返回 $\\\\texttt{true}$。
\\n思路
\\n我们需要:
\\n\\n
\\n- 求出前 $\\\\textit{maxRow}$ 个水桶中,第一个剩余容量 $\\\\ge k$,也就是接水量 $\\\\le m-k$ 的水桶。
\\n- 维护每个水桶的接水量。
\\n- 维护前 $\\\\textit{maxRow}$ 个水桶的接水量之和,从而判断 $\\\\texttt{scatter}$ 能否倒入总量为 $k$ 升的水。
\\n这些都可以用线段树解决。线段树维护每个区间的接水量的最小值 $\\\\textit{min}$,以及每个区间的接水量之和 $\\\\textit{sum}$。
\\n对于 $\\\\texttt{gather}$,从线段树的根节点开始递归:
\\n\\n
\\n- 如果当前区间 $\\\\textit{min}>m-k$,则无法倒入 $k$ 升水,返回 $0$。
\\n- 如果当前区间长度为 $1$,返回区间端点。
\\n- 如果左半区间 $\\\\textit{min}\\\\le m-k$,则答案在左半区间中,递归左半区间。
\\n- 否则如果 $\\\\textit{maxRow}$ 在右半区间内,递归右半区间。
\\n- 否则返回 $-1$,表示没有这样的水桶。
\\n\\n\\n上述过程叫做线段树二分。
\\n对于 $\\\\texttt{scatter}$,如果区间 $[0,\\\\textit{maxRow}]$ 的接水量之和大于 $m\\\\cdot (\\\\textit{maxRow}+1)-k$,则无法执行操作。
\\n否则可以执行操作。从第一个没有装满,也就是接水量 $\\\\le m-1$ 的水桶开始倒水,这也可以用线段树二分求出。
\\n关于线段树需要开多大的数组,推导过程可以看 OI Wiki。
\\n\\n\\n本题只有单点修改,没有区间更新,无需懒标记。
\\n\\nclass BookMyShow:\\n def __init__(self, n: int, m: int):\\n self.n = n\\n self.m = m\\n self.min = [0] * (2 << n.bit_length()) # 相比 4n 空间更小\\n self.sum = [0] * (2 << n.bit_length())\\n\\n # 线段树:把下标 i 上的元素值增加 val\\n def update(self, o: int, l: int, r: int, i: int, val: int) -> None:\\n if l == r:\\n self.min[o] += val\\n self.sum[o] += val\\n return\\n m = (l + r) // 2\\n if i <= m:\\n self.update(o * 2, l, m, i, val)\\n else:\\n self.update(o * 2 + 1, m + 1, r, i, val)\\n self.min[o] = min(self.min[o * 2], self.min[o * 2 + 1])\\n self.sum[o] = self.sum[o * 2] + self.sum[o * 2 + 1]\\n\\n # 线段树:返回区间 [L,R] 内的元素和\\n def query_sum(self, o: int, l: int, r: int, L: int, R: int) -> int:\\n if L <= l and r <= R:\\n return self.sum[o]\\n res = 0\\n m = (l + r) // 2\\n if L <= m:\\n res = self.query_sum(o * 2, l, m, L, R)\\n if R > m:\\n res += self.query_sum(o * 2 + 1, m + 1, r, L, R)\\n return res\\n\\n # 线段树:返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\n def find_first(self, o: int, l: int, r: int, R: int, val: int) -> int:\\n if self.min[o] > val:\\n return -1 # 整个区间的元素值都大于 val\\n if l == r:\\n return l\\n m = (l + r) // 2\\n if self.min[o * 2] <= val:\\n return self.find_first(o * 2, l, m, R, val)\\n if R > m:\\n return self.find_first(o * 2 + 1, m + 1, r, R, val)\\n return -1\\n\\n def gather(self, k: int, maxRow: int) -> List[int]:\\n # 找第一个能倒入 k 升水的水桶\\n r = self.find_first(1, 0, self.n - 1, maxRow, self.m - k)\\n if r < 0: # 没有这样的水桶\\n return []\\n c = self.query_sum(1, 0, self.n - 1, r, r)\\n self.update(1, 0, self.n - 1, r, k) # 倒水\\n return [r, c]\\n\\n def scatter(self, k: int, maxRow: int) -> bool:\\n # [0,maxRow] 的接水量之和\\n s = self.query_sum(1, 0, self.n - 1, 0, maxRow)\\n if s > self.m * (maxRow + 1) - k:\\n return False # 水桶已经装了太多的水\\n # 从第一个没有装满的水桶开始\\n i = self.find_first(1, 0, self.n - 1, maxRow, self.m - 1)\\n while k:\\n left = min(self.m - self.query_sum(1, 0, self.n - 1, i, i), k)\\n self.update(1, 0, self.n - 1, i, left) # 倒水\\n k -= left\\n i += 1\\n return True\\n
\\nclass BookMyShow {\\n private int n;\\n private int m;\\n private int[] min;\\n private long[] sum;\\n\\n public BookMyShow(int n, int m) {\\n this.n = n;\\n this.m = m;\\n int size = 2 << (32 - Integer.numberOfLeadingZeros(n)); // 比 4n 更小\\n min = new int[size];\\n sum = new long[size];\\n }\\n\\n public int[] gather(int k, int maxRow) {\\n // 找第一个能倒入 k 升水的水桶\\n int r = findFirst(1, 0, n - 1, maxRow, m - k);\\n if (r < 0) { // 没有这样的水桶\\n return new int[]{};\\n }\\n int c = (int) querySum(1, 0, n - 1, r, r);\\n update(1, 0, n - 1, r, k); // 倒水\\n return new int[]{r, c};\\n }\\n\\n public boolean scatter(int k, int maxRow) {\\n // [0,maxRow] 的接水量之和\\n long s = querySum(1, 0, n - 1, 0, maxRow);\\n if (s > (long) m * (maxRow + 1) - k) {\\n return false; // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n int i = findFirst(1, 0, n - 1, maxRow, m - 1);\\n while (k > 0) {\\n int left = Math.min(m - (int) querySum(1, 0, n - 1, i, i), k);\\n update(1, 0, n - 1, i, left); // 倒水\\n k -= left;\\n i++;\\n }\\n return true;\\n }\\n\\n // 把下标 i 上的元素值增加 val\\n private void update(int o, int l, int r, int i, int val) {\\n if (l == r) {\\n min[o] += val;\\n sum[o] += val;\\n return;\\n }\\n int m = (l + r) / 2;\\n if (i <= m) {\\n update(o * 2, l, m, i, val);\\n } else {\\n update(o * 2 + 1, m + 1, r, i, val);\\n }\\n min[o] = Math.min(min[o * 2], min[o * 2 + 1]);\\n sum[o] = sum[o * 2] + sum[o * 2 + 1];\\n }\\n\\n // 返回区间 [L,R] 内的元素和\\n private long querySum(int o, int l, int r, int L, int R) {\\n if (L <= l && r <= R) {\\n return sum[o];\\n }\\n long res = 0;\\n int m = (l + r) / 2;\\n if (L <= m) {\\n res = querySum(o * 2, l, m, L, R);\\n }\\n if (R > m) {\\n res += querySum(o * 2 + 1, m + 1, r, L, R);\\n }\\n return res;\\n }\\n\\n // 返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\n private int findFirst(int o, int l, int r, int R, int val) {\\n if (min[o] > val) {\\n return -1; // 整个区间的元素值都大于 val\\n }\\n if (l == r) {\\n return l;\\n }\\n int m = (l + r) / 2;\\n if (min[o * 2] <= val) {\\n return findFirst(o * 2, l, m, R, val);\\n }\\n if (R > m) {\\n return findFirst(o * 2 + 1, m + 1, r, R, val);\\n }\\n return -1;\\n }\\n}\\n
\\nclass BookMyShow {\\n int n, m;\\n vector<int> mn;\\n vector<long long> sum;\\n\\n // 把下标 i 上的元素值增加 val\\n void update(int o, int l, int r, int i, int val) {\\n if (l == r) {\\n mn[o] += val;\\n sum[o] += val;\\n return;\\n }\\n int m = (l + r) / 2;\\n if (i <= m) {\\n update(o * 2, l, m, i, val);\\n } else {\\n update(o * 2 + 1, m + 1, r, i, val);\\n }\\n mn[o] = min(mn[o * 2], mn[o * 2 + 1]);\\n sum[o] = sum[o * 2] + sum[o * 2 + 1];\\n }\\n\\n // 返回区间 [L,R] 内的元素和\\n long long querySum(int o, int l, int r, int L, int R) {\\n if (L <= l && r <= R) {\\n return sum[o];\\n }\\n long long res = 0;\\n int m = (l + r) / 2;\\n if (L <= m) {\\n res = querySum(o * 2, l, m, L, R);\\n }\\n if (R > m) {\\n res += querySum(o * 2 + 1, m + 1, r, L, R);\\n }\\n return res;\\n }\\n\\n // 返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\n int findFirst(int o, int l, int r, int R, int val) {\\n if (mn[o] > val) {\\n return -1; // 整个区间的元素值都大于 val\\n }\\n if (l == r) {\\n return l;\\n }\\n int m = (l + r) / 2;\\n if (mn[o * 2] <= val) {\\n return findFirst(o * 2, l, m, R, val);\\n }\\n if (R > m) {\\n return findFirst(o * 2 + 1, m + 1, r, R, val);\\n }\\n return -1;\\n }\\n\\npublic:\\n BookMyShow(int n, int m) : n(n), m(m), mn(4 << __lg(n)), sum(4 << __lg(n)) {}\\n\\n vector<int> gather(int k, int maxRow) {\\n // 找第一个能倒入 k 升水的水桶\\n int r = findFirst(1, 0, n - 1, maxRow, m - k);\\n if (r < 0) { // 没有这样的水桶\\n return {};\\n }\\n int c = querySum(1, 0, n - 1, r, r);\\n update(1, 0, n - 1, r, k); // 倒水\\n return {r, c};\\n }\\n\\n bool scatter(int k, int maxRow) {\\n // [0,maxRow] 的接水量之和\\n long long s = querySum(1, 0, n - 1, 0, maxRow);\\n if (s > (long long) m * (maxRow + 1) - k) {\\n return false; // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n int i = findFirst(1, 0, n - 1, maxRow, m - 1);\\n while (k) {\\n int left = min(m - (int) querySum(1, 0, n - 1, i, i), k);\\n update(1, 0, n - 1, i, left); // 倒水\\n k -= left;\\n i++;\\n }\\n return true;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\ntypedef struct {\\n int n, m;\\n int* min;\\n long long* sum;\\n} BookMyShow;\\n\\nBookMyShow* bookMyShowCreate(int n, int m) {\\n BookMyShow* obj = malloc(sizeof(BookMyShow));\\n obj->n = n;\\n obj->m = m;\\n int size = 2 << (32 - __builtin_clz(n)); // 比 4n 更小\\n obj->min = calloc(size, sizeof(int));\\n obj->sum = calloc(size, sizeof(long long));\\n return obj;\\n}\\n\\n// 把下标 i 上的元素值增加 val\\nvoid update(BookMyShow* obj, int o, int l, int r, int i, int val) {\\n if (l == r) {\\n obj->min[o] += val;\\n obj->sum[o] += val;\\n return;\\n }\\n int mid = (l + r) / 2;\\n if (i <= mid) {\\n update(obj, o * 2, l, mid, i, val);\\n } else {\\n update(obj, o * 2 + 1, mid + 1, r, i, val);\\n }\\n obj->min[o] = MIN(obj->min[o * 2], obj->min[o * 2 + 1]);\\n obj->sum[o] = obj->sum[o * 2] + obj->sum[o * 2 + 1];\\n}\\n\\n// 返回区间 [L,R] 内的元素和\\nlong long querySum(BookMyShow* obj, int o, int l, int r, int L, int R) {\\n if (L <= l && r <= R) {\\n return obj->sum[o];\\n }\\n long long res = 0;\\n int m = (l + r) / 2;\\n if (L <= m) {\\n res = querySum(obj, o * 2, l, m, L, R);\\n }\\n if (R > m) {\\n res += querySum(obj, o * 2 + 1, m + 1, r, L, R);\\n }\\n return res;\\n}\\n\\n// 返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\nint findFirst(BookMyShow* obj, int o, int l, int r, int R, int val) {\\n if (obj->min[o] > val) {\\n return -1; // 整个区间的元素值都大于 val\\n }\\n if (l == r) {\\n return l;\\n }\\n int m = (l + r) / 2;\\n if (obj->min[o * 2] <= val) {\\n return findFirst(obj, o * 2, l, m, R, val);\\n }\\n if (R > m) {\\n return findFirst(obj, o * 2 + 1, m + 1, r, R, val);\\n }\\n return -1;\\n}\\n\\nint* bookMyShowGather(BookMyShow* obj, int k, int maxRow, int* retSize) {\\n // 找第一个能倒入 k 升水的水桶\\n int r = findFirst(obj, 1, 0, obj->n - 1, maxRow, obj->m - k);\\n if (r < 0) {\\n *retSize = 0;\\n return NULL; // 没有这样的水桶\\n }\\n int c = querySum(obj, 1, 0, obj->n - 1, r, r);\\n update(obj, 1, 0, obj->n - 1, r, k); // 倒水\\n *retSize = 2;\\n int* ans = malloc(2 * sizeof(int));\\n ans[0] = r;\\n ans[1] = c;\\n return ans;\\n}\\n\\nbool bookMyShowScatter(BookMyShow* obj, int k, int maxRow) {\\n // [0,maxRow] 的接水量之和\\n long long s = querySum(obj, 1, 0, obj->n - 1, 0, maxRow);\\n if (s > (long long) obj->m * (maxRow + 1) - k) {\\n return false; // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n int i = findFirst(obj, 1, 0, obj->n - 1, maxRow, obj->m - 1);\\n while (k > 0) {\\n int left = obj->m - (int) querySum(obj, 1, 0, obj->n - 1, i, i);\\n left = MIN(left, k);\\n update(obj, 1, 0, obj->n - 1, i, left); // 倒水\\n k -= left;\\n i++;\\n }\\n return true;\\n}\\n\\nvoid bookMyShowFree(BookMyShow* obj) {\\n free(obj->min);\\n free(obj->sum);\\n free(obj);\\n}\\n
\\ntype seg []struct{ l, r, min, sum int }\\n\\nfunc (t seg) build(o, l, r int) {\\n t[o].l, t[o].r = l, r\\n if l == r {\\n return\\n }\\n m := (l + r) >> 1\\n t.build(o<<1, l, m)\\n t.build(o<<1|1, m+1, r)\\n}\\n\\n// 把下标 i 上的元素值增加 val\\nfunc (t seg) update(o, i, val int) {\\n if t[o].l == t[o].r {\\n t[o].min += val\\n t[o].sum += val\\n return\\n }\\n m := (t[o].l + t[o].r) >> 1\\n if i <= m {\\n t.update(o<<1, i, val)\\n } else {\\n t.update(o<<1|1, i, val)\\n }\\n lo, ro := t[o<<1], t[o<<1|1]\\n t[o].min = min(lo.min, ro.min)\\n t[o].sum = lo.sum + ro.sum\\n}\\n\\n// 返回区间 [l,r] 内的元素和\\nfunc (t seg) querySum(o, l, r int) (sum int) {\\n if l <= t[o].l && t[o].r <= r {\\n return t[o].sum\\n }\\n m := (t[o].l + t[o].r) >> 1\\n if l <= m {\\n sum = t.querySum(o<<1, l, r)\\n }\\n if r > m {\\n sum += t.querySum(o<<1|1, l, r)\\n }\\n return\\n}\\n\\n// 返回区间 [0,r] 中 <= val 的最靠左的位置,不存在时返回 -1\\nfunc (t seg) findFirst(o, r, val int) int {\\n if t[o].min > val {\\n return -1 // 整个区间的元素值都大于 val\\n }\\n if t[o].l == t[o].r {\\n return t[o].l\\n }\\n m := (t[o].l + t[o].r) / 2\\n if t[o*2].min <= val {\\n return t.findFirst(o*2, r, val)\\n }\\n if r > m {\\n return t.findFirst(o*2+1, r, val)\\n }\\n return -1\\n}\\n\\ntype BookMyShow struct {\\n seg\\n n, m int\\n}\\n\\nfunc Constructor(n, m int) BookMyShow {\\n t := make(seg, 2<<bits.Len(uint(n-1))) // 比 4n 更小\\n t.build(1, 0, n-1)\\n return BookMyShow{t, n, m}\\n}\\n\\nfunc (t *BookMyShow) Gather(k, maxRow int) []int {\\n // 找第一个能倒入 k 升水的水桶\\n r := t.findFirst(1, maxRow, t.m-k)\\n if r < 0 { // 没有这样的水桶\\n return nil\\n }\\n c := t.querySum(1, r, r)\\n t.update(1, r, k) // 倒水\\n return []int{r, c}\\n}\\n\\nfunc (t *BookMyShow) Scatter(k, maxRow int) bool {\\n // [0,maxRow] 的接水量之和\\n s := t.querySum(1, 0, maxRow)\\n if s > t.m*(maxRow+1)-k {\\n return false // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n i := t.findFirst(1, maxRow, t.m-1)\\n for k > 0 {\\n left := min(t.m-t.querySum(1, i, i), k)\\n t.update(1, i, left) // 倒水\\n k -= left\\n i++\\n }\\n return true\\n}\\n
\\nclass BookMyShow {\\n constructor(n, m) {\\n this.n = n;\\n this.m = m;\\n const size = 2 << (32 - Math.clz32(n)); // 比 4n 更小\\n this.min = Array(size).fill(0);\\n this.sum = Array(size).fill(0);\\n }\\n\\n // 把下标 i 上的元素值增加 val\\n update(o, l, r, i, val) {\\n if (l === r) {\\n this.min[o] += val;\\n this.sum[o] += val;\\n return;\\n }\\n const m = Math.floor((l + r) / 2);\\n if (i <= m) {\\n this.update(o * 2, l, m, i, val);\\n } else {\\n this.update(o * 2 + 1, m + 1, r, i, val);\\n }\\n this.min[o] = Math.min(this.min[o * 2], this.min[o * 2 + 1]);\\n this.sum[o] = this.sum[o * 2] + this.sum[o * 2 + 1];\\n }\\n\\n // 返回区间 [L,R] 内的元素和\\n querySum(o, l, r, L, R) {\\n if (L <= l && r <= R) {\\n return this.sum[o];\\n }\\n let res = 0;\\n const m = Math.floor((l + r) / 2);\\n if (L <= m) {\\n res = this.querySum(o * 2, l, m, L, R);\\n }\\n if (R > m) {\\n res += this.querySum(o * 2 + 1, m + 1, r, L, R);\\n }\\n return res;\\n }\\n\\n // 返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\n findFirst(o, l, r, R, val) {\\n if (this.min[o] > val) {\\n return -1; // 整个区间的元素值都大于 val\\n }\\n if (l === r) {\\n return l;\\n }\\n const m = Math.floor((l + r) / 2);\\n if (this.min[o * 2] <= val) {\\n return this.findFirst(o * 2, l, m, R, val);\\n }\\n if (R > m) {\\n return this.findFirst(o * 2 + 1, m + 1, r, R, val);\\n }\\n return -1;\\n }\\n\\n gather(k, maxRow) {\\n // 找第一个能倒入 k 升水的水桶\\n const r = this.findFirst(1, 0, this.n - 1, maxRow, this.m - k);\\n if (r < 0) { // 没有这样的水桶\\n return [];\\n }\\n const c = this.querySum(1, 0, this.n - 1, r, r);\\n this.update(1, 0, this.n - 1, r, k); // 倒水\\n return [r, c];\\n }\\n\\n scatter(k, maxRow) {\\n // [0,maxRow] 的接水量之和\\n const s = this.querySum(1, 0, this.n - 1, 0, maxRow);\\n if (s > this.m * (maxRow + 1) - k) {\\n return false; // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n let i = this.findFirst(1, 0, this.n - 1, maxRow, this.m - 1);\\n while (k) {\\n const left = Math.min(this.m - this.querySum(1, 0, this.n - 1, i, i), k);\\n this.update(1, 0, this.n - 1, i, left); // 倒水\\n k -= left;\\n i++;\\n }\\n return true;\\n }\\n}\\n
\\nstruct BookMyShow {\\n n: usize,\\n m: i32,\\n min: Vec<i32>,\\n sum: Vec<i64>,\\n}\\n\\nimpl BookMyShow {\\n // 把下标 i 上的元素值增加 val\\n fn update(&mut self, o: usize, l: usize, r: usize, i: usize, val: i32) {\\n if l == r {\\n self.min[o] += val;\\n self.sum[o] += val as i64;\\n return;\\n }\\n let m = (l + r) / 2;\\n if i <= m {\\n self.update(o * 2, l, m, i, val);\\n } else {\\n self.update(o * 2 + 1, m + 1, r, i, val);\\n }\\n self.min[o] = self.min[o * 2].min(self.min[o * 2 + 1]);\\n self.sum[o] = self.sum[o * 2] + self.sum[o * 2 + 1];\\n }\\n\\n // 返回区间 [L,R] 内的元素和\\n fn query_sum(&self, o: usize, l: usize, r: usize, L: usize, R: usize) -> i64 {\\n if L <= l && r <= R {\\n return self.sum[o];\\n }\\n let mut res = 0;\\n let m = (l + r) / 2;\\n if L <= m {\\n res = self.query_sum(o * 2, l, m, L, R);\\n }\\n if R > m {\\n res += self.query_sum(o * 2 + 1, m + 1, r, L, R);\\n }\\n res\\n }\\n\\n // 返回区间 [0,R] 中 <= val 的最靠左的位置,不存在时返回 -1\\n fn find_first(&self, o: usize, l: usize, r: usize, R: usize, val: i32) -> i32 {\\n if self.min[o] > val {\\n return -1; // 整个区间的元素值都大于 val\\n }\\n if l == r {\\n return l as i32;\\n }\\n let m = (l + r) / 2;\\n if self.min[o * 2] <= val {\\n return self.find_first(o * 2, l, m, R, val);\\n }\\n if R > m {\\n return self.find_first(o * 2 + 1, m + 1, r, R, val);\\n }\\n -1\\n }\\n\\n fn new(n: i32, m: i32) -> Self {\\n let size = 2 << (32 - n.leading_zeros()) as usize;\\n BookMyShow {\\n n: n as usize,\\n m,\\n min: vec![0; size],\\n sum: vec![0; size],\\n }\\n }\\n\\n fn gather(&mut self, k: i32, max_row: i32) -> Vec<i32> {\\n // 找第一个能倒入 k 升水的水桶\\n let r = self.find_first(1, 0, self.n - 1, max_row as usize, self.m - k);\\n if r < 0 {\\n return vec![]; // 没有这样的水桶\\n }\\n let c = self.query_sum(1, 0, self.n - 1, r as usize, r as usize) as i32;\\n self.update(1, 0, self.n - 1, r as usize, k); // 倒水\\n vec![r, c]\\n }\\n\\n fn scatter(&mut self, mut k: i32, max_row: i32) -> bool {\\n // [0,maxRow] 的接水量之和\\n let s = self.query_sum(1, 0, self.n - 1, 0, max_row as usize);\\n if s > (self.m as i64 * (max_row + 1) as i64) - k as i64 {\\n return false; // 水桶已经装了太多的水\\n }\\n // 从第一个没有装满的水桶开始\\n let mut i = self.find_first(1, 0, self.n - 1, max_row as usize, self.m - 1) as usize;\\n while k > 0 {\\n let left = k.min(self.m - self.query_sum(1, 0, self.n - 1, i, i) as i32);\\n self.update(1, 0, self.n - 1, i, left); // 倒水\\n k -= left;\\n i += 1;\\n }\\n true\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:\\n
\\n\\n
\\n- 初始化为 $\\\\mathcal{O}(n)$。
\\n- $\\\\texttt{gather}$ 为 $\\\\mathcal{O}(\\\\log n)$。由于每次要么递归左半区间,要么递归右半区间,因此线段树二分的时间复杂度为线段树的树高,即 $\\\\mathcal{O}(\\\\log n)$。
\\n- $\\\\texttt{scatter}$ 可以从整体上来分析:由于装满的水桶后面不会再遍历了,所有 $\\\\texttt{scatter}$ 的循环次数之和为 $\\\\mathcal{O}(n+q)$($q$ 为 $\\\\texttt{scatter}$ 的调用次数),所以时间复杂度之和为 $\\\\mathcal{O}((n+q)\\\\log n)$。如果近似认为 $n=q$,那么均摊复杂度为 $\\\\mathcal{O}(\\\\log n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。线段树需要 $\\\\mathcal{O}(n)$ 的空间。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意(换一个场景) 一开始有 $n$ 个空水桶,每个水桶的容量都是 $m$ 升。水桶编号从 $0$ 到 $n-1$。\\n\\n$\\\\texttt{gather}$:在前 $\\\\textit{maxRow}$ 个水桶中,找第一个还能装至少 $k$ 升水的水桶,往里面倒入 $k$ 升水。如果有这样的水桶,返回水桶编号,以及在倒水前,水桶有多少升水;如果没有这样的水桶,返回空列表。\\n$\\\\texttt{scatter}$:往前 $\\\\textit{maxRow}$ 个水桶中倒入总量为 $k$ 升的水。从左到右选择没有装满的水桶依次倒入。如果无法倒入总量为 $k$ 升的水…","guid":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-endlesscheng-okcu","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-28T23:21:46.636Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种不同的线段树用法 + 树状数组","url":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-megurine-xrqz","content":"解法一:丐版树套树(雾
\\n树状数组维护剩余座位数的前缀和
\\n
\\n线段树区间 $[l, r]$ 维护的是剩余座位数在 $[l, r]$ 之间的的所有排的编号(使用set
存储,平衡树)
\\ngather
操作:\\n
\\n- 查询剩余座位数在 $[k, m]$ 之间的所有排的最小编号,如果大于
\\nmaxRow
,说明不能满足,否则更新线段树及树状数组\\n
scatter
操作:\\n
\\n- 如果前
\\nmaxRow
排剩余座位数大于k
,排号从小往大依次填满即可,并更新线段树及树状数组\\n\\n空间复杂度:瓶颈为树套树的空间,因为每个排号最多存在于 $\\\\log{m}$ 个线段树节点中,故空间复杂度为 $O(n\\\\log{m})$
\\n时间复杂度:线段树的时间复杂度为 $O(n\\\\log{m})$,又因为对线段树节点
\\nset
的操作复杂度为 $O(\\\\log{n})$,故总的时间复杂度为 $O(n\\\\log{mn})$代码
\\n###cpp
\\n\\nconst int INF = 1e9;\\n\\nstruct Node {\\n set<int> v;\\n int ls, rs;\\n\\n Node() : v(), ls(0), rs(0) {}\\n};\\n\\nclass SegTree {\\n vector<Node> tr;\\n int cnt, LO, HI;\\n\\n void push_down(int p) {\\n if (!tr[p].ls) tr[p].ls = ++cnt, tr.emplace_back(Node{});\\n if (!tr[p].rs) tr[p].rs = ++cnt, tr.emplace_back(Node{});\\n }\\n void insert(int l, int r, int L, int R, int v, int p) {\\n if (l <= L && r >= R) {\\n tr[p].v.insert(v);\\n return;\\n }\\n int mid = (L + R - 1) / 2;\\n push_down(p);\\n if (mid >= l) insert(l, r, L, mid, v, tr[p].ls);\\n if (mid < r) insert(l, r, mid + 1, R, v, tr[p].rs);\\n tr[p].v.insert(v);\\n }\\n\\n void extract(int l, int r, int L, int R, int v, int p) {\\n if (l <= L && r >= R) {\\n tr[p].v.extract(v);\\n return;\\n }\\n int mid = (L + R - 1) / 2;\\n push_down(p);\\n if (mid >= l) extract(l, r, L, mid, v, tr[p].ls);\\n if (mid < r) extract(l, r, mid + 1, R, v, tr[p].rs);\\n tr[p].v.extract(v);\\n }\\n\\n int query(int l, int r, int L, int R, int p) {\\n if (l <= L && r >= R) {\\n if (tr[p].v.empty()) {\\n return INF;\\n }\\n int fi = *(tr[p].v.begin());\\n return fi;\\n }\\n int mid = (L + R - 1) / 2, ret = INF;\\n push_down(p);\\n if (mid >= l) ret = min(ret, query(l, r, L, mid, tr[p].ls));\\n if (mid < r) ret = min(ret, query(l, r, mid + 1, R, tr[p].rs));\\n return ret;\\n }\\npublic:\\n SegTree(int lo, int hi) : tr(1), cnt(0), LO(lo), HI(hi) {\\n tr.reserve(100000);\\n }\\n void insert(int l, int r, int v) {\\n insert(l, r, LO, HI, v, 0);\\n }\\n void extract(int l, int r, int v) {\\n extract(l, r, LO, HI, v, 0);\\n }\\n int query(int l, int r) {\\n return query(l, r, LO, HI, 0);\\n }\\n};\\n\\ntemplate<typename T>\\nclass fenwick {\\nprivate:\\n vector<T> fenw;\\n int n;\\npublic:\\n explicit fenwick(int _n) : n(_n), fenw(_n) {}\\n\\n void modify(int x, T v) {\\n for (int i = x; i < n; i |= i + 1)\\n fenw[i] += v;\\n }\\n\\n T get(int x) {\\n T v{};\\n for (int i = x; i >= 0; i = (i & (i + 1)) - 1)\\n v += fenw[i];\\n return v;\\n }\\n};\\n\\nclass BookMyShow {\\n using PII = pair<int, int>;\\n using ll = long long;\\n vector<int> rem;\\n fenwick<ll> fenw;\\n SegTree seg;\\n int M, start;\\npublic:\\n BookMyShow(int n, int m) : M(m), fenw(n), start(0), seg(1, m) {\\n for (int i = 0; i < n; ++i) {\\n fenw.modify(i, m);\\n seg.insert(m, m, i);\\n }\\n rem.resize(n, m);\\n }\\n\\n vector<int> gather(int k, int maxRow) {\\n if (k > M) return {};\\n int row = seg.query(k, M);\\n if (row > maxRow) {\\n return {};\\n } else {\\n fenw.modify(row, -k);\\n seg.extract(rem[row], rem[row], row);\\n rem[row] -= k;\\n if (rem[row])\\n seg.insert(rem[row], rem[row], row);\\n return {row, M - rem[row] - k};\\n }\\n }\\n\\n bool scatter(int k, int maxRow) {\\n if (fenw.get(maxRow) < k) return false;\\n while (k) {\\n int row = start, t = min(rem[row], k);\\n if (t) {\\n k -= t;\\n fenw.modify(row, -t);\\n seg.extract(rem[row], rem[row], row);\\n rem[row] -= t;\\n if (rem[row])\\n seg.insert(rem[row], rem[row], row);\\n }\\n if (!rem[row]) {\\n ++start;\\n }\\n }\\n return true;\\n }\\n};\\n
解法二:线段树上二分
\\n树状数组用处同解法一
\\n
\\n线段树节点 $[l, r]$ 维护的是第l
排到第r
排的最大剩余座位数\\n
gather
操作:\\n
\\n- 查询满足 $[0, r]$ 区间最大值大于
\\nk
的最小排号r
,如果 $[0, r]$ 区间最大值小于k
,则说明不能满足\\n
scatter
操作:\\n
\\n- 如果前
\\nmaxRow
排剩余座位数大于k
,排号从小往大依次填满即可,并更新线段树及树状数组\\n\\n空间复杂度:$O(n)$
\\n
\\n时间复杂度:$O(n\\\\log{n})$,线段树上的二分操作可以一个 $\\\\log{n}$ 实现代码
\\n###cpp
\\n\\n","description":"解法一:丐版树套树(雾 树状数组维护剩余座位数的前缀和\\n 线段树区间 $[l, r]$ 维护的是剩余座位数在 $[l, r]$ 之间的的所有排的编号(使用 set 存储,平衡树)\\n gather 操作:\\n\\n查询剩余座位数在 $[k, m]$ 之间的所有排的最小编号,如果大于 maxRow,说明不能满足,否则更新线段树及树状数组\\n\\nscatter 操作:\\n\\n如果前 maxRow 排剩余座位数大于 k,排号从小往大依次填满即可,并更新线段树及树状数组\\n\\n空间复杂度:瓶颈为树套树的空间,因为每个排号最多存在于 $\\\\log{m}$ 个线段树节点中,故空间复杂度为…","guid":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-megurine-xrqz","author":"megurine","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-28T21:02:58.697Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线段树上二分","url":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-tsreaper-mnr7","content":"const int INF = 1e9;\\n\\nstruct Node {\\n int v, lz, lzv, ls, rs;\\n Node() : v(0), lz(0), lzv(0), ls(0), rs(0) {}\\n};\\n\\nclass SegTree {\\n vector<Node> tr;\\n int cnt, LO, HI;\\n\\n void push_down(int p, int len) {\\n if (!tr[p].ls) tr[p].ls = ++cnt, tr.emplace_back(Node{});\\n if (!tr[p].rs) tr[p].rs = ++cnt, tr.emplace_back(Node{});\\n if (!tr[p].lz) return;\\n int lzv = tr[p].lzv;\\n tr[tr[p].ls].v = lzv;\\n tr[tr[p].rs].v = lzv;\\n tr[tr[p].ls].lz = tr[tr[p].rs].lz = 1;\\n tr[tr[p].ls].lzv = tr[tr[p].rs].lzv = lzv;\\n tr[p].lz = tr[p].lzv = 0;\\n }\\n\\n void push_up(int p) {\\n tr[p].v = max(tr[tr[p].ls].v, tr[tr[p].rs].v);\\n }\\n\\n void modify(int l, int r, int L, int R, int v, int p) {\\n if (l <= L && r >= R) {\\n tr[p].lz = 1, tr[p].lzv = v;\\n tr[p].v = v;\\n return;\\n }\\n int mid = (L + R - 1) / 2;\\n push_down(p, R - L + 1);\\n if (mid >= l) modify(l, r, L, mid, v, tr[p].ls);\\n if (mid < r) modify(l, r, mid + 1, R, v, tr[p].rs);\\n push_up(p);\\n }\\n\\n int query(int l, int r, int L, int R, int p) {\\n if (l <= L && r >= R) return tr[p].v;\\n int mid = (L + R - 1) / 2, ret = 0;\\n push_down(p, R - L + 1);\\n if (mid >= l) ret = max(ret, query(l, r, L, mid, tr[p].ls));\\n if (mid < r) ret = max(ret, query(l, r, mid + 1, R, tr[p].rs));\\n return ret;\\n }\\n\\n int bound(int L, int R, int k, int p) {\\n if (L == R) return L;\\n int mid = (L + R - 1) / 2;\\n push_down(p, R - L + 1);\\n if (tr[tr[p].ls].v >= k) {\\n return bound(L, mid, k, tr[p].ls);\\n } else {\\n return bound(mid + 1, R, k, tr[p].rs);\\n }\\n }\\n\\npublic:\\n SegTree(int lo, int hi) : tr(1), cnt(0), LO(lo), HI(hi) {\\n tr.reserve(1000000);\\n }\\n\\n void modify(int l, int r, int v) {\\n return modify(l, r, LO, HI, v, 0);\\n }\\n\\n int query(int l, int r) {\\n return query(l, r, LO, HI, 0);\\n }\\n\\n int bound(int k) {\\n return bound(LO, HI, k, 0);\\n }\\n};\\n\\ntemplate<typename T>\\nclass fenwick {\\nprivate:\\n vector<T> fenw;\\n int n;\\npublic:\\n explicit fenwick(int _n) : n(_n), fenw(_n) {}\\n\\n void modify(int x, T v) {\\n for (int i = x; i < n; i |= i + 1)\\n fenw[i] += v;\\n }\\n\\n T get(int x) {\\n T v{};\\n for (int i = x; i >= 0; i = (i & (i + 1)) - 1)\\n v += fenw[i];\\n return v;\\n }\\n};\\n\\nclass BookMyShow {\\n using PII = pair<int, int>;\\n using ll = long long;\\n vector<int> rem;\\n fenwick<ll> fenw;\\n SegTree seg;\\n int M, idx;\\npublic:\\n BookMyShow(int n, int m) : M(m), fenw(n), idx(0), seg(0, n - 1) {\\n for (int i = 0; i < n; ++i) {\\n fenw.modify(i, m);\\n seg.modify(i, i, m);\\n }\\n rem.resize(n, m);\\n }\\n\\n vector<int> gather(int k, int maxRow) {\\n if (k > M) return {};\\n int mx = seg.query(0, maxRow);\\n if (mx < k) {\\n return {};\\n } else {\\n int row = seg.bound(k);\\n fenw.modify(row, -k);\\n rem[row] -= k;\\n seg.modify(row, row, rem[row]);\\n return {row, M - rem[row] - k};\\n }\\n }\\n\\n bool scatter(int k, int maxRow) {\\n if (fenw.get(maxRow) < k) return false;\\n while (k) {\\n int row = idx, t = min(rem[row], k);\\n if (t) {\\n k -= t;\\n fenw.modify(row, -t);\\n rem[row] -= t;\\n seg.modify(row, row, rem[row]);\\n }\\n if (!rem[row]) {\\n ++idx;\\n }\\n }\\n return true;\\n }\\n};\\n
解法:线段树上二分
\\n重要观察:由于所有操作都会在排数最小的情况下选择列数小的,因此经过任意次操作后,每一行都是前若干列坐满了人,后面的座位都是空的。
\\n因此可以记 $a_i$ 表示行 $i$ 现在还剩多少座位。操作处理如下:
\\n\\n
\\n- \\n
gather
操作要求所有人坐在同一行且座位连续,也就是说我们要找到最小的 $i$,使得 $a_i \\\\ge k$。找到这个 $i$ 以后,就把 $a_i$ 减少 $k$。- \\n
scatter
操作只要所有人都坐下就行,但选择座位时优先选择行号小的。也就是说我们要找到最小的 $i$,使得 $\\\\sum\\\\limits_{j=0}^i a_j \\\\ge k$。找到这个 $i$ 以后,清空 $0$ 到 $(i - 1)$ 行的座位,$a_i$ 也要做相应减少。总结一下,我们需要一个数据结构,在线维护一个数组并支持以下操作:
\\n\\n
\\n- 求第一个大等于 $k$ 的元素;
\\n- 求第一个前缀和大等于 $k$ 的下标;
\\n- 修改单个元素的值。
\\n在线段树上二分即可支持这些操作。以第一个操作为例,维护线段树上每个区间的最大值,寻找答案时先从最大的区间(也就是 $[0, n)$)开始。由于我们要找最左边的大等于 $k$ 的元素,那么对于当前区间,判断前半区间的最大值是否符合要求,如果是说明答案就在前半区间内,递归查询前半区间;否则说明答案只能在后半区间内,递归查询后半区间。由于线段树的深度是 $\\\\mathcal{O}(\\\\log n)$ 的,每次询问的复杂度也为 $\\\\mathcal{O}(\\\\log n)$。
\\n关于
\\nscatter
操作中的清空座位,由于剩余座位数只减不增,因此每行最多被清空一次,也就是说总的操作数是 $\\\\mathcal{O}(n + q)$ 的。我们可以通过以下两种方式之一正确实现:\\n
\\n- 由于我们每次都是清空前缀,我们需要记录目前被清空的最长的前缀
\\nlast
。下次清空时,直接从last
清空到i - 1
,然后把last
赋值为max(last, i - 1)
;- 我们也可以专门实现一个清空前缀的操作,请注意代码示例中关于复杂度的注释。
\\n###c++
\\n\\n// 把 0 ~ pos 行座位数清空\\nvoid setZero(int id, int l, int r, int pos) {\\n if (l == r) {\\n sumo[id] = maxo[id] = 0;\\n } else {\\n int nxt = id << 1, mid = (l + r) >> 1;\\n // 保证复杂度的关键:如果整个区间已经没有空座位了,就不用清空了\\n // 这样每次递归就只会进入需要清空的元素的父区间,所以复杂度是均摊 O(logn) 的\\n if (maxo[nxt] > 0) setZero(nxt, l, mid, pos);\\n if (maxo[nxt | 1] > 0 && pos > mid) setZero(nxt | 1, mid + 1, r, pos);\\n MAINTAIN\\n }\\n}\\n
因此总体复杂度是 $\\\\mathcal{O}((n + q)\\\\log n)$。请注意不是线段树 + 二分(这样会导致复杂度变为 $\\\\mathcal{O}((n + q)\\\\log^2 n)$,以力扣的评测速度应该过不了的)。具体实现和注释可以参考下方代码。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:线段树上二分 重要观察:由于所有操作都会在排数最小的情况下选择列数小的,因此经过任意次操作后,每一行都是前若干列坐满了人,后面的座位都是空的。\\n\\n因此可以记 $a_i$ 表示行 $i$ 现在还剩多少座位。操作处理如下:\\n\\ngather 操作要求所有人坐在同一行且座位连续,也就是说我们要找到最小的 $i$,使得 $a_i \\\\ge k$。找到这个 $i$ 以后,就把 $a_i$ 减少 $k$。\\nscatter 操作只要所有人都坐下就行,但选择座位时优先选择行号小的。也就是说我们要找到最小的 $i$,使得 $\\\\sum\\\\limits_{j=0}^i a_j…","guid":"https://leetcode.cn/problems/booking-concert-tickets-in-groups//solution/by-tsreaper-mnr7","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-28T17:56:14.875Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"字母在字符串中的百分比(一次遍历)-294周赛","url":"https://leetcode.cn/problems/percentage-of-letter-in-string//solution/by-an-ran-ran-z-8jvl","content":"class BookMyShow {\\n int n, m;\\n // sumo:区间座位之和,maxo:区间座位最大值\\n vector<long long> sumo, maxo;\\n // 记录本次操作后被修改的那一行还剩多少座位\\n long long ans;\\n // 记录最长清空到哪个前缀\\n int last = -1;\\n\\n #define MAINTAIN { \\\\\\n sumo[id] = sumo[nxt] + sumo[nxt | 1]; \\\\\\n maxo[id] = max(maxo[nxt], maxo[nxt | 1]); \\\\\\n }\\n\\n void build(int id, int l, int r) {\\n if (l == r) {\\n sumo[id] = maxo[id] = m;\\n } else {\\n int nxt = id << 1, mid = (l + r) >> 1;\\n build(nxt, l, mid); build(nxt | 1, mid + 1, r);\\n MAINTAIN\\n }\\n }\\n\\n // 把 pos 行的座位数改为 val\\n void modify(int id, int l, int r, int pos, long long val) {\\n if (l == r) {\\n sumo[id] = maxo[id] = val;\\n } else {\\n int nxt = id << 1, mid = (l + r) >> 1;\\n if (pos <= mid) modify(nxt, l, mid, pos, val);\\n else modify(nxt | 1, mid + 1, r, pos, val);\\n MAINTAIN\\n }\\n }\\n\\n // 询问编号最小的,且座位数至少为 val 的行\\n int queryMx(int id, int l, int r, long long val) {\\n if (l == r) {\\n // 记录如果修改这一行,那么这一行还剩多少座位\\n ans = maxo[id] - val;\\n return l;\\n } else {\\n int nxt = id << 1, mid = (l + r) >> 1;\\n // 区间前半的最大值已经满足条件了,直接在前半段寻找答案\\n if (maxo[nxt] >= val) return queryMx(nxt, l, mid, val);\\n // 区间前半无法满足条件,但整个区间满足条件,说明答案在后半段\\n else return queryMx(nxt | 1, mid + 1, r, val);\\n }\\n }\\n\\n // 询问编号最小的,且座位数前缀和至少为 val 的行\\n int querySm(int id, int l, int r, long long val) {\\n if (l == r) {\\n // 记录如果修改这一行,那么这一行还剩多少座位\\n ans = sumo[id] - val;\\n return l;\\n } else {\\n int nxt = id << 1, mid = (l + r) >> 1;\\n // 区间前半的和已经满足条件了,直接在前半段寻找答案\\n if (sumo[nxt] >= val) return querySm(nxt, l, mid, val);\\n // 区间前半无法满足条件,但整个区间满足条件,说明答案在后半段\\n //\\n // 注意这里 val 减少了 sumo[nxt],因为 sumo 记录的是区间的座位数之和(而不是前缀和)\\n // 因为我们的询问和前缀和有关,所以要把区间前半的座位数减去\\n else return querySm(nxt | 1, mid + 1, r, val - sumo[nxt]);\\n }\\n }\\n\\npublic:\\n BookMyShow(int n, int m): n(n), m(m) {\\n sumo.resize(n * 4 + 10);\\n maxo.resize(n * 4 + 10);\\n build(1, 0, n - 1);\\n }\\n \\n vector<int> gather(int k, int maxRow) {\\n // 所有行的最大值都无法满足条件,返回无解\\n if (maxo[1] < k) return vector<int>{};\\n // 询问编号最小的,且座位数至少为 k 的行\\n int t = queryMx(1, 0, n - 1, k);\\n if (t > maxRow) return vector<int>{};\\n // 把 t 行的座位数改为 ans\\n modify(1, 0, n - 1, t, ans);\\n return vector<int>{t, (int) (m - ans - k)};\\n }\\n \\n bool scatter(int k, int maxRow) {\\n // 剩余座位总数都无法满足条件,返回无解\\n if (sumo[1] < k) return false;\\n // 询问编号最小的,且座位数前缀和至少为 k 的行\\n int t = querySm(1, 0, n - 1, k);\\n if (t > maxRow) return false;\\n // 0 ~ t - 1 行全部坐满,清空座位;也可以使用题解中描述的 setZero\\n for (int i = last + 1; i < t; i++) modify(1, 0, n - 1, i, 0);\\n last = max(last, t - 1);\\n // 把 t 行的座位数改为 ans\\n modify(1, 0, n - 1, t, ans);\\n return true;\\n }\\n};\\n\\n/**\\n * Your BookMyShow object will be instantiated and called as such:\\n * BookMyShow* obj = new BookMyShow(n, m);\\n * vector<int> param_1 = obj->gather(k,maxRow);\\n * bool param_2 = obj->scatter(k,maxRow);\\n */\\n
一次遍历
\\n\\n","description":"一次遍历 class Solution {\\npublic:\\n int percentageLetter(string s, char letter) {\\n int n = s.size();\\n int count = 0;\\n for(int i = 0;iclass Solution {\\npublic:\\n int percentageLetter(string s, char letter) {\\n int n = s.size();\\n int count = 0;\\n for(int i = 0;i<n;i++){\\n if(s[i] == letter) count++;\\n }\\n return count*100/n;\\n }\\n};\\n
统计 $s$ 中 $\\\\textit{letter}$ 的个数,记作 $c$。\\n 根据题意,答案为
\\n$$
\\n
\\n\\\\left\\\\lfloor\\\\dfrac{100c}{n}\\\\right\\\\rfloor
\\n$$其中 $n$ 是 $s$ 的长度。
\\n\\nclass Solution:\\n def percentageLetter(self, s: str, letter: str) -> int:\\n return s.count(letter) * 100 // len(s)\\n
\\nclass Solution {\\n public int percentageLetter(String s, char letter) {\\n return (int) s.chars().filter(c -> c == letter).count() * 100 / s.length();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int percentageLetter(string s, char letter) {\\n return ranges::count(s, letter) * 100 / s.size();\\n }\\n};\\n
\\nfunc percentageLetter(s string, letter byte) int {\\n return strings.Count(s, string(letter)) * 100 / len(s)\\n}\\n
\\nvar percentageLetter = function(s, letter) {\\n return Math.floor((s.split(letter).length - 1) * 100 / s.length);\\n};\\n
\\nimpl Solution {\\n pub fn percentage_letter(s: String, letter: char) -> i32 {\\n (s.bytes().filter(|&c| c == letter as u8).count() * 100 / s.len()) as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"统计 $s$ 中 $\\\\textit{letter}$ 的个数,记作 $c$。 根据题意,答案为\\n\\n$$\\n \\\\left\\\\lfloor\\\\dfrac{100c}{n}\\\\right\\\\rfloor\\n $$\\n\\n其中 $n$ 是 $s$ 的长度。\\n\\nclass Solution:\\n def percentageLetter(self, s: str, letter: str) -> int:\\n return s.count(letter) * 100 // len(s)\\n\\nclass Solution {\\n public int…","guid":"https://leetcode.cn/problems/percentage-of-letter-in-string//solution/ku-han-shu-mo-ni-by-endlesscheng-fqad","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-22T04:12:56.100Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「模拟」","url":"https://leetcode.cn/problems/percentage-of-letter-in-string//solution/by-hu-li-hu-wai-2083","content":"方法:「模拟」
\\n思路及算法
\\n统计字符个数及总和
\\n代码
\\n###java
\\n\\nclass Solution {\\n public int percentageLetter(String s, char letter) {\\n int[] cnts = new int[26];\\n for (char c : s.toCharArray()) cnts[c - \'a\']++;\\n int sum = 0;\\n for (int i = 0; i < 26; i++) sum += cnts[i];\\n return 100 * cnts[letter - \'a\'] / sum;\\n }\\n}\\n
复杂度分析
\\n
\\n时间复杂度:O(Cn), C为26
\\n空间复杂度:O(C)结语
\\n","description":"方法:「模拟」 思路及算法\\n\\n统计字符个数及总和\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int percentageLetter(String s, char letter) {\\n int[] cnts = new int[26];\\n for (char c : s.toCharArray()) cnts[c - \'a\']++;\\n int sum = 0;\\n for (int i = 0; i < 26; i++) sum += cnts[i];\\n return 100 * cnts…","guid":"https://leetcode.cn/problems/percentage-of-letter-in-string//solution/by-hu-li-hu-wai-2083","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-22T04:09:05.526Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"不含特殊楼层的最大连续楼层数","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/bu-han-te-shu-lou-ceng-de-zui-da-lian-xu-ktg1","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!方法一:排序
\\n思路与算法
\\n如果我们将给定的数组 $\\\\textit{special}$ 按照升序排序,那么相邻两个元素之间的楼层就都不是特殊楼层。如果相邻的两个元素分别为 $x, y$,那么非特殊楼层的数量即为 $y-x-1$。
\\n但这样会忽略最开始和结束的非特殊楼层,因此我们可以在排序前将 $\\\\textit{bottom}-1$ 和 $\\\\textit{top}+1$ 也放入数组中,一起进行排序。这样一来,所有 $y-x-1$ 中的最大值即为答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int maxConsecutive(int bottom, int top, vector<int>& special) {\\n special.push_back(bottom - 1);\\n special.push_back(top + 1);\\n sort(special.begin(), special.end());\\n\\n int n = special.size();\\n int ans = 0;\\n for (int i = 0; i < n - 1; ++i) {\\n ans = max(ans, special[i + 1] - special[i] - 1);\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def maxConsecutive(self, bottom: int, top: int, special: List[int]) -> int:\\n special.extend([bottom - 1, top + 1])\\n special.sort()\\n \\n n = len(special)\\n ans = 0\\n for i in range(n - 1):\\n ans = max(ans, special[i + 1] - special[i] - 1)\\n return ans\\n
###Go
\\n\\nfunc maxConsecutive(bottom int, top int, special []int) int {\\n special = append(special, bottom - 1)\\n special = append(special, top + 1)\\n sort.Ints(special)\\n ans := 0\\n for i := 0; i < len(special) - 1; i++ {\\n ans = max(ans, special[i + 1] - special[i] - 1)\\n }\\n return ans\\n}\\n
###Java
\\n\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] special) {\\n Arrays.sort(special);\\n int ans = 0;\\n ans = Math.max(ans, special[0] - bottom);\\n for (int i = 1; i < special.length; ++i) {\\n ans = Math.max(ans, special[i] - special[i - 1] - 1);\\n }\\n ans = Math.max(ans, top - special[special.length - 1]);\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxConsecutive(int bottom, int top, int[] special) {\\n Array.Sort(special);\\n int ans = 0;\\n ans = Math.Max(ans, special[0] - bottom);\\n for (int i = 1; i < special.Length; ++i) {\\n ans = Math.Max(ans, special[i] - special[i - 1] - 1);\\n }\\n ans = Math.Max(ans, top - special[special.Length - 1]);\\n return ans;\\n }\\n}\\n
###C
\\n\\nint compare(const void* a, const void* b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint maxConsecutive(int bottom, int top, int* special, int specialSize) {\\n qsort(special, specialSize, sizeof(int), compare);\\n int ans = 0;\\n ans = fmax(special[0] - bottom, ans);\\n for (int i = 1; i < specialSize; ++i) {\\n ans = fmax(ans, special[i] - special[i - 1] - 1);\\n }\\n ans = fmax(ans, top - special[specialSize - 1]);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar maxConsecutive = function(bottom, top, special) {\\n special.push(bottom - 1);\\n special.push(top + 1);\\n special.sort((a, b) => a - b);\\n let ans = 0;\\n for (let i = 0; i < special.length - 1; ++i) {\\n ans = Math.max(ans, special[i + 1] - special[i] - 1);\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction maxConsecutive(bottom: number, top: number, special: number[]): number {\\n special.push(bottom - 1);\\n special.push(top + 1);\\n special.sort((a, b) => a - b);\\n let ans = 0;\\n for (let i = 0; i < special.length - 1; ++i) {\\n ans = Math.max(ans, special[i + 1] - special[i] - 1);\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn max_consecutive(bottom: i32, top: i32, special: Vec<i32>) -> i32 {\\n let mut special = special;\\n special.push(bottom - 1);\\n special.push(top + 1);\\n special.sort();\\n let mut ans = 0;\\n for i in 0..special.len() - 1 {\\n ans = ans.max(special[i + 1] - special[i] - 1);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:排序 思路与算法\\n\\n如果我们将给定的数组 $\\\\textit{special}$ 按照升序排序,那么相邻两个元素之间的楼层就都不是特殊楼层。如果相邻的两个元素分别为 $x, y$,那么非特殊楼层的数量即为 $y-x-1$。\\n\\n但这样会忽略最开始和结束的非特殊楼层,因此我们可以在排序前将 $\\\\textit{bottom}-1$ 和 $\\\\textit{top}+1$ 也放入数组中,一起进行排序。这样一来,所有 $y-x-1$ 中的最大值即为答案。\\n\\n代码\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/bu-han-te-shu-lou-ceng-de-zui-da-lian-xu-ktg1","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-17T15:06:18.584Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分割数组的方案数","url":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/fen-ge-shu-zu-de-fang-an-shu-by-leetcode-3ygv","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是数组 $\\\\textit{special}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$,即为排序需要使用的栈空间。
\\n方法一:枚举 + 前缀和
\\n思路与算法
\\n我们只需要枚举所有的分割位置,并找出其中的合法分割即可。
\\n具体地,我们用 $\\\\textit{left}$ 和 $\\\\textit{right}$ 分别表示分割左侧和右侧的所有元素之和。
\\n
\\n初始时,$\\\\textit{left} = 0$,$\\\\textit{right}$ 的值为给定数组 $\\\\textit{nums}$ 的所有元素之和。我们从小到大依次枚举每一个分割位置,当枚举到位置 $i$ 时,我们将 $\\\\textit{left}$ 加上 $\\\\textit{nums}[i]$,并将 $\\\\textit{right}$ 减去 $\\\\textit{nums}[i]$,这样就可以实时正确地维护分割左侧和右侧的元素之和。
\\n如果 $\\\\textit{left} \\\\geq \\\\textit{right}$,那么就找出了一个合法分割。代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int waysToSplitArray(vector<int>& nums) {\\n int n = nums.size();\\n long long left = 0, right = accumulate(nums.begin(), nums.end(), 0LL);\\n int ans = 0;\\n for (int i = 0; i < n - 1; ++i) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def waysToSplitArray(self, nums: List[int]) -> int:\\n n, left, right = len(nums), 0, sum(nums)\\n ans = 0\\n for i in range(n - 1):\\n left += nums[i]\\n right -= nums[i]\\n if left >= right:\\n ans += 1\\n return ans\\n
###C
\\n\\nint waysToSplitArray(int* nums, int numsSize) {\\n int n = numsSize;\\n long long left = 0, right = 0;\\n for (int i = 0; i < n; ++i) {\\n right += nums[i];\\n }\\n int ans = 0;\\n for (int i = 0; i < n - 1; ++i) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ++ans;\\n }\\n }\\n return ans;\\n}\\n
###Go
\\n\\nfunc waysToSplitArray(nums []int) int {\\n n := len(nums)\\n left, right := int64(0), int64(0)\\n for _, num := range nums {\\n right += int64(num)\\n }\\n ans := 0\\n for i := 0; i < n-1; i++ {\\n left += int64(nums[i])\\n right -= int64(nums[i])\\n if left >= right {\\n ans++\\n }\\n }\\n return ans\\n}\\n
###Java
\\n\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n int n = nums.length;\\n long left = 0, right = 0;\\n for (int num : nums) {\\n right += num;\\n }\\n int ans = 0;\\n for (int i = 0; i < n - 1; i++) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int WaysToSplitArray(int[] nums) {\\n int n = nums.Length;\\n long left = 0, right = 0;\\n foreach (int num in nums) {\\n right += num;\\n }\\n int ans = 0;\\n for (int i = 0; i < n - 1; i++) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###JavaScript
\\n\\nvar waysToSplitArray = function(nums) {\\n let n = nums.length;\\n let left = 0, right = nums.reduce((a, b) => a + b, 0);\\n let ans = 0;\\n for (let i = 0; i < n - 1; i++) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction waysToSplitArray(nums: number[]): number {\\n let n = nums.length;\\n let left = 0, right = nums.reduce((a, b) => a + b, 0);\\n let ans = 0;\\n for (let i = 0; i < n - 1; i++) {\\n left += nums[i];\\n right -= nums[i];\\n if (left >= right) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn ways_to_split_array(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let mut left: i64 = 0;\\n let mut right: i64 = nums.iter().map(|&x| x as i64).sum();\\n let mut ans = 0;\\n for i in 0..n-1 {\\n left += nums[i] as i64;\\n right -= nums[i] as i64;\\n if left >= right {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举 + 前缀和 思路与算法\\n\\n我们只需要枚举所有的分割位置,并找出其中的合法分割即可。\\n\\n具体地,我们用 $\\\\textit{left}$ 和 $\\\\textit{right}$ 分别表示分割左侧和右侧的所有元素之和。\\n 初始时,$\\\\textit{left} = 0$,$\\\\textit{right}$ 的值为给定数组 $\\\\textit{nums}$ 的所有元素之和。我们从小到大依次枚举每一个分割位置,当枚举到位置 $i$ 时,我们将 $\\\\textit{left}$ 加上 $\\\\textit{nums}[i]$,并将 $\\\\textit{right}$ 减去…","guid":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/fen-ge-shu-zu-de-fang-an-shu-by-leetcode-3ygv","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-17T15:02:20.245Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:字符串 / 数学(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/find-the-k-beauty-of-a-number//solution/mo-ni-by-endlesscheng-burh","content":"- \\n
\\n时间复杂度:$O(n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法一:字符串
\\n把 $\\\\textit{num}$ 转成十进制字符串 $s$。
\\n枚举 $s$ 的所有长为 $k$ 的子串,设子串对应的数值为 $x$,如果 $x>0$ 且 $\\\\textit{num}\\\\bmod x = 0$,那么子串符合题目要求,答案加一。
\\n\\nclass Solution:\\n def divisorSubstrings(self, num: int, k: int) -> int:\\n s = str(num)\\n ans = 0\\n for i in range(k, len(s) + 1):\\n x = int(s[i - k: i]) # 长为 k 的子串\\n if x > 0 and num % x == 0: # 子串能整除 num \\n ans += 1\\n return ans\\n
\\nclass Solution {\\n public int divisorSubstrings(int num, int k) {\\n String s = String.valueOf(num);\\n int ans = 0;\\n for (int i = k; i <= s.length(); i++) {\\n int x = Integer.parseInt(s.substring(i - k, i)); // 长为 k 的子串\\n if (x > 0 && num % x == 0) { // 子串能整除 num\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int divisorSubstrings(int num, int k) {\\n string s = to_string(num);\\n int ans = 0;\\n for (int i = k; i <= s.size(); i++) {\\n int x = stoi(s.substr(i - k, k)); // 长为 k 的子串\\n if (x > 0 && num % x == 0) { // 子串能整除 num\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc divisorSubstrings(num, k int) (ans int) {\\n s := strconv.Itoa(num)\\n for i := k; i <= len(s); i++ {\\n x, _ := strconv.Atoi(s[i-k : i]) // 长为 k 的子串\\n if x > 0 && num%x == 0 { // 子串能整除 num \\n ans++\\n }\\n }\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(nk)$,其中 $n=\\\\mathcal{O}(\\\\log \\\\textit{num})$ 是 $\\\\textit{num}$ 的十进制长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n方法二:数学
\\n例如 $\\\\textit{num}=1234,\\\\ k=2$。
\\n设 $m=10^k=100$。
\\n\\n
\\n- 初始化 $n=\\\\textit{num}$。
\\n- 计算 $n\\\\bmod m = 34$,这是最右边的长为 $k$ 的子串。判断是否合法。然后去掉 $n$ 的个位数,也就是把 $n$ 除以 $10$ 下取整,现在 $n = 123$。
\\n- 计算 $n\\\\bmod m = 23$,这是中间的长为 $k$ 的子串。判断是否合法。然后去掉 $n$ 的个位数,也就是把 $n$ 除以 $10$ 下取整,现在 $n = 12$。
\\n- 计算 $n\\\\bmod m = 12$,这是最左边的长为 $k$ 的子串。判断是否合法。然后去掉 $n$ 的个位数,也就是把 $n$ 除以 $10$ 下取整,现在 $n = 1 < m/10 = 10^{k-1}$,说明 $n$ 的十进制长度不足 $k$,结束。
\\n\\nclass Solution:\\n def divisorSubstrings(self, num: int, k: int) -> int:\\n m = 10 ** k\\n ans = 0\\n n = num\\n while n >= m // 10:\\n x = n % m\\n if x > 0 and num % x == 0:\\n ans += 1\\n n //= 10\\n return ans\\n
\\nclass Solution {\\n public int divisorSubstrings(int num, int k) {\\n long m = (long) Math.pow(10, k);\\n int ans = 0;\\n for (int n = num; n >= m / 10; n /= 10) {\\n int x = (int) (n % m);\\n if (x > 0 && num % x == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int divisorSubstrings(int num, int k) {\\n long long m = pow(10, k);\\n int ans = 0;\\n for (int n = num; n >= m / 10; n /= 10) {\\n int x = n % m;\\n if (x > 0 && num % x == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc divisorSubstrings(num, k int) (ans int) {\\n m := int(math.Pow10(k))\\n for n := num; n >= m/10; n /= 10 {\\n x := n % m\\n if x > 0 && num%x == 0 {\\n ans++\\n }\\n }\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n-k)$,其中 $n=\\\\mathcal{O}(\\\\log \\\\textit{num})$ 是 $\\\\textit{num}$ 的十进制长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:字符串 把 $\\\\textit{num}$ 转成十进制字符串 $s$。\\n\\n枚举 $s$ 的所有长为 $k$ 的子串,设子串对应的数值为 $x$,如果 $x>0$ 且 $\\\\textit{num}\\\\bmod x = 0$,那么子串符合题目要求,答案加一。\\n\\nclass Solution:\\n def divisorSubstrings(self, num: int, k: int) -> int:\\n s = str(num)\\n ans = 0\\n for i in range(k, len(s) + 1):…","guid":"https://leetcode.cn/problems/find-the-k-beauty-of-a-number//solution/mo-ni-by-endlesscheng-burh","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T11:47:04.681Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/by-endlesscheng-nufi","content":"设 $\\\\textit{nums}$ 的元素之和为 $\\\\textit{total}$。
\\n设 $s=\\\\textit{nums}[0]+\\\\textit{nums}[1] + \\\\cdots + \\\\textit{nums}[i]$。其余元素之和为 $\\\\textit{total}-s$。
\\n题目要求 $s\\\\ge \\\\textit{total}-s$,即
\\n$$
\\n
\\n2s\\\\ge \\\\textit{total}
\\n$$也就是
\\n$$
\\n
\\ns\\\\ge \\\\left\\\\lceil\\\\dfrac{\\\\textit{total}}{2}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{\\\\textit{total}+1}{2}\\\\right\\\\rfloor
\\n$$从左到右遍历数组,一边遍历,一边累加元素更新 $s$,检查是否满足上式,满足则把答案加一。
\\n注意题目要求 $i$ 右边至少有一个元素,所以 $i$ 至多遍历到 $n-2$ 为止。
\\n\\nclass Solution:\\n def waysToSplitArray(self, nums: List[int]) -> int:\\n t = (sum(nums) + 1) // 2\\n return sum(s >= t for s in accumulate(nums[:-1]))\\n
\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n long total = 0;\\n for (int x : nums) {\\n total += x;\\n }\\n\\n int ans = 0;\\n long s = 0;\\n for (int i = 0; i < nums.length - 1; i++) {\\n s += nums[i];\\n if (s * 2 >= total) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int waysToSplitArray(vector<int>& nums) {\\n long long total = reduce(nums.begin(), nums.end(), 0LL);\\n int ans = 0;\\n long long s = 0;\\n for (int i = 0; i + 1 < nums.size(); i++) {\\n s += nums[i];\\n if (s * 2 >= total) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint waysToSplitArray(int* nums, int numsSize) {\\n long long total = 0;\\n for (int i = 0; i < numsSize; i++) {\\n total += nums[i];\\n }\\n\\n int ans = 0;\\n long long s = 0;\\n for (int i = 0; i < numsSize - 1; i++) {\\n s += nums[i];\\n if (s * 2 >= total) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\nfunc waysToSplitArray(nums []int) (ans int) {\\ntotal := 0\\nfor _, x := range nums {\\ntotal += x\\n}\\n\\ns := 0\\nfor _, x := range nums[:len(nums)-1] {\\ns += x\\nif s*2 >= total {\\nans++\\n}\\n}\\nreturn\\n}\\n
\\nvar waysToSplitArray = function(nums) {\\n let total = 0;\\n for (const x of nums) {\\n total += x;\\n }\\n const t = Math.ceil(total / 2);\\n\\n let ans = 0;\\n let s = 0;\\n for (let i = 0; i < nums.length - 1; i++) {\\n s += nums[i];\\n if (s >= t) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn ways_to_split_array(nums: Vec<i32>) -> i32 {\\n let total = nums.iter().map(|&x| x as i64).sum();\\n let mut ans = 0;\\n let mut s = 0;\\n for &x in &nums[..nums.len() - 1] {\\n s += x as i64;\\n if s * 2 >= total {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面动态规划题单中的「专题:前后缀分解」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"设 $\\\\textit{nums}$ 的元素之和为 $\\\\textit{total}$。 设 $s=\\\\textit{nums}[0]+\\\\textit{nums}[1] + \\\\cdots + \\\\textit{nums}[i]$。其余元素之和为 $\\\\textit{total}-s$。\\n\\n题目要求 $s\\\\ge \\\\textit{total}-s$,即\\n\\n$$\\n 2s\\\\ge \\\\textit{total}\\n $$\\n\\n也就是\\n\\n$$\\n s\\\\ge \\\\left\\\\lceil\\\\dfrac{\\\\textit{total}}{2}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac…","guid":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/by-endlesscheng-nufi","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T11:45:19.162Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计 1 的个数(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-endlesscheng-dwja","content":"题意
\\n从 $\\\\textit{candidates}$ 中选一个子序列,要求子序列所有元素的 AND 大于 $0$。返回这个子序列的最长长度。
\\n思路
\\n既然要求 AND 大于 $0$,那么这个 AND 值中,一定有一个比特位是 $1$。
\\n枚举这个比特位:
\\n\\n
\\n- 如果 AND 的最低位是 $1$,最多可以从 $\\\\textit{candidates}$ 中选多少个数?
\\n- 如果 AND 的次低位是 $1$,最多可以从 $\\\\textit{candidates}$ 中选多少个数?
\\n- 依此类推。
\\n如果最低位是 $1$,那么从 $\\\\textit{candidates}$ 中选的数,最低位也必须是 $1$。这样问题就变成:
\\n\\n
\\n- $\\\\textit{candidates}$ 中有多少个数,最低位是 $1$?
\\n遍历 $\\\\textit{candidates}$ 即可算出。
\\n写法一:枚举比特位
\\n\\nclass Solution:\\n def largestCombination(self, candidates: List[int]) -> int:\\n m = max(candidates).bit_length()\\n return max(sum(x >> i & 1 for x in candidates) for i in range(m))\\n
\\nclass Solution {\\n public int largestCombination(int[] candidates) {\\n int mx = 0;\\n for (int x : candidates) {\\n mx = Math.max(mx, x);\\n }\\n int m = 32 - Integer.numberOfLeadingZeros(mx); // mx 的二进制长度\\n\\n int ans = 0;\\n for (int i = 0; i < m; i++) {\\n int cnt = 0;\\n for (int x : candidates) {\\n cnt += x >> i & 1;\\n }\\n ans = Math.max(ans, cnt);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestCombination(vector<int>& candidates) {\\n int m = bit_width((unsigned) ranges::max(candidates));\\n int ans = 0;\\n for (int i = 0; i < m; i++) {\\n int cnt = 0;\\n for (int x : candidates) {\\n cnt += x >> i & 1;\\n }\\n ans = max(ans, cnt);\\n }\\n return ans;\\n }\\n};\\n
\\nfunc largestCombination(candidates []int) (ans int) {\\n m := bits.Len(uint(slices.Max(candidates)))\\n for i := range m {\\n cnt := 0\\n for _, x := range candidates {\\n cnt += x >> i & 1\\n }\\n ans = max(ans, cnt)\\n }\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log U)$,其中 $n$ 是 $\\\\textit{candidates}$ 的长度,$U=\\\\max(\\\\textit{candidates})$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n写法二:一次遍历
\\n方法一需要多次遍历 $\\\\textit{candidates}$ 数组,我们也可以只遍历一次。
\\n创建一个大小为 $24$($10^7$ 的二进制长度)的 $\\\\textit{cnt}$ 数组,统计每个比特位上的 $1$ 的个数。
\\n最后答案为 $\\\\textit{cnt}$ 的最大值。
\\n\\nclass Solution:\\n def largestCombination(self, candidates: List[int]) -> int:\\n cnt = [0] * 24\\n for x in candidates:\\n i = 0\\n while x:\\n cnt[i] += x & 1\\n x >>= 1\\n i += 1\\n return max(cnt)\\n
\\nclass Solution {\\n public int largestCombination(int[] candidates) {\\n int[] cnt = new int[24];\\n for (int x : candidates) {\\n for (int i = 0; x > 0; i++) {\\n cnt[i] += x & 1;\\n x >>= 1;\\n }\\n }\\n return Arrays.stream(cnt).max().getAsInt();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestCombination(vector<int>& candidates) {\\n int cnt[24]{};\\n for (int x : candidates) {\\n for (int i = 0; x; i++) {\\n cnt[i] += x & 1;\\n x >>= 1;\\n }\\n }\\n return ranges::max(cnt);\\n }\\n};\\n
\\nfunc largestCombination(candidates []int) int {\\n cnt := [24]int{}\\n for _, x := range candidates {\\n for i := 0; x > 0; i++ {\\n cnt[i] += x & 1\\n x >>= 1\\n }\\n }\\n return slices.Max(cnt[:])\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log U)$,其中 $n$ 是 $\\\\textit{candidates}$ 的长度,$U=\\\\max(\\\\textit{candidates})$。
\\n- 空间复杂度:$\\\\mathcal{O}(\\\\log U)$。
\\n思考题
\\n本题选的是 $\\\\textit{candidates}$ 的子序列,如果将其改成子数组,要怎么做?
\\n欢迎在评论区分享你的思路/代码。
\\n更多相似题目,见下面位运算题单中的「四、拆位 / 贡献法」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 【本题相关】位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意 从 $\\\\textit{candidates}$ 中选一个子序列,要求子序列所有元素的 AND 大于 $0$。返回这个子序列的最长长度。\\n\\n思路\\n\\n既然要求 AND 大于 $0$,那么这个 AND 值中,一定有一个比特位是 $1$。\\n\\n枚举这个比特位:\\n\\n如果 AND 的最低位是 $1$,最多可以从 $\\\\textit{candidates}$ 中选多少个数?\\n如果 AND 的次低位是 $1$,最多可以从 $\\\\textit{candidates}$ 中选多少个数?\\n依此类推。\\n\\n如果最低位是 $1$,那么从 $\\\\textit{candidates…","guid":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-endlesscheng-dwja","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T08:52:55.636Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【一看就懂】你看了直呼:这是简单题!——「按位与结果大于零的最长组合」题解","url":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-super9du-4bea","content":"题解
\\n我们先假设
\\ncandidates[i]
为 8 位无符号数(即 0-255 的正整数),那么请问:如下几个二进制数相与,最多有几个数相与结果不为 0 ?\\n编号 数值\\n0 1111 1111\\n1 0000 0010\\n2 0000 0011\\n3 1000 0000\\n4 0100 0000\\n
编号 0、1、2 相与,最多有 3 个数相与结果不为 0。
\\n
\\n编号 0、3、4 相与,最多有 2 个数相与结果不为 0。
\\n编号 0-4 相与,最多有 3 个数相与结果不为 0。我们发现如果某几个数某一位都有 1,那么这几个数相与,结果必定不为 0 (即一定大于 0)。我们还发现,如果设这几个数每一位 1 的个数为
\\ncnt
,不论几个数相与,那么一定有「最多有max(cnt)
个结果不为 0」。所以我们简单计算每一位分别有多少个 1,并选择他们的最大值即可得到答案。
\\n代码
\\n参考答案来自排行最靠前的一位 Java 大佬 @arignote :
\\n###java
\\n\\n\\nclass Solution {\\n public int largestCombination(int[] candidates) {\\n int max = Integer.MIN_VALUE;\\n\\n // 设 i 为需要统计的位数,最高需要统计 24 位。\\n //\\n // 因为 candidates[i] 最大值为 10^7;\\n // 10^7 可以拆解为 10^6 * 10;\\n // 2^10 是 1024,两个 2^10 大约就是 10^6 (1024 x 1024);\\n // 2^4 = 16 远远大于 10。\\n // 所以 2^24 远远大于 10^7。\\n for (int i = 0; i < 24; i++) {\\n // 使用 cnt 统计某一位 1 的个数\\n int cnt = 0;\\n for (int cand : candidates) {\\n // 1 << i 用于表示哪一位为 1,首先统计从右数第一位为 1 的个数。\\n // 由于 1 << i 只有一位为 1,其余位都为 0,\\n // 所以 cand & 1 << i 如果等于 0,则表示当前位为 0,否则 cnt 加 1。\\n max = Math.max(max, cnt += (cand & 1 << i) == 0 ? 0 : 1);\\n }\\n }\\n return max;\\n }\\n}\\n
\\n\\n","description":"题解 我们先假设 candidates[i] 为 8 位无符号数(即 0-255 的正整数),那么请问:如下几个二进制数相与,最多有几个数相与结果不为 0 ?\\n\\n编号 数值\\n0 1111 1111\\n1 0000 0010\\n2 0000 0011\\n3 1000 0000\\n4 0100 0000\\n\\n\\n编号 0、1、2 相与,最多有 3 个数相与结果不为 0。\\n 编号 0、3、4 相与,最多有 2 个数相与结果不为 0。\\n 编号 0-4 相与,最多有 3 个数相与结果不为 0。\\n\\n我们发现如果某几个数某一位都有 1…","guid":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-super9du-4bea","author":"super9du","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T08:44:26.991Z","media":[{"url":"https://pic.leetcode-cn.com/1652606701-hqPzVJ-%E5%9B%BE%E7%89%87.png","type":"photo","width":1181,"height":643,"blurhash":"LDRyvo.8o#~p-;NHozR-xdNIofs,"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"JAVA 简单思路","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/java-jian-dan-si-lu-by-wuzhenyu-d3y0","content":"各位姥爷,点个赞再走吧,求求了!
\\n解题思路
\\n这应该是一道简单题
\\n
\\n排序后直接遍历求最大值就行, 但要注意尾巴部分的处理 = =代码
\\n###java
\\n\\n","description":"解题思路 这应该是一道简单题\\n 排序后直接遍历求最大值就行, 但要注意尾巴部分的处理 = =\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] s) {\\n Arrays.sort(s);\\n int len = s.length, ans = 0;\\n ans = Math.max(s[0] - bottom, ans);\\n for (int i = 1; i < len; ++i) {…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/java-jian-dan-si-lu-by-wuzhenyu-d3y0","author":"wuzhenyu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T05:44:42.181Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序,计算相邻元素差的最大值(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/pai-xu-by-endlesscheng-nm1r","content":"class Solution {\\n public int maxConsecutive(int bottom, int top, int[] s) {\\n Arrays.sort(s);\\n int len = s.length, ans = 0;\\n ans = Math.max(s[0] - bottom, ans);\\n for (int i = 1; i < len; ++i) {\\n ans = Math.max(s[i] - s[i - 1] - 1, ans);\\n }\\n return Math.max(ans, top - s[len - 1]);\\n }\\n}\\n
核心思路:统计两个特殊楼层间的楼层数。
\\n把 $\\\\textit{special}$ 从小到大排序后,计算如下数字的最大值,即为答案:
\\n\\n
\\n- $\\\\textit{special}[i]-\\\\textit{special}[i-1]-1$。
\\n- $\\\\textit{special}[0]-\\\\textit{bottom}$。
\\n- $\\\\textit{top}-\\\\textit{special}[n-1]$。其中 $n$ 是 $\\\\textit{special}$ 的长度。
\\n\\nclass Solution:\\n def maxConsecutive(self, bottom: int, top: int, special: List[int]) -> int:\\n special.sort()\\n ans = max(special[0] - bottom, top - special[-1])\\n for x, y in pairwise(special):\\n ans = max(ans, y - x - 1)\\n return ans\\n
\\nclass Solution:\\n def maxConsecutive(self, bottom: int, top: int, special: List[int]) -> int:\\n return max(y - x - 1 for x, y in pairwise(sorted(special + [bottom - 1, top + 1])))\\n
\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] special) {\\n Arrays.sort(special);\\n int n = special.length;\\n int ans = Math.max(special[0] - bottom, top - special[n - 1]);\\n for (int i = 1; i < n; i++) {\\n ans = Math.max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxConsecutive(int bottom, int top, vector<int>& special) {\\n ranges::sort(special);\\n int n = special.size();\\n int ans = max(special[0] - bottom, top - special[n - 1]);\\n for (int i = 1; i < n; i++) {\\n ans = max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint cmp(const void* a, const void* b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint maxConsecutive(int bottom, int top, int* special, int n) {\\n qsort(special, n, sizeof(int), cmp);\\n int ans = MAX(special[0] - bottom, top - special[n - 1]);\\n for (int i = 1; i < n; i++) {\\n ans = MAX(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n}\\n
\\nfunc maxConsecutive(bottom, top int, special []int) int {\\nslices.Sort(special)\\nn := len(special)\\nans := max(special[0]-bottom, top-special[n-1])\\nfor i := 1; i < n; i++ {\\nans = max(ans, special[i]-special[i-1]-1)\\n}\\nreturn ans\\n}\\n
\\nvar maxConsecutive = function(bottom, top, special) {\\n special.sort((a, b) => a - b);\\n let n = special.length;\\n let ans = Math.max(special[0] - bottom, top - special[n - 1]);\\n for (let i = 1; i < n; i++) {\\n ans = Math.max(ans, special[i] - special[i - 1] - 1);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn max_consecutive(bottom: i32, top: i32, mut special: Vec<i32>) -> i32 {\\n special.sort_unstable();\\n let n = special.len();\\n let mut ans = (special[0] - bottom).max(top - special[n - 1]);\\n for i in 1..n {\\n ans = ans.max(special[i] - special[i - 1] - 1);\\n }\\n ans\\n }\\n}\\n
\\nimpl Solution {\\n pub fn max_consecutive(bottom: i32, top: i32, mut special: Vec<i32>) -> i32 {\\n special.push(bottom - 1);\\n special.push(top + 1);\\n special.sort_unstable();\\n special.windows(2).map(|w| w[1] - w[0] - 1).max().unwrap()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 是 $\\\\textit{special}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:统计两个特殊楼层间的楼层数。 把 $\\\\textit{special}$ 从小到大排序后,计算如下数字的最大值,即为答案:\\n\\n$\\\\textit{special}[i]-\\\\textit{special}[i-1]-1$。\\n$\\\\textit{special}[0]-\\\\textit{bottom}$。\\n$\\\\textit{top}-\\\\textit{special}[n-1]$。其中 $n$ 是 $\\\\textit{special}$ 的长度。\\nclass Solution:\\n def maxConsecutive(self, bottom: int…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/pai-xu-by-endlesscheng-nm1r","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T05:44:40.699Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Javascript 6064. 不含特殊楼层的最大连续楼层数","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/by-scnu_evan-u3u0","content":"解题思路
\\n简单模拟,先将
\\nspecial
从小到大排序,然后依次遍历找到最大的间隔就行。我将
\\nbottom
理解为下一个连续楼层的开始,出现一个special[i]
,就用它减去bottom
算出间隔,之后bottom = special[i] + 1
。代码
\\n###js
\\n\\n\\n","description":"解题思路 简单模拟,先将 special 从小到大排序,然后依次遍历找到最大的间隔就行。\\n\\n我将 bottom 理解为下一个连续楼层的开始,出现一个 special[i],就用它减去 bottom 算出间隔,之后 bottom = special[i] + 1。\\n\\n代码\\n\\n###js\\n\\nvar maxConsecutive = function(bottom, top, special) {\\n special.sort((a, b) => a - b)\\n let ans = 0\\n for (let i = 0; i < special…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/by-scnu_evan-u3u0","author":"scnu_evan","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T04:13:46.459Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「枚举」","url":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/by-hu-li-hu-wai-94pj","content":"var maxConsecutive = function(bottom, top, special) {\\n special.sort((a, b) => a - b)\\n let ans = 0\\n for (let i = 0; i < special.length; i ++) {\\n ans = Math.max(ans, special[i] - bottom)\\n bottom = special[i] + 1\\n }\\n if (special[special.length - 1] !== top) {\\n ans = Math.max(ans, top - bottom + 1) \\n }\\n return ans\\n};\\n
方法:「枚举」
\\n思路及算法
\\n
\\n先排序,再枚举计算间隔代码
\\n###java
\\n\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] special) {\\n Arrays.sort(special);\\n int n = special.length, res = special[0] - bottom;\\n for (int i = 1; i < n; i++) {\\n res = Math.max(res, special[i] - special[i - 1] - 1);\\n }\\n res = Math.max(res, top - special[n - 1]);\\n return res;\\n }\\n}\\n
复杂度分析
\\n
\\n时间复杂度:O(NlogN), 排序复杂度
\\n空间复杂度:O(1)结语
\\n","description":"方法:「枚举」 思路及算法\\n 先排序,再枚举计算间隔\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int maxConsecutive(int bottom, int top, int[] special) {\\n Arrays.sort(special);\\n int n = special.length, res = special[0] - bottom;\\n for (int i = 1; i < n; i++) {\\n res = Math.max(res, special[i…","guid":"https://leetcode.cn/problems/maximum-consecutive-floors-without-special-floors//solution/by-hu-li-hu-wai-94pj","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T04:09:26.009Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「位运算」","url":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-hu-li-hu-wai-6kum","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!方法:「位运算」
\\n思路及算法
\\n
\\n既然要求按位与大于 0, 那么一定存在某一位多个数都为 1
\\n统计 32 位上哪一位出现的个数最多,那么这几个数 按位与 则大于0代码
\\n###java
\\n\\nclass Solution {\\n public int largestCombination(int[] candidates) {\\n int[] cnt = new int[32];\\n int max = 0;\\n for (int c : candidates) {\\n for (int i = 0; i < 32; i++) {\\n if (((1 << i) & c) > 0) cnt[i]++;\\n }\\n }\\n for (int i = 0; i < 32; i++) {\\n max = Math.max(max, cnt[i]);\\n }\\n return max;\\n }\\n}\\n
复杂度分析
\\n
\\n时间复杂度:O(Cn), C = 32, 当然由于 1e7 的数据范围,位数系数 C 可以从32减少到24
\\n空间复杂度:O(C)结语
\\n","description":"方法:「位运算」 思路及算法\\n 既然要求按位与大于 0, 那么一定存在某一位多个数都为 1\\n 统计 32 位上哪一位出现的个数最多,那么这几个数 按位与 则大于0\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int largestCombination(int[] candidates) {\\n int[] cnt = new int[32];\\n int max = 0;\\n for (int c : candidates) {\\n for (int i = 0; i < 32; i++) {…","guid":"https://leetcode.cn/problems/largest-combination-with-bitwise-and-greater-than-zero//solution/by-hu-li-hu-wai-6kum","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T04:09:06.758Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【什码情况】前缀和","url":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/by-smqk-d3yw","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!\\n
解题思路
\\n1、注意 int 越界问题;
\\n代码
\\n###java
\\n\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n int cnt = 0;\\n long total = 0, sum = 0;\\n for (int num : nums) total += num;\\n\\n for (int i = 0; i < nums.length - 1; i++) {\\n sum += nums[i];\\n if (sum >= total - sum) cnt++;\\n }\\n return cnt;\\n }\\n}\\n
错误笔记
\\n###java
\\n\\n// 求和想到数据范围问题,因此用 long 类型接受\\n // 万万没注意到 IntStream 内部求和是使用 int 进行汇总并返回的,以此会出现溢出问题\\n long total = IntStream.of(nums).sum();\\n
最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎加我微信『 code5bug 』和 加入我们的「组队打卡」小群。
\\n","description":"解题思路 1、注意 int 越界问题;\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int waysToSplitArray(int[] nums) {\\n int cnt = 0;\\n long total = 0, sum = 0;\\n for (int num : nums) total += num;\\n\\n for (int i = 0; i < nums.length - 1; i++) {\\n sum += nums[i];…","guid":"https://leetcode.cn/problems/number-of-ways-to-split-array//solution/by-smqk-d3yw","author":"smqk","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T01:44:35.694Z","media":[{"url":"https://pic.leetcode-cn.com/1652579075-HcObzS-image.png","type":"photo","width":759,"height":632,"blurhash":"LER:Na-;s._2_3Rjofof~1j[WXV["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最大子数组和 + 状态机 DP(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/substring-with-largest-variance//solution/by-endlesscheng-5775","content":"分析
\\n根据题意,最大波动值只由 $s$ 中的两种字母决定,至于是哪两种我们还不知道,可以枚举这两种字母。
\\n由于 $s$ 只包含小写字母,我们可以从 $26$ 个小写字母中选出 $2$ 个不同的字母(相同字母无需考虑,波动值为 $0$),并假设这两个字母是答案子串中出现次数最多的和最少的。这一共需要枚举 $A_{26}^2=26\\\\cdot 25=650$ 种不同的字母组合。
\\n例如子串只有 $3$ 个 $a$ 和 $2$ 个 $b$,那么波动值为 $3-2=1$。若把 $a$ 视作 $1$,$b$ 视作 $-1$,也可以算出波动值为 $1+1+1+(-1)+(-1)=1$。
\\n设出现次数最多的字母为 $a$,出现次数最少的字母为 $b$。把 $a$ 视作 $1$,$b$ 视作 $-1$,其余字母视作 $0$,最大波动值就等同于 53. 最大子数组和。
\\n状态机 DP
\\n和 53 题的不同之处是,$a$ 和 $b$ 必须都在子串中。
\\n\\n
\\n- 只包含 $a$ 的子串,实际波动值为 $0$,不能用 $a$ 的个数作为波动值。
\\n- 只包含 $b$ 的子串,由于我们把 $b$ 当作 $-1$,这样算出的「子数组和」是负数,不会更新答案的最大值。
\\n- 当子串包含 $b$ 且「子数组和」是正数,那么子串一定包含 $a$。所以只要保证子串包含 $b$ 就行(子串全为 $b$ 也没关系,不会影响答案)。
\\n状态定义。在 53 题的基础上,用一个额外的参数 $j$ 表示是否需要包含 $b$:
\\n\\n
\\n- 定义 $f[i+1][0]$ 表示以 $s[i]$ 结尾的最大子数组和,包不包含 $b$ 都可以。加一是方便定义初始值。
\\n- 定义 $f[i+1][1]$ 表示以 $s[i]$ 结尾的、一定包含 $b$ 的最大子数组和。
\\n状态转移方程:
\\n\\n
\\n- 对于 $f[i+1][0]$,转移方程同 53 题,即 $f[i+1][0] = \\\\max(f[i][0], 0) + v$,其中 $v$ 等于 $1$、$-1$ 或 $0$,见前文的分析。
\\n- 对于 $f[i+1][1]$:\\n
\\n\\n
\\n- 如果 $s[i]=a$,那么只能在以 $s[i-1]$ 结尾的、一定包含 $b$ 的子数组后面加上 $s[i]$,即 $f[i+1][1] = f[i][1] + 1$。
\\n- 如果 $s[i]=b$,那么问题等价于以 $s[i]$ 结尾的最大子数组和(必然包含 $b$),即 $f[i+1][1] = f[i+1][0]$。
\\n- 其他情况和 $s[i]=a$ 是一样的,只能在以 $s[i-1]$ 结尾的、一定包含 $b$ 的子数组后面加上 $s[i]$,即 $f[i+1][1] = f[i][1] + 0 = f[i][1]$。
\\n初始值:
\\n\\n
\\n- $f[0][0]=0$。一开始什么也没有,子数组和为 $0$。
\\n- $f[0][1]=-\\\\infty$。一开始什么也没有,一定包含 $b$ 的情况不存在,用 $-\\\\infty$ 表示,这样计算 $\\\\max$ 不会取到 $-\\\\infty$。
\\n答案:$\\\\max\\\\limits_{i=1}^{n} f[i][1]$。注意答案不是 $f[n][1]$,因为这仅仅表示以 $s[n-1]$ 结尾的子串。
\\n代码实现时,$f$ 的第一个维度可以去掉,用两个变量 $f_0$ 和 $f_1$ 分别表示 $f[i][0]$ 和 $f[i][1]$:
\\n\\n
\\n- $f_0 = \\\\max(f_0, 0) + v$,这和 53 题完全一样。
\\n- 如果 $s[i]=a$,$f_1 = f_1 + 1$。
\\n- 如果 $s[i]=b$,$f_1 = f_0$。
\\n- 其余情况 $f_1$ 不变。
\\n初始值 $f_0=0,\\\\ f_1=-\\\\infty$。
\\n循环末尾用 $f_1$ 更新答案的最大值。
\\n\\nclass Solution:\\n def largestVariance(self, s: str) -> int:\\n ans = 0\\n for a, b in permutations(ascii_lowercase, 2): # 枚举所有小写字母对\\n f0, f1 = 0, -inf\\n for ch in s:\\n if ch == a:\\n f0 = max(f0, 0) + 1\\n f1 += 1\\n elif ch == b:\\n f1 = f0 = max(f0, 0) - 1\\n # else: f0 = max(f0, 0) 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = max(ans, f1)\\n return ans\\n
\\nclass Solution {\\n public int largestVariance(String S) {\\n char[] s = S.toCharArray();\\n int ans = 0;\\n for (char a = \'a\'; a <= \'z\'; a++) {\\n for (char b = \'a\'; b <= \'z\'; b++) {\\n if (b == a) {\\n continue;\\n }\\n int f0 = 0;\\n int f1 = Integer.MIN_VALUE;\\n for (char ch : s) {\\n if (ch == a) {\\n f0 = Math.max(f0, 0) + 1;\\n f1++;\\n } else if (ch == b) {\\n f1 = f0 = Math.max(f0, 0) - 1;\\n } // else f0 = Math.max(f0, 0); 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = Math.max(ans, f1);\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestVariance(string s) {\\n int ans = 0;\\n for (char a = \'a\'; a <= \'z\'; a++) {\\n for (char b = \'a\'; b <= \'z\'; b++) {\\n if (b == a) {\\n continue;\\n }\\n int f0 = 0, f1 = INT_MIN;\\n for (char ch : s) {\\n if (ch == a) {\\n f0 = max(f0, 0) + 1;\\n f1++;\\n } else if (ch == b) {\\n f1 = f0 = max(f0, 0) - 1;\\n } // else f0 = max(f0, 0); 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = max(ans, f1);\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint largestVariance(char* s) {\\n int ans = 0;\\n for (char a = \'a\'; a <= \'z\'; a++) {\\n for (char b = \'a\'; b <= \'z\'; b++) {\\n if (b == a) {\\n continue;\\n }\\n int f0 = 0, f1 = INT_MIN;\\n for (int i = 0; s[i]; i++) {\\n if (s[i] == a) {\\n f0 = MAX(f0, 0) + 1;\\n f1++;\\n } else if (s[i] == b) {\\n f1 = f0 = MAX(f0, 0) - 1;\\n } // else f0 = MAX(f0, 0); 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = MAX(ans, f1);\\n }\\n }\\n }\\n return ans;\\n}\\n
\\nfunc largestVariance(s string) (ans int) {\\n for a := \'a\'; a <= \'z\'; a++ {\\n for b := \'a\'; b <= \'z\'; b++ {\\n if b == a {\\n continue\\n }\\n f0, f1 := 0, math.MinInt\\n for _, ch := range s {\\n if ch == a {\\n f0 = max(f0, 0) + 1\\n f1++\\n } else if ch == b {\\n f1, f0 = max(f0, 0)-1, max(f0, 0)-1\\n } // else { f0 = max(f0, 0) } 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = max(ans, f1)\\n }\\n }\\n }\\n return\\n}\\n
\\nvar largestVariance = function(s) {\\n let ans = 0;\\n for (let a = 97; a <= 122; a++) { // \'a\'.charCodeAt(0) === 97\\n for (let b = 97; b <= 122; b++) {\\n if (b === a) {\\n continue;\\n }\\n let f0 = 0, f1 = -Infinity;\\n for (const ch of s) {\\n if (ch.charCodeAt(0) === a) {\\n f0 = Math.max(f0, 0) + 1;\\n f1++;\\n } else if (ch.charCodeAt(0) === b) {\\n f1 = f0 = Math.max(f0, 0) - 1;\\n } // else f0 = Math.max(f0, 0); 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = Math.max(ans, f1);\\n }\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn largest_variance(s: String) -> i32 {\\n let mut ans = 0;\\n for a in b\'a\'..=b\'z\' {\\n for b in b\'a\'..=b\'z\' {\\n if b == a {\\n continue;\\n }\\n let mut f0 = 0;\\n let mut f1 = i32::MIN;\\n for ch in s.bytes() {\\n if ch == a {\\n f0 = f0.max(0) + 1;\\n f1 += 1;\\n } else if ch == b {\\n f1 = f0.max(0) - 1;\\n f0 = f1;\\n } // else { f0 = f0.max(0); } 可以留到 ch 等于 a 或者 b 的时候计算,f1 不变\\n ans = ans.max(f1);\\n }\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n|\\\\Sigma|^2)$,其中 $|\\\\Sigma|=26$ 为字符集合的大小。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n优化
\\n在上面的代码中,我们只在 $s[i]=a$ 或者 $s[i]=b$ 时才更新状态,因为其他情况下 $f$ 值是不变的。
\\n这意味着,我们浪费了大量时间,在遍历那些既不等于 $a$ 又不等于 $b$ 的 $s[i]$ 上了。
\\n珍惜时间,改为只遍历 $s$ 一次,同时更新所有会变化的状态。
\\n创建两个 $26\\\\times 26$ 的矩阵 $f_0[a][b]$ 和 $f_1[a][b]$,在遍历 $s$ 的同时,只更新那些会变化的状态,即 $a=s[i]$ 或者 $b=s[i]$ 的状态。
\\n\\nclass Solution:\\n def largestVariance(self, s: str) -> int:\\n ans = 0\\n f0 = [[0] * 26 for _ in range(26)]\\n f1 = [[-inf] * 26 for _ in range(26)]\\n for ch in map(ord, s):\\n ch -= ord(\'a\')\\n # 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for i in range(26):\\n if i == ch:\\n continue\\n # 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = max(f0[ch][i], 0) + 1\\n f1[ch][i] += 1\\n # 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f1[i][ch] = f0[i][ch] = max(f0[i][ch], 0) - 1\\n ans = max(ans, f1[ch][i], f1[i][ch])\\n return ans\\n
\\nclass Solution:\\n def largestVariance(self, s: str) -> int:\\n ans = 0\\n f0 = [[0] * 26 for _ in range(26)]\\n f1 = [[-inf] * 26 for _ in range(26)]\\n for ch in map(ord, s):\\n ch -= ord(\'a\')\\n # 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for i in range(26):\\n if i == ch:\\n continue\\n # 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n if f0[ch][i] < 0:\\n f0[ch][i] = 1\\n else:\\n f0[ch][i] += 1\\n f1[ch][i] += 1\\n v = f1[ch][i]\\n if v > ans:\\n ans = v\\n # 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n if f0[i][ch] < 0:\\n f0[i][ch] = -1\\n else:\\n f0[i][ch] -= 1\\n f1[i][ch] = v = f0[i][ch]\\n if v > ans:\\n ans = v\\n return ans\\n
\\nclass Solution {\\n public int largestVariance(String s) {\\n int ans = 0;\\n int[][] f0 = new int[26][26];\\n int[][] f1 = new int[26][26];\\n for (int[] row : f1) {\\n Arrays.fill(row, Integer.MIN_VALUE);\\n }\\n\\n for (char ch : s.toCharArray()) {\\n ch -= \'a\';\\n // 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for (int i = 0; i < 26; i++) {\\n if (i == ch) {\\n continue;\\n }\\n // 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = Math.max(f0[ch][i], 0) + 1;\\n f1[ch][i]++;\\n // 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f1[i][ch] = f0[i][ch] = Math.max(f0[i][ch], 0) - 1;\\n ans = Math.max(ans, Math.max(f1[ch][i], f1[i][ch]));\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestVariance(string s) {\\n int ans = 0;\\n int f0[26][26]{}, f1[26][26];\\n memset(f1, -0x3f, sizeof(f1)); // 初始化成一个很小的负数\\n\\n for (char ch : s) {\\n ch -= \'a\';\\n // 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for (int i = 0; i < 26; i++) {\\n if (i == ch) {\\n continue;\\n }\\n // 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = max(f0[ch][i], 0) + 1;\\n f1[ch][i]++;\\n // 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f1[i][ch] = f0[i][ch] = max(f0[i][ch], 0) - 1;\\n ans = max(ans, max(f1[ch][i], f1[i][ch])); // 或者 max({ans, f1[ch][i], f1[i][ch]})\\n }\\n }\\n return ans;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint largestVariance(char* s) {\\n int ans = 0;\\n int f0[26][26] = {}, f1[26][26];\\n memset(f1, -0x3f, sizeof(f1)); // 初始化成一个很小的负数\\n\\n for (int k = 0; s[k]; k++) {\\n int ch = s[k] - \'a\';\\n // 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for (int i = 0; i < 26; i++) {\\n if (i == ch) {\\n continue;\\n }\\n // 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = MAX(f0[ch][i], 0) + 1;\\n f1[ch][i]++;\\n // 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f1[i][ch] = f0[i][ch] = MAX(f0[i][ch], 0) - 1;\\n ans = MAX(ans, MAX(f1[ch][i], f1[i][ch]));\\n }\\n }\\n return ans;\\n}\\n
\\nfunc largestVariance(s string) (ans int) {\\nvar f0, f1 [26][26]int\\nfor i := range f1 {\\nfor j := range f1[i] {\\nf1[i][j] = math.MinInt\\n}\\n}\\n\\nfor _, ch := range s {\\nch -= \'a\'\\n// 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\nfor i := range 26 {\\nif i == int(ch) {\\ncontinue\\n}\\n// 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\nf0[ch][i] = max(f0[ch][i], 0) + 1\\nf1[ch][i]++\\n// 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\nf0[i][ch] = max(f0[i][ch], 0) - 1\\nf1[i][ch] = f0[i][ch]\\nans = max(ans, f1[ch][i], f1[i][ch])\\n}\\n}\\nreturn\\n}\\n
\\nvar largestVariance = function(s) {\\n let ans = 0;\\n const f0 = Array.from({ length: 26 }, () => Array(26).fill(0));\\n const f1 = Array.from({ length: 26 }, () => Array(26).fill(-Infinity));\\n\\n for (let ch of s) {\\n ch = ch.charCodeAt(0) - 97; // \'a\'.charCodeAt(0) === 97\\n // 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for (let i = 0; i < 26; i++) {\\n if (i === ch) {\\n continue;\\n }\\n // 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = Math.max(f0[ch][i], 0) + 1;\\n f1[ch][i]++;\\n // 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f1[i][ch] = f0[i][ch] = Math.max(f0[i][ch], 0) - 1;\\n ans = Math.max(ans, f1[ch][i], f1[i][ch]);\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn largest_variance(s: String) -> i32 {\\n let mut ans = 0;\\n let mut f0 = [[0; 26]; 26];\\n let mut f1 = [[i32::MIN; 26]; 26];\\n\\n for ch in s.bytes() {\\n let ch = (ch - b\'a\') as usize;\\n // 遍历到 ch 时,只需计算 a=ch 或者 b=ch 的状态,其他状态和 ch 无关,f 值不变\\n for i in 0..26 {\\n if i == ch {\\n continue;\\n }\\n // 假设出现次数最多的字母 a=ch,更新所有 b=i 的状态\\n f0[ch][i] = f0[ch][i].max(0) + 1;\\n f1[ch][i] += 1;\\n // 假设出现次数最少的字母 b=ch,更新所有 a=i 的状态\\n f0[i][ch] = f0[i][ch].max(0) - 1;\\n f1[i][ch] = f0[i][ch];\\n ans = ans.max(f1[ch][i]).max(f1[i][ch]);\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n|\\\\Sigma|)$,其中 $|\\\\Sigma|=26$ 为字符集合的大小。
\\n- 空间复杂度:$\\\\mathcal{O}(|\\\\Sigma|^2)$。
\\n更多相似题目,见下面动态规划题单中的「§1.3 最大子数组和」以及「六、状态机 DP」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 根据题意,最大波动值只由 $s$ 中的两种字母决定,至于是哪两种我们还不知道,可以枚举这两种字母。\\n\\n由于 $s$ 只包含小写字母,我们可以从 $26$ 个小写字母中选出 $2$ 个不同的字母(相同字母无需考虑,波动值为 $0$),并假设这两个字母是答案子串中出现次数最多的和最少的。这一共需要枚举 $A_{26}^2=26\\\\cdot 25=650$ 种不同的字母组合。\\n\\n例如子串只有 $3$ 个 $a$ 和 $2$ 个 $b$,那么波动值为 $3-2=1$。若把 $a$ 视作 $1$,$b$ 视作 $-1$,也可以算出波动值为 $1+1+1+(-1)+…","guid":"https://leetcode.cn/problems/substring-with-largest-variance//solution/by-endlesscheng-5775","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-15T01:27:18.490Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举出现最多和最少的字符","url":"https://leetcode.cn/problems/substring-with-largest-variance//solution/mei-ju-chu-xian-zui-duo-he-zui-shao-de-z-g9gz","content":"解法:枚举 + 动态规划
\\n暴力枚举两个字符(不能相同),以字符 $a$ 和 $b$ 为例,我们把字符串中的所有 $a$ 视为 $1$,所有的 $b$ 视为 $-1$,其它为 $0$。这样,只需要求出转换后的 最大子数组和,即可得所有子串中 $\\\\text{count}(a) - \\\\text{count}(b)$ 的最大值。
\\n但是,还需要注意一个条件,就是子字符串中必须存在 $b$。那么,稍微更改一下求 最大子数组和 的思路即可。设
\\n$dp_{i,0}=$ 以字符串的第 $i$ 个字符结尾的,转换后的最大子数组和;
\\n
\\n$dp_{i,1}=$ 以字符串的第 $i$ 个字符结尾的,且包含至少一个 $b$ 的最大子数组和。那么,状态转移为:
\\n\\n
\\n- \\n
\\n$dp_{i,0} = \\\\max(dp_{i-1,0} + v, v)$,其中,$v = \\\\left{ \\\\begin{aligned} 1&, s[i] = a\\\\ -1&,s[i] = b \\\\ 0&, \\\\text{otherwise}\\\\ \\\\end{aligned} \\\\right.$。
\\n
\\n(这里套用了最大子数组和的 $dp$)。- \\n
\\n$dp_{i,1} =\\\\left{ \\\\begin{aligned} &\\\\max(dp_{i-1,1} + v,\\\\ dp_{i-1,0} + v,\\\\ v) &, s[i] = b \\\\ &dp_{i-1,1} + v&, s[i] \\\\ne b \\\\end{aligned} \\\\right.$ 。
\\n
\\n注意当 $s[i] = b$ 时和 $s[i] \\\\ne b$ 时的状态转移的不同。这样我们暴力枚举完所有的字符对之后,总有一个是正确答案,其它答案都比正确答案小。因此答案就是之前求的所有的 $dp_{i,1}$ 的最大值。
\\n时间复杂度:$O(n\\\\cdot 26^2)$
\\n代码实现要注意 $dp_0$ 和 $dp_1$ 的更新顺序。
\\n###c++
\\n\\n","description":"解法:枚举 + 动态规划 暴力枚举两个字符(不能相同),以字符 $a$ 和 $b$ 为例,我们把字符串中的所有 $a$ 视为 $1$,所有的 $b$ 视为 $-1$,其它为 $0$。这样,只需要求出转换后的 最大子数组和,即可得所有子串中 $\\\\text{count}(a) - \\\\text{count}(b)$ 的最大值。\\n\\n但是,还需要注意一个条件,就是子字符串中必须存在 $b$。那么,稍微更改一下求 最大子数组和 的思路即可。设\\n\\n$dp_{i,0}=$ 以字符串的第 $i$ 个字符结尾的,转换后的最大子数组和;\\n $dp_{i,1}=$ 以字符串的第 $i…","guid":"https://leetcode.cn/problems/substring-with-largest-variance//solution/mei-ju-chu-xian-zui-duo-he-zui-shao-de-z-g9gz","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-14T16:10:48.588Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & DP","url":"https://leetcode.cn/problems/substring-with-largest-variance//solution/by-tsreaper-d748","content":"class Solution {\\npublic:\\n int largestVariance(string s) {\\n int res = 0;\\n for(char a = \'a\'; a <= \'z\'; ++a) {\\n for(char b = \'a\'; b <= \'z\'; ++b) {\\n if(a == b) continue;\\n for(int i = 0, dp0 = -1e8, dp1 = -1e8; i < s.size(); ++i) {\\n int v = ((s[i] == a)? 1 : ((s[i] == b)? -1 : 0));\\n \\n if(s[i] == b) dp1 = max(dp1 + v, max(dp0 + v, v));\\n else dp1 = dp1 + v;\\n \\n dp0 = max(dp0 + v, v);\\n \\n res = max(res, dp1);\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
解法:枚举 & DP
\\n枚举哪个字符是出现最多的(记为 x),哪个字符是出现最少的(记为 y)。把字符串中所有 x 改成 1,所有 y 改成 -1,其它的都改成 0。那么该序列的最大非空子段和就是以 x 为出现最多字符,y 为出现最少字符的答案。
\\n最大非空子段和很好计算。令 $f_i$ 表示以第 $i$ 个元素为结尾的前缀和,那么以第 $i$ 个元素为结尾的最大子段和就是 $f_i - \\\\min (f_j)$,其中 $0 \\\\le j < i$。
\\n但是有特殊情况!我们不能把一段全是 x 的子串计入答案,也就是说子串里最少出现一个 y 才行。因此我们维护最近出现的 y 的位置 $k$,只允许从该位置之前作为子串的开头,即 $0 \\\\le j < k$。复杂度 $\\\\mathcal{O}(n|\\\\Sigma|^2)$,其中 $|\\\\Sigma|$ 是字符集大小。
\\n有朋友可能担心,如果我们枚举的 x 和 y 不是出现最多或者最少的字符怎么办?其实这不影响答案。对于同一个子串,如果 x 不是出现最多的字符,那么算出来的最大子段和一定不超过正确答案;如果 y 不是出现最少的字符同理。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:枚举 & DP 枚举哪个字符是出现最多的(记为 x),哪个字符是出现最少的(记为 y)。把字符串中所有 x 改成 1,所有 y 改成 -1,其它的都改成 0。那么该序列的最大非空子段和就是以 x 为出现最多字符,y 为出现最少字符的答案。\\n\\n最大非空子段和很好计算。令 $f_i$ 表示以第 $i$ 个元素为结尾的前缀和,那么以第 $i$ 个元素为结尾的最大子段和就是 $f_i - \\\\min (f_j)$,其中 $0 \\\\le j < i$。\\n\\n但是有特殊情况!我们不能把一段全是 x 的子串计入答案,也就是说子串里最少出现一个 y 才行…","guid":"https://leetcode.cn/problems/substring-with-largest-variance//solution/by-tsreaper-d748","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-14T16:10:31.567Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「模拟」","url":"https://leetcode.cn/problems/find-the-k-beauty-of-a-number//solution/by-hu-li-hu-wai-eyxl","content":"class Solution {\\npublic:\\n int largestVariance(string s) {\\n int n = s.size();\\n int ans = 0;\\n for (char x = \'a\'; x <= \'z\'; x++) for (char y = \'a\'; y <= \'z\'; y++) if (x != y) {\\n vector<int> f(n + 1);\\n int t = 0, mn = 1e9;\\n for (int i = 1, j = 0; i <= n; i++) {\\n char c = s[i - 1];\\n if (c == x) f[i] = ++t;\\n else if (c == y) {\\n f[i] = --t;\\n while (j < i) mn = min(mn, f[j++]);\\n }\\n else f[i] = t;\\n ans = max(ans, f[i] - mn);\\n }\\n }\\n return ans;\\n }\\n};\\n\\n
方法:模拟
\\n字符串模拟, 非 0 则判断是否整除
\\n代码
\\n###java
\\n\\nclass Solution {\\n public int divisorSubstrings(int num, int k) {\\n String s = String.valueOf(num);\\n int n = s.length(), cnt = 0;\\n for (int i = 0; i + k <= n; i++) {\\n int b = Integer.parseInt(s.substring(i, i + k));\\n if (b != 0 && num % b == 0) cnt++;\\n }\\n return cnt;\\n }\\n}\\n
结语
\\n","description":"方法:模拟 字符串模拟, 非 0 则判断是否整除\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int divisorSubstrings(int num, int k) {\\n String s = String.valueOf(num);\\n int n = s.length(), cnt = 0;\\n for (int i = 0; i + k <= n; i++) {\\n int b = Integer.parseInt(s.substring(i, i + k));\\n if (b…","guid":"https://leetcode.cn/problems/find-the-k-beauty-of-a-number//solution/by-hu-li-hu-wai-eyxl","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-14T16:10:06.351Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【直接遍历简单搞定】","url":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/by-nehzil-mrfp","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!思路分析:
\\n
\\n因为题目是 找到 nums 中最接近 0 的数(如果有多个答案,请你返回它们中的 最大值 ) 即 nums[i] 与 0 的距离即为该数的绝对值,因此只需要找出数组 nums 里面绝对值最小的元素的最大值。实现步骤:
\\n\\n
\\n- 遍历数组定义一个变量 result 记录已遍历元素中绝对值最小且数值最大的元素;\\n
\\n\\n
\\n- ∣num∣ < result,此时需要更新 result 的值为 num;
\\n- ∣num∣ = result,此时需要比较 result 和 num 的大小 然后将两者较大值更新为 result ;
\\n- ∣num∣ > result,此时无需进行任何操作。
\\n- 返回值 result 即为数组 nums 里面绝对值最小的元素的最大值。
\\n具体代码如下:
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int findClosestNumber(vector<int>& nums) {\\n int result = nums[0];\\n for (auto& num:nums)\\n /* nums中最接近0的数字,如果距离相等记录较大的 */\\n if(abs(num) < abs(result) || (abs(num) == abs(result) && num > result))\\n result = num;\\n return result;\\n }\\n};\\n
复杂度分析:
\\n\\n
\\n","description":"思路分析: 因为题目是 找到 nums 中最接近 0 的数(如果有多个答案,请你返回它们中的 最大值 ) 即 nums[i] 与 0 的距离即为该数的绝对值,因此只需要找出数组 nums 里面绝对值最小的元素的最大值。\\n\\n实现步骤:\\n\\n遍历数组定义一个变量 result 记录已遍历元素中绝对值最小且数值最大的元素;\\n∣num∣ < result,此时需要更新 result 的值为 num;\\n∣num∣ = result,此时需要比较 result 和 num 的大小 然后将两者较大值更新为 result ;\\n∣num∣ > result…","guid":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/by-nehzil-mrfp","author":"Nehzil","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-11T11:32:17.458Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"设计一个 ATM 机器","url":"https://leetcode.cn/problems/design-an-atm-machine//solution/she-ji-yi-ge-atm-ji-qi-by-leetcode-solut-etxe","content":"- 时间复杂度:O(n),其中 n 为 nums 的长度。
\\n- 空间复杂度:O(1)。
\\n方法一:维护每种钞票的剩余数目
\\n思路与算法
\\n首先我们尝试分析各个方法对应的需求:
\\n\\n
\\n- \\n
\\n对于 $\\\\texttt{deposit}()$ 方法,我们需要更新每张钞票的数目;
\\n- \\n
\\n对于 $\\\\texttt{withdraw}()$ 方法,我们需要模拟机器从高面额至低面额尝试取钱的过程,判断是否可行,并尝试更新每张钞票的数目,以及返回取出各种面额钞票的数目。
\\n我们可以用一个数组 $\\\\textit{cnt}$ 来维护每种钞票的剩余数目,同时用数组 $\\\\textit{value}$ 来维护 $\\\\textit{cnt}$ 数组对应下标钞票的面额。为了方便起见,我们需要让 $\\\\textit{value}$ 数组保持升序。
\\n那么,对于 $\\\\texttt{deposit}()$ 方法,我们只需要遍历输入数组,并将每个元素的值加在 $\\\\textit{cnt}$ 中的对应元素上即可。
\\n而对于 $\\\\texttt{withdraw}()$ 方法,我们需要倒序(即从高面额至低面额)遍历 $\\\\textit{cnt}$ 数组,并模拟取钱操作。
\\n具体而言,我们用数组 $\\\\textit{res}$ 表示(如果可行)取出各种钞票的数目,同时倒序遍历 $\\\\textit{cnt}$ 数组,并更新还需要取的金额数目 $\\\\textit{amount}$。当遍历到下标 $i$ 时,我们首先计算该面额钞票需要取出的数量 $\\\\textit{res}[i]$。对应钞票的数量不能多余取款机中该种钞票的数量,且总面额不能高于还需取出的金额数目。因此我们有 $\\\\textit{res}[i] = \\\\min(\\\\textit{cnt}[i], \\\\lfloor \\\\textit{amount} / \\\\min(\\\\textit{value}[i] \\\\rfloor)$(其中 $\\\\lfloor \\\\dots \\\\rfloor$ 代表向下取整)。同时,我们需要对应地将 $\\\\textit{amount}$ 减去 $\\\\textit{res}[i] \\\\times \\\\textit{value}[i]$。
\\n当遍历完成后,如果 $\\\\textit{amount} = 0$,即代表可以进行该取出操作,我们将 $\\\\textit{cnt}$ 数组地每个元素减去 $\\\\textit{res}$ 数组的对应元素,并返回 $\\\\textit{res}$ 作为答案;而如果 $\\\\textit{amount} > 0$,则说明无法进行取出操作,我们应当不进行任何操作,直接返回 $[-1]$。
\\n细节
\\n在操作过程中,$\\\\textit{cnt}$ 数组的元素数值有可能超过 $32$ 位有符号整数的上限,因此对于 $\\\\texttt{C++}$ 等语言,我们需要用 $64$ 位整数存储每种钞票的剩余数目。
\\n代码
\\n###C++
\\n\\nclass ATM {\\nprivate:\\n vector<long long> cnt; // 每张钞票剩余数量\\n vector<long long> value; // 每张钞票面额\\n \\npublic:\\n ATM() {\\n cnt = {0, 0, 0, 0, 0};\\n value = {20, 50, 100, 200, 500};\\n }\\n \\n void deposit(vector<int> banknotesCount) {\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n \\n vector<int> withdraw(int amount) {\\n vector<int> res(5);\\n // 模拟尝试取出钞票的过程\\n for (int i = 4; i >= 0; --i) {\\n res[i] = min(cnt[i], amount / value[i]);\\n amount -= res[i] * value[i];\\n }\\n if (amount) {\\n // 无法完成该操作\\n return {-1};\\n } else {\\n // 可以完成该操作\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] -= res[i];\\n }\\n return res;\\n }\\n }\\n};\\n
###Python
\\n\\nclass ATM:\\n\\n def __init__(self):\\n self.cnt = [0] * 5 # 每张钞票剩余数量\\n self.value = [20, 50, 100, 200, 500] # 每张钞票面额\\n\\n\\n def deposit(self, banknotesCount: List[int]) -> None:\\n for i in range(5):\\n self.cnt[i] += banknotesCount[i]\\n\\n\\n def withdraw(self, amount: int) -> List[int]:\\n res = [0] * 5\\n # 模拟尝试取出钞票的过程\\n for i in range(4, -1, -1):\\n res[i] = min(self.cnt[i], amount // self.value[i])\\n amount -= res[i] * self.value[i]\\n if amount:\\n # 无法完成该操作\\n return [-1]\\n else:\\n # 可以完成该操作\\n for i in range(5):\\n self.cnt[i] -= res[i]\\n return res\\n
###Go
\\n\\ntype ATM struct {\\n cnt []int64 // 每张钞票剩余数量\\n value []int64 // 每张钞票面额\\n}\\n\\nfunc Constructor() ATM {\\n return ATM {\\n cnt: make([]int64, 5),\\n value: []int64 {\\n 20, 50, 100, 200, 500,\\n },\\n }\\n}\\n\\nfunc (this *ATM) Deposit(banknotesCount []int) {\\n for i := 0; i < 5; i++ {\\n this.cnt[i] += int64(banknotesCount[i])\\n }\\n}\\n\\nfunc (this *ATM) Withdraw(amount int) []int {\\n res := make([]int, 5)\\n // 模拟尝试取出钞票的过程\\n for i := 4; i >= 0; i-- {\\n res[i] = int(min(this.cnt[i], int64(amount) / this.value[i]))\\n amount -= res[i] * int(this.value[i])\\n }\\n if amount > 0 {\\n // 无法完成该操作\\n return []int{-1}\\n }\\n // 可以完成该操作\\n for i := 0; i < 5; i++ {\\n this.cnt[i] -= int64(res[i])\\n }\\n return res\\n}\\n
###C
\\n\\ntypedef struct {\\n long long cnt[5]; // 每张钞票剩余数量\\n long long value[5]; // 每张钞票面额\\n} ATM;\\n\\nATM* aTMCreate() {\\n ATM *atm = (ATM *)malloc(sizeof(ATM));\\n memset(atm, 0, sizeof(ATM));\\n atm->value[0] = 20;\\n atm->value[1] = 50;\\n atm->value[2] = 100;\\n atm->value[3] = 200;\\n atm->value[4] = 500;\\n return atm;\\n}\\n\\nvoid aTMDeposit(ATM* obj, int* banknotesCount, int banknotesCountSize) {\\n for (int i = 0; i < 5; ++i) {\\n obj->cnt[i] += banknotesCount[i];\\n }\\n}\\n\\nint* aTMWithdraw(ATM* obj, int amount, int* retSize) {\\n int *res = malloc(sizeof(int) * 5);\\n *retSize = 5;\\n // 模拟尝试取出钞票的过程\\n for (int i = 4; i >= 0; --i) {\\n res[i] = fmin(obj->cnt[i], amount / obj->value[i]);\\n amount -= res[i] * obj->value[i];\\n }\\n if (amount) {\\n // 无法完成该操作\\n res = malloc(sizeof(int));\\n *retSize = 1;\\n res[0] = -1;\\n return res;\\n } else {\\n // 可以完成该操作\\n for (int i = 0; i < 5; ++i) {\\n obj->cnt[i] -= res[i];\\n }\\n return res;\\n }\\n}\\n\\nvoid aTMFree(ATM* obj) {\\n free(obj);\\n}\\n
###Java
\\n\\nclass ATM {\\n private long[] cnt; // 每张钞票剩余数量\\n private long[] value; // 每张钞票面额\\n\\n public ATM() {\\n cnt = new long[]{0, 0, 0, 0, 0};\\n value = new long[]{20, 50, 100, 200, 500};\\n }\\n\\n public void deposit(int[] banknotesCount) {\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n public int[] withdraw(int amount) {\\n int[] res = new int[5];\\n // 模拟尝试取出钞票的过程\\n for (int i = 4; i >= 0; --i) {\\n res[i] = (int) Math.min(cnt[i], amount / value[i]);\\n amount -= res[i] * value[i];\\n }\\n if (amount > 0) {\\n // 无法完成该操作\\n return new int[]{-1};\\n } else {\\n // 可以完成该操作\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] -= res[i];\\n }\\n return res;\\n }\\n }\\n}\\n
###C#
\\n\\npublic class ATM {\\n private long[] cnt; // 每张钞票剩余数量\\n private long[] value; // 每张钞票面额\\n\\n public ATM() {\\n cnt = new long[]{0, 0, 0, 0, 0};\\n value = new long[]{20, 50, 100, 200, 500};\\n }\\n\\n public void Deposit(int[] banknotesCount) {\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n public int[] Withdraw(int amount) {\\n int[] res = new int[5];\\n // 模拟尝试取出钞票的过程\\n for (int i = 4; i >= 0; --i) {\\n res[i] = (int)Math.Min(cnt[i], amount / value[i]);\\n amount -= res[i] * (int)value[i];\\n }\\n if (amount > 0) {\\n // 无法完成该操作\\n return new int[]{-1};\\n } else {\\n // 可以完成该操作\\n for (int i = 0; i < 5; ++i) {\\n cnt[i] -= res[i];\\n }\\n return res;\\n }\\n }\\n}\\n
###JavaScript
\\n\\nvar ATM = function() {\\n this.cnt = [0, 0, 0, 0, 0]; // 每张钞票剩余数量\\n this.value = [20, 50, 100, 200, 500]; // 每张钞票面额\\n};\\n\\nATM.prototype.deposit = function(banknotesCount) {\\n for (let i = 0; i < 5; ++i) {\\n this.cnt[i] += banknotesCount[i];\\n }\\n};\\n\\nATM.prototype.withdraw = function(amount) {\\n let res = new Array(5).fill(0);\\n // 模拟尝试取出钞票的过程\\n for (let i = 4; i >= 0; --i) {\\n res[i] = Math.min(this.cnt[i], Math.floor(amount / this.value[i]));\\n amount -= res[i] * this.value[i];\\n }\\n if (amount > 0) {\\n // 无法完成该操作\\n return [-1];\\n } else {\\n // 可以完成该操作\\n for (let i = 0; i < 5; ++i) {\\n this.cnt[i] -= res[i];\\n }\\n return res;\\n }\\n};\\n
###TypeScript
\\n\\nclass ATM {\\n private cnt: number[]; // 每张钞票剩余数量\\n private value: number[]; // 每张钞票面额\\n\\n constructor() {\\n this.cnt = [0, 0, 0, 0, 0];\\n this.value = [20, 50, 100, 200, 500];\\n }\\n\\n deposit(banknotesCount: number[]): void {\\n for (let i = 0; i < 5; ++i) {\\n this.cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n withdraw(amount: number): number[] {\\n let res: number[] = new Array(5).fill(0);\\n // 模拟尝试取出钞票的过程\\n for (let i = 4; i >= 0; --i) {\\n res[i] = Math.min(this.cnt[i], Math.floor(amount / this.value[i]));\\n amount -= res[i] * this.value[i];\\n }\\n if (amount > 0) {\\n // 无法完成该操作\\n return [-1];\\n } else {\\n // 可以完成该操作\\n for (let i = 0; i < 5; ++i) {\\n this.cnt[i] -= res[i];\\n }\\n return res;\\n }\\n }\\n}\\n
###Rust
\\n\\nstruct ATM {\\n cnt: Vec<i64>, // 每张钞票剩余数量\\n value: Vec<i64>, // 每张钞票面额\\n}\\n\\nimpl ATM {\\n fn new() -> Self {\\n ATM {\\n cnt: vec![0, 0, 0, 0, 0],\\n value: vec![20, 50, 100, 200, 500],\\n }\\n }\\n \\n fn deposit(&mut self, banknotes_count: Vec<i32>) {\\n for i in 0..5 {\\n self.cnt[i] += banknotes_count[i] as i64;\\n }\\n }\\n \\n fn withdraw(&mut self, mut amount: i32) -> Vec<i32> {\\n let mut res = vec![0; 5];\\n // 模拟尝试取出钞票的过程\\n for i in (0..5).rev() {\\n res[i] = std::cmp::min(self.cnt[i], amount as i64 / self.value[i]) as i32;\\n amount -= res[i] * self.value[i] as i32;\\n }\\n if amount > 0 {\\n // 无法完成该操作\\n vec![-1]\\n } else {\\n // 可以完成该操作\\n for i in 0..5 {\\n self.cnt[i] -= res[i] as i64;\\n }\\n res\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:维护每种钞票的剩余数目 思路与算法\\n\\n首先我们尝试分析各个方法对应的需求:\\n\\n对于 $\\\\texttt{deposit}()$ 方法,我们需要更新每张钞票的数目;\\n\\n对于 $\\\\texttt{withdraw}()$ 方法,我们需要模拟机器从高面额至低面额尝试取钱的过程,判断是否可行,并尝试更新每张钞票的数目,以及返回取出各种面额钞票的数目。\\n\\n我们可以用一个数组 $\\\\textit{cnt}$ 来维护每种钞票的剩余数目,同时用数组 $\\\\textit{value}$ 来维护 $\\\\textit{cnt}$ 数组对应下标钞票的面额。为了方便起见…","guid":"https://leetcode.cn/problems/design-an-atm-machine//solution/she-ji-yi-ge-atm-ji-qi-by-leetcode-solut-etxe","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-11T03:18:50.765Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"substring的简单应用","url":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/substringde-jian-dan-ying-yong-by-xin-an-hjl6","content":"- \\n
\\n时间复杂度:$O(nk)$,其中 $n$ 为 $\\\\texttt{withdraw}()$ 和 $\\\\texttt{deposit}()$ 操作的次数,$k = 5$ 为不同面值钞票的数量。每一次 $\\\\texttt{withdraw}()$ 或 $\\\\texttt{deposit}()$ 操作的时间复杂度为 $O(k)$。
\\n- \\n
\\n空间复杂度:$O(k)$,即为存储每种钞票面额和剩余数量数组的空间开销。
\\n解题思路
\\n此处撰写解题思路
\\n代码
\\n###java
\\n\\n","description":"解题思路 此处撰写解题思路\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int countPrefixes(String[] words, String s) {\\n int count = 0;\\n for (int i = 0; i < words.length; i++)\\n {\\n if (words[i].length() <= s.length() && words[i].equals(s.substring(0, words[i].length()))…","guid":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/substringde-jian-dan-ying-yong-by-xin-an-hjl6","author":"xin-an-chao","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-10T02:47:07.027Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】博弈论 DP 困难题","url":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/by-ac_oier-gse8","content":"class Solution {\\n public int countPrefixes(String[] words, String s) {\\n int count = 0;\\n for (int i = 0; i < words.length; i++)\\n {\\n if (words[i].length() <= s.length() && words[i].equals(s.substring(0, words[i].length())))\\n {\\n count++;\\n }\\n }\\n return count;\\n }\\n}\\n
博弈论 DP
\\n当时在 (题解) 913. 猫和老鼠 没能分析出来更小 $K$ 值(回合数)的正确性,只能确保 $2n^2$ 是对的,其余题解说 $2 n$ 合法,后来也被证实是错误的。
\\n对于本题如果用相同的分析思路,状态数多达 $8 * 8 * 8 * 8 * 2 = 8192$ 种,题目很贴心调整了规则为 $1000$ 步以内为猫获胜,但证明 $K$ 的理论上界仍是困难(上次分析不出来,这次压根不想分析
\\n如果忽略 $K$ 值分析,代码还是很好写的:定义函数
\\nint dfs(int x, int y, int p, int q, int k)
并配合记忆化搜索,其中鼠位于 $(x, y)$,猫位于 $(p, q)$,当前轮数为 $k$(由 $k$ 的奇偶性可知是谁的回合)。对边界情况进行讨论,移动过程中按照规则进行(四联通,移动最大距离为
\\nmouseJump
和catJump
),注意一旦遇到边界或者墙就要截断。Java 使用静态数组,用一个
\\nint
代表双方所在位置,最大回合数 $K = 1000$,2022-05-10
可以过。这道题给的时间上限很高,我调整为 $K = 1500$ 跑成 $2.5s$ 也可以过。本来想要加个卡常,每 $200$ 轮检查一下运行总时长,尽量将时间压在 $850ms$ 以内,现在看来好像用不到。\\n
代码:
\\n###Java
\\n\\nimport java.time.Clock;\\nclass Solution {\\n static int S = 8 * 8 * 8 * 8, K = 1000;\\n static int[][] f = new int[S][K]; // mouse : 0 / cat : 1\\n String[] g;\\n int n, m, a, b, tx, ty;\\n int[][] dirs = new int[][]{{1,0}, {-1,0}, {0,1}, {0,-1}};\\n // mouse : (x, y) / cat : (p, q)\\n int dfs(int x, int y, int p, int q, int k) {\\n int state = (x << 9) | (y << 6) | (p << 3) | q;\\n if (k == K - 1) return f[state][k] = 1;\\n if (x == p && y == q) return f[state][k] = 1;\\n if (x == tx && y == ty) return f[state][k] = 0;\\n if (p == tx && q == ty) return f[state][k] = 1;\\n if (f[state][k] != -1) return f[state][k];\\n if (k % 2 == 0) { // mouse\\n for (int[] di : dirs) {\\n for (int i = 0; i <= b; i++) {\\n int nx = x + di[0] * i, ny = y + di[1] * i;\\n if (nx < 0 || nx >= n || ny < 0 || ny >= m) break;\\n if (g[nx].charAt(ny) == \'#\') break;\\n if (dfs(nx, ny, p, q, k + 1) == 0) return f[state][k] = 0;\\n }\\n }\\n return f[state][k] = 1;\\n } else { // cat\\n for (int[] di : dirs) {\\n for (int i = 0; i <= a; i++) {\\n int np = p + di[0] * i, nq = q + di[1] * i;\\n if (np < 0 || np >= n || nq < 0 || nq >= m) break;\\n if (g[np].charAt(nq) == \'#\') break;\\n if (dfs(x, y, np, nq, k + 1) == 1) return f[state][k] = 1;\\n }\\n }\\n return f[state][k] = 0;\\n }\\n }\\n public boolean canMouseWin(String[] grid, int catJump, int mouseJump) {\\n g = grid;\\n n = g.length; m = g[0].length(); a = catJump; b = mouseJump;\\n for (int i = 0; i < S; i++) Arrays.fill(f[i], -1);\\n int x = 0, y = 0, p = 0, q = 0;\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < m; j++) {\\n if (g[i].charAt(j) == \'M\') {\\n x = i; y = j;\\n } else if (g[i].charAt(j) == \'C\') {\\n p = i; q = j;\\n } else if (g[i].charAt(j) == \'F\') {\\n tx = i; ty = j;\\n }\\n }\\n }\\n return dfs(x, y, p, q, 0) == 0;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $n$ 和 $m$ 分别为矩阵的长宽,最长移动距离为 $L$,复杂度为 $O(n^2 * m^2 * 1000 * 4 * L)$
\\n- 空间复杂度:$O(n^2 * m^2 * 1000)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"博弈论 DP 当时在 (题解) 913. 猫和老鼠 没能分析出来更小 $K$ 值(回合数)的正确性,只能确保 $2n^2$ 是对的,其余题解说 $2 n$ 合法,后来也被证实是错误的。\\n\\n对于本题如果用相同的分析思路,状态数多达 $8 * 8 * 8 * 8 * 2 = 8192$ 种,题目很贴心调整了规则为 $1000$ 步以内为猫获胜,但证明 $K$ 的理论上界仍是困难(上次分析不出来,这次压根不想分析\\n\\n如果忽略 $K$ 值分析,代码还是很好写的:定义函数 int dfs(int x, int y, int p, int q, int k) 并配合记忆…","guid":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/by-ac_oier-gse8","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-10T00:56:54.463Z","media":[{"url":"https://pic.leetcode-cn.com/1652145352-GynEJi-image.png","type":"photo","width":1204,"height":620,"blurhash":"LORfqX%Mxt?H~SWCWEj]S7t6axj@"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"时间击败100% 空间击败100%","url":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/shi-jian-ji-bai-100-kong-jian-ji-bai-100-pr5k","content":"class Solution {
\\npublic String largestGoodInteger(String num) {
\\nString[] e = {\\"000\\",\\"111\\",\\"222\\",\\"333\\",\\"444\\",\\"555\\",
\\n\\"666\\",\\"777\\",\\"888\\",\\"999\\"};
\\nfor(int i = 9; i >= 0; i--){
\\nif(num.indexOf(e[i]) >= 0){return e[i];}
\\n}
\\nreturn \\"\\";
\\n}
\\n}
\\n","description":"class Solution { public String largestGoodInteger(String num) {\\n\\nString[] e = {\\"000\\",\\"111\\",\\"222\\",\\"333\\",\\"444\\",\\"555\\",\\n\\n\\"666\\",\\"777\\",\\"888\\",\\"999\\"};\\n\\nfor(int i = 9; i >= 0; i--){\\n\\nif(num.indexOf(e[i]) >= 0){return e[i];}\\n\\n}…","guid":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/shi-jian-ji-bai-100-kong-jian-ji-bai-100-pr5k","author":"da-qiang-de-bu-yao-1","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-09T08:11:38.060Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【道哥刷题】动态规划分类讨论解题(2次优化,耗时优化到11ms)","url":"https://leetcode.cn/problems/count-number-of-texts//solution/dao-ge-shua-ti-by-lcfgrn-z2cp","content":"解题思路
\\n提示一
\\n当字符为7或9时,可以变化的字母为4个。
\\n提示二
\\ndp[i]定义:以pressKey[i]结尾的方案个数
\\n
\\nbase code
\\ndp[0] = 1
(解释:如果只有一个字符时,仅有一种方案,以“2”为例子:只能为“a”)
\\n动规方程,分类讨论:\\n
\\n- 当chs[i]与chs[i-1]不同时,
\\ndp[i] = dp[i-1]
;- 当chs[i]与chs[i-1]相同时,
\\ndp[i] = dp[i-1] + dp[i-2]
;- 当chs[i]与chs[i-1]、chs[i-2]都相同时,
\\ndp[i] = dp[i-1] + dp[i-2] + dp[i-3]
;- 当chs[i]为7或9,且与chs[i-1]、chs[i-2]、chs[i-3]都相同时,
\\ndp[i] = dp[i-1] + dp[i-2] + dp[i-3] + dp[i-4]
。- 当上面的dp[i-2]、dp[i-3]、dp[i-4]无意义(i的值大小不满足时)时,应将相加的值更改为1,即
\\n\\ndp[i] = dp[i-1] + 1;\\ndp[i] = dp[i-1] + dp[i-2] + 1;\\ndp[i] = dp[i-1] + dp[i-2] + dp[i-3] + 1;\\n
如何理解动规方程?
\\n\\n\\n以“22233”为例
\\n\\n
\\n- 计算dp[1]:\\n
\\n\\n
\\n- 此时i<2,dp[i-2]无意义,
\\ndp[1] = dp[0] + 1
,值为2;- 可以得到的方案为“aa”、“b”
\\n- 计算dp[2]:\\n
\\n\\n
\\n- 此时i<3,dp[i-3]无意义,
\\ndp[2] = dp[1] + dp[0] + 1
,值为4;- 可以得到的方案为“aaa”、“ba”、“ab”、“c”
\\n- 计算dp[3]:\\n
\\n\\n
\\n- 此时chs[i] != chs[i-1],
\\ndp[3] = dp[2]
,值为4;- 可以得到的方案为“aaad”、“bad”、“abd”、“cd”
\\n- 计算dp[4]:\\n
\\n\\n
\\n- 此时i>2,
\\ndp[4] = dp[3] + dp[2]
,值为8;- 可以得到的方案为“aaadd”、“badd”、“abdd”、“cdd”、“aaae”、“bae”、“abe”、“ce”;
\\n提示三
\\n上面动规方程的解释:
\\n\\n
\\n- 当已知dp[i-1]的情况下,我们多加了一个字符,如果
\\nchs[i]!=chs[i-1]
,则相当于在原来的方案后加一个固定的字母;\\n\\n222 + 3 ==> 则前面的222可以组成的方案,后面加上一个3所代表的字符,方案数目不变,即dp[i]=dp[i-1]
\\n\\n
\\n- 当已知dp[i-1]的情况下,我们多加1个字符,如果
\\nchs[i] == chs[i-1]
,则加的那个字符可以作为一个单独字母跟之前的方案组成方案(方案数为dp[i-1]),也可以与第i-1个字符组合变成另一个字母,然后与前面的字符组成新方案(方案数目为dp[i-2]),但如果i-2<0
,则第i与i-1的字符组成的字母单独作为1个方案。提示四
\\n\\n
由于答案可能很大,将它对 10^9 + 7 取余 后返回。
\\n我们每次计算完dp[i]的值后,都需要取余操作。
\\ndp[i] %= mod;
代码
\\n###java
\\n\\nclass Solution {\\n public int countTexts(String pressedKeys) {\\n int mod = (int) 1e9 + 7;\\n // 分两种情况,当前i与i-1的字符相同,不相同\\n // 不相同:dp[i] += dp[i - 1]\\n // 相同:与i-1相同 dp[i] += dp[i-1] + dp[i-2]\\n // 与i-2也相同:dp[i] += dp[i-3]\\n int n = pressedKeys.length();\\n char[] chs = pressedKeys.toCharArray();\\n // dp[i]表示以pressKey[i]结尾的方案个数\\n long[] dp = new long[n];\\n dp[0] = 1;\\n for (int i = 1; i < n; i++) {\\n dp[i] = dp[i - 1];\\n if (chs[i] == chs[i - 1]) {\\n dp[i] += i >= 2 ? dp[i - 2] : 1;\\n if (i >= 2 && chs[i] == chs[i - 2]) {\\n dp[i] += i >= 3 ? dp[i - 3] : 1;\\n if ((chs[i] == \'7\' || chs[i] == \'9\') && i >= 3 && chs[i] == chs[i - 3]) {\\n dp[i] += i >= 4 ? dp[i - 4] : 1;\\n }\\n }\\n }\\n dp[i] %= mod;\\n }\\n return (int) dp[n - 1];\\n }\\n}\\n
\\n
优化
\\n\\n
\\n- 修改dp[i]定义为:以pressKey[i-1]结尾的方案个数;
\\n- base code:\\n
\\n\\n
\\n- dp[0]:没有字符,也算1个方案;
\\n- dp[1]:只有1个字符时,方案数目为1;
\\n- 动规方程:\\n
\\n\\n
\\n- 当chs[i-1]与chs[i-2]不同时,
\\ndp[i] = dp[i-1]
;- 当chs[i-1]与chs[i-2]相同时,
\\ndp[i] = dp[i-1] + dp[i-2]
;- 当chs[i-1]与chs[i-2]、chs[i-3]都相同时,
\\ndp[i] = dp[i-1] + dp[i-2] + dp[i-3]
;- 当chs[i-1]为7或9,且与chs[i-2]、chs[i-3]、chs[i-4]都相同时,
\\ndp[i] = dp[i-1] + dp[i-2] + dp[i-3] + dp[i-4]
。代码(优化后)
\\n###java
\\n\\nclass Solution {\\n public int countTexts(String pressedKeys) {\\n int mod = (int) 1e9 + 7;\\n // 分两种情况,当前第i-1与第i-2个的字符相同,或不相同\\n // 不相同:dp[i] += dp[i - 1]\\n // 相同:与i-2相同 dp[i] += dp[i-1] + dp[i-2]\\n // 与i-3也相同:dp[i] += dp[i-3]\\n int n = pressedKeys.length();\\n char[] chs = pressedKeys.toCharArray();\\n // dp[i]表示以pressKey[i-1]结尾的方案个数\\n long[] dp = new long[n + 1];\\n // 没有字符,也算1个方案\\n dp[0] = 1;\\n // 只有1个字符时,方案数目为1\\n dp[1] = 1;\\n for (int i = 2; i <= n; i++) {\\n dp[i] = dp[i - 1];\\n if (chs[i - 1] == chs[i - 2]) {\\n dp[i] += dp[i - 2];\\n if (i >= 3 && chs[i - 1] == chs[i - 3]) {\\n dp[i] += dp[i - 3];\\n if ((chs[i - 1] == \'7\' || chs[i - 1] == \'9\') && i >= 4 && chs[i - 1] == chs[i - 4]) {\\n dp[i] += dp[i - 4];\\n }\\n }\\n }\\n dp[i] %= mod;\\n }\\n return (int) dp[n];\\n }\\n}\\n
第二次优化
\\n根据观察可知,dp[i]的值的大小只与dp[i-1]、dp[i-2]、dp[i-3]、dp[i-4]有关,故我们可以压缩空间来进行优化。
\\n
\\n我们分别用a0代表dp[i],a1来代表dp[i-1],a2来代表dp[i-2],a3来代表dp[i-3],a4来代表dp[i-4]来对上一版代码进行优化。代码(第二次优化)
\\n###java
\\n\\nclass Solution {\\n public int countTexts(String pressedKeys) {\\n int mod = (int) 1e9 + 7;\\n // 分两种情况,当前第i-1与第i-2个的字符相同,或不相同\\n // 不相同:dp[i] += dp[i - 1]\\n // 相同:与i-2相同 dp[i] += dp[i-1] + dp[i-2]\\n // 与i-3也相同:dp[i] += dp[i-3]\\n int n = pressedKeys.length();\\n char[] chs = pressedKeys.toCharArray();\\n // dp[i]表示以pressKey[i-1]结尾的方案个数\\n long a0 = 1, a1 = 1, a2 = 1, a3 = 1, a4 = 1;\\n for (int i = 2; i <= n; i++) {\\n a0 = a1;\\n if (chs[i - 1] == chs[i - 2]) {\\n a0 += a2;\\n if (i >= 3 && chs[i - 1] == chs[i - 3]) {\\n a0 += a3;\\n if ((chs[i - 1] == \'7\' || chs[i - 1] == \'9\') && i >= 4 && chs[i - 1] == chs[i - 4]) {\\n a0 += a4;\\n }\\n }\\n }\\n a0 %= mod;\\n // 依次赋值\\n a4 = a3;\\n a3 = a2;\\n a2 = a1;\\n a1 = a0;\\n }\\n return (int) a0;\\n }\\n}\\n
\\n","description":"解题思路 提示一\\n\\n当字符为7或9时,可以变化的字母为4个。\\n\\n提示二\\n\\ndp[i]定义:以pressKey[i]结尾的方案个数\\n base code\\n dp[0] = 1(解释:如果只有一个字符时,仅有一种方案,以“2”为例子:只能为“a”)\\n 动规方程,分类讨论:\\n\\n当chs[i]与chs[i-1]不同时,dp[i] = dp[i-1];\\n当chs[i]与chs[i-1]相同时,dp[i] = dp[i-1] + dp[i-2];\\n当chs[i]与chs[i-1]、chs[i-2]都相同时,dp[i] = dp[i-1] + dp[i-2] + dp[i-3];…","guid":"https://leetcode.cn/problems/count-number-of-texts//solution/dao-ge-shua-ti-by-lcfgrn-z2cp","author":"lcfgrn","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-08T08:43:39.334Z","media":[{"url":"https://pic.leetcode-cn.com/1652001287-czpkns-image.png","type":"photo","width":496,"height":282,"blurhash":"LRRD1cxut7%M~Raxa#j]ESofj?of"},{"url":"https://pic.leetcode-cn.com/1652180967-jbxgxv-image.png","type":"photo","width":504,"height":290,"blurhash":"LRRMlB%2xt-;~RWVWFa$Iwofj=oe"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一看就懂不解释","url":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/by-freeyourmind-ee7w","content":"
\\n","description":"R = [\'999\', \'888\', \'777\', \'666\', \'555\', \'444\', \'333\', \'222\', \'111\', \'000\'] class Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n return next(filter(lambda r: r in num, R), \'\')","guid":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/by-freeyourmind-ee7w","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-08T04:17:06.034Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:十次遍历 / 一次遍历+优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/ku-han-shu-mo-ni-by-endlesscheng-95jd","content":"R = [\'999\', \'888\', \'777\', \'666\', \'555\', \'444\', \'333\', \'222\', \'111\', \'000\']\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n return next(filter(lambda r: r in num, R), \'\')\\n
方法一:十次遍历
\\n依次判断 $\\\\texttt{999},\\\\texttt{888},\\\\ldots,\\\\texttt{000}$ 是否在 $\\\\textit{num}$ 中,在就作为答案返回。
\\n如果都不在,返回空字符串。
\\n\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n for d in reversed(digits):\\n s = d * 3\\n if s in num:\\n return s\\n return \\"\\"\\n
\\nclass Solution {\\n public String largestGoodInteger(String num) {\\n for (char d = \'9\'; d >= \'0\'; d--) {\\n String s = String.valueOf(d).repeat(3);\\n if (num.contains(s)) {\\n return s;\\n }\\n }\\n return \\"\\";\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string largestGoodInteger(string num) {\\n for (char d = \'9\'; d >= \'0\'; d--) {\\n string s(3, d);\\n if (num.find(s) != string::npos) {\\n return s;\\n }\\n }\\n return \\"\\";\\n }\\n};\\n
\\nfunc largestGoodInteger(num string) string {\\nfor d := \'9\'; d >= \'0\'; d-- {\\ns := strings.Repeat(string(d), 3)\\nif strings.Contains(num, s) {\\nreturn s\\n}\\n}\\nreturn \\"\\"\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(Dnk)$,其中 $n$ 是 $\\\\textit{num}$ 的长度,$D=10$,$k=3$。在 $\\\\textit{num}$ 中寻找长为 $k$ 的子串的时间复杂度为 $\\\\mathcal{O}(nk)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。返回值不计入。
\\n方法二:一次遍历
\\n遍历 $\\\\textit{num}$,比较当前字符和右边两个字符是否相同,相同则更新答案的最大值。
\\n优化前
\\n\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n mx = \\"\\"\\n for i in range(len(num) - 2):\\n if mx < num[i] == num[i + 1] == num[i + 2]:\\n mx = num[i]\\n return mx * 3\\n
\\nclass Solution {\\n String largestGoodInteger(String num) {\\n char mx = 0;\\n for (int i = 0; i < num.length() - 2; i++) {\\n char d = num.charAt(i);\\n if (d > mx && d == num.charAt(i + 1) && d == num.charAt(i + 2)) {\\n mx = d;\\n }\\n }\\n return mx > 0 ? String.valueOf(mx).repeat(3) : \\"\\";\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string largestGoodInteger(string num) {\\n char mx = 0;\\n for (int i = 0; i + 2 < num.length(); i++) {\\n char d = num[i];\\n if (d > mx && d == num[i + 1] && d == num[i + 2]) {\\n mx = d;\\n }\\n }\\n return mx ? string(3, mx) : \\"\\";\\n }\\n};\\n
\\nfunc largestGoodInteger(num string) string {\\nmx := byte(0)\\nfor i := range len(num) - 2 {\\nd := num[i]\\nif d > mx && d == num[i+1] && d == num[i+2] {\\nmx = d\\n}\\n}\\nif mx == 0 {\\nreturn \\"\\"\\n}\\nreturn strings.Repeat(string(mx), 3)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(nk)$,其中 $n$ 是 $\\\\textit{num}$ 的长度,$k=3$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。返回值不计入。
\\n优化
\\n用一个计数器 $\\\\textit{cnt}$ 记录当前匹配长度,如果长度等于 $3$,则更新答案的最大值。如果相邻字母不同,则重置 $\\\\textit{cnt}$。
\\n这样可以做到完美的线性复杂度,即使题目把 $3$ 改成其他数字,时间复杂度仍然是 $\\\\mathcal{O}(n)$ 的。
\\n\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n mx = \\"\\"\\n cnt = 1\\n for i in range(1, len(num)):\\n if num[i] != num[i - 1]:\\n cnt = 1\\n continue\\n cnt += 1\\n if cnt == 3 and num[i] > mx:\\n mx = num[i]\\n return mx * 3\\n
\\nclass Solution {\\n String largestGoodInteger(String num) {\\n char mx = 0;\\n int cnt = 1;\\n for (int i = 1; i < num.length(); i++) {\\n char d = num.charAt(i);\\n if (d != num.charAt(i - 1)) {\\n cnt = 1;\\n continue;\\n }\\n cnt++;\\n if (cnt == 3 && d > mx) {\\n mx = d;\\n }\\n }\\n return mx > 0 ? String.valueOf(mx).repeat(3) : \\"\\";\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string largestGoodInteger(string num) {\\n char mx = 0;\\n int cnt = 1;\\n for (int i = 1; i < num.length(); i++) {\\n char d = num[i];\\n if (d != num[i - 1]) {\\n cnt = 1;\\n continue;\\n }\\n cnt++;\\n if (cnt == 3 && d > mx) {\\n mx = d;\\n }\\n }\\n return mx ? string(3, mx) : \\"\\";\\n }\\n};\\n
\\nfunc largestGoodInteger(num string) string {\\nmx := byte(0)\\ncnt := 1\\nfor i := 1; i < len(num); i++ {\\nd := num[i]\\nif d != num[i-1] {\\ncnt = 1\\ncontinue\\n}\\ncnt++\\nif cnt == 3 && d > mx {\\nmx = d\\n}\\n}\\nif mx == 0 {\\nreturn \\"\\"\\n}\\nreturn strings.Repeat(string(mx), 3)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{num}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。返回值不计入。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:十次遍历 依次判断 $\\\\texttt{999},\\\\texttt{888},\\\\ldots,\\\\texttt{000}$ 是否在 $\\\\textit{num}$ 中,在就作为答案返回。\\n\\n如果都不在,返回空字符串。\\n\\nclass Solution:\\n def largestGoodInteger(self, num: str) -> str:\\n for d in reversed(digits):\\n s = d * 3\\n if s in num:\\n return s…","guid":"https://leetcode.cn/problems/largest-3-same-digit-number-in-string//solution/ku-han-shu-mo-ni-by-endlesscheng-95jd","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-08T04:07:50.635Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分组 + 线性 DP + 乘法原理(Python/Go/C++/Java)","url":"https://leetcode.cn/problems/count-number-of-texts//solution/by-endlesscheng-gj8f","content":"本质上是 70. 爬楼梯,每次可以跳 $1$ 到 $3$ 或者 $1$ 到 $4$ 个台阶,计算跳 $\\\\textit{cnt}$ 个台阶的方案数。其中 $\\\\textit{cnt}$ 表示连续相同子串的长度。
\\n对于字符不为 $\\\\texttt{7}$ 或 $\\\\texttt{9}$ 的情况,定义一个类似爬楼梯的 DP,即 $f[i]$ 表示长为 $i$ 的只有一种字符的字符串所对应的文字信息种类数,我们可以将末尾的 $1$ 个、$2$ 个或 $3$ 个字符变成一个字母,那么有转移方程
\\n$$
\\n
\\nf[i] = f[i-1]+f[i-2]+f[i-3]
\\n$$对于字符为 $\\\\texttt{7}$ 或 $\\\\texttt{9}$ 的情况,定义 $g[i]$ 表示长为 $i$ 的只有一种字符的字符串对应的文字信息种类数,可以得到类似的转移方程
\\n$$
\\n
\\ng[i] = g[i-1]+g[i-2]+g[i-3]+g[i-4]
\\n$$由于各个组(连续相同子串)的打字方案互相独立,根据乘法原理,把各个组的方案数相乘,即为答案。
\\n记得取模。关于取模的知识点,见 模运算的世界:当加减乘除遇上取模。
\\n\\nMOD = 1_000_000_007\\nf = [1, 1, 2, 4]\\ng = [1, 1, 2, 4]\\nfor _ in range(10 ** 5 - 3): # 预处理所有长度的结果\\n f.append((f[-1] + f[-2] + f[-3]) % MOD)\\n g.append((g[-1] + g[-2] + g[-3] + g[-4]) % MOD)\\n\\nclass Solution:\\n def countTexts(self, pressedKeys: str) -> int:\\n ans = 1\\n for ch, s in groupby(pressedKeys):\\n m = len(list(s))\\n ans = ans * (g[m] if ch in \\"79\\" else f[m]) % MOD\\n return ans\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n private static final int MX = 100_001;\\n private static final long[] f = new long[MX];\\n private static final long[] g = new long[MX];\\n\\n static {\\n f[0] = g[0] = 1;\\n f[1] = g[1] = 1;\\n f[2] = g[2] = 2;\\n f[3] = g[3] = 4;\\n for (int i = 4; i < MX; i++) {\\n f[i] = (f[i - 1] + f[i - 2] + f[i - 3]) % MOD;\\n g[i] = (g[i - 1] + g[i - 2] + g[i - 3] + g[i - 4]) % MOD;\\n }\\n }\\n\\n public int countTexts(String s) {\\n long ans = 1;\\n int cnt = 0;\\n for (int i = 0; i < s.length(); i++) {\\n char c = s.charAt(i);\\n cnt++;\\n if (i == s.length() - 1 || c != s.charAt(i + 1)) {\\n ans = ans * (c != \'7\' && c != \'9\' ? f[cnt] : g[cnt]) % MOD;\\n cnt = 0;\\n }\\n }\\n return (int) ans;\\n }\\n}\\n
\\nconst int MOD = 1\'000\'000\'007;\\nconst int MX = 100\'001;\\n\\nlong long f[MX], g[MX];\\n\\nint init = []() {\\n f[0] = g[0] = 1;\\n f[1] = g[1] = 1;\\n f[2] = g[2] = 2;\\n f[3] = g[3] = 4;\\n for (int i = 4; i < MX; ++i) {\\n f[i] = (f[i - 1] + f[i - 2] + f[i - 3]) % MOD;\\n g[i] = (g[i - 1] + g[i - 2] + g[i - 3] + g[i - 4]) % MOD;\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int countTexts(string s) {\\n long long ans = 1;\\n int cnt = 0;\\n for (int i = 0; i < s.length(); i++) {\\n char c = s[i];\\n cnt++;\\n if (i == s.length() - 1 || c != s[i + 1]) {\\n ans = ans * (c != \'7\' && c != \'9\' ? f[cnt] : g[cnt]) % MOD;\\n cnt = 0;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nconst mod = 1_000_000_007\\nconst mx = 100_001\\n\\nvar f = [mx]int{1, 1, 2, 4}\\nvar g = f\\n\\nfunc init() {\\n for i := 4; i < mx; i++ {\\n f[i] = (f[i-1] + f[i-2] + f[i-3]) % mod\\n g[i] = (g[i-1] + g[i-2] + g[i-3] + g[i-4]) % mod\\n }\\n}\\n\\nfunc countTexts(s string) int {\\n ans, cnt := 1, 0\\n for i, c := range s {\\n cnt++\\n if i == len(s)-1 || byte(c) != s[i+1] {\\n if c != \'7\' && c != \'9\' {\\n ans = ans * f[cnt] % mod\\n } else {\\n ans = ans * g[cnt] % mod\\n }\\n cnt = 0\\n }\\n }\\n return ans\\n}\\n
复杂度分析
\\n忽略预处理的时间和空间。
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{pressedKeys}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"本质上是 70. 爬楼梯,每次可以跳 $1$ 到 $3$ 或者 $1$ 到 $4$ 个台阶,计算跳 $\\\\textit{cnt}$ 个台阶的方案数。其中 $\\\\textit{cnt}$ 表示连续相同子串的长度。 对于字符不为 $\\\\texttt{7}$ 或 $\\\\texttt{9}$ 的情况,定义一个类似爬楼梯的 DP,即 $f[i]$ 表示长为 $i$ 的只有一种字符的字符串所对应的文字信息种类数,我们可以将末尾的 $1$ 个、$2$ 个或 $3$ 个字符变成一个字母,那么有转移方程\\n\\n$$\\n f[i] = f[i-1]+f[i-2]+f[i-3]\\n $$\\n\\n对于字符…","guid":"https://leetcode.cn/problems/count-number-of-texts//solution/by-endlesscheng-gj8f","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-08T04:06:44.128Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DP","url":"https://leetcode.cn/problems/count-number-of-texts//solution/by-hu-li-hu-wai-14s1","content":"方法:动态规划
\\n思路及算法
\\n
\\n理解到该题是斐波那契数列的变种就很好做了
\\n定义 f[n] 为同一个字母的连续长度 n 含有的组合可能数,则有\\n
\\n- 对于3个字母的数字有 f[n] = f[n-1] + f[n-2] + f[n-3]
\\n- 对于4个字母的数字7、9,有 f[n] = f[n-1] + f[n-2] + f[n-3] + f[n-4]
\\n不同数字乘积组合数即可
\\nTips:
\\n
\\n1.用 long 保存, 加了 3/4 次, 有可能溢出
\\n2.题中数据长度 1e5, 这里我们先预处理好 N 含有的组合可能数, 避免内层找不同的数字时重复计算代码
\\n###java
\\n\\nclass Solution {\\n\\n static int N = 100010;\\n static long[] three = new long[N];\\n static long[] four = new long[N];\\n\\n static int MOD = (int) 1e9 + 7;\\n\\n static {\\n three[0] = 1;\\n three[1] = 1;\\n three[2] = 2;\\n three[3] = 4;\\n four[0] = 1;\\n four[1] = 1;\\n four[2] = 2;\\n four[3] = 4;\\n for (int i = 4; i < N; i++) {\\n three[i] = three[i - 1] + three[i - 2] + three[i - 3];\\n three[i] %= MOD;\\n four[i] = four[i - 1] + four[i - 2] + four[i - 3] + four[i - 4];\\n four[i] %= MOD;\\n }\\n }\\n\\n\\n public int countTexts(String pressedKeys) {\\n char[] cs = pressedKeys.toCharArray();\\n int n = cs.length, i = 0;\\n long ans = 1;\\n while (i < n) {\\n int j = i;\\n while (j + 1 < n && cs[j] == cs[j + 1]) j++;\\n boolean isFour = cs[i] == \'7\' | cs[i] == \'9\';\\n long cur = isFour ? four[j - i + 1] : three[j - i + 1];\\n ans = (ans * cur) % MOD;\\n i = j + 1;\\n }\\n return (int) ans;\\n }\\n}\\n
复杂度分析
\\n
\\n时间复杂度:O(N)
\\n空间复杂度:O(N)结语
\\n","description":"方法:动态规划 思路及算法\\n 理解到该题是斐波那契数列的变种就很好做了\\n 定义 f[n] 为同一个字母的连续长度 n 含有的组合可能数,则有\\n\\n对于3个字母的数字有 f[n] = f[n-1] + f[n-2] + f[n-3]\\n对于4个字母的数字7、9,有 f[n] = f[n-1] + f[n-2] + f[n-3] + f[n-4]\\n\\n不同数字乘积组合数即可\\n\\nTips:\\n 1.用 long 保存, 加了 3/4 次, 有可能溢出\\n 2.题中数据长度 1e5, 这里我们先预处理好 N 含有的组合可能数, 避免内层找不同的数字时重复计算\\n\\n代码\\n\\n###java…","guid":"https://leetcode.cn/problems/count-number-of-texts//solution/by-hu-li-hu-wai-14s1","author":"hu-li-hu-wai","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-08T04:06:25.593Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"猫和老鼠 II","url":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/mao-he-lao-shu-ii-by-leetcode-solution-e5io","content":"
\\n如果对您有帮助,欢迎点赞、收藏、关注 沪里户外,让更多的小伙伴看到,祝大家offer多多,AC多多!前言
\\n这道题是「913. 猫和老鼠」的进阶,建议读者在阅读本文之前首先阅读「913. 猫和老鼠的官方题解」,了解博弈问题中的必胜状态、必败状态与必和状态的概念,以及最优策略。
\\n博弈问题通常可以使用动态规划求解。由于动态规划的时间复杂度和游戏轮数有关,因此动态规划的时间复杂度较高。本文不具体介绍动态规划的解法,感兴趣的读者可以自行尝试。
\\n博弈问题的另一种解法是拓扑排序。和动态规划相比,拓扑排序的时间复杂度和游戏轮数无关,因此拓扑排序的时间复杂度较低。
\\n方法一:拓扑排序
\\n概述
\\n给定的网格包含 $\\\\textit{rows}$ 行和 $\\\\textit{cols}$ 列,网格中的单元格总数是 $\\\\textit{total} = \\\\textit{rows} \\\\times \\\\textit{cols}$。每个单元格对应一个编号,第 $i$ 行第 $j$ 列的单元格编号是 $i \\\\times \\\\textit{cols} + j$,其中 $0 \\\\le i < \\\\textit{rows}$,$0 \\\\le j < \\\\textit{cols}$。
\\n首先遍历网格,得到猫和老鼠初始时所在的单元格以及食物所在的单元格,然后计算获胜方。
\\n求解简化问题
\\n这道题规定了移动次数上限为 $1000$,如果在 $1000$ 次移动之内老鼠不能获胜,则猫获胜。可以首先考虑一个简化问题,在没有移动次数上限的情况下计算获胜方。该简化问题可以使用拓扑排序得到结果。
\\n游戏中的状态由老鼠的位置、猫的位置和轮到移动的一方三个因素决定。初始时,只有边界情况的胜负结果已知,其余所有状态的结果都初始化为未知。边界情况为直接确定胜负的情况,包括三种情况:
\\n\\n
\\n- \\n
\\n猫和老鼠在同一个单元格,无论在哪个单元格,都是猫获胜;
\\n- \\n
\\n猫和食物在同一个单元格,无论老鼠在哪个单元格,都是猫获胜;
\\n- \\n
\\n老鼠和食物在同一个单元格,只要猫和食物不在同一个单元格,无论猫在哪个单元格,都是老鼠获胜。
\\n从边界情况出发遍历其他情况。对于当前状态,可以得到老鼠所在的单元格、猫所在的单元格和轮到移动的一方,根据当前状态可知上一轮的所有可能状态,其中上一轮的移动方和当前的移动方相反,上一轮的移动方在上一轮状态和当前状态所在的单元格相同或不同(注意可以停留在原地)。假设当前状态是老鼠所在的单元格编号是 $\\\\textit{mouse}$,猫所在的单元格编号是 $\\\\textit{cat}$,则根据当前的移动方,可以得到上一轮的所有可能状态:
\\n\\n
\\n- \\n
\\n如果当前的移动方是老鼠,则上一轮的移动方是猫,上一轮状态中老鼠所在的单元格编号是 $\\\\textit{mouse}$,猫所在的单元格编号可能是 $\\\\textit{cat}$ 或者向四个方向之一跳跃到达的单元格编号,跳跃的距离不超过 $\\\\textit{catJump}$ 且不能跳过墙及不能跳出网格;
\\n- \\n
\\n如果当前的移动方是猫,则上一轮的移动方是老鼠,上一轮状态中猫所在的单元格编号是 $\\\\textit{cat}$,老鼠所在的单元格编号可能是 $\\\\textit{mouse}$ 或者向四个方向之一跳跃到达的单元格编号,跳跃的距离不超过 $\\\\textit{mouseJump}$ 且不能跳过墙及不能跳出网格。
\\n对于上一轮的每一种可能的状态,如果该状态的结果已知,则不需要重复计算该状态的结果,只有对结果未知的状态,才需要计算该状态的结果。对于上一轮的移动方,只有当可以确定上一轮状态是必胜状态或者必败状态时,才更新上一轮状态的结果。
\\n\\n
\\n- \\n
\\n如果上一轮的移动方和当前状态的获胜方相同,由于当前状态为上一轮的移动方的必胜状态,因此上一轮的移动方一定可以移动到当前状态而获胜,上一轮状态为上一轮的移动方的必胜状态。
\\n- \\n
\\n如果上一轮的移动方和当前状态的获胜方不同,则上一轮的移动方需要尝试其他可能的移动,可能有以下三种情况:
\\n\\n
\\n- \\n
\\n如果存在一种移动可以到达上一轮的移动方的必胜状态,则上一轮状态为上一轮的移动方的必胜状态;
\\n- \\n
\\n如果所有的移动都到达上一轮的移动方的必败状态,则上一轮状态为上一轮的移动方的必败状态;
\\n- \\n
\\n如果所有的移动都不能到达上一轮的移动方的必胜状态,但是存在一种移动可以到达上一轮的移动方的未知状态,则上一轮状态为上一轮的移动方的未知状态。
\\n其中,对于必败状态与未知状态的判断依据为上一轮的移动方可能的移动是都到达必败状态还是可以到达未知状态。为了实现必败状态与未知状态的判断,需要记录每个状态的度,初始时每个状态的度为当前玩家在当前单元格可以到达的单元格数,由于可以停留在原地,因此初始时每个状态的度为当前玩家在当前单元格可以跳跃到达的单元格数加 $1$。
\\n遍历过程中,从当前状态出发遍历上一轮的所有可能状态,如果上一轮状态的结果未知且上一轮的移动方和当前状态的获胜方不同,则将上一轮状态的度减 $1$。如果上一轮状态的度减少到 $0$,则从上一轮状态出发到达的所有状态都是上一轮的移动方的必败状态,因此上一轮状态也是上一轮的移动方的必败状态。
\\n在确定上一轮状态的结果(必胜或必败)之后,即可从上一轮状态出发,遍历其他的未知状态。当没有更多的状态可以确定胜负结果时,遍历结束,此时即可得到初始状态的结果。
\\n求解原始问题
\\n上述解法为简化问题的解法,没有考虑移动次数的上限。由于移动次数的限制只会影响到平局以及老鼠获胜的条件,因此只需要对平局和老鼠获胜的情况考虑移动次数。
\\n平局对应上述解法中的未知状态,表示当猫和老鼠都按照最优策略参与游戏时,双方都无法在有限的移动次数内到达食物所在的单元格,移动次数一定会超过老鼠获胜的上限,因此未知状态对应的结果都是猫获胜。
\\n如果在简化问题中,从初始状态开始游戏的结果是老鼠获胜,即老鼠先到达食物,则在原始问题中,需要计算从初始状态至老鼠到达食物的移动次数,只有当移动次数不超过 $1000$ 时,老鼠才能获胜,否则猫获胜。
\\n为了计算从初始状态至老鼠到达食物的移动次数,在拓扑排序的过程中除了记录每个状态的结果以外,还需要记录从边界情况到达每个状态的移动次数,等价于从每个状态到边界情况的移动次数。每个状态对应的移动次数计算方法如下:
\\n\\n
\\n- \\n
\\n边界情况可以直接确定胜负,因此移动次数为 $0$;
\\n- \\n
\\n如果状态 $s_1$ 和状态 $s_2$ 相邻(即状态 $s_2$ 是状态 $s_1$ 的上一轮的状态之一),且状态 $s_1$ 的结果和移动次数已知,记状态 $s_1$ 的移动次数为 $x$,如果可以确定状态 $s_2$ 的结果,则状态 $s_2$ 的移动次数为 $x + 1$。
\\n证明
\\n对于上述解法的正确性证明,需要证明两点,一是未知状态的正确性,二是移动次数的正确性。
\\n证明一:未知状态的正确性
\\n遍历结束之后,如果一个状态的结果未知,则该状态满足以下两个条件:
\\n\\n
\\n- \\n
\\n从该状态出发,任何移动都无法到达该状态的移动方的必胜状态;
\\n- \\n
\\n从该状态出发,存在一种移动可以到达未知状态。
\\n对于结果未知的状态,如果其实际结果是该状态的移动方必胜,则一定存在一个下一轮状态,为当前状态的移动方的必胜状态,在根据下一轮状态的结果标记当前状态的结果时会将当前状态标记为当前状态的移动方的必胜状态,和结果未知矛盾。
\\n对于结果未知的状态,如果其实际结果是该状态的移动方必败,则所有的下一轮状态都为当前状态的移动方的必败状态,在根据下一轮状态的结果标记当前状态的结果时会将当前状态标记为当前状态的移动方的必败状态,和结果未知矛盾。
\\n因此,对于结果不是任何一方必胜的状态,实际结果一定是未知。根据游戏规则,未知状态表示在该状态下当猫和老鼠都按照最优策略参与游戏时,双方都无法在有限的移动次数内到达食物所在的单元格,移动次数一定会超过老鼠获胜的上限,因此未知状态对应的结果都是猫获胜。
\\n证明二:移动次数的正确性
\\n在考虑移动次数的情况下,每个玩家的最优策略应满足以下三点:
\\n\\n
\\n- \\n
\\n当自己可以到达必胜状态时,应将移动次数最小化;
\\n- \\n
\\n当自己无法到达必胜状态时,如果可以避免自己到达必败状态,则应到达未知状态;
\\n- \\n
\\n当无法避免自己到达必败状态时,应将移动次数最大化。
\\n由于拓扑排序的实现方式是广度优先搜索,因此拓扑排序的过程中遍历状态的顺序为移动次数递增的顺序。
\\n边界情况的移动次数为 $0$。从已知状态出发计算未知状态的结果和移动次数,将已知状态记为 $s_1$,未知状态记为 $s_2$,且状态 $s_1$ 和状态 $s_2$ 相邻(即状态 $s_2$ 是状态 $s_1$ 的上一轮的状态之一),记状态 $s_1$ 的移动次数为 $x$,考虑以下两种情况。
\\n\\n
\\n- \\n
\\n如果状态 $s_2$ 的移动方和状态 $s_1$ 的获胜方相同,则状态 $s_2$ 的移动方会移动到状态 $s_1$ 从而确保胜利,因此状态 $s_2$ 的移动方必胜,移动次数为 $x + 1$,且该移动次数为状态 $s_2$ 到边界情况的最少移动次数。
\\n\\n\\n假设存在另一个已知状态 $s_3$ 的获胜方和状态 $s_1$ 相同且状态 $s_3$ 的移动次数小于 $x$,则状态 $s_3$ 在状态 $s_1$ 之前被遍历,在遍历到状态 $s_3$ 时就会更新状态 $s_2$ 的结果,和遍历到状态 $s_1$ 时状态 $s_2$ 的结果未知矛盾。因此状态 $s_2$ 的最少移动次数为 $x + 1$。
\\n- \\n
\\n如果状态 $s_2$ 的移动方和状态 $s_1$ 的获胜方不同,则只有当状态 $s_2$ 的所有相邻状态都已知是状态 $s_2$ 的移动方的必败状态时,才能确定状态 $s_2$ 的移动方必败。如果在遍历到状态 $s_1$ 时可以确定状态 $s_2$ 的结果为移动方必败,则在遍历到状态 $s_1$ 之前,状态 $s_2$ 的所有相邻状态都已经遍历过,即状态 $s_1$ 是最后一个遍历到的状态 $s_2$ 的相邻状态,因此在状态 $s_2$ 的所有相邻状态中,状态 $s_1$ 的移动次数最多,状态 $s_2$ 的移动次数是 $x + 1$ 符合必败状态下将移动次数最大化。
\\n代码
\\n###Python
\\n\\nMOUSE_TURN = 0\\nCAT_TURN = 1\\nUNKNOWN = 0\\nMOUSE_WIN = 1\\nCAT_WIN = 2\\nMAX_MOVES = 1000\\nDIRS = ((-1, 0), (1, 0), (0, -1), (0, 1))\\n\\nclass Solution:\\n def canMouseWin(self, grid: List[str], catJump: int, mouseJump: int) -> bool:\\n rows, cols = len(grid), len(grid[0])\\n\\n def getPos(row: int, col: int) -> int:\\n return row * cols + col\\n\\n startMouse = startCat = food = 0\\n for i, row in enumerate(grid):\\n for j, ch in enumerate(row):\\n if ch == \'M\':\\n startMouse = getPos(i, j)\\n elif ch == \'C\':\\n startCat = getPos(i, j)\\n elif ch == \'F\':\\n food = getPos(i, j)\\n\\n # 计算每个状态的度\\n total = rows * cols\\n degrees = [[[0, 0] for _ in range(total)] for _ in range(total)]\\n for mouse in range(total):\\n mouseRow, mouseCol = divmod(mouse, cols)\\n if grid[mouseRow][mouseCol] == \'#\':\\n continue\\n for cat in range(total):\\n catRow, catCol = divmod(cat, cols)\\n if grid[catRow][catCol] == \'#\':\\n continue\\n degrees[mouse][cat][MOUSE_TURN] += 1\\n degrees[mouse][cat][CAT_TURN] += 1\\n for dx, dy in DIRS:\\n row, col, jump = mouseRow + dx, mouseCol + dy, 1\\n while 0 <= row < rows and 0 <= col < cols and grid[row][col] != \'#\' and jump <= mouseJump:\\n nextMouse = getPos(row, col)\\n nextCat = getPos(catRow, catCol)\\n degrees[nextMouse][nextCat][MOUSE_TURN] += 1\\n row += dx\\n col += dy\\n jump += 1\\n row, col, jump = catRow + dx, catCol + dy, 1\\n while 0 <= row < rows and 0 <= col < cols and grid[row][col] != \'#\' and jump <= catJump:\\n nextMouse = getPos(mouseRow, mouseCol)\\n nextCat = getPos(row, col)\\n degrees[nextMouse][nextCat][CAT_TURN] += 1\\n row += dx\\n col += dy\\n jump += 1\\n\\n results = [[[[0, 0], [0, 0]] for _ in range(total)] for _ in range(total)]\\n q = deque()\\n\\n # 猫和老鼠在同一个单元格,猫获胜\\n for pos in range(total):\\n row, col = divmod(pos, cols)\\n if grid[row][col] == \'#\':\\n continue\\n results[pos][pos][MOUSE_TURN][0] = CAT_WIN\\n results[pos][pos][MOUSE_TURN][1] = 0\\n results[pos][pos][CAT_TURN][0] = CAT_WIN\\n results[pos][pos][CAT_TURN][1] = 0\\n q.append((pos, pos, MOUSE_TURN))\\n q.append((pos, pos, CAT_TURN))\\n\\n # 猫和食物在同一个单元格,猫获胜\\n for mouse in range(total):\\n mouseRow, mouseCol = divmod(mouse, cols)\\n if grid[mouseRow][mouseCol] == \'#\' or mouse == food:\\n continue\\n results[mouse][food][MOUSE_TURN][0] = CAT_WIN\\n results[mouse][food][MOUSE_TURN][1] = 0\\n results[mouse][food][CAT_TURN][0] = CAT_WIN\\n results[mouse][food][CAT_TURN][1] = 0\\n q.append((mouse, food, MOUSE_TURN))\\n q.append((mouse, food, CAT_TURN))\\n\\n # 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for cat in range(total):\\n catRow, catCol = divmod(cat, cols)\\n if grid[catRow][catCol] == \'#\' or cat == food:\\n continue\\n results[food][cat][MOUSE_TURN][0] = MOUSE_WIN\\n results[food][cat][MOUSE_TURN][1] = 0\\n results[food][cat][CAT_TURN][0] = MOUSE_WIN\\n results[food][cat][CAT_TURN][1] = 0\\n q.append((food, cat, MOUSE_TURN))\\n q.append((food, cat, CAT_TURN))\\n\\n def getPrevStates(mouse: int, cat: int, turn: int) -> List[Tuple[int, int, int]]:\\n mouseRow, mouseCol = divmod(mouse, cols)\\n catRow, catCol = divmod(cat, cols)\\n prevTurn = CAT_TURN if turn == MOUSE_TURN else MOUSE_TURN\\n maxJump = mouseJump if prevTurn == MOUSE_TURN else catJump\\n startRow = mouseRow if prevTurn == MOUSE_TURN else catRow\\n startCol = mouseCol if prevTurn == MOUSE_TURN else catCol\\n prevStates = [(mouse, cat, prevTurn)]\\n for dx, dy in DIRS:\\n i, j, jump = startRow + dx, startCol + dy, 1\\n while 0 <= i < rows and 0 <= j < cols and grid[i][j] != \'#\' and jump <= maxJump:\\n prevMouseRow = i if prevTurn == MOUSE_TURN else mouseRow\\n prevMouseCol = j if prevTurn == MOUSE_TURN else mouseCol\\n prevCatRow = catRow if prevTurn == MOUSE_TURN else i\\n prevCatCol = catCol if prevTurn == MOUSE_TURN else j\\n prevMouse = getPos(prevMouseRow, prevMouseCol)\\n prevCat = getPos(prevCatRow, prevCatCol)\\n prevStates.append((prevMouse, prevCat, prevTurn))\\n i += dx\\n j += dy\\n jump += 1\\n return prevStates\\n\\n # 拓扑排序\\n while q:\\n mouse, cat, turn = q.popleft()\\n result = results[mouse][cat][turn][0]\\n moves = results[mouse][cat][turn][1]\\n for prevMouse, prevCat, prevTurn in getPrevStates(mouse, cat, turn):\\n if results[prevMouse][prevCat][prevTurn][0] == UNKNOWN:\\n if result == MOUSE_WIN and prevTurn == MOUSE_TURN or result == CAT_WIN and prevTurn == CAT_TURN:\\n results[prevMouse][prevCat][prevTurn][0] = result\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1\\n q.append((prevMouse, prevCat, prevTurn))\\n else:\\n degrees[prevMouse][prevCat][prevTurn] -= 1\\n if degrees[prevMouse][prevCat][prevTurn] == 0:\\n loseResult = CAT_WIN if prevTurn == MOUSE_TURN else MOUSE_WIN\\n results[prevMouse][prevCat][prevTurn][0] = loseResult\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1\\n q.append((prevMouse, prevCat, prevTurn))\\n return results[startMouse][startCat][MOUSE_TURN][0] == MOUSE_WIN and results[startMouse][startCat][MOUSE_TURN][1] <= MAX_MOVES\\n
###Java
\\n\\nclass Solution {\\n static final int MOUSE_TURN = 0, CAT_TURN = 1;\\n static final int UNKNOWN = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\n static final int MAX_MOVES = 1000;\\n int[][] dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n int rows, cols;\\n String[] grid;\\n int catJump, mouseJump;\\n int food;\\n int[][][] degrees;\\n int[][][][] results;\\n\\n public boolean canMouseWin(String[] grid, int catJump, int mouseJump) {\\n this.rows = grid.length;\\n this.cols = grid[0].length();\\n this.grid = grid;\\n this.catJump = catJump;\\n this.mouseJump = mouseJump;\\n int startMouse = -1, startCat = -1;\\n for (int i = 0; i < rows; i++) {\\n for (int j = 0; j < cols; j++) {\\n char c = grid[i].charAt(j);\\n if (c == \'M\') {\\n startMouse = getPos(i, j);\\n } else if (c == \'C\') {\\n startCat = getPos(i, j);\\n } else if (c == \'F\') {\\n food = getPos(i, j);\\n }\\n }\\n }\\n int total = rows * cols;\\n degrees = new int[total][total][2];\\n results = new int[total][total][2][2];\\n Queue<int[]> queue = new ArrayDeque<int[]>();\\n // 计算每个状态的度\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow].charAt(mouseCol) == \'#\') {\\n continue;\\n }\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow].charAt(catCol) == \'#\') {\\n continue;\\n }\\n degrees[mouse][cat][MOUSE_TURN]++;\\n degrees[mouse][cat][CAT_TURN]++;\\n for (int[] dir : dirs) {\\n for (int row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row].charAt(col) != \'#\' && jump <= mouseJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(row, col), nextCat = getPos(catRow, catCol);\\n degrees[nextMouse][nextCat][MOUSE_TURN]++;\\n }\\n for (int row = catRow + dir[0], col = catCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row].charAt(col) != \'#\' && jump <= catJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(mouseRow, mouseCol), nextCat = getPos(row, col);\\n degrees[nextMouse][nextCat][CAT_TURN]++;\\n }\\n }\\n }\\n }\\n // 猫和老鼠在同一个单元格,猫获胜\\n for (int pos = 0; pos < total; pos++) {\\n int row = pos / cols, col = pos % cols;\\n if (grid[row].charAt(col) == \'#\') {\\n continue;\\n }\\n results[pos][pos][MOUSE_TURN][0] = CAT_WIN;\\n results[pos][pos][MOUSE_TURN][1] = 0;\\n results[pos][pos][CAT_TURN][0] = CAT_WIN;\\n results[pos][pos][CAT_TURN][1] = 0;\\n queue.offer(new int[]{pos, pos, MOUSE_TURN});\\n queue.offer(new int[]{pos, pos, CAT_TURN});\\n }\\n // 猫和食物在同一个单元格,猫获胜\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow].charAt(mouseCol) == \'#\' || mouse == food) {\\n continue;\\n }\\n results[mouse][food][MOUSE_TURN][0] = CAT_WIN;\\n results[mouse][food][MOUSE_TURN][1] = 0;\\n results[mouse][food][CAT_TURN][0] = CAT_WIN;\\n results[mouse][food][CAT_TURN][1] = 0;\\n queue.offer(new int[]{mouse, food, MOUSE_TURN});\\n queue.offer(new int[]{mouse, food, CAT_TURN});\\n }\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow].charAt(catCol) == \'#\' || cat == food) {\\n continue;\\n }\\n results[food][cat][MOUSE_TURN][0] = MOUSE_WIN;\\n results[food][cat][MOUSE_TURN][1] = 0;\\n results[food][cat][CAT_TURN][0] = MOUSE_WIN;\\n results[food][cat][CAT_TURN][1] = 0;\\n queue.offer(new int[]{food, cat, MOUSE_TURN});\\n queue.offer(new int[]{food, cat, CAT_TURN});\\n }\\n // 拓扑排序\\n while (!queue.isEmpty()) {\\n int[] state = queue.poll();\\n int mouse = state[0], cat = state[1], turn = state[2];\\n int result = results[mouse][cat][turn][0];\\n int moves = results[mouse][cat][turn][1];\\n List<int[]> prevStates = getPrevStates(mouse, cat, turn);\\n for (int[] prevState : prevStates) {\\n int prevMouse = prevState[0], prevCat = prevState[1], prevTurn = prevState[2];\\n if (results[prevMouse][prevCat][prevTurn][0] == UNKNOWN) {\\n boolean canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn][0] = result;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue.offer(new int[]{prevMouse, prevCat, prevTurn});\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--;\\n if (degrees[prevMouse][prevCat][prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn][0] = loseResult;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue.offer(new int[]{prevMouse, prevCat, prevTurn});\\n }\\n }\\n }\\n }\\n }\\n return results[startMouse][startCat][MOUSE_TURN][0] == MOUSE_WIN && results[startMouse][startCat][MOUSE_TURN][1] <= MAX_MOVES;\\n }\\n\\n public List<int[]> getPrevStates(int mouse, int cat, int turn) {\\n List<int[]> prevStates = new ArrayList<int[]>();\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n int catRow = cat / cols, catCol = cat % cols;\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n int maxJump = prevTurn == MOUSE_TURN ? mouseJump : catJump;\\n int startRow = prevTurn == MOUSE_TURN ? mouseRow : catRow;\\n int startCol = prevTurn == MOUSE_TURN ? mouseCol : catCol;\\n prevStates.add(new int[]{mouse, cat, prevTurn});\\n for (int[] dir : dirs) {\\n for (int i = startRow + dir[0], j = startCol + dir[1], jump = 1; i >= 0 && i < rows && j >= 0 && j < cols && grid[i].charAt(j) != \'#\' && jump <= maxJump; i += dir[0], j += dir[1], jump++) {\\n int prevMouseRow = prevTurn == MOUSE_TURN ? i : mouseRow;\\n int prevMouseCol = prevTurn == MOUSE_TURN ? j : mouseCol;\\n int prevCatRow = prevTurn == MOUSE_TURN ? catRow : i;\\n int prevCatCol = prevTurn == MOUSE_TURN ? catCol : j;\\n int prevMouse = getPos(prevMouseRow, prevMouseCol);\\n int prevCat = getPos(prevCatRow, prevCatCol);\\n prevStates.add(new int[]{prevMouse, prevCat, prevTurn});\\n }\\n }\\n return prevStates;\\n }\\n\\n public int getPos(int row, int col) {\\n return row * cols + col;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n const int MOUSE_TURN = 0, CAT_TURN = 1;\\n const int UNKNOWN = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\n const int MAX_MOVES = 1000;\\n int[][] dirs = {new int[]{-1, 0}, new int[]{1, 0}, new int[]{0, -1}, new int[]{0, 1}};\\n int rows, cols;\\n string[] grid;\\n int catJump, mouseJump;\\n int food;\\n int[,,] degrees;\\n int[,,,] results;\\n\\n public bool CanMouseWin(string[] grid, int catJump, int mouseJump) {\\n this.rows = grid.Length;\\n this.cols = grid[0].Length;\\n this.grid = grid;\\n this.catJump = catJump;\\n this.mouseJump = mouseJump;\\n int startMouse = -1, startCat = -1;\\n for (int i = 0; i < rows; i++) {\\n for (int j = 0; j < cols; j++) {\\n char c = grid[i][j];\\n if (c == \'M\') {\\n startMouse = GetPos(i, j);\\n } else if (c == \'C\') {\\n startCat = GetPos(i, j);\\n } else if (c == \'F\') {\\n food = GetPos(i, j);\\n }\\n }\\n }\\n int total = rows * cols;\\n degrees = new int[total, total, 2];\\n results = new int[total, total, 2, 2];\\n Queue<Tuple<int, int, int>> queue = new Queue<Tuple<int, int, int>>();\\n // 计算每个状态的度\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow][mouseCol] == \'#\') {\\n continue;\\n }\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow][catCol] == \'#\') {\\n continue;\\n }\\n degrees[mouse, cat, MOUSE_TURN]++;\\n degrees[mouse, cat, CAT_TURN]++;\\n foreach (int[] dir in dirs) {\\n for (int row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= mouseJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = GetPos(row, col), nextCat = GetPos(catRow, catCol);\\n degrees[nextMouse, nextCat, MOUSE_TURN]++;\\n }\\n for (int row = catRow + dir[0], col = catCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= catJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = GetPos(mouseRow, mouseCol), nextCat = GetPos(row, col);\\n degrees[nextMouse, nextCat, CAT_TURN]++;\\n }\\n }\\n }\\n }\\n // 猫和老鼠在同一个单元格,猫获胜\\n for (int pos = 0; pos < total; pos++) {\\n int row = pos / cols, col = pos % cols;\\n if (grid[row][col] == \'#\') {\\n continue;\\n }\\n results[pos, pos, MOUSE_TURN, 0] = CAT_WIN;\\n results[pos, pos, MOUSE_TURN, 1] = 0;\\n results[pos, pos, CAT_TURN, 0] = CAT_WIN;\\n results[pos, pos, CAT_TURN, 1] = 0;\\n queue.Enqueue(new Tuple<int, int, int>(pos, pos, MOUSE_TURN));\\n queue.Enqueue(new Tuple<int, int, int>(pos, pos, CAT_TURN));\\n }\\n // 猫和食物在同一个单元格,猫获胜\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow][mouseCol] == \'#\' || mouse == food) {\\n continue;\\n }\\n results[mouse, food, MOUSE_TURN, 0] = CAT_WIN;\\n results[mouse, food, MOUSE_TURN, 1] = 0;\\n results[mouse, food, CAT_TURN, 0] = CAT_WIN;\\n results[mouse, food, CAT_TURN, 1] = 0;\\n queue.Enqueue(new Tuple<int, int, int>(mouse, food, MOUSE_TURN));\\n queue.Enqueue(new Tuple<int, int, int>(mouse, food, CAT_TURN));\\n }\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow][catCol] == \'#\' || cat == food) {\\n continue;\\n }\\n results[food, cat, MOUSE_TURN, 0] = MOUSE_WIN;\\n results[food, cat, MOUSE_TURN, 1] = 0;\\n results[food, cat, CAT_TURN, 0] = MOUSE_WIN;\\n results[food, cat, CAT_TURN, 1] = 0;\\n queue.Enqueue(new Tuple<int, int, int>(food, cat, MOUSE_TURN));\\n queue.Enqueue(new Tuple<int, int, int>(food, cat, CAT_TURN));\\n }\\n // 拓扑排序\\n while (queue.Count > 0) {\\n Tuple<int, int, int> state = queue.Dequeue();\\n int mouse = state.Item1, cat = state.Item2, turn = state.Item3;\\n int result = results[mouse, cat, turn, 0];\\n int moves = results[mouse, cat, turn, 1];\\n IList<Tuple<int, int, int>> prevStates = GetPrevStates(mouse, cat, turn);\\n foreach (Tuple<int, int, int> prevState in prevStates) {\\n int prevMouse = prevState.Item1, prevCat = prevState.Item2, prevTurn = prevState.Item3;\\n if (results[prevMouse, prevCat, prevTurn, 0] == UNKNOWN) {\\n bool canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse, prevCat, prevTurn, 0] = result;\\n results[prevMouse, prevCat, prevTurn, 1] = moves + 1;\\n queue.Enqueue(new Tuple<int, int, int>(prevMouse, prevCat, prevTurn));\\n } else {\\n degrees[prevMouse, prevCat, prevTurn]--;\\n if (degrees[prevMouse, prevCat, prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse, prevCat, prevTurn, 0] = loseResult;\\n results[prevMouse, prevCat, prevTurn, 1] = moves + 1;\\n queue.Enqueue(new Tuple<int, int, int>(prevMouse, prevCat, prevTurn));\\n }\\n }\\n }\\n }\\n }\\n return results[startMouse, startCat, MOUSE_TURN, 0] == MOUSE_WIN && results[startMouse, startCat, MOUSE_TURN, 1] <= MAX_MOVES;\\n }\\n\\n public IList<Tuple<int, int, int>> GetPrevStates(int mouse, int cat, int turn) {\\n IList<Tuple<int, int, int>> prevStates = new List<Tuple<int, int, int>>();\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n int catRow = cat / cols, catCol = cat % cols;\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n int maxJump = prevTurn == MOUSE_TURN ? mouseJump : catJump;\\n int startRow = prevTurn == MOUSE_TURN ? mouseRow : catRow;\\n int startCol = prevTurn == MOUSE_TURN ? mouseCol : catCol;\\n prevStates.Add(new Tuple<int, int, int>(mouse, cat, prevTurn));\\n foreach (int[] dir in dirs) {\\n for (int i = startRow + dir[0], j = startCol + dir[1], jump = 1; i >= 0 && i < rows && j >= 0 && j < cols && grid[i][j] != \'#\' && jump <= maxJump; i += dir[0], j += dir[1], jump++) {\\n int prevMouseRow = prevTurn == MOUSE_TURN ? i : mouseRow;\\n int prevMouseCol = prevTurn == MOUSE_TURN ? j : mouseCol;\\n int prevCatRow = prevTurn == MOUSE_TURN ? catRow : i;\\n int prevCatCol = prevTurn == MOUSE_TURN ? catCol : j;\\n int prevMouse = GetPos(prevMouseRow, prevMouseCol);\\n int prevCat = GetPos(prevCatRow, prevCatCol);\\n prevStates.Add(new Tuple<int, int, int>(prevMouse, prevCat, prevTurn));\\n }\\n }\\n return prevStates;\\n }\\n\\n public int GetPos(int row, int col) {\\n return row * cols + col;\\n }\\n}\\n
###C++
\\n\\nstatic const int MOUSE_TURN = 0, CAT_TURN = 1;\\nstatic const int UNKNOWN = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\nstatic const int MAX_MOVES = 1000;\\n\\nclass Solution {\\npublic: \\n vector<vector<int>> dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n int rows, cols;\\n vector<string> grid;\\n int catJump, mouseJump;\\n int food;\\n int degrees[64][64][2];\\n int results[64][64][2][2];\\n\\n bool canMouseWin(vector<string> grid, int catJump, int mouseJump) {\\n this->rows = grid.size();\\n this->cols = grid[0].size();\\n this->grid = grid;\\n this->catJump = catJump;\\n this->mouseJump = mouseJump;\\n int startMouse = -1, startCat = -1;\\n for (int i = 0; i < rows; i++) {\\n for (int j = 0; j < cols; j++) {\\n char c = grid[i][j];\\n if (c == \'M\') {\\n startMouse = getPos(i, j);\\n } else if (c == \'C\') {\\n startCat = getPos(i, j);\\n } else if (c == \'F\') {\\n food = getPos(i, j);\\n }\\n }\\n }\\n int total = rows * cols;\\n memset(degrees, 0, sizeof(degrees));\\n memset(results, 0, sizeof(results));\\n queue<tuple<int, int, int>> qu;\\n // 计算每个状态的度\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow][mouseCol] == \'#\') {\\n continue;\\n }\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow][catCol] == \'#\') {\\n continue;\\n }\\n degrees[mouse][cat][MOUSE_TURN]++;\\n degrees[mouse][cat][CAT_TURN]++;\\n for (auto & dir : dirs) {\\n for (int row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= mouseJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(row, col), nextCat = getPos(catRow, catCol);\\n degrees[nextMouse][nextCat][MOUSE_TURN]++;\\n }\\n for (int row = catRow + dir[0], col = catCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= catJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(mouseRow, mouseCol), nextCat = getPos(row, col);\\n degrees[nextMouse][nextCat][CAT_TURN]++;\\n }\\n }\\n }\\n }\\n // 猫和老鼠在同一个单元格,猫获胜\\n for (int pos = 0; pos < total; pos++) {\\n int row = pos / cols, col = pos % cols;\\n if (grid[row][col] == \'#\') {\\n continue;\\n }\\n results[pos][pos][MOUSE_TURN][0] = CAT_WIN;\\n results[pos][pos][MOUSE_TURN][1] = 0;\\n results[pos][pos][CAT_TURN][0] = CAT_WIN;\\n results[pos][pos][CAT_TURN][1] = 0;\\n qu.emplace(pos, pos, MOUSE_TURN);\\n qu.emplace(pos, pos, CAT_TURN);\\n }\\n // 猫和食物在同一个单元格,猫获胜\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n if (grid[mouseRow][mouseCol] == \'#\' || mouse == food) {\\n continue;\\n }\\n results[mouse][food][MOUSE_TURN][0] = CAT_WIN;\\n results[mouse][food][MOUSE_TURN][1] = 0;\\n results[mouse][food][CAT_TURN][0] = CAT_WIN;\\n results[mouse][food][CAT_TURN][1] = 0;\\n qu.emplace(mouse, food, MOUSE_TURN);\\n qu.emplace(mouse, food, CAT_TURN);\\n }\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / cols, catCol = cat % cols;\\n if (grid[catRow][catCol] == \'#\' || cat == food) {\\n continue;\\n }\\n results[food][cat][MOUSE_TURN][0] = MOUSE_WIN;\\n results[food][cat][MOUSE_TURN][1] = 0;\\n results[food][cat][CAT_TURN][0] = MOUSE_WIN;\\n results[food][cat][CAT_TURN][1] = 0;\\n qu.emplace(food, cat, MOUSE_TURN);\\n qu.emplace(food, cat, CAT_TURN);\\n }\\n // 拓扑排序\\n while (!qu.empty()) {\\n auto [mouse, cat, turn] = qu.front();\\n qu.pop();\\n int result = results[mouse][cat][turn][0];\\n int moves = results[mouse][cat][turn][1];\\n vector<tuple<int, int, int>> prevStates = getPrevStates(mouse, cat, turn);\\n for (auto [prevMouse, prevCat, prevTurn] : prevStates) {\\n if (results[prevMouse][prevCat][prevTurn][0] == UNKNOWN) {\\n bool canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn][0] = result;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n qu.emplace(prevMouse, prevCat, prevTurn);\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--;\\n if (degrees[prevMouse][prevCat][prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn][0] = loseResult;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n qu.emplace(prevMouse, prevCat, prevTurn);\\n }\\n }\\n }\\n }\\n }\\n return results[startMouse][startCat][MOUSE_TURN][0] == MOUSE_WIN && results[startMouse][startCat][MOUSE_TURN][1] <= MAX_MOVES;\\n }\\n \\n vector<tuple<int, int, int>> getPrevStates(int mouse, int cat, int turn) {\\n vector<tuple<int, int, int>> prevStates;\\n int mouseRow = mouse / cols, mouseCol = mouse % cols;\\n int catRow = cat / cols, catCol = cat % cols;\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n int maxJump = prevTurn == MOUSE_TURN ? mouseJump : catJump;\\n int startRow = prevTurn == MOUSE_TURN ? mouseRow : catRow;\\n int startCol = prevTurn == MOUSE_TURN ? mouseCol : catCol;\\n prevStates.emplace_back(mouse, cat, prevTurn);\\n for (auto & dir : dirs) {\\n for (int i = startRow + dir[0], j = startCol + dir[1], jump = 1; i >= 0 && i < rows && j >= 0 && j < cols && grid[i][j] != \'#\' && jump <= maxJump; i += dir[0], j += dir[1], jump++) {\\n int prevMouseRow = prevTurn == MOUSE_TURN ? i : mouseRow;\\n int prevMouseCol = prevTurn == MOUSE_TURN ? j : mouseCol;\\n int prevCatRow = prevTurn == MOUSE_TURN ? catRow : i;\\n int prevCatCol = prevTurn == MOUSE_TURN ? catCol : j;\\n int prevMouse = getPos(prevMouseRow, prevMouseCol);\\n int prevCat = getPos(prevCatRow, prevCatCol);\\n prevStates.emplace_back(prevMouse, prevCat, prevTurn);\\n }\\n }\\n return prevStates;\\n }\\n\\n int getPos(int row, int col) {\\n return row * cols + col;\\n }\\n};\\n
###C
\\n\\nstatic const int MOUSE_TURN = 0, CAT_TURN = 1;\\nstatic const int UNKNOWN = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\nstatic const int MAX_MOVES = 1000;\\n\\n#define MAX_QUEUE_SIZE 10000\\n\\nint dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\nint g_rows, g_cols;\\nchar **g_grid;\\nint g_catJump, g_mouseJump;\\nint g_food;\\nint g_degrees[64][64][2];\\nint g_results[64][64][2][2];\\n\\nint getPos(int row, int col) {\\n return row * g_cols + col;\\n}\\n\\ntypedef struct State {\\n int mouse;\\n int cat;\\n int turn;\\n} State;\\n\\ntypedef struct Node {\\n State currState;\\n struct Node * next;\\n} Node;\\n\\nNode * getPrevStates(int mouse, int cat, int turn) {\\n Node * prevStates = NULL;\\n Node * tail = NULL;\\n int mouseRow = mouse / g_cols, mouseCol = mouse % g_cols;\\n int catRow = cat / g_cols, catCol = cat % g_cols;\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n int maxJump = prevTurn == MOUSE_TURN ? g_mouseJump : g_catJump;\\n int startRow = prevTurn == MOUSE_TURN ? mouseRow : catRow;\\n int startCol = prevTurn == MOUSE_TURN ? mouseCol : catCol;\\n prevStates = (Node *)malloc(sizeof(Node));\\n tail = prevStates;\\n tail->currState.mouse = mouse;\\n tail->currState.cat = cat;\\n tail->currState.turn = prevTurn;\\n tail->next = NULL;\\n for (int k = 0; k < 4; k++) {\\n int *dir = dirs[k];\\n for (int i = startRow + dir[0], j = startCol + dir[1], jump = 1; i >= 0 && i < g_rows && j >= 0 && j < g_cols && g_grid[i][j] != \'#\' && jump <= maxJump; i += dir[0], j += dir[1], jump++) {\\n int prevMouseRow = prevTurn == MOUSE_TURN ? i : mouseRow;\\n int prevMouseCol = prevTurn == MOUSE_TURN ? j : mouseCol;\\n int prevCatRow = prevTurn == MOUSE_TURN ? catRow : i;\\n int prevCatCol = prevTurn == MOUSE_TURN ? catCol : j;\\n int prevMouse = getPos(prevMouseRow, prevMouseCol);\\n int prevCat = getPos(prevCatRow, prevCatCol);\\n tail->next = (Node *)malloc(sizeof(Node));\\n tail = tail->next;\\n tail->currState.mouse = prevMouse;\\n tail->currState.cat = prevCat;\\n tail->currState.turn = prevTurn;\\n tail->next = NULL;\\n }\\n }\\n return prevStates;\\n}\\n\\nbool canMouseWin(char ** grid, int gridSize, int catJump, int mouseJump){\\n g_rows = gridSize;\\n g_cols = strlen(grid[0]);\\n g_grid = grid;\\n g_catJump = catJump;\\n g_mouseJump = mouseJump;\\n int startMouse = -1, startCat = -1;\\n for (int i = 0; i < g_rows; i++) {\\n for (int j = 0; j < g_cols; j++) {\\n char c = grid[i][j];\\n if (c == \'M\') {\\n startMouse = getPos(i, j);\\n } else if (c == \'C\') {\\n startCat = getPos(i, j);\\n } else if (c == \'F\') {\\n g_food = getPos(i, j);\\n }\\n }\\n }\\n int total = g_rows * g_cols;\\n memset(g_degrees, 0, sizeof(g_degrees));\\n memset(g_results, 0, sizeof(g_results));\\n State * queue = (State *)malloc(sizeof(State) * MAX_QUEUE_SIZE);\\n int head = 0, tail = 0;\\n // 计算每个状态的度\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / g_cols, mouseCol = mouse % g_cols;\\n if (grid[mouseRow][mouseCol] == \'#\') {\\n continue;\\n }\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / g_cols, catCol = cat % g_cols;\\n if (grid[catRow][catCol] == \'#\') {\\n continue;\\n }\\n g_degrees[mouse][cat][MOUSE_TURN]++;\\n g_degrees[mouse][cat][CAT_TURN]++;\\n for (int i = 0; i < 4; i++) {\\n int * dir = dirs[i];\\n for (int row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; row >= 0 && row < g_rows && col >= 0 && col < g_cols && grid[row][col] != \'#\' && jump <= mouseJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(row, col), nextCat = getPos(catRow, catCol);\\n g_degrees[nextMouse][nextCat][MOUSE_TURN]++;\\n }\\n for (int row = catRow + dir[0], col = catCol + dir[1], jump = 1; row >= 0 && row < g_rows && col >= 0 && col < g_cols && grid[row][col] != \'#\' && jump <= catJump; row += dir[0], col += dir[1], jump++) {\\n int nextMouse = getPos(mouseRow, mouseCol), nextCat = getPos(row, col);\\n g_degrees[nextMouse][nextCat][CAT_TURN]++;\\n }\\n }\\n }\\n }\\n // 猫和老鼠在同一个单元格,猫获胜\\n for (int pos = 0; pos < total; pos++) {\\n int row = pos / g_cols, col = pos % g_cols;\\n if (grid[row][col] == \'#\') {\\n continue;\\n }\\n g_results[pos][pos][MOUSE_TURN][0] = CAT_WIN;\\n g_results[pos][pos][MOUSE_TURN][1] = 0;\\n g_results[pos][pos][CAT_TURN][0] = CAT_WIN;\\n g_results[pos][pos][CAT_TURN][1] = 0;\\n queue[tail].mouse = pos;\\n queue[tail].cat = pos;\\n queue[tail].turn = MOUSE_TURN;\\n tail++;\\n queue[tail].mouse = pos;\\n queue[tail].cat = pos;\\n queue[tail].turn = CAT_TURN;\\n tail++;\\n }\\n // 猫和食物在同一个单元格,猫获胜\\n for (int mouse = 0; mouse < total; mouse++) {\\n int mouseRow = mouse / g_cols, mouseCol = mouse % g_cols;\\n if (grid[mouseRow][mouseCol] == \'#\' || mouse == g_food) {\\n continue;\\n }\\n g_results[mouse][g_food][MOUSE_TURN][0] = CAT_WIN;\\n g_results[mouse][g_food][MOUSE_TURN][1] = 0;\\n g_results[mouse][g_food][CAT_TURN][0] = CAT_WIN;\\n g_results[mouse][g_food][CAT_TURN][1] = 0;\\n queue[tail].mouse = mouse;\\n queue[tail].cat = g_food;\\n queue[tail].turn = MOUSE_TURN;\\n tail++;\\n queue[tail].mouse = mouse;\\n queue[tail].cat = g_food;\\n queue[tail].turn = CAT_TURN;\\n tail++;\\n }\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for (int cat = 0; cat < total; cat++) {\\n int catRow = cat / g_cols, catCol = cat % g_cols;\\n if (grid[catRow][catCol] == \'#\' || cat == g_food) {\\n continue;\\n }\\n g_results[g_food][cat][MOUSE_TURN][0] = MOUSE_WIN;\\n g_results[g_food][cat][MOUSE_TURN][1] = 0;\\n g_results[g_food][cat][CAT_TURN][0] = MOUSE_WIN;\\n g_results[g_food][cat][CAT_TURN][1] = 0;\\n queue[tail].mouse = g_food;\\n queue[tail].cat = cat;\\n queue[tail].turn = MOUSE_TURN;\\n tail++;\\n queue[tail].mouse = g_food;\\n queue[tail].cat = cat;\\n queue[tail].turn = CAT_TURN;\\n tail++;\\n }\\n // 拓扑排序\\n while (head != tail) {\\n int mouse = queue[head].mouse;\\n int cat = queue[head].cat;\\n int turn = queue[head].turn;\\n head++;\\n int result = g_results[mouse][cat][turn][0];\\n int moves = g_results[mouse][cat][turn][1];\\n Node * prevStates = getPrevStates(mouse, cat, turn);\\n for (Node * curr = prevStates; curr; curr = curr->next) {\\n int prevMouse = curr->currState.mouse;\\n int prevCat = curr->currState.cat;\\n int prevTurn = curr->currState.turn;\\n if (g_results[prevMouse][prevCat][prevTurn][0] == UNKNOWN) {\\n bool canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n g_results[prevMouse][prevCat][prevTurn][0] = result;\\n g_results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue[tail].mouse = prevMouse;\\n queue[tail].cat = prevCat;\\n queue[tail].turn = prevTurn;\\n tail++;\\n } else {\\n g_degrees[prevMouse][prevCat][prevTurn]--;\\n if (g_degrees[prevMouse][prevCat][prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n g_results[prevMouse][prevCat][prevTurn][0] = loseResult;\\n g_results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue[tail].mouse = prevMouse;\\n queue[tail].cat = prevCat;\\n queue[tail].turn = prevTurn;\\n tail++;\\n }\\n }\\n }\\n }\\n }\\n free(queue);\\n return g_results[startMouse][startCat][MOUSE_TURN][0] == MOUSE_WIN && g_results[startMouse][startCat][MOUSE_TURN][1] <= MAX_MOVES;\\n}\\n
###JavaScript
\\n\\nconst MOUSE_TURN = 0, CAT_TURN = 1;\\nconst UNKNOWN = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\nconst MAX_MOVES = 1000;\\nconst dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]];\\nvar canMouseWin = function(grid, catJump, mouseJump) {\\n this.rows = grid.length;\\n this.cols = grid[0].length;\\n let startMouse = -1, startCat = -1;\\n\\n const getPos = (row, col) => {\\n return row * this.cols + col;\\n };\\n\\n const getPrevStates = (mouse, cat, turn) => {\\n const prevStates = [];\\n const mouseRow = Math.floor(mouse / this.cols), mouseCol = mouse % this.cols;\\n const catRow = Math.floor(cat / this.cols), catCol = cat % this.cols;\\n const prevTurn = turn === MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n const maxJump = prevTurn === MOUSE_TURN ? mouseJump : catJump;\\n const startRow = prevTurn === MOUSE_TURN ? mouseRow : catRow;\\n const startCol = prevTurn === MOUSE_TURN ? mouseCol : catCol;\\n prevStates.push([mouse, cat, prevTurn]);\\n for (const dir of dirs) {\\n for (let i = startRow + dir[0], j = startCol + dir[1], jump = 1; i >= 0 && i < rows && j >= 0 && j < this.cols && grid[i].charAt(j) !== \'#\' && jump <= maxJump; i += dir[0], j += dir[1], jump++) {\\n const prevMouseRow = prevTurn === MOUSE_TURN ? i : mouseRow;\\n const prevMouseCol = prevTurn === MOUSE_TURN ? j : mouseCol;\\n const prevCatRow = prevTurn === MOUSE_TURN ? catRow : i;\\n const prevCatCol = prevTurn === MOUSE_TURN ? catCol : j;\\n const prevMouse = getPos(prevMouseRow, prevMouseCol);\\n const prevCat = getPos(prevCatRow, prevCatCol);\\n prevStates.push([prevMouse, prevCat, prevTurn]);\\n }\\n }\\n return prevStates;\\n }\\n\\n for (let i = 0; i < rows; i++) {\\n for (let j = 0; j < this.cols; j++) {\\n const c = grid[i][j];\\n if (c === \'M\') {\\n startMouse = getPos(i, j);\\n } else if (c === \'C\') {\\n startCat = getPos(i, j);\\n } else if (c === \'F\') {\\n food = getPos(i, j);\\n }\\n }\\n }\\n const total = rows * this.cols;\\n const degrees = new Array(total).fill(0).map(() => new Array(total).fill(0).map(() => new Array(2).fill(0)));\\n const results = new Array(total).fill(0).map(() => new Array(total).fill(0).map(() => new Array(2).fill(0).map(() => new Array(2).fill(0))));\\n const queue = [];\\n // 计算每个状态的度\\n for (let mouse = 0; mouse < total; mouse++) {\\n let mouseRow = Math.floor(mouse / this.cols), mouseCol = mouse % this.cols;\\n if (grid[mouseRow][mouseCol] === \'#\') {\\n continue;\\n }\\n for (let cat = 0; cat < total; cat++) {\\n let catRow = Math.floor(cat / this.cols), catCol = cat % this.cols;\\n if (grid[catRow][catCol] === \'#\') {\\n continue;\\n }\\n degrees[mouse][cat][MOUSE_TURN]++;\\n degrees[mouse][cat][CAT_TURN]++;\\n for (const dir of dirs) {\\n for (let row = mouseRow + dir[0], col = mouseCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < this.cols && grid[row][col] !== \'#\' && jump <= mouseJump; row += dir[0], col += dir[1], jump++) {\\n const nextMouse = getPos(row, col), nextCat = getPos(catRow, catCol);\\n degrees[nextMouse][nextCat][MOUSE_TURN]++;\\n }\\n for (let row = catRow + dir[0], col = catCol + dir[1], jump = 1; row >= 0 && row < rows && col >= 0 && col < this.cols && grid[row][col] !== \'#\' && jump <= catJump; row += dir[0], col += dir[1], jump++) {\\n const nextMouse = getPos(mouseRow, mouseCol), nextCat = getPos(row, col);\\n degrees[nextMouse][nextCat][CAT_TURN]++;\\n }\\n }\\n }\\n }\\n // 猫和老鼠在同一个单元格,猫获胜\\n for (let pos = 0; pos < total; pos++) {\\n const row = Math.floor(pos / this.cols), col = pos % this.cols;\\n if (grid[row][col] === \'#\') {\\n continue;\\n }\\n results[pos][pos][MOUSE_TURN][0] = CAT_WIN;\\n results[pos][pos][MOUSE_TURN][1] = 0;\\n results[pos][pos][CAT_TURN][0] = CAT_WIN;\\n results[pos][pos][CAT_TURN][1] = 0;\\n queue.push([pos, pos, MOUSE_TURN]);\\n queue.push([pos, pos, CAT_TURN]);\\n }\\n // 猫和食物在同一个单元格,猫获胜\\n for (let mouse = 0; mouse < total; mouse++) {\\n const mouseRow = Math.floor(mouse / this.cols), mouseCol = mouse % this.cols;\\n if (grid[mouseRow][mouseCol] === \'#\' || mouse === food) {\\n continue;\\n }\\n results[mouse][food][MOUSE_TURN][0] = CAT_WIN;\\n results[mouse][food][MOUSE_TURN][1] = 0;\\n results[mouse][food][CAT_TURN][0] = CAT_WIN;\\n results[mouse][food][CAT_TURN][1] = 0;\\n queue.push([mouse, food, MOUSE_TURN]);\\n queue.push([mouse, food, CAT_TURN]);\\n }\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for (let cat = 0; cat < total; cat++) {\\n const catRow = Math.floor(cat / this.cols), catCol = cat % this.cols;\\n if (grid[catRow][catCol] === \'#\' || cat === food) {\\n continue;\\n }\\n results[food][cat][MOUSE_TURN][0] = MOUSE_WIN;\\n results[food][cat][MOUSE_TURN][1] = 0;\\n results[food][cat][CAT_TURN][0] = MOUSE_WIN;\\n results[food][cat][CAT_TURN][1] = 0;\\n queue.push([food, cat, MOUSE_TURN]);\\n queue.push([food, cat, CAT_TURN]);\\n }\\n // 拓扑排序\\n while (queue.length) {\\n const state = queue.shift();\\n const mouse = state[0], cat = state[1], turn = state[2];\\n const result = results[mouse][cat][turn][0];\\n const moves = results[mouse][cat][turn][1];\\n const prevStates = getPrevStates(mouse, cat, turn);\\n for (const prevState of prevStates) {\\n const prevMouse = prevState[0], prevCat = prevState[1], prevTurn = prevState[2];\\n if (results[prevMouse][prevCat][prevTurn][0] === UNKNOWN) {\\n const canWin = (result === MOUSE_WIN && prevTurn === MOUSE_TURN) || (result === CAT_WIN && prevTurn === CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn][0] = result;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue.push([prevMouse, prevCat, prevTurn]);\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--;\\n if (degrees[prevMouse][prevCat][prevTurn] === 0) {\\n const loseResult = prevTurn === MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn][0] = loseResult;\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1;\\n queue.push([prevMouse, prevCat, prevTurn]);\\n }\\n }\\n }\\n }\\n }\\n\\n return results[startMouse][startCat][MOUSE_TURN][0] === MOUSE_WIN && results[startMouse][startCat][MOUSE_TURN][1] <= MAX_MOVES;\\n}\\n
###go
\\n\\nconst (\\n MouseTurn = 0\\n CatTurn = 1\\n UNKNOWN = 0\\n MouseWin = 1\\n CatWin = 2\\n MaxMoves = 1000\\n)\\n\\nvar dirs = []struct{ x, y int }{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}\\n\\nfunc canMouseWin(grid []string, catJump int, mouseJump int) bool {\\n rows, cols := len(grid), len(grid[0])\\n getPos := func(row, col int) int { return row*cols + col }\\n var startMouse, startCat, food int\\n for i, row := range grid {\\n for j, ch := range row {\\n if ch == \'M\' {\\n startMouse = getPos(i, j)\\n } else if ch == \'C\' {\\n startCat = getPos(i, j)\\n } else if ch == \'F\' {\\n food = getPos(i, j)\\n }\\n }\\n }\\n\\n // 计算每个状态的度\\n total := rows * cols\\n degrees := [64][64][2]int{}\\n for mouse := 0; mouse < total; mouse++ {\\n mouseRow := mouse / cols\\n mouseCol := mouse % cols\\n if grid[mouseRow][mouseCol] == \'#\' {\\n continue\\n }\\n for cat := 0; cat < total; cat++ {\\n catRow := cat / cols\\n catCol := cat % cols\\n if grid[catRow][catCol] == \'#\' {\\n continue\\n }\\n degrees[mouse][cat][MouseTurn]++\\n degrees[mouse][cat][CatTurn]++\\n for _, dir := range dirs {\\n for row, col, jump := mouseRow+dir.x, mouseCol+dir.y, 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= mouseJump; jump++ {\\n nextMouse := getPos(row, col)\\n nextCat := getPos(catRow, catCol)\\n degrees[nextMouse][nextCat][MouseTurn]++\\n row += dir.x\\n col += dir.y\\n }\\n for row, col, jump := catRow+dir.x, catCol+dir.y, 1; row >= 0 && row < rows && col >= 0 && col < cols && grid[row][col] != \'#\' && jump <= catJump; jump++ {\\n nextMouse := getPos(mouseRow, mouseCol)\\n nextCat := getPos(row, col)\\n degrees[nextMouse][nextCat][CatTurn]++\\n row += dir.x\\n col += dir.y\\n }\\n }\\n }\\n }\\n\\n results := [64][64][2][2]int{}\\n type state struct{ mouse, cat, turn int }\\n q := []state{}\\n\\n // 猫和老鼠在同一个单元格,猫获胜\\n for pos := 0; pos < total; pos++ {\\n row := pos / cols\\n col := pos % cols\\n if grid[row][col] == \'#\' {\\n continue\\n }\\n results[pos][pos][MouseTurn][0] = CatWin\\n results[pos][pos][MouseTurn][1] = 0\\n results[pos][pos][CatTurn][0] = CatWin\\n results[pos][pos][CatTurn][1] = 0\\n q = append(q, state{pos, pos, MouseTurn}, state{pos, pos, CatTurn})\\n }\\n\\n // 猫和食物在同一个单元格,猫获胜\\n for mouse := 0; mouse < total; mouse++ {\\n mouseRow := mouse / cols\\n mouseCol := mouse % cols\\n if grid[mouseRow][mouseCol] == \'#\' || mouse == food {\\n continue\\n }\\n results[mouse][food][MouseTurn][0] = CatWin\\n results[mouse][food][MouseTurn][1] = 0\\n results[mouse][food][CatTurn][0] = CatWin\\n results[mouse][food][CatTurn][1] = 0\\n q = append(q, state{mouse, food, MouseTurn}, state{mouse, food, CatTurn})\\n }\\n\\n // 老鼠和食物在同一个单元格且猫和食物不在同一个单元格,老鼠获胜\\n for cat := 0; cat < total; cat++ {\\n catRow := cat / cols\\n catCol := cat % cols\\n if grid[catRow][catCol] == \'#\' || cat == food {\\n continue\\n }\\n results[food][cat][MouseTurn][0] = MouseWin\\n results[food][cat][MouseTurn][1] = 0\\n results[food][cat][CatTurn][0] = MouseWin\\n results[food][cat][CatTurn][1] = 0\\n q = append(q, state{food, cat, MouseTurn}, state{food, cat, CatTurn})\\n }\\n\\n getPrevStates := func(mouse, cat, turn int) []state {\\n mouseRow := mouse / cols\\n mouseCol := mouse % cols\\n catRow := cat / cols\\n catCol := cat % cols\\n prevTurn := MouseTurn\\n if turn == MouseTurn {\\n prevTurn = CatTurn\\n }\\n maxJump, startRow, startCol := catJump, catRow, catCol\\n if prevTurn == MouseTurn {\\n maxJump, startRow, startCol = mouseJump, mouseRow, mouseCol\\n }\\n prevStates := []state{{mouse, cat, prevTurn}}\\n for _, dir := range dirs {\\n for i, j, jump := startRow+dir.x, startCol+dir.y, 1; i >= 0 && i < rows && j >= 0 && j < cols && grid[i][j] != \'#\' && jump <= maxJump; jump++ {\\n prevMouseRow := mouseRow\\n prevMouseCol := mouseCol\\n prevCatRow := i\\n prevCatCol := j\\n if prevTurn == MouseTurn {\\n prevMouseRow = i\\n prevMouseCol = j\\n prevCatRow = catRow\\n prevCatCol = catCol\\n }\\n prevMouse := getPos(prevMouseRow, prevMouseCol)\\n prevCat := getPos(prevCatRow, prevCatCol)\\n prevStates = append(prevStates, state{prevMouse, prevCat, prevTurn})\\n i += dir.x\\n j += dir.y\\n }\\n }\\n return prevStates\\n }\\n\\n // 拓扑排序\\n for len(q) > 0 {\\n s := q[0]\\n q = q[1:]\\n mouse, cat, turn := s.mouse, s.cat, s.turn\\n result := results[mouse][cat][turn][0]\\n moves := results[mouse][cat][turn][1]\\n for _, s := range getPrevStates(mouse, cat, turn) {\\n prevMouse, prevCat, prevTurn := s.mouse, s.cat, s.turn\\n if results[prevMouse][prevCat][prevTurn][0] == UNKNOWN {\\n canWin := result == MouseWin && prevTurn == MouseTurn || result == CatWin && prevTurn == CatTurn\\n if canWin {\\n results[prevMouse][prevCat][prevTurn][0] = result\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1\\n q = append(q, state{prevMouse, prevCat, prevTurn})\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--\\n if degrees[prevMouse][prevCat][prevTurn] == 0 {\\n loseResult := MouseWin\\n if prevTurn == MouseTurn {\\n loseResult = CatWin\\n }\\n results[prevMouse][prevCat][prevTurn][0] = loseResult\\n results[prevMouse][prevCat][prevTurn][1] = moves + 1\\n q = append(q, state{prevMouse, prevCat, prevTurn})\\n }\\n }\\n }\\n }\\n }\\n return results[startMouse][startCat][MouseTurn][0] == MouseWin && results[startMouse][startCat][MouseTurn][1] <= MaxMoves\\n}\\n
复杂度分析
\\n\\n
\\n","description":"前言 这道题是「913. 猫和老鼠」的进阶,建议读者在阅读本文之前首先阅读「913. 猫和老鼠的官方题解」,了解博弈问题中的必胜状态、必败状态与必和状态的概念,以及最优策略。\\n\\n博弈问题通常可以使用动态规划求解。由于动态规划的时间复杂度和游戏轮数有关,因此动态规划的时间复杂度较高。本文不具体介绍动态规划的解法,感兴趣的读者可以自行尝试。\\n\\n博弈问题的另一种解法是拓扑排序。和动态规划相比,拓扑排序的时间复杂度和游戏轮数无关,因此拓扑排序的时间复杂度较低。\\n\\n方法一:拓扑排序\\n\\n概述\\n\\n给定的网格包含 $\\\\textit{rows}$ 行和 $\\\\textit{cols}…","guid":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/mao-he-lao-shu-ii-by-leetcode-solution-e5io","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-05-06T12:47:00.633Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"库函数写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/mo-ni-by-endlesscheng-0hxy","content":"- \\n
\\n时间复杂度:$O(\\\\textit{rows}^2 \\\\times \\\\textit{cols}^2 \\\\times (\\\\textit{rows} + \\\\textit{cols}))$,其中 $\\\\textit{rows}$ 和 $\\\\textit{cols}$ 分别是网格的行数和列数。状态数是 $O(\\\\textit{rows}^2 \\\\times \\\\textit{cols}^2)$,对于每个状态需要 $O(\\\\textit{rows} + \\\\textit{cols})$ 的时间计算状态值,因此总时间复杂度是 $O(\\\\textit{rows}^2 \\\\times \\\\textit{cols}^2 \\\\times (\\\\textit{rows} + \\\\textit{cols}))$。
\\n- \\n
\\n空间复杂度:$O(\\\\textit{rows}^2 \\\\times \\\\textit{cols}^2)$,其中 $\\\\textit{rows}$ 和 $\\\\textit{cols}$ 分别是网格的行数和列数。需要记录每个状态的度和结果,状态数是 $O(\\\\textit{rows}^2 \\\\times \\\\textit{cols}^2)$。
\\n用库函数判断 $\\\\textit{words}[i]$ 是否为 $s$ 的前缀,是就把答案加一。
\\n\\nclass Solution:\\n def countPrefixes(self, words: List[str], s: str) -> int:\\n return sum(s.startswith(word) for word in words)\\n
\\nclass Solution {\\n int countPrefixes(String[] words, String s) {\\n int ans = 0;\\n for (String word : words) {\\n if (s.startsWith(word)) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\n int countPrefixes(String[] words, String s) {\\n return (int) Arrays.stream(words).filter(s::startsWith).count();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int countPrefixes(vector<string>& words, string s) {\\n return ranges::count_if(words, [&](auto& word) {\\n return s.starts_with(word);\\n });\\n }\\n};\\n
\\nint countPrefixes(char** words, int wordsSize, char* s) {\\n int ans = 0;\\n for (int i = 0; i < wordsSize; i++) {\\n if (strncmp(words[i], s, strlen(words[i])) == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
\\nfunc countPrefixes(words []string, s string) (ans int) {\\n for _, word := range words {\\n if strings.HasPrefix(s, word) {\\n ans++\\n }\\n }\\n return\\n}\\n
\\nvar countPrefixes = function(words, s) {\\n return words.filter(word => s.startsWith(word)).length;\\n};\\n
\\nimpl Solution {\\n pub fn count_prefixes(words: Vec<String>, s: String) -> i32 {\\n words.into_iter()\\n .filter(|w| s.starts_with(w.as_str()))\\n .count() as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(nm)$,其中 $n$ 是 $\\\\textit{words}$ 的长度,$m$ 是 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用库函数判断 $\\\\textit{words}[i]$ 是否为 $s$ 的前缀,是就把答案加一。 class Solution:\\n def countPrefixes(self, words: List[str], s: str) -> int:\\n return sum(s.startswith(word) for word in words)\\n\\nclass Solution {\\n int countPrefixes(String[] words, String s) {\\n int ans = 0…","guid":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/mo-ni-by-endlesscheng-0hxy","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-30T16:42:41.336Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【统计是给定字符串前缀的字符串数目】【C语言详解】","url":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/by-goodgoodday-8n6a","content":"解题思路
\\n【生哥刷题】【单片机工程师】
\\n
\\n----这里道题目用C语言来做的思路。模拟法
\\n关键点:
\\n
\\n1:首先字符串的数据量比较少
\\n2:直接使用模拟的方法进行解决
\\n时间复杂度:O(n^2)
\\n空间复杂度:O(n^2)
\\n点个赞收藏,我们一起变强,这里是只会C也要学算法的生哥
\\n代码
\\n###c
\\n\\nint countPrefixes(char ** words, int wordsSize, char * s)\\n{\\n //暴力\\n int lenS = strlen(s);\\n int count = 0;\\n for(int i = 0; i < wordsSize; i++) \\n {\\n int lenWord = strlen(words[i]);\\n if(lenWord <= lenS)\\n {\\n bool isMatch = true;\\n for(int j = 0; j < lenWord; j++)\\n {\\n if(s[j] != words[i][j])\\n {\\n isMatch = false;\\n break;\\n }\\n }\\n if(isMatch)\\n {\\n count++;\\n }\\n \\n }\\n \\n }\\n return count;\\n}\\n
###c
\\n\\n","description":"解题思路 【生哥刷题】【单片机工程师】\\n ----这里道题目用C语言来做的思路。\\n\\n模拟法\\n\\n关键点:\\n 1:首先字符串的数据量比较少\\n 2:直接使用模拟的方法进行解决\\n 时间复杂度:O(n^2)\\n 空间复杂度:O(n^2)\\n\\n\\n点个赞收藏,我们一起变强,这里是只会C也要学算法的生哥\\n代码\\n\\n###c\\n\\nint countPrefixes(char ** words, int wordsSize, char * s)\\n{\\n //暴力\\n int lenS = strlen(s);\\n int count = 0;\\n for(int i = 0; i…","guid":"https://leetcode.cn/problems/count-prefixes-of-a-given-string//solution/by-goodgoodday-8n6a","author":"goodgoodday","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-30T16:09:15.603Z","media":[{"url":"https://pic.leetcode-cn.com/1651334960-LeEBfZ-image.png","type":"photo","width":573,"height":320,"blurhash":"LE3+$iogahofm$j?k9jZkZayafa}"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】脑筋急转弯模拟题","url":"https://leetcode.cn/problems/smallest-range-i//solution/by-ac_oier-7fh0","content":"int countPrefixes(char ** words, int wordsSize, char * s)\\n{\\n //暴力\\n int lenS = strlen(s);\\n int count = 0;\\n for(int i = 0; i < wordsSize; i++) \\n {\\n int lenWord = strlen(words[i]);\\n if(lenWord <= lenS)\\n {\\n bool isMatch = true;\\n for(int j = 0; j < lenWord; j++)\\n {\\n if(s[j] != words[i][j])\\n {\\n isMatch = false;\\n break;\\n }\\n }\\n if(isMatch)\\n {\\n count++;\\n }\\n \\n }\\n \\n }\\n return count;\\n}\\n
脑筋急转弯
\\n今天胃不是很舒服,来晚了。
\\n根据题意,对于任意一个数 $nums[i]$ 而言,其所能变化的范围为 $[nums[i] - k, nums[i] + k]$,我们需要最小化变化后的差值。而当 $k$ 足够大时,我们必然能够将所有数变为同一个值,此时答案为 $0$,而更一般的情况,我们能够缩减的数值距离为 $2 * k$,因此如果原来的最大差值为 $d = \\\\max - \\\\min$,若 $d <= 2 * k$ 时,答案为 $0$,否则答案为 $d - 2 * k$。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int smallestRangeI(int[] nums, int k) {\\n int max = nums[0], min = nums[0];\\n for (int i : nums) {\\n max = Math.max(max, i);\\n min = Math.min(min, i);\\n }\\n return Math.max(0, max - min - 2 * k);\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(1)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"脑筋急转弯 今天胃不是很舒服,来晚了。\\n\\n根据题意,对于任意一个数 $nums[i]$ 而言,其所能变化的范围为 $[nums[i] - k, nums[i] + k]$,我们需要最小化变化后的差值。而当 $k$ 足够大时,我们必然能够将所有数变为同一个值,此时答案为 $0$,而更一般的情况,我们能够缩减的数值距离为 $2 * k$,因此如果原来的最大差值为 $d = \\\\max - \\\\min$,若 $d <= 2 * k$ 时,答案为 $0$,否则答案为 $d - 2 * k$。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/smallest-range-i//solution/by-ac_oier-7fh0","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-30T01:53:58.469Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【负雪明烛】多图解析,不要「矫枉过正」","url":"https://leetcode.cn/problems/smallest-range-i//solution/by-fuxuemingzhu-qp4g","content":"大家好,我是 @负雪明烛。点击右上方的「+关注」↗,优质题解不间断!
\\n题目大意
\\n经常看我题解的同学都知道,我每次都上来先分析题目意思,这是做对题的第一步。
\\n本题是说对于数组中的每个元素,都可以对其值进行修改:加上
\\n[-k, k]
内的任意整数。问如何对整个数组修改后,数组的最大值减去最小值的差,是最小的。举个例子,假如输入数组是 $[3, 6]$,$k = 2$。
\\n那么对于 $3$ 来说,可以变成 $[1, 2 ,3, 4, 5]$ 中的一个:
\\n\\n
那么对于 $6$ 来说,可以变成 $[4, 5 ,6, 7, 8]$ 中的一个:
\\n\\n
因此,可以把数组 $[3, 6]$ 变成 $[4, 4]$ 或者 $[5, 5]$,此时的最大值和最小值相等,即差值为 $0$。
\\n\\n
解题方法
\\n对于本题,我的第一想法是把
\\n最小值 + k,最大值 - k
,修改后的数组最大值与最小值的差 “应该是” 最小的。\\n
转念一想,不对啊!假如
\\n最小值 + k > 最大值 - k
,经过上面的转化,矮的变成高的了、高的变成矮的了。其实本来差值可以变成 $0$ 的,但是却导致「矫枉过正」了。\\n
因此,需要多加个判断,思路也就有了:
\\n\\n
\\n- 当原数组的
\\n最大值 - 最小值 > 2 * k
,那么把最小值 + k,最大值 - k
,得到的新数组的最大值和最小值的差最小。- 否则,得到的新数组的最大值和最小值的差就是 $0$。(不要「矫枉过正」)
\\n代码如下:
\\n###python
\\n\\nclass Solution(object):\\n def smallestRangeI(self, nums, k):\\n \\"\\"\\"\\n :type nums: List[int]\\n :type k: int\\n :rtype: int\\n \\"\\"\\"\\n diff = max(nums) - min(nums)\\n if diff > 2 * k:\\n return diff - 2 * k\\n return 0\\n
复杂度
\\n\\n
\\n- 时间复杂度:$O(N)$
\\n- 空间复杂度:$O(1)$
\\n总结
\\n\\n
\\n- 对于这种题目,并不需要真正的把“新数组”的每个值都计算出来,只需要求出一个值来,那么一般就是靠思路和规律取胜,不要暴力求“新数组”哦~
\\n
\\n我是 @负雪明烛 ,刷算法题 1000 多道,写了 1000 多篇算法题解,收获阅读量 300 万。
\\n
\\n关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。\\n
\\n","description":"大家好,我是 @负雪明烛。点击右上方的「+关注」↗,优质题解不间断! 题目大意\\n\\n经常看我题解的同学都知道,我每次都上来先分析题目意思,这是做对题的第一步。\\n\\n本题是说对于数组中的每个元素,都可以对其值进行修改:加上 [-k, k] 内的任意整数。问如何对整个数组修改后,数组的最大值减去最小值的差,是最小的。\\n\\n举个例子,假如输入数组是 $[3, 6]$,$k = 2$。\\n\\n那么对于 $3$ 来说,可以变成 $[1, 2 ,3, 4, 5]$ 中的一个:\\n\\n那么对于 $6$ 来说,可以变成 $[4, 5 ,6, 7, 8]$ 中的一个:\\n\\n因此,可以把数组…","guid":"https://leetcode.cn/problems/smallest-range-i//solution/by-fuxuemingzhu-qp4g","author":"fuxuemingzhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-30T01:20:55.827Z","media":[{"url":"https://pic.leetcode-cn.com/1651281695-Xtsksb-908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.001.png","type":"photo","width":1920,"height":1080,"blurhash":"UdS=*P%hkC%1.8ogadofyZi^WUbv-TjEkXj?"},{"url":"https://pic.leetcode-cn.com/1651281707-JJjxvA-908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.002.png","type":"photo","width":1920,"height":1080,"blurhash":"UjS=*Px^kXxt-;kCaxkCyZjEWAbb-Vn$g3fk"},{"url":"https://pic.leetcode-cn.com/1651281719-YOoFrO-908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.003.png","type":"photo","width":1920,"height":1080,"blurhash":"UVS~YI-;ax%L?wjYbHozyZadbboL,TozbFad"},{"url":"https://pic.leetcode-cn.com/1651281740-WbKnnA-908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.004.png","type":"photo","width":1920,"height":1080,"blurhash":"UPS=^y-:tl%M?vofjskWyZa{Rit7^jj]X8nh"},{"url":"https://pic.leetcode-cn.com/1651281751-xXjxaN-908.%20%E6%9C%80%E5%B0%8F%E5%B7%AE%E5%80%BC%20I.005.png","type":"photo","width":1920,"height":1080,"blurhash":"UWS$Mx-ob_%0.9s;bHofyZbve-kD?FWVj=ae"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【track & traning】击败10000%,思路简单,性能高效","url":"https://leetcode.cn/problems/smallest-range-i//solution/track-traning-di-gui-qiu-jie-si-lu-jian-3sw8x","content":"- 在刷题的时候,如果你不知道该怎么刷题,可以看 LeetCode 应该怎么刷?
\\n- 如果你觉得题目太多,想在短时间内快速提高,可以看 LeetCode 最经典的 100 道题。
\\n- 送你一份刷题的代码模板:【LeetCode】代码模板,刷题必会
\\n- 我写的 1000 道 LeetCode 题解,都在这里了,免费拿走。
\\n方便快速学习算法与理解~
\\n🌇 点赞 👍 收藏 ⭐留言 📝 一键三连 ~关注Jam,从你我做起!
\\n兄弟会背叛你,女人会离开你,金钱会诱惑你,生活会刁难你,只有数学不会,不会就是不会
\\n
\\n天才与否,取决于最终达到的高度。真正的天才是那些脚踏实地的人
\\n静下心来好好做自己,走稳脚下每一步,就是最好的路,强者都是孤独的推荐 python 算法的书籍,体系化学习算法与数据结构,用正确的方式成为offer收割机
\\n
\\nleetcode —— 系统化快速学习算法,这不是内卷,这只是悄悄地努力,然后惊艳所有的人
\\n\\n
求解思路
\\n读懂题意,代码用脚写 本题对最大的数减去k, 最小的数加上k, 结果才会出现最低分数,有两种情况:
\\n谁说你Jam爷不敢打竞赛,我就花一天就学会了新语言Go,看到我这knight勋章没?我随手一打就拿到了,下一把就是Guardian
\\n为了照顾萌新,我又学了一种新语言C++
\\n\\n
\\n- \\n
\\nmin(nums) + k >= max(nums) - k
\\n- \\n
\\nmin(nums) + k < max(nums) - k
\\n- \\n
\\n由于分数不能为负数,转换为 max(nums) - min(nums) - 2k if max(nums) - min(nums) - 2k > 0 else 0
\\n代码
\\n###golang
\\n\\nfunc smallestRangeI(nums []int, k int) int {\\n maxN, minN := nums[0], nums[0]\\n for _, v := range nums {\\n if maxN < v {\\n maxN = v\\n } \\n if minN > v {\\n minN = v\\n }\\n }\\n ans := maxN - minN - 2 * k\\n if ans > 0 {\\n return ans\\n }\\n return 0\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int smallestRangeI(vector<int>& nums, int k) {\\n int maxV = nums[0], minV = nums[0];\\n for(auto &v: nums) {\\n maxV = max(maxV, v);\\n minV = min(minV, v);\\n }\\n return max(maxV - minV - 2 * k, 0);\\n }\\n};\\n
###C++
\\n\\n","description":"方便快速学习算法与理解~ 🌇 点赞 👍 收藏 ⭐留言 📝 一键三连 ~关注Jam,从你我做起!\\n\\n兄弟会背叛你,女人会离开你,金钱会诱惑你,生活会刁难你,只有数学不会,不会就是不会\\n 天才与否,取决于最终达到的高度。真正的天才是那些脚踏实地的人\\n 静下心来好好做自己,走稳脚下每一步,就是最好的路,强者都是孤独的\\n\\n推荐 python 算法的书籍,体系化学习算法与数据结构,用正确的方式成为offer收割机\\n leetcode —— 系统化快速学习算法,这不是内卷,这只是悄悄地努力,然后惊艳所有的人\\n\\n\\n求解思路\\n\\n读懂题意,代码用脚写 本题对最大的数减去k…","guid":"https://leetcode.cn/problems/smallest-range-i//solution/track-traning-di-gui-qiu-jie-si-lu-jian-3sw8x","author":"Everythone","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-29T17:08:49.551Z","media":[{"url":"https://pic.leetcode-cn.com/1651282525-zExiZV-QQ%E5%9B%BE%E7%89%8720220430093451.png","type":"photo","width":1665,"height":960,"blurhash":"L56Rr*veFzU]L}pd-Sk@x]kDMwae"},{"url":"https://pic.leetcode-cn.com/1651252216-ORTbtj-Screenshot%202022-04-30%20010824.png","type":"photo","width":896,"height":272,"blurhash":"L03uo|-;t7?b_3M{Mxt74nxuxuWB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【道哥刷题】简单构造,注意long型","url":"https://leetcode.cn/problems/design-an-atm-machine//solution/dao-ge-shua-ti-by-lcfgrn-7pdt","content":"class Solution {\\npublic:\\n int smallestRangeI(vector<int>& nums, int k) {\\n return *max_element(nums.begin(), nums.end()) - *min_element(nums.begin(), nums.end()) > 2 * k? *max_element(nums.begin(), nums.end()) - *min_element(nums.begin(), nums.end()) - 2 * k: 0;\\n }\\n};\\n
解题思路
\\n本题可以构造一个cnt数组来存储不同币值的个数,注意这里应该是long型,这里
\\n0 <= banknotesCount[i] <= 109
,且总共 最多有 5000 次 withdraw 和 deposit 的调用
,会导致cnt中部分值超过整型范围。用nums来存储不同的币值。
\\n代码
\\n###java
\\n\\nclass ATM {\\n\\n private long[] cnt;\\n private int[] nums;\\n\\n public ATM() {\\n this.cnt = new long[5];\\n this.nums = new int[]{20, 50, 100, 200, 500};\\n }\\n\\n public void deposit(int[] banknotesCount) {\\n for (int i = 0; i < 5; i++) {\\n cnt[i] += banknotesCount[i];\\n }\\n }\\n\\n public int[] withdraw(int amount) {\\n int[] res = new int[5];\\n for (int i = 4; i >= 0; i--) {\\n if (amount >= nums[i] && cnt[i] > 0) {\\n int num = (amount / nums[i]) > cnt[i] ? (int) cnt[i] : (amount / nums[i]);\\n res[i] = num;\\n amount -= nums[i] * num;\\n }\\n }\\n // 如果能取出来,再对cnt真正操作\\n if (amount == 0) {\\n for (int i = 0; i < 5; i++) {\\n cnt[i] -= res[i];\\n }\\n return res;\\n }\\n return new int[]{-1};\\n }\\n}\\n\\n/**\\n * Your ATM object will be instantiated and called as such:\\n * ATM obj = new ATM();\\n * obj.deposit(banknotesCount);\\n * int[] param_2 = obj.withdraw(amount);\\n */\\n
\\n","description":"解题思路 本题可以构造一个cnt数组来存储不同币值的个数,注意这里应该是long型,这里0 <= banknotesCount[i] <= 109,且总共 最多有 5000 次 withdraw 和 deposit 的调用,会导致cnt中部分值超过整型范围。\\n\\n用nums来存储不同的币值。\\n\\n代码\\n\\n###java\\n\\nclass ATM {\\n\\n private long[] cnt;\\n private int[] nums;\\n\\n public ATM() {\\n this.cnt = new long[5…","guid":"https://leetcode.cn/problems/design-an-atm-machine//solution/dao-ge-shua-ti-by-lcfgrn-7pdt","author":"lcfgrn","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-17T07:49:29.881Z","media":[{"url":"https://pic.leetcode-cn.com/1650181965-VcBnwL-image.png","type":"photo","width":503,"height":389,"blurhash":"LERfqX-:od_3~VWBWCt7^YoyWVWB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java深度优先搜索,详细图文讲解(任何编程语言都能看懂)","url":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/by-weilai-wwg98","content":"
主要思路
\\n我们要做的事情其实很简单,那就是遍历一整个二叉树也就是root,然后具体步骤如下:
\\n\\n
\\n- 查看当前节点的内容与head的第一个内容是否相同,相同的话查看二叉树的走右子树是否与head的下一个节点内容相同,如此往复即可找到相同路径。如果不相同则跳过当前节点,查看当前节点的左右子树是否与head第一个节点相同。
\\n- 如果head的下一个节点已经为空(null)说明我们已经遍历完head,也就说明我们在二叉树上找到了所有的head节点,因为只有我们找到了二叉树上与head连续相同的一位时,才会去寻找连续相同的下一位head,所以这时我们直接返回true。
\\n- 如果root节点已经为空(null),则说明我们当前的路径就已经是错误的了,直接返回false即可。
\\n如果上面的文字不是很好理解,那么看下面的图你就应该能看懂了:
\\n\\n
\\n
\\n
\\n
\\n
\\n
\\n这样讲完是不是感觉这题很简单呢,所以我们直接来看代码,给大伙上上难度(没有用到Java的库,所以任何编程语言都能看懂)!
\\n###Java
\\n\\nclass Solution {\\n public boolean isSubPath(ListNode head, TreeNode root) {\\n if (head == null) {//满足条件,直接返回true。\\n return true;\\n }\\n \\n if (root == null) {//能执行到这里说明head不为空,此时root为空的话不可能有正确结果,直接返回false。\\n return false;\\n }\\n //这里可能比较难以理解,这里是调用了一个DfsSame和两个isSubPath.\\n //DfsSame的含义大家可以看下面,注释很详细。\\n //isSubPath就是当前的这个方法,所干的事情就是深度优先遍历DFS,他利用本身这个方法调用,来遍历root中每一个节点。\\n //所以总结一下就是两个isSubPath用来遍历当前位置的左右子树。\\n //一个DfsSame判断当前的root节点的值是否与head的第一个节点的值相同。\\n //在本方法中调用的DfsSame,root节点比较的永远是head第一个节点的值,也必须比较head第一个节点的值。\\n //判断head后续节点的值需要的前提条件是之前的节点已经找到了连续向下的路径。\\n return DfsSame(head,root) || isSubPath(head,root.left) || isSubPath(head,root.right);\\n }\\n \\n //判断当前root节点与当前head节点的内容是否相同。\\n private boolean DfsSame(ListNode head,TreeNode root) {\\n if(head == null) {//则说明head已经遍历完毕,已经在root中寻找到了与head相同连续向下的路径\\n return true;\\n }\\n \\n if (root == null) {//head不为空但root以为空,在此路径上不可能寻找到相同路径了,返回false\\n return false;\\n }\\n \\n if (root.val != head.val) {//值不相同,直接返回false.\\n return false;\\n }\\n //当前位置相同,root向下(向下寻找左右子树),head向后寻找下一个节点\\n return DfsSame(head.next,root.left) || DfsSame(head.next,root.right);\\n }\\n}\\n
这是一种很取巧的coding方式(取巧不一定是坏事,在本题它可以让你的代码看起来很简洁、高级),在搜索一个二叉树的某些内容时,这个方法非常常用,所以非常建议将此题这种coding方式当作一个模板。
\\n","description":"主要思路 我们要做的事情其实很简单,那就是遍历一整个二叉树也就是root,然后具体步骤如下:\\n\\n查看当前节点的内容与head的第一个内容是否相同,相同的话查看二叉树的走右子树是否与head的下一个节点内容相同,如此往复即可找到相同路径。如果不相同则跳过当前节点,查看当前节点的左右子树是否与head第一个节点相同。\\n如果head的下一个节点已经为空(null)说明我们已经遍历完head,也就说明我们在二叉树上找到了所有的head节点,因为只有我们找到了二叉树上与head连续相同的一位时,才会去寻找连续相同的下一位head,所以这时我们直接返回true。\\n如…","guid":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/by-weilai-wwg98","author":"WeiLai-","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-17T03:02:32.105Z","media":[{"url":"https://pic.leetcode-cn.com/1650165470-SYrpog-s902.jpg","type":"photo","width":1382,"height":1020,"blurhash":"L7S6V$_N?v_3.80ekps.tOxtIUV@"},{"url":"https://pic.leetcode-cn.com/1650165481-PxoGwJ-s903.jpg","type":"photo","width":1549,"height":1006,"blurhash":"LDS6V$~q-;?b%gIot7s:t7NaM|Rj"},{"url":"https://pic.leetcode-cn.com/1650165488-FPIdAL-s904.jpg","type":"photo","width":1317,"height":996,"blurhash":"LDS6St^+%f~q-;xGIo%M%2xuoeoL"},{"url":"https://pic.leetcode-cn.com/1650165493-TMaFWU-s905.jpg","type":"photo","width":1191,"height":1002,"blurhash":"LCR:NW~W%M~q-;xaM|%M=|kVoKxa"},{"url":"https://pic.leetcode-cn.com/1650165497-keWMIC-s906.jpg","type":"photo","width":1280,"height":1000,"blurhash":"LCR:NX~q%M~p.7s:IU%M?aocV[s;"},{"url":"https://pic.leetcode-cn.com/1650165502-JFnNRz-s907.jpg","type":"photo","width":1242,"height":1009,"blurhash":"LDR{#[?vxu~q?bt6IUxu%zsDoet6"},{"url":"https://pic.leetcode-cn.com/1650165506-jUAMGb-s908.jpg","type":"photo","width":1230,"height":1013,"blurhash":"LDR{x-?b?a~q-;t6IV%LtQ%fRje."}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Java】ATM机,注意处理long和int","url":"https://leetcode.cn/problems/design-an-atm-machine//solution/chu-li-by-relll-1037-kcs2","content":"模拟
\\n根据题意模拟即可,不过需要注意存储钱数量的数组需要long类型(卡112案例最后再看了一眼数据范围才想起来)
\\n昨晚被这道题绕晕了,很简单的题目到最后几分钟才注意到细节,差点没过😭😭
\\n###java
\\n\\n","description":"模拟 根据题意模拟即可,不过需要注意存储钱数量的数组需要long类型(卡112案例最后再看了一眼数据范围才想起来)\\n\\n昨晚被这道题绕晕了,很简单的题目到最后几分钟才注意到细节,差点没过😭😭\\n\\n###java\\n\\nclass ATM {\\n //int[] money = {20,50,100,200,500};\\n long[] quantitiy = new long[5];\\n\\n public ATM() {\\n\\n }\\n \\n public void deposit(int[] banknotesCount) {…","guid":"https://leetcode.cn/problems/design-an-atm-machine//solution/chu-li-by-relll-1037-kcs2","author":"endless_developy","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-17T01:13:41.752Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/mo-ni-by-endlesscheng-p5va","content":"class ATM {\\n //int[] money = {20,50,100,200,500};\\n long[] quantitiy = new long[5];\\n\\n public ATM() {\\n\\n }\\n \\n public void deposit(int[] banknotesCount) {\\n for (int i = 0; i < 5; i++) {\\n quantitiy[i] += banknotesCount[i];\\n }\\n }\\n \\n public int[] withdraw(int amount) {\\n int[] ans = new int[5], demo = {-1};\\n\\n int p = 4;\\n while (amount > 0) {\\n if (amount >= 500 && quantitiy[4] > 0) {\\n //需要和剩余\\n int need = amount / 500, extra = amount % 500;\\n if (need <= quantitiy[4]) { //取得出的话直接减去need,amount变为extra(剩余值)\\n ans[4] = need;\\n quantitiy[4] -= need;\\n amount = extra;\\n } else { //取不出的话变0\\n ans[4] = (int) quantitiy[4];\\n amount -= 500 * quantitiy[4];\\n quantitiy[4] = 0;\\n }\\n } else if (amount >= 200 && quantitiy[3] > 0) {\\n int need = amount / 200, extra = amount % 200;\\n if (need <= quantitiy[3]) {//取得出的话直接减去need,amount变为extra(剩余值)\\n ans[3] = need;\\n quantitiy[3] -= need;\\n amount = extra;\\n } else { //取不出的话变0\\n ans[3] = (int) quantitiy[3];\\n amount -= 200 * quantitiy[3];\\n quantitiy[3] = 0;\\n }\\n } else if (amount >= 100 && quantitiy[2] > 0) {\\n int need = amount / 100, extra = amount % 100;\\n if (need <= quantitiy[2]) {//取得出的话直接减去need,amount变为extra(剩余值)\\n ans[2] = need;\\n quantitiy[2] -= need;\\n amount = extra;\\n } else {//取不出的话变0\\n ans[2] = (int) quantitiy[2];\\n amount -= 100 * quantitiy[2];\\n quantitiy[2] = 0;\\n }\\n } else if (amount >= 50 && quantitiy[1] > 0) {\\n int need = amount / 50, extra = amount % 50;\\n if (need <= quantitiy[1]) {//取得出的话直接减去need,amount变为extra(剩余值)\\n ans[1] = need;\\n quantitiy[1] -= need;\\n amount = extra;\\n } else {//取不出的话变0\\n ans[1] = (int) quantitiy[1];\\n amount -= 50 * quantitiy[1];\\n quantitiy[1] = 0;\\n }\\n } else if (amount >= 20 && quantitiy[0] > 0) {\\n int need = amount / 20, extra = amount % 20;\\n if (need <= quantitiy[0]) {//取得出的话直接减去need,amount变为extra(剩余值)\\n ans[0] = need;\\n quantitiy[0] -= need;\\n amount = extra;\\n } else {//取不出的话变0\\n ans[0] = (int) quantitiy[0];\\n amount -= 20 * quantitiy[0];\\n quantitiy[0] = 0;\\n }\\n } else break;\\n }\\n //无法完全提取存款,需要把钱加回去\\n if (amount != 0) for (int i = 0; i < 5; i++) quantitiy[i] += ans[i];\\n //根据存款是否为0返回答案\\n return amount == 0 ? ans : demo;\\n }\\n}\\n
「最接近 $0$」等价于「绝对值最小」。
\\n遍历数组:
\\n\\n
\\n- 如果 $|\\\\textit{nums}[i]|$ 比答案的绝对值更小,则更新答案为 $\\\\textit{nums}[i]$。
\\n- 如果 $|\\\\textit{nums}[i]|$ 和答案的绝对值相同,且 $\\\\textit{nums}[i] > 0$,则更新答案为 $\\\\textit{nums}[i]$。
\\n\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n ans = nums[0]\\n for x in nums:\\n if abs(x) < abs(ans) or abs(x) == abs(ans) and x > 0:\\n ans = x\\n return ans\\n
\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n return -min((abs(x), -x) for x in nums)[1]\\n
\\nclass Solution {\\n public int findClosestNumber(int[] nums) {\\n int ans = nums[0];\\n for (int x : nums) {\\n if (Math.abs(x) < Math.abs(ans) || Math.abs(x) == Math.abs(ans) && x > 0) {\\n ans = x;\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int findClosestNumber(vector<int>& nums) {\\n int ans = nums[0];\\n for (int x : nums) {\\n if (abs(x) < abs(ans) || abs(x) == abs(ans) && x > 0) {\\n ans = x;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nclass Solution {\\npublic:\\n int findClosestNumber(vector<int>& nums) {\\n return ranges::min(nums, {}, [](int x) { return pair(abs(x), -x); });\\n }\\n};\\n
\\nint findClosestNumber(int* nums, int numsSize) {\\n int ans = nums[0];\\n for (int i = 1; i < numsSize; i++) {\\n int x = nums[i];\\n if (abs(x) < abs(ans) || abs(x) == abs(ans) && x > 0) {\\n ans = x;\\n }\\n }\\n return ans;\\n}\\n
\\nfunc findClosestNumber(nums []int) int {\\nans := nums[0]\\nfor _, x := range nums {\\nif abs(x) < abs(ans) || abs(x) == abs(ans) && x > 0 {\\nans = x\\n}\\n}\\nreturn ans\\n}\\n\\nfunc abs(x int) int { if x < 0 { return -x }; return x }\\n
\\nvar findClosestNumber = function(nums) {\\n let ans = nums[0];\\n for (const x of nums) {\\n if (Math.abs(x) < Math.abs(ans) || Math.abs(x) === Math.abs(ans) && x > 0) {\\n ans = x;\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn find_closest_number(nums: Vec<i32>) -> i32 {\\n *nums.iter().min_by_key(|&x| (x.abs(), -x)).unwrap()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"「最接近 $0$」等价于「绝对值最小」。 遍历数组:\\n\\n如果 $|\\\\textit{nums}[i]|$ 比答案的绝对值更小,则更新答案为 $\\\\textit{nums}[i]$。\\n如果 $|\\\\textit{nums}[i]|$ 和答案的绝对值相同,且 $\\\\textit{nums}[i] > 0$,则更新答案为 $\\\\textit{nums}[i]$。\\nclass Solution:\\n def findClosestNumber(self, nums: List[int]) -> int:\\n ans = nums[0…","guid":"https://leetcode.cn/problems/find-closest-number-to-zero//solution/mo-ni-by-endlesscheng-p5va","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-16T16:12:25.206Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/design-an-atm-machine//solution/mo-ni-by-endlesscheng-lnia","content":"用一个数组 $\\\\textit{banknotes}$ 维护 ATM 中每种钞票的数量。
\\n取钱时,先计算每种钞票所需数量。
\\n从大到小枚举钞票面额。设当前钞票的面额为 $d$,数量为 $\\\\textit{banknote}$,那么可以取出
\\n$$
\\n
\\nc = \\\\min\\\\left(\\\\left\\\\lfloor\\\\dfrac{amount}{d}\\\\right\\\\rfloor, \\\\textit{banknote}\\\\right)
\\n$$张钞票。然后把 $\\\\textit{amount}$ 减少 $c\\\\cdot d$,枚举下一个钞票面额。
\\n枚举结束后,如果 $\\\\textit{amount}$ 仍然大于 $0$,返回 $[-1]$。否则正常取钱,更新 ATM 中的钞票数量,然后返回答案。
\\n细节
\\n代码中,我没有显示地写出数字 $5$(除了 Rust 有一处显式指定)。这样做可维护性更好,如果要添加一种面额,仅需修改
\\nDENOMINATIONS
。\\nDENOMINATIONS = [20, 50, 100, 200, 500]\\nKINDS = len(DENOMINATIONS)\\n\\nclass ATM:\\n def __init__(self):\\n self.banknotes = [0] * KINDS\\n\\n def deposit(self, banknotesCount: List[int]) -> None:\\n # 存钱\\n for i, count in enumerate(banknotesCount):\\n self.banknotes[i] += count\\n\\n def withdraw(self, amount: int) -> List[int]:\\n ans = [0] * KINDS\\n\\n # 计算每种钞票所需数量\\n for i in range(KINDS - 1, -1, -1):\\n ans[i] = min(amount // DENOMINATIONS[i], self.banknotes[i])\\n amount -= ans[i] * DENOMINATIONS[i]\\n\\n # 无法取恰好 amount\\n if amount > 0:\\n return [-1]\\n\\n # 取钱\\n for i, count in enumerate(ans):\\n self.banknotes[i] -= count\\n\\n return ans\\n
\\nclass ATM {\\n private static final int[] DENOMINATIONS = {20, 50, 100, 200, 500};\\n private static final int KINDS = DENOMINATIONS.length;\\n\\n private final int[] banknotes = new int[KINDS];\\n\\n public void deposit(int[] banknotesCount) {\\n // 存钱\\n for (int i = 0; i < KINDS; i++) {\\n banknotes[i] += banknotesCount[i];\\n }\\n }\\n\\n public int[] withdraw(int amount) {\\n int[] ans = new int[KINDS];\\n\\n // 计算每种钞票所需数量\\n for (int i = KINDS - 1; i >= 0; i--) {\\n ans[i] = Math.min(amount / DENOMINATIONS[i], banknotes[i]);\\n amount -= ans[i] * DENOMINATIONS[i];\\n }\\n\\n // 无法取恰好 amount\\n if (amount > 0) {\\n return new int[]{-1};\\n }\\n\\n // 取钱\\n for (int i = 0; i < KINDS; i++) {\\n banknotes[i] -= ans[i];\\n }\\n\\n return ans;\\n }\\n}\\n
\\nclass ATM {\\n static constexpr int DENOMINATIONS[] = {20, 50, 100, 200, 500};\\n static constexpr int KINDS = size(DENOMINATIONS);\\n\\n int banknotes[KINDS]{};\\n\\npublic:\\n void deposit(vector<int> banknotesCount) {\\n // 存钱\\n for (int i = 0; i < KINDS; i++) {\\n banknotes[i] += banknotesCount[i];\\n }\\n }\\n\\n vector<int> withdraw(int amount) {\\n vector<int> ans(KINDS);\\n\\n // 计算每种钞票所需数量\\n for (int i = KINDS - 1; i >= 0; i--) {\\n ans[i] = min(amount / DENOMINATIONS[i], banknotes[i]);\\n amount -= ans[i] * DENOMINATIONS[i];\\n }\\n\\n // 无法取恰好 amount\\n if (amount > 0) {\\n return {-1};\\n }\\n\\n // 取钱\\n for (int i = 0; i < KINDS; i++) {\\n banknotes[i] -= ans[i];\\n }\\n\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nstatic const int DENOMINATIONS[] = {20, 50, 100, 200, 500};\\n#define KINDS (sizeof(DENOMINATIONS) / sizeof(DENOMINATIONS[0]))\\n\\ntypedef struct {\\n int banknotes[KINDS];\\n} ATM;\\n\\nATM* aTMCreate() {\\n return calloc(1, sizeof(ATM));\\n}\\n\\nvoid aTMDeposit(ATM* obj, int* banknotesCount, int) {\\n // 存钱\\n for (int i = 0; i < KINDS; i++) {\\n obj->banknotes[i] += banknotesCount[i];\\n }\\n}\\n\\nint* aTMWithdraw(ATM* obj, int amount, int* returnSize) {\\n int* ans = malloc(KINDS * sizeof(int));\\n\\n // 计算每种钞票所需数量\\n for (int i = KINDS - 1; i >= 0; i--) {\\n ans[i] = MIN(amount / DENOMINATIONS[i], obj->banknotes[i]);\\n amount -= ans[i] * DENOMINATIONS[i];\\n }\\n\\n // 无法取恰好 amount\\n if (amount > 0) {\\n *returnSize = 1;\\n ans[0] = -1;\\n return ans;\\n }\\n\\n // 取钱\\n *returnSize = KINDS;\\n for (int i = 0; i < KINDS; i++) {\\n obj->banknotes[i] -= ans[i];\\n }\\n\\n return ans;\\n}\\n\\nvoid aTMFree(ATM* obj) {\\n free(obj);\\n}\\n
\\nvar denominations = [...]int{20, 50, 100, 200, 500}\\n\\nconst kinds = len(denominations)\\n\\ntype ATM [kinds]int\\n\\nfunc Constructor() ATM {\\n return ATM{}\\n}\\n\\nfunc (banknotes *ATM) Deposit(banknotesCount []int) {\\n // 存钱\\n for i, count := range banknotesCount {\\n banknotes[i] += count\\n }\\n}\\n\\nfunc (banknotes *ATM) Withdraw(amount int) []int {\\n ans := make([]int, kinds)\\n\\n // 计算每种钞票所需数量\\n for i := kinds - 1; i >= 0; i-- {\\n ans[i] = min(amount/denominations[i], banknotes[i])\\n amount -= ans[i] * denominations[i]\\n }\\n\\n // 无法取恰好 amount\\n if amount > 0 {\\n return []int{-1}\\n }\\n\\n // 取钱\\n for i, count := range ans {\\n banknotes[i] -= count\\n }\\n\\n return ans\\n}\\n
\\nconst DENOMINATIONS = [20, 50, 100, 200, 500];\\nconst KINDS = DENOMINATIONS.length;\\n\\nvar ATM = function() {\\n this.banknotes = Array(KINDS).fill(0);\\n};\\n\\nATM.prototype.deposit = function(banknotesCount) {\\n // 存钱\\n for (let i = 0; i < KINDS; i++) {\\n this.banknotes[i] += banknotesCount[i];\\n }\\n};\\n\\nATM.prototype.withdraw = function(amount) {\\n const ans = Array(KINDS).fill(0);\\n\\n // 计算每种钞票所需数量\\n for (let i = KINDS - 1; i >= 0; i--) {\\n ans[i] = Math.min(Math.floor(amount / DENOMINATIONS[i]), this.banknotes[i]);\\n amount -= ans[i] * DENOMINATIONS[i];\\n }\\n\\n // 无法取恰好 amount\\n if (amount > 0) {\\n return [-1];\\n }\\n\\n // 取钱\\n for (let i = 0; i < KINDS; i++) {\\n this.banknotes[i] -= ans[i];\\n }\\n\\n return ans;\\n};\\n
\\nstruct ATM {\\n banknotes: [i32; ATM::KINDS],\\n}\\n\\nimpl ATM {\\n const DENOMINATIONS: [i32; 5] = [20, 50, 100, 200, 500];\\n const KINDS: usize = Self::DENOMINATIONS.len();\\n\\n fn new() -> Self {\\n Self {\\n banknotes: [0; Self::KINDS],\\n }\\n }\\n\\n fn deposit(&mut self, banknotes_count: Vec<i32>) {\\n // 存钱\\n for i in 0..Self::KINDS {\\n self.banknotes[i] += banknotes_count[i];\\n }\\n }\\n\\n fn withdraw(&mut self, mut amount: i32) -> Vec<i32> {\\n let mut ans = vec![0; Self::KINDS];\\n\\n // 计算每种钞票所需数量\\n for i in (0..Self::KINDS).rev() {\\n ans[i] = self.banknotes[i].min(amount / Self::DENOMINATIONS[i]);\\n amount -= ans[i] * Self::DENOMINATIONS[i];\\n }\\n\\n // 无法取恰好 amount\\n if amount > 0 {\\n return vec![-1];\\n }\\n\\n // 取钱\\n for i in 0..Self::KINDS {\\n self.banknotes[i] -= ans[i];\\n }\\n\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:所有操作均为 $\\\\mathcal{O}(C)$,其中 $C=5$。
\\n- 空间复杂度:$\\\\mathcal{O}(C)$。
\\n思考题
\\n如果去掉「优先取较大数额的钱」这个约束呢?
\\n欢迎在评论区分享你的思路/代码。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"用一个数组 $\\\\textit{banknotes}$ 维护 ATM 中每种钞票的数量。 取钱时,先计算每种钞票所需数量。\\n\\n从大到小枚举钞票面额。设当前钞票的面额为 $d$,数量为 $\\\\textit{banknote}$,那么可以取出\\n\\n$$\\n c = \\\\min\\\\left(\\\\left\\\\lfloor\\\\dfrac{amount}{d}\\\\right\\\\rfloor, \\\\textit{banknote}\\\\right)\\n $$\\n\\n张钞票。然后把 $\\\\textit{amount}$ 减少 $c\\\\cdot d$,枚举下一个钞票面额。\\n\\n枚举结束后,如果 $\\\\textit…","guid":"https://leetcode.cn/problems/design-an-atm-machine//solution/mo-ni-by-endlesscheng-lnia","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-16T16:11:27.580Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"花园的最大总美丽值","url":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/hua-yuan-de-zui-da-zong-mei-li-zhi-by-le-18d8","content":"方法一:枚举「完善」和「不完善」的分界线
\\n思路与算法
\\n从贪心的角度,我们首先可以发现,最优的方案一定具有如下的形式:
\\n\\n\\n我们选择数组 $\\\\textit{flowers}$ 中最大的若干个元素,将它们加到至少 $\\\\textit{target}$ 朵花,成为「完善」的花园;对于剩余的花朵,我们将它们加入 $\\\\textit{flowers}$ 剩余的元素中,使得最终最小的元素尽可能大。
\\n证明的方法也很简单,假设数组 $\\\\textit{flowers}$ 中有两个元素 $x, y$ 满足 $x > y$,我们一定是优先将 $x$ 加到 $\\\\textit{target}$ 的。这里可以使用反正 + 构造法,如果我们优先将 $y$ 加到 $\\\\textit{target}$,那么添加的花朵数 $\\\\textit{target} - y$ 可以拆分成 $\\\\textit{target} - x$ 以及 $x - y$ 这两部分之和,我们将前者添加到 $x$ 中,后者添加到 $y$ 中,这样最终同样得到了 $\\\\textit{target}$ 和 $x$。因此优先将更大的元素加到 $\\\\textit{target}$ 一定是优的。
\\n因此我们可以将 $\\\\textit{flowers}$ 首先进行降序排序。记其长度为 $n$,这样一来,我们可以枚举「完善」和「不完善」的分界线 $i$,表示将 $[0, i)$ 变成完善的花园,$[i, n)$ 为不完善的花园。
\\n对于完善的部分,我们需要添加的花朵数量为:
\\n$$
\\n
\\n\\\\textit{target} \\\\cdot i - \\\\sum_{k=0}^{i-1} \\\\textit{flowers}[k] \\\\tag{1}
\\n$$这个值需要小于等于 $\\\\textit{newFlowers}$。如果我们递增枚举 $i$,那么 $(1)$ 式中的求和部分就是一个前缀和,我们可以很方便地进行维护。
\\n记剩余可以添加的花的数目为 $\\\\textit{rest}$,有 $\\\\textit{rest} = \\\\textit{newFlowers} - (1)$。我们需要找到一个严格小于 $\\\\textit{target}$ 的值,使得将所有剩余的花园的花的数量添加到至少为这个值时,添加的花朵总数小于等于 $\\\\textit{rest}$。我们可以将寻找这个值的过程分成两部分:第一步我们保证这个值一定在 $\\\\textit{flowers}[i .. n-1]$ 中出现,第二步我们再在这个值的基础上继续添加花朵。也就是说,我们需要找到一个下标 $j$ 满足:
\\n$$
\\n
\\n\\\\textit{flowers}[j] \\\\cdot (n-j) - \\\\sum_{k=j}^{n-1} \\\\textit{flower}[k] \\\\leq \\\\textit{rest} \\\\tag{2}
\\n$$并且:
\\n$$
\\n
\\n\\\\textit{flowers}[j-1] \\\\cdot (n-j+1) - \\\\sum_{k=j-1}^{n-1} \\\\textit{flower}[k] > \\\\textit{rest}
\\n$$即我们需要找出的这个值在 $\\\\big[\\\\textit{flowers}[j], \\\\textit{flowers}[j-1]\\\\big)$ 的范围内,因此我们就可以首先保证所有剩余的花园的花的数量都至少为 $\\\\textit{flowers}[j]$,再继续对下标为 $[j, n)$ 的花园平均地添加花朵,直到所有的花朵用完。在这一步中,下标为 $[i, j)$ 的花园是不变的。
\\n当我们递增地枚举 $i$ 时,$\\\\textit{rest}$ 是单调递减的,因此我们可以使用一个不断向右移动的指针来维护 $j$:即当 $i$ 递增后,我们需要不断增加 $j$,直到 $(2)$ 成立。在 $j$ 递增的过程中,$(2)$ 式中的求和部分是一个后缀和,我们可以很方便地进行维护。
\\n当我们得到了当前的 $i$ 对应的 $j$ 之后,我们需要将 $\\\\textit{rest}$ 减去 $(2)$ 式左侧的值。下标为 $[j, n)$ 的花园的数量为 $n-j$,因此我们还可以给每个花园添加 $\\\\lfloor \\\\dfrac{\\\\textit{rest}}{n-j} \\\\rfloor$ 朵花,其中 $\\\\lfloor \\\\cdot \\\\rfloor$ 表示向下取整。
\\n此时我们就能计算美丽值了。即为:
\\n$$
\\n
\\n\\\\textit{full} \\\\cdot i + \\\\textit{partial} \\\\cdot \\\\left( \\\\min\\\\left{ \\\\textit{flowers}[j] + \\\\lfloor \\\\frac{\\\\textit{rest}}{n-j} \\\\rfloor, \\\\textit{target} - 1 \\\\right} \\\\right)
\\n$$细节
\\n本题中没有规定 $\\\\textit{flowers}$ 中的元素初始时一定小于等于 $\\\\textit{target}$,因此我们可以在一开始对其进行一次遍历,把所有大于 $\\\\textit{target}$ 的元素都减小为 $\\\\textit{target}$。这样做也是合理的,显然我们没有必要给已经完善的花园再添加花朵。
\\n同时在枚举 $i$ 时,我们需要保证 $[i, n)$ 对应的元素都严格小于 $\\\\textit{target}$,否则它们就不是不完善的了。由于数组已经按照降序排序,我们只需要验证是否有 $\\\\textit{flowers}[i] \\\\neq \\\\textit{target}$ 即可。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {\\n int n = flowers.size();\\n for (int& x: flowers) {\\n x = min(x, target);\\n }\\n sort(flowers.begin(), flowers.end(), greater<int>());\\n long long sum = accumulate(flowers.begin(), flowers.end(), 0LL);\\n long long ans = 0;\\n if (static_cast<long long>(target) * n - sum <= newFlowers) {\\n ans = static_cast<long long>(full) * n;\\n }\\n\\n long long pre = 0;\\n int ptr = 0;\\n for (int i = 0; i < n; ++i) {\\n if (i != 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] == target) {\\n continue;\\n }\\n long long rest = newFlowers - (static_cast<long long>(target) * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && static_cast<long long>(flowers[ptr]) * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ++ptr;\\n }\\n rest -= static_cast<long long>(flowers[ptr]) * (n - ptr) - sum;\\n ans = max(ans, static_cast<long long>(full) * i + static_cast<long long>(partial) * (min(flowers[ptr] + rest / (n - ptr), static_cast<long long>(target) - 1)));\\n }\\n\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def maximumBeauty(self, flowers: List[int], newFlowers: int, target: int, full: int, partial: int) -> int:\\n n = len(flowers)\\n flowers = sorted([min(x, target) for x in flowers], reverse=True)\\n total = sum(flowers)\\n ans = 0\\n \\n if target * n - total <= newFlowers:\\n ans = full * n\\n \\n pre = ptr = 0\\n for i in range(n):\\n if i != 0:\\n pre += flowers[i - 1]\\n if flowers[i] == target:\\n continue\\n \\n rest = newFlowers - (target * i - pre)\\n if rest < 0:\\n break\\n \\n while not (ptr >= i and flowers[ptr] * (n - ptr) - total <= rest):\\n total -= flowers[ptr]\\n ptr += 1\\n \\n rest -= flowers[ptr] * (n - ptr) - total\\n ans = max(ans, full * i + partial * (min(flowers[ptr] + rest // (n - ptr), target - 1)))\\n \\n return ans\\n
###Java
\\n\\nclass Solution {\\n public long maximumBeauty(int[] flowers, long newFlowers, int target, int full, int partial) {\\n int n = flowers.length;\\n for (int i = 0; i < n; i++) {\\n flowers[i] = Math.min(flowers[i], target);\\n }\\n Arrays.sort(flowers);\\n reverse(flowers);\\n long sum = 0;\\n for (int flower : flowers) {\\n sum += flower;\\n }\\n long ans = 0;\\n if ((long) target * n - sum <= newFlowers) {\\n ans = (long) full * n;\\n }\\n long pre = 0;\\n int ptr = 0;\\n for (int i = 0; i < n; i++) {\\n if (i != 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] == target) {\\n continue;\\n }\\n long rest = newFlowers - ((long) target * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && (long) flowers[ptr] * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ptr++;\\n }\\n rest -= (long) flowers[ptr] * (n - ptr) - sum;\\n ans = Math.max(ans, (long) full * i + (long) partial * Math.min(flowers[ptr] + rest / (n - ptr), (long) target - 1));\\n }\\n return ans;\\n }\\n\\n private void reverse(int[] nums) {\\n for (int i = 0, j = nums.length - 1; i < j; i++, j--) {\\n int temp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = temp;\\n }\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public long MaximumBeauty(int[] flowers, long newFlowers, int target, int full, int partial) {\\n int n = flowers.Length;\\n for (int i = 0; i < n; i++) {\\n flowers[i] = Math.Min(flowers[i], target);\\n }\\n Array.Sort(flowers, (a, b) => b.CompareTo(a));\\n long sum = flowers.Sum(x => (long)x);\\n long ans = 0;\\n if ((long)target * n - sum <= newFlowers) {\\n ans = (long)full * n;\\n }\\n long pre = 0;\\n int ptr = 0;\\n for (int i = 0; i < n; i++) {\\n if (i != 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] == target) {\\n continue;\\n }\\n long rest = newFlowers - ((long)target * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && (long)flowers[ptr] * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ptr++;\\n }\\n rest -= (long)flowers[ptr] * (n - ptr) - sum;\\n ans = Math.Max(ans, (long)full * i + (long)partial * Math.Min(flowers[ptr] + rest / (n - ptr), (long)target - 1));\\n }\\n\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc maximumBeauty(flowers []int, newFlowers int64, target int, full int, partial int) int64 {\\n n := len(flowers)\\nfor i := 0; i < n; i++ {\\nif flowers[i] > target {\\nflowers[i] = target\\n}\\n}\\nsort.Sort(sort.Reverse(sort.IntSlice(flowers)))\\nvar sum int64\\nfor _, flower := range flowers {\\nsum += int64(flower)\\n}\\nvar ans int64\\nif int64(target)*int64(n) - sum <= newFlowers {\\nans = int64(full) * int64(n)\\n}\\npre, ptr := int64(0), 0\\nfor i := 0; i < n; i++ {\\nif i != 0 {\\npre += int64(flowers[i-1])\\n}\\nif flowers[i] == target {\\ncontinue\\n}\\nrest := newFlowers - (int64(target) * int64(i) - pre)\\nif rest < 0 {\\nbreak\\n}\\nfor !(ptr >= i && int64(flowers[ptr]) * int64(n - ptr) - sum <= rest) {\\nsum -= int64(flowers[ptr])\\nptr++\\n}\\nrest -= int64(flowers[ptr]) * int64(n - ptr) - sum\\nans = max(ans, int64(full) * int64(i) + int64(partial) * min(int64(flowers[ptr]) + rest /int64(n - ptr), int64(target) - 1))\\n}\\n\\nreturn ans\\n}\\n
###C
\\n\\nint cmp(const void* a, const void* b) {\\n return *(int*)b - *(int*)a;\\n}\\n\\nlong long maximumBeauty(int* flowers, int flowersSize, long long newFlowers, int target, int full, int partial) {\\n int n = flowersSize;\\n for (int i = 0; i < n; i++) {\\n if (flowers[i] > target) {\\n flowers[i] = target;\\n }\\n }\\n qsort(flowers, n, sizeof(int), cmp);\\n\\n long sum = 0;\\n for (int i = 0; i < n; i++) {\\n sum += flowers[i];\\n }\\n long ans = 0;\\n if ((long)target * n - sum <= newFlowers) {\\n ans = (long)full * n;\\n }\\n long pre = 0;\\n int ptr = 0;\\n for (int i = 0; i < n; i++) {\\n if (i != 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] == target) {\\n continue;\\n }\\n long rest = newFlowers - ((long)target * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && (long)flowers[ptr] * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ptr++;\\n }\\n rest -= (long)flowers[ptr] * (n - ptr) - sum;\\n ans = fmax(ans, (long)full * i + (long)partial * fmin(flowers[ptr] + rest / (n - ptr), (long)target - 1));\\n }\\n\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar maximumBeauty = function(flowers, newFlowers, target, full, partial) {\\n const n = flowers.length;\\n flowers = flowers.map(flower => Math.min(flower, target));\\n flowers.sort((a, b) => b - a);\\n let sum = flowers.reduce((acc, flower) => acc + flower, 0);\\n let ans = 0;\\n if (target * n - sum <= newFlowers) {\\n ans = full * n;\\n }\\n let pre = 0, ptr = 0;\\n for (let i = 0; i < n; i++) {\\n if (i !== 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] === target) {\\n continue;\\n }\\n let rest = newFlowers - (target * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && flowers[ptr] * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ptr++;\\n }\\n rest -= flowers[ptr] * (n - ptr) - sum;\\n ans = Math.max(ans, full * i + partial * Math.min(flowers[ptr] + Math.floor(rest / (n - ptr)), target - 1));\\n }\\n\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction maximumBeauty(flowers: number[], newFlowers: number, target: number, full: number, partial: number): number {\\n const n = flowers.length;\\n flowers = flowers.map(flower => Math.min(flower, target));\\n flowers.sort((a, b) => b - a);\\n let sum = flowers.reduce((acc, flower) => acc + flower, 0);\\n let ans = 0;\\n if (target * n - sum <= newFlowers) {\\n ans = full * n;\\n }\\n let pre = 0, ptr = 0;\\n for (let i = 0; i < n; i++) {\\n if (i !== 0) {\\n pre += flowers[i - 1];\\n }\\n if (flowers[i] === target) {\\n continue;\\n }\\n let rest = newFlowers - (target * i - pre);\\n if (rest < 0) {\\n break;\\n }\\n while (!(ptr >= i && flowers[ptr] * (n - ptr) - sum <= rest)) {\\n sum -= flowers[ptr];\\n ptr++;\\n }\\n rest -= flowers[ptr] * (n - ptr) - sum;\\n ans = Math.max(ans, full * i + partial * Math.min(flowers[ptr] + Math.floor(rest / (n - ptr)), target - 1));\\n }\\n\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::cmp::{max, min};\\n\\nimpl Solution {\\n pub fn maximum_beauty(flowers: Vec<i32>, new_flowers: i64, target: i32, full: i32, partial: i32) -> i64 {\\n let mut flowers = flowers.clone();\\n let n = flowers.len();\\n for flower in flowers.iter_mut() {\\n if *flower > target {\\n *flower = target;\\n }\\n }\\n flowers.sort_by(|a, b| b.cmp(a));\\n let mut sum: i64 = flowers.iter().map(|&flower| flower as i64).sum();\\n let mut ans = 0;\\n if (target as i64) * n as i64 - sum <= new_flowers {\\n ans = full as i64 * n as i64;\\n }\\n let mut pre = 0;\\n let mut ptr = 0;\\n for i in 0..n {\\n if i != 0 {\\n pre += flowers[i - 1] as i64;\\n }\\n if flowers[i] == target {\\n continue;\\n }\\n let mut rest = new_flowers - ((target as i64) * i as i64 - pre);\\n if rest < 0 {\\n break;\\n }\\n while !(ptr >= i && (flowers[ptr] as i64) * (n - ptr) as i64 - sum <= rest) {\\n sum -= flowers[ptr] as i64;\\n ptr += 1;\\n }\\n rest -= (flowers[ptr] as i64) * (n - ptr) as i64 - sum;\\n ans = max(ans, full as i64 * i as i64 + partial as i64 * min(flowers[ptr] as i64 + rest / (n - ptr) as i64, (target - 1) as i64));\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举「完善」和「不完善」的分界线 思路与算法\\n\\n从贪心的角度,我们首先可以发现,最优的方案一定具有如下的形式:\\n\\n我们选择数组 $\\\\textit{flowers}$ 中最大的若干个元素,将它们加到至少 $\\\\textit{target}$ 朵花,成为「完善」的花园;对于剩余的花朵,我们将它们加入 $\\\\textit{flowers}$ 剩余的元素中,使得最终最小的元素尽可能大。\\n\\n证明的方法也很简单,假设数组 $\\\\textit{flowers}$ 中有两个元素 $x, y$ 满足 $x > y$,我们一定是优先将 $x$ 加到 $\\\\textit…","guid":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/hua-yuan-de-zui-da-zong-mei-li-zhi-by-le-18d8","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-13T08:33:21.975Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举 & 二分","url":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-tsreaper-dbvt","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$,即为排序需要的栈空间。
\\n解法:枚举 & 二分
\\n枚举最后一共有几座完善的花园,再二分不完善的花园中,花的最少数目最大可以是多少。复杂度 $\\\\mathcal{O}(n \\\\log n)$。
\\n本题实现起来有一定细节,细节的阐述见参考代码。
\\n参考代码(c++)
\\n###c++
\\n\\n","description":"解法:枚举 & 二分 枚举最后一共有几座完善的花园,再二分不完善的花园中,花的最少数目最大可以是多少。复杂度 $\\\\mathcal{O}(n \\\\log n)$。\\n\\n本题实现起来有一定细节,细节的阐述见参考代码。\\n\\n参考代码(c++)\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n long long maximumBeauty(vectorclass Solution {\\npublic:\\n long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {\\n int n = flowers.size();\\n vector<int> A(n + 1);\\n sort(flowers.begin(), flowers.end());\\n for (int i = 0; i < n; i++) A[i + 1] = flowers[i];\\n vector<long long> f(n + 1);\\n for (int i = 1; i <= n; i++) f[i] = f[i - 1] + A[i];\\n\\n // 统计一开始就有几座完善的花园\\n int start;\\n for (start = 0; start < n; start++) if (A[n - start] < target) break;\\n // sm 表示为了获得规定数量的完善花园,需要多种几朵花\\n long long ans = 0, sm = 0;\\n for (int i = start; i <= n && sm <= newFlowers; i++) {\\n // 二分,用剩下的花,可以把不完善的花园里最少的花变得和哪个花园一样多\\n int head = 0, tail = n - i;\\n while (head < tail) {\\n int mid = (head + tail + 1) >> 1;\\n long long t = 1LL * mid * A[mid] - f[mid];\\n if (sm + t <= newFlowers) head = mid;\\n else tail = mid - 1;\\n }\\n // 所有花园里花的数目至少和第 head 个花园一样多,可能还剩下一些花,还能再提高花的数目\\n // 不完善花园里的花不能超过 target,否则就变成完善花园了\\n long long x = newFlowers - sm - (1LL * head * A[head] - f[head]);\\n long long y = min(head ? A[head] + x / head : 0, target - 1LL);\\n ans = max(ans, 1LL * i * full + 1LL * y * partial);\\n sm += target - A[n - i];\\n }\\n return ans;\\n }\\n};\\n
& flowers, long long newFlowers, int target, int full, int partial) {\\n int n = flowers…","guid":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-tsreaper-dbvt","author":"tsreaper","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-10T04:24:25.896Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心+排序+双指针(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-endlesscheng-10i7","content":" 核心思路:枚举把多少个花园种满(至少有 $\\\\textit{target}$ 朵花),剩余的花种其他花园,让最小花朵数最大。
\\n\\n
贪心地想,那些要种满的花园,原有的花朵数越多越好,这样我们能有更多的花,去增大花的最少数目。
\\n将 $\\\\textit{flowers}$ 从小到大排序,这样 $\\\\textit{flowers}$ 的后缀就是要种满的花园。
\\n枚举 $i$,把 $\\\\textit{flowers}[i]$ 到 $\\\\textit{flowers}[n-1]$ 都种满花。那么剩下要解决的问题就是,怎么最大化花的最少数目。
\\n例如 $\\\\textit{flowers}=[1,3,5,7,10,10]$,还剩下 $9$ 朵花。
\\n\\n
\\n- 为了增大最小值,我们先把 $\\\\textit{flowers}[0]=1$ 增大,种下 $2$ 朵花,增大到 $\\\\textit{flowers}[1]=3$,还剩下 $9-2=7$ 朵花。你可以把这个过程想象成我们在往前缀中倒水。
\\n- 继续种花(倒水),必须把 $\\\\textit{flowers}[0]$ 和 $\\\\textit{flowers}[1]$ 同时增大,那么各自种下 $2$ 朵花,增大到 $\\\\textit{flowers}[2]=5$,还剩下 $7-4=3$ 朵花。
\\n- 继续种花(倒水),必须把 $\\\\textit{flowers}[0]$,$\\\\textit{flowers}[1]$ 和 $\\\\textit{flowers}[2]$ 同时增大,由于剩余的花朵数无法让这三个花园都有 $\\\\textit{flowers}[3]=7$ 朵花,所以只能平均每个花园都种 $1$ 朵花。
\\n- 最终,$\\\\textit{flowers}=[6,6,6,7,10,10]$,最小的花园有 $6$ 朵花。换句话说,我们把 $\\\\textit{flowers}$ 的一个长为 $3$ 的前缀都变成了 $6$ 朵花。
\\n如果每次枚举 $i$,都模拟一遍上述流程的话,时间复杂度是 $\\\\mathcal{O}(n^2)$,太慢了。
\\n注意到,随着 $i$ 的变大(后缀变短),剩余能填充到前缀中的花也越多,那么前缀也就越长。有单调性,我们可以用双指针,枚举 $i$(后缀长度),同时维护前缀的最长长度。
\\n设在填充后缀之后,还剩下 $\\\\textit{leftFlowers}$ 朵花可以分配。我们把这些花种到长为 $j$ 的前缀 $[0,j-1]$ 中。设最终最小值为 $\\\\textit{avg}$,那么这 $j$ 个花园一共有 $\\\\textit{avg}\\\\cdot j$ 朵花,这个总数不能超过 $\\\\textit{leftFlowers}$ 加上原有的花,即
\\n$$
\\n
\\n\\\\textit{avg}\\\\cdot j \\\\le \\\\textit{leftFlowers} + \\\\sum_{k=0}^{k=j-1} \\\\textit{flowers}[k]
\\n$$解得 $\\\\textit{avg}$ 的最大值为
\\n$$
\\n
\\n\\\\left\\\\lfloor\\\\dfrac{\\\\textit{leftFlowers}+\\\\sum_{k=0}^{k=j-1} \\\\textit{flowers}[k]}{j}\\\\right\\\\rfloor
\\n$$由于 $j$ 是单调增加的,上式中的前缀和 $\\\\sum_{k=0}^{k=j-1} \\\\textit{flowers}[k]$ 可以(在双指针的过程中)用一个变量 $\\\\textit{preSum}$ 维护。
\\n最后,根据题目给出的计算公式
\\n$$
\\n
\\n\\\\textit{avg} \\\\cdot \\\\textit{partial} + (n-i) \\\\cdot \\\\textit{full}
\\n$$计算总美丽值,更新答案的最大值。(其中 $n-i$ 是后缀 $[i,n-1]$ 的长度)
\\n优化
\\n由于双指针是 $\\\\mathcal{O}(n)$ 的,时间复杂度的瓶颈在排序上。如果能在排序之前,特判某些情况,从而提前返回,就能减少运行时间。
\\n\\n
\\n- 如果所有花园都至少有 $\\\\textit{target}$ 朵花,那么总美丽值只能是 $n\\\\cdot \\\\textit{full}$。(注意不能减少花的数量)
\\n- 否则,如果 $\\\\textit{newFlowers}$ 足以让所有花园都至少有 $\\\\textit{target}$ 朵花,那么我们有两种策略,取其中最大值作为答案:\\n
\\n\\n
\\n- 留一个花园种 $\\\\textit{target}-1$ 朵花,其余种满,总美丽值为 $(\\\\textit{target}-1) \\\\cdot \\\\textit{partial} + (n-1) \\\\cdot \\\\textit{full}$。
\\n- 全部种满,总美丽值为 $n\\\\cdot \\\\textit{full}$。
\\n细节
\\n把超过 $\\\\textit{target}$ 的 $\\\\textit{flowers}[i]$ 改成 $\\\\textit{target}$。这一来可以简化双指针的计算,二来可以加快排序的效率,尤其是当很多 $\\\\textit{flowers}[i]$ 都超过 $\\\\textit{target}$ 的情况。
\\n\\n\\n注:也可以用计数排序。
\\n\\nclass Solution:\\n def maximumBeauty(self, flowers: List[int], newFlowers: int, target: int, full: int, partial: int) -> int:\\n n = len(flowers)\\n for i in range(n):\\n flowers[i] = min(flowers[i], target)\\n\\n # 如果全部种满,还剩下多少朵花?\\n left_flowers = newFlowers - (target * n - sum(flowers))\\n\\n # 没有种花,所有花园都已种满\\n if left_flowers == newFlowers:\\n return n * full # 答案只能是 n*full(注意不能减少花的数量)\\n\\n # 可以全部种满\\n if left_flowers >= 0:\\n # 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return max((target - 1) * partial + (n - 1) * full, n * full)\\n\\n flowers.sort() # 时间复杂度的瓶颈在这,尽量写在后面\\n\\n ans = pre_sum = j = 0\\n # 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for i in range(1, n + 1):\\n # 撤销,flowers[i-1] 不变成 target\\n left_flowers += target - flowers[i - 1]\\n if left_flowers < 0: # 花不能为负数,需要继续撤销\\n continue\\n\\n # 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while j < i and flowers[j] * j <= pre_sum + left_flowers:\\n pre_sum += flowers[j]\\n j += 1\\n\\n # 计算总美丽值\\n # 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n avg = (left_flowers + pre_sum) // j # 由于上面特判了,这里 avg 一定小于 target\\n total_beauty = avg * partial + (n - i) * full\\n ans = max(ans, total_beauty)\\n\\n return ans\\n
\\nclass Solution {\\n public long maximumBeauty(int[] flowers, long newFlowers, int target, int full, int partial) {\\n int n = flowers.length;\\n\\n // 如果全部种满,还剩下多少朵花?\\n long leftFlowers = newFlowers - (long) target * n; // 先减掉\\n for (int i = 0; i < n; i++) {\\n flowers[i] = Math.min(flowers[i], target);\\n leftFlowers += flowers[i]; // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if (leftFlowers == newFlowers) {\\n return (long) n * full; // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if (leftFlowers >= 0) {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return Math.max((long) (target - 1) * partial + (long) (n - 1) * full, (long) n * full);\\n }\\n\\n Arrays.sort(flowers); // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n long ans = 0;\\n long preSum = 0;\\n int j = 0;\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for (int i = 1; i <= n; i++) {\\n // 撤销,flowers[i-1] 不变成 target\\n leftFlowers += target - flowers[i - 1];\\n if (leftFlowers < 0) { // 花不能为负数,需要继续撤销\\n continue;\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while (j < i && (long) flowers[j] * j <= preSum + leftFlowers) {\\n preSum += flowers[j];\\n j++;\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n long avg = (leftFlowers + preSum) / j; // 由于上面特判了,这里 avg 一定小于 target\\n long totalBeauty = avg * partial + (long) (n - i) * full;\\n ans = Math.max(ans, totalBeauty);\\n }\\n\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {\\n int n = flowers.size();\\n\\n // 如果全部种满,还剩下多少朵花?\\n long long left_flowers = newFlowers - 1LL * target * n; // 先减掉\\n for (int& flower : flowers) {\\n flower = min(flower, target);\\n left_flowers += flower; // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if (left_flowers == newFlowers) {\\n return 1LL * n * full; // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if (left_flowers >= 0) {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return max(1LL * (target - 1) * partial + 1LL * (n - 1) * full, 1LL * n * full);\\n }\\n\\n ranges::sort(flowers); // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n long long ans = 0, pre_sum = 0;\\n int j = 0;\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for (int i = 1; i <= n; i++) {\\n // 撤销,flowers[i-1] 不变成 target\\n left_flowers += target - flowers[i - 1];\\n if (left_flowers < 0) { // 花不能为负数,需要继续撤销\\n continue;\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while (j < i && 1LL * flowers[j] * j <= pre_sum + left_flowers) {\\n pre_sum += flowers[j];\\n j++;\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n long long avg = (left_flowers + pre_sum) / j; // 由于上面特判了,这里 avg 一定小于 target\\n long long total_beauty = avg * partial + 1LL * (n - i) * full;\\n ans = max(ans, total_beauty);\\n }\\n\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint cmp(const void *a, const void *b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nlong long maximumBeauty(int *flowers, int n, long long newFlowers, int target, int full, int partial) {\\n // 如果全部种满,还剩下多少朵花?\\n long long left_flowers = newFlowers - 1LL * target * n; // 先减掉\\n for (int i = 0; i < n; i++) {\\n flowers[i] = MIN(flowers[i], target);\\n left_flowers += flowers[i]; // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if (left_flowers == newFlowers) {\\n return 1LL * n * full; // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if (left_flowers >= 0) {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return MAX(1LL * (target - 1) * partial + 1LL * (n - 1) * full, 1LL * n * full);\\n }\\n\\n qsort(flowers, n, sizeof(int), cmp); // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n long long ans = 0, pre_sum = 0;\\n int j = 0;\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for (int i = 1; i <= n; i++) {\\n // 撤销,flowers[i-1] 不变成 target\\n left_flowers += target - flowers[i - 1];\\n if (left_flowers < 0) { // 花不能为负数,需要继续撤销\\n continue;\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while (j < i && 1LL * flowers[j] * j <= pre_sum + left_flowers) {\\n pre_sum += flowers[j];\\n j++;\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n long long avg = (left_flowers + pre_sum) / j; // 由于上面特判了,这里 avg 一定小于 target\\n long long total_beauty = avg * partial + 1LL * (n - i) * full;\\n ans = MAX(ans, total_beauty);\\n }\\n\\n return ans;\\n}\\n
\\nfunc maximumBeauty(flowers []int, newFlowers int64, target, full, partial int) int64 {\\n n := len(flowers)\\n\\n // 如果全部种满,还剩下多少朵花?\\n leftFlowers := int(newFlowers) - target*n // 先减掉\\n for i, flower := range flowers {\\n flowers[i] = min(flower, target)\\n leftFlowers += flowers[i] // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if leftFlowers == int(newFlowers) {\\n return int64(n * full) // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if leftFlowers >= 0 {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return int64(max((target-1)*partial+(n-1)*full, n*full))\\n }\\n\\n slices.Sort(flowers) // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n var ans, preSum, j int\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for i := 1; i <= n; i++ {\\n // 撤销,flowers[i-1] 不变成 target\\n leftFlowers += target - flowers[i-1]\\n if leftFlowers < 0 { // 花不能为负数,需要继续撤销\\n continue\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n for j < i && flowers[j]*j <= preSum+leftFlowers {\\n preSum += flowers[j]\\n j++\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n avg := (leftFlowers + preSum) / j // 由于上面特判了,这里 avg 一定小于 target\\n totalBeauty := avg*partial + (n-i)*full\\n ans = max(ans, totalBeauty)\\n }\\n\\n return int64(ans)\\n}\\n
\\nvar maximumBeauty = function(flowers, newFlowers, target, full, partial) {\\n const n = flowers.length;\\n\\n // 如果全部种满,还剩下多少朵花?\\n let leftFlowers = newFlowers - target * n; // 先减掉\\n for (let i = 0; i < n; i++) {\\n flowers[i] = Math.min(flowers[i], target);\\n leftFlowers += flowers[i]; // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if (leftFlowers === newFlowers) {\\n return n * full; // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if (leftFlowers >= 0) {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return Math.max((target - 1) * partial + (n - 1) * full, n * full);\\n }\\n\\n flowers.sort((a, b) => a - b); // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n let ans = 0, preSum = 0, j = 0;\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for (let i = 1; i <= n; i++) {\\n // 撤销,flowers[i-1] 不变成 target\\n leftFlowers += target - flowers[i - 1];\\n if (leftFlowers < 0) { // 花不能为负数,需要继续撤销\\n continue;\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while (j < i && flowers[j] * j <= preSum + leftFlowers) {\\n preSum += flowers[j];\\n j++;\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n const avg = Math.floor((leftFlowers + preSum) / j); // 由于上面特判了,这里 avg 一定小于 target\\n const totalBeauty = avg * partial + (n - i) * full;\\n ans = Math.max(ans, totalBeauty);\\n }\\n\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximum_beauty(mut flowers: Vec<i32>, new_flowers: i64, target: i32, full: i32, partial: i32) -> i64 {\\n let n = flowers.len() as i64;\\n let full = full as i64;\\n let partial = partial as i64;\\n\\n // 如果全部种满,还剩下多少朵花?\\n let mut left_flowers = new_flowers - target as i64 * n;\\n for flower in &mut flowers {\\n *flower = (*flower).min(target);\\n left_flowers += *flower as i64; // 把已有的加回来\\n }\\n\\n // 没有种花,所有花园都已种满\\n if left_flowers == new_flowers {\\n return n * full; // 答案只能是 n*full(注意不能减少花的数量)\\n }\\n\\n // 可以全部种满\\n if left_flowers >= 0 {\\n // 两种策略取最大值:留一个花园种 target-1 朵花,其余种满;或者,全部种满\\n return ((target - 1) as i64 * partial + (n - 1) * full).max(n * full);\\n }\\n\\n flowers.sort_unstable(); // 时间复杂度的瓶颈在这,尽量写在后面\\n\\n let mut ans = 0;\\n let mut pre_sum = 0;\\n let mut j = 0;\\n // 枚举 i,表示后缀 [i, n-1] 种满(i=0 的情况上面已讨论)\\n for i in 1..=n as usize {\\n // 撤销,flowers[i-1] 不变成 target\\n left_flowers += (target - flowers[i - 1]) as i64;\\n if left_flowers < 0 { // 花不能为负数,需要继续撤销\\n continue;\\n }\\n\\n // 满足以下条件说明 [0, j] 都可以种 flowers[j] 朵花\\n while j < i && flowers[j] as i64 * j as i64 <= pre_sum + left_flowers {\\n pre_sum += flowers[j] as i64;\\n j += 1;\\n }\\n\\n // 计算总美丽值\\n // 在前缀 [0, j-1] 中均匀种花,这样最小值最大\\n let avg = (left_flowers + pre_sum) / j as i64; // 由于上面特判了,这里 avg 一定小于 target\\n let total_beauty = avg * partial + (n - i as i64) * full;\\n ans = ans.max(total_beauty);\\n }\\n\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 是 $\\\\textit{flowers}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路:枚举把多少个花园种满(至少有 $\\\\textit{target}$ 朵花),剩余的花种其他花园,让最小花朵数最大。 贪心地想,那些要种满的花园,原有的花朵数越多越好,这样我们能有更多的花,去增大花的最少数目。\\n\\n将 $\\\\textit{flowers}$ 从小到大排序,这样 $\\\\textit{flowers}$ 的后缀就是要种满的花园。\\n\\n枚举 $i$,把 $\\\\textit{flowers}[i]$ 到 $\\\\textit{flowers}[n-1]$ 都种满花。那么剩下要解决的问题就是,怎么最大化花的最少数目。\\n\\n例如 $\\\\textit{flowers…","guid":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-endlesscheng-10i7","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-10T04:13:17.828Z","media":[{"url":"https://pic.leetcode.cn/1740017810-PgjAgW-lc2234-c.png","type":"photo","width":2607,"height":2142,"blurhash":"LSR{#:-:t7-;.AoJaxofxAayWXof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"枚举变为完整花坛的数量 + 双指针","url":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-newhar-nk6l","content":"首先排序数组。一开始,如果花坛已经是 完整的,那么它就永远是完整的了。因此,可以把这些花坛 除去,并统计它们的数量 $fcnt$。
\\n\\n
\\n- \\n
\\n如果所有的花坛一开始都是完整的,那么直接根据题意,返回答案 $fcnt \\\\times full$ 即可。
\\n- \\n
\\n否则,我们可以 **枚举变为完整的花坛的数量 **$x$。只需选取 花朵数目最多 的 $x$ 个花坛(但不大于 $target$),将其变为 完整的,因为这样消耗花朵的数量最少。
\\n- \\n
\\n然后,我们需要在剩下的 $a$ 个花坛中,种 $b$ 朵花,使得花朵最少的花坛中的花尽可能大。也就是:寻找最大的 $T$,使得每个花坛中花的数目不少于 $T$ 朵,所消耗的花的数量不超过 $b$。这可以用以下的双指针思路解决:
\\n\\n
\\n- \\n
\\n首先设初始的 $T= target - 1$。(因为如果大于等于 $target$ 就是 完整的花坛 了)。
\\n- \\n
\\n如果 $T \\\\times a - sum(剩余的花坛中花的数量) > b$,那么代表这个 $T$ 太大,我们需要将 $T = T-1$,然后继续尝试。
\\n- \\n
\\n注意,当 $T = T-1$ 后,那些花朵数量超过 $T$ 的花坛显然不再需要再种花了,在后续的计算中,应当把这些花坛除去。
\\n- \\n
\\n希望下图能够帮助理解代码实现。
\\n- \\n
\\n这里证明一个关键点:随着遍历,$T$ 是单调下降的。参考下图,在遍历的过程中,每次 $i$ 向左移动一个单位,剩余花的数量总是会减少 $target - flowers[i]$,但是下图中需要补充的花的数量 最多减少 $(target - 1) - flowers[i]$,因此花的数量总会向着不够用的方向去变化。
\\n\\n
代码:
\\n###c++
\\n\\ntypedef long long ll;\\nclass Solution {\\npublic:\\n long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {\\n sort(flowers.begin(), flowers.end());\\n \\n ll fcnt = 0;\\n while(flowers.size() && flowers.back() >= target) {\\n flowers.pop_back();\\n fcnt += 1;\\n }\\n \\n /* 所有花坛都是 full */\\n if(!flowers.size()) {\\n return fcnt * full;\\n }\\n \\n ll res = 0, sum = 0;\\n for(int i = 0; i < flowers.size(); ++i) {\\n sum += flowers[i];\\n }\\n ll T = target - 1;\\n \\n /* 枚举变为 full 的花坛数量 */\\n /* 这里 i = flowers.size() 开始, 代表的是所有的花坛都还是 partial */\\n for(int i = flowers.size(), j = i-1; i >= 0; --i, ++fcnt) {\\n if(i < (int)flowers.size()) {\\n newFlowers -= target - flowers[i];\\n }\\n /* 如果 newFlowers 不足以填充 full 的花坛 */\\n if(newFlowers < 0) {\\n break;\\n }\\n /* 存在 partial */\\n if(i > 0) {\\n /* 双指针 */\\n while(j >= i) {\\n sum -= flowers[j];\\n j--;\\n }\\n while(T * (j + 1) - sum > newFlowers) {\\n T--;\\n while(flowers[j] > T) {\\n sum -= flowers[j];\\n j--;\\n }\\n }\\n res = max(res, T * partial + fcnt * full);\\n }\\n /* 不存在 partial, 全是 full */\\n else {\\n res = max(res, fcnt * full);\\n }\\n }\\n\\n return res;\\n }\\n};\\n
###c++
\\n\\ntypedef long long ll;\\nclass Solution {\\npublic:\\n long long maximumBeauty(vector<int>& flowers, long long newFlowers, int target, int full, int partial) {\\n sort(flowers.begin(), flowers.end());\\n \\n ll n = flowers.size(), sum = 0, fcnt = 0, j = (int)flowers.size() - 1, T = target - 1;\\n for(int i : flowers) {\\n sum += i;\\n }\\n \\n ll res = 0;\\n \\n /* flowers 为剩余需要成为 partial 花坛 */\\n while(flowers.size() && newFlowers >= 0) {\\n /* 数量超过 T 的花坛不需要再种花 */\\n while(j >= 0 && flowers[j] > T) {\\n sum -= flowers[j];\\n j--;\\n }\\n \\n if(j >= 0) {\\n /* 双指针 */\\n while(T * (j + 1) - sum > newFlowers) {\\n --T;\\n while(j >= 0 && (flowers[j] > T)) {\\n sum -= flowers[j];\\n --j;\\n }\\n }\\n res = max(res, T * partial + (n - (ll)flowers.size()) * full);\\n }\\n \\n newFlowers -= max(0, target - flowers.back());\\n if(j == (int)flowers.size() - 1) {\\n sum -= flowers[j];\\n j--;\\n }\\n flowers.pop_back();\\n }\\n \\n /* 所有花坛都是 full */\\n if(newFlowers >= 0) {\\n res = max(res, n * full);\\n }\\n return res;\\n }\\n};\\n
###python
\\n\\n","description":"首先排序数组。一开始,如果花坛已经是 完整的,那么它就永远是完整的了。因此,可以把这些花坛 除去,并统计它们的数量 $fcnt$。 如果所有的花坛一开始都是完整的,那么直接根据题意,返回答案 $fcnt \\\\times full$ 即可。\\n\\n否则,我们可以 **枚举变为完整的花坛的数量 **$x$。只需选取 花朵数目最多 的 $x$ 个花坛(但不大于 $target$),将其变为 完整的,因为这样消耗花朵的数量最少。\\n\\n然后,我们需要在剩下的 $a$ 个花坛中,种 $b$ 朵花,使得花朵最少的花坛中的花尽可能大。也就是:寻找最大的 $T…","guid":"https://leetcode.cn/problems/maximum-total-beauty-of-the-gardens//solution/by-newhar-nk6l","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-10T04:09:56.069Z","media":[{"url":"https://pic.leetcode-cn.com/1649564077-luPNgB-image.png","type":"photo","width":414,"height":244,"blurhash":"LOR{Sz?wyE.9-;xaofaypen#ROad"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"大道至简,直接一个数组解决问题!","url":"https://leetcode.cn/problems/count-largest-group//solution/suan-fa-shi-sui-zhao-shu-ju-zou-de-by-ch-yn64","content":"class Solution:\\n def maximumBeauty(self, flowers: List[int], newFlowers: int, target: int, full: int, partial: int) -> int:\\n flowers.sort()\\n n, s, fcnt, j, T = len(flowers), sum(flowers), 0, len(flowers) - 1, target - 1\\n \\n res = 0\\n \\n while flowers and newFlowers >= 0:\\n while j >= 0 and flowers[j] > T:\\n s -= flowers[j]\\n j -= 1\\n \\n if j >= 0:\\n while T * (j + 1) - s > newFlowers:\\n T -= 1\\n while j >= 0 and flowers[j] > T:\\n s -= flowers[j]\\n j -= 1\\n res = max(res, T * partial + (n - len(flowers)) * full)\\n \\n newFlowers -= max(0, target - flowers[-1])\\n if j == len(flowers) - 1:\\n s -= flowers[j]\\n j -= 1\\n flowers.pop()\\n \\n if newFlowers >= 0:\\n res = max(res, n * full)\\n \\n return res\\n
解题思路
\\n这些天,我明白了一个道理,
\\n算法是随着数据走的
。数据量有多少,那么就选取和这个数据量最佳匹配的算法。回到这个题上来,不看数据,估计很多伙伴会用Map,但是这个题
\\n1 <= n <= 10^4
,其实数位和最大也就是36
(9999的数位和最大为36),直接一个数组即可。
\\n代码
\\n###java
\\n\\n","description":"解题思路 这些天,我明白了一个道理,算法是随着数据走的。数据量有多少,那么就选取和这个数据量最佳匹配的算法。\\n\\n回到这个题上来,不看数据,估计很多伙伴会用Map,但是这个题1 <= n <= 10^4,其实数位和最大也就是36(9999的数位和最大为36),直接一个数组即可。\\n\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int countLargestGroup(int n) {\\n int[]cnt=new int[37]; //直接一个数组\\n int max_sum=0…","guid":"https://leetcode.cn/problems/count-largest-group//solution/suan-fa-shi-sui-zhao-shu-ju-zou-de-by-ch-yn64","author":"Chuancey","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-04-09T12:28:34.862Z","media":[{"url":"https://pic.leetcode-cn.com/1649507674-UhxkZJ-image.png","type":"photo","width":578,"height":297,"blurhash":"LPRMlBxut7-;~RWVWEoMNNt7f6j["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C语言,用strstr()来做","url":"https://leetcode.cn/problems/seat-reservation-manager//solution/c-by-re1nhard-mnfv","content":"class Solution {\\n public int countLargestGroup(int n) {\\n int[]cnt=new int[37]; //直接一个数组\\n int max_sum=0; //确定最大数位和\\n for (int i = 1; i <= n; i++) {\\n int index=getNumSum(i);\\n cnt[index]++;\\n max_sum=Math.max(cnt[index],max_sum);\\n }\\n int ans=0;\\n for (int i = 36; i >0 ; i--) {\\n if(cnt[i]==max_sum){\\n ans++;\\n }\\n }\\n return ans;\\n }\\n\\n private int getNumSum(int n){\\n int sum=0;\\n while (n>=10){\\n sum+=n%10;\\n n=n/10;\\n }\\n sum+=n;\\n return sum;\\n }\\n}\\n
解题思路
\\n本方法不考虑效率,我只是想到了一种比较简洁的方法
\\n
\\n
\\n如果在结构体中定义一个数组arr[100001],每次都通过遍历查询最小未被占用座位,这种暴力解法会在最后一个用例超时;
\\n因此,我想到了C语言自带的一种查询方法strstr(),于是想到使用char[]来代替int[],虽然我不认为O(N^2)的时间复杂度比遍历好多少,但是能过代码
\\n###c
\\n\\n","description":"解题思路 本方法不考虑效率,我只是想到了一种比较简洁的方法\\n \\n 如果在结构体中定义一个数组arr[100001],每次都通过遍历查询最小未被占用座位,这种暴力解法会在最后一个用例超时;\\n 因此,我想到了C语言自带的一种查询方法strstr(),于是想到使用char[]来代替int[],虽然我不认为O(N^2)的时间复杂度比遍历好多少,但是能过\\n\\n代码\\n\\n###c\\n\\ntypedef struct {\\n char availableSeat[100002];\\n} SeatManager;\\n\\n\\nSeatManager* seatManagerCreate(int…","guid":"https://leetcode.cn/problems/seat-reservation-manager//solution/c-by-re1nhard-mnfv","author":"re1nhard","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-31T09:11:12.429Z","media":[{"url":"https://pic.leetcode-cn.com/1648718032-yOjEMm-lc.png","type":"photo","width":914,"height":478,"blurhash":"L07UI{j[fQj[offQfQfQfQfQfQfQ"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python] 前缀和 + 记忆化递归","url":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/-by-himymben-qzs2","content":"typedef struct {\\n char availableSeat[100002];\\n} SeatManager;\\n\\n\\nSeatManager* seatManagerCreate(int n) {\\n SeatManager *newSys = (SeatManager *)malloc(sizeof(SeatManager));\\n newSys->availableSeat[0] = \'1\';\\n for (int i = 1; i < 100001; ++i) {\\n newSys->availableSeat[i] = \'0\';\\n }\\n newSys->availableSeat[100001] = \'\\\\0\';\\n return newSys;\\n}\\n\\nint seatManagerReserve(SeatManager* obj) {\\n char *temp = strstr(obj->availableSeat, \\"0\\");\\n temp[0] = \'1\';\\n return temp - obj->availableSeat;\\n}\\n\\nvoid seatManagerUnreserve(SeatManager* obj, int seatNumber) {\\n obj->availableSeat[seatNumber] = \'0\';\\n}\\n\\nvoid seatManagerFree(SeatManager* obj) {\\n free(obj);\\n}\\n\\n/**\\n * Your SeatManager struct will be instantiated and called as such:\\n * SeatManager* obj = seatManagerCreate(n);\\n * int param_1 = seatManagerReserve(obj);\\n \\n * seatManagerUnreserve(obj, seatNumber);\\n \\n * seatManagerFree(obj);\\n*/\\n
解题思路
\\n换句话说就是暴力解法,对每个栈都讨论了当前能取到的最大值
\\ndfs(idx, r)表示从piles[idx]开始,还可以取r次的最大值。
\\n
\\n那么讨论从piles[idx]里取$x$个,其结果为后面还能取的最大值以及当前取走的和,也即 dfs(idx + 1, r - x) + sum(piles[idx][:x]) 【这里进行前缀和优化避免重复计算】,
\\n找到其中的最大值即可代码
\\n###python3
\\n\\n","description":"解题思路 换句话说就是暴力解法,对每个栈都讨论了当前能取到的最大值\\n\\ndfs(idx, r)表示从piles[idx]开始,还可以取r次的最大值。\\n 那么讨论从piles[idx]里取$x$个,其结果为后面还能取的最大值以及当前取走的和,也即 dfs(idx + 1, r - x) + sum(piles[idx][:x]) 【这里进行前缀和优化避免重复计算】,\\n 找到其中的最大值即可\\n\\n代码\\n\\n###python3\\n\\nclass Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k…","guid":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/-by-himymben-qzs2","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-27T04:12:52.981Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python动态规划","url":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/by-tenderdanger-f3hs","content":"class Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k: int) -> int:\\n presums, n = [[0] + list(accumulate(p)) for p in piles], len(piles)\\n \\n @lru_cache(None)\\n def dfs(idx, r):\\n return max(dfs(idx + 1, r - i) + presums[idx][i] for i in range(min(r, len(piles[idx])) + 1)) if idx < n and r else 0\\n \\n return dfs(0, k)\\n
提供一个python版本动态规划代码,供大家嘲笑/鞠躬
\\n思路:
\\n\\n状态:\\n dp[curIdx][y]: 从 0 - curIdx 的堆中取 y 个, 至多能得多少分\\n\\n 递推式:\\n dp[curIdx][y] = dp[curIdx - 1][y - x] + pre[x]\\n x 为从 curIdx 中取多少个\\n pre[x] 表示从 curIdx 中取 x 个的分值, 可以用前缀和处理, 所以这样取名\\n\\n 初始化:\\n dp[curIdx][y] = pre[x]\\n
###python3
\\n\\n","description":"提供一个python版本动态规划代码,供大家嘲笑/鞠躬 思路:\\n\\n 状态:\\n dp[curIdx][y]: 从 0 - curIdx 的堆中取 y 个, 至多能得多少分\\n\\n 递推式:\\n dp[curIdx][y] = dp[curIdx - 1][y - x] + pre[x]\\n x 为从 curIdx 中取多少个\\n pre[x] 表示从 curIdx 中取 x 个的分值, 可以用前缀和处理, 所以这样取名\\n\\n 初始化:\\n dp[curIdx][y] = pre[x]…","guid":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/by-tenderdanger-f3hs","author":"tenderdanger","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-27T04:07:40.030Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"转化成分组背包:记忆化搜索 / 递推 / 空间优化(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/zhuan-hua-cheng-fen-zu-bei-bao-pythongoc-3xnk","content":"\\nclass Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k: int) -> int:\\n\\n # 前缀和\\n pre = []\\n for pile in piles:\\n acc = [0]\\n for dp in pile:\\n acc.append(acc[-1] + dp)\\n pre.append(acc)\\n\\n # 初始化\\n dp = defaultdict(lambda:defaultdict(int))\\n for x in range(min(k, len(piles[0])) + 1): # 不能超过堆的长度\\n dp[0][x] = pre[0][x]\\n\\n # 递推\\n for curIdx in range(1, len(piles)):\\n for y in range(k + 1):\\n for x in range(min(y, len(piles[curIdx])) + 1): # 不能超过堆的长度\\n dp[curIdx][y] = max(\\n dp[curIdx][y],\\n dp[curIdx - 1][y - x] + pre[curIdx][x])\\n\\n return dp[len(piles) - 1][k]\\n\\n
分析
\\n对于一个栈(数组),我们只能移除其前缀。注意题目说数组 $\\\\textit{piles}[i]$ 从左到右表示栈顶到栈底。
\\n对每个栈求前缀和 $s$,其中 $s[w]$ 表示一个体积为 $w+1$,价值为 $s[w]$ 的物品。
\\n问题转化成:
\\n\\n
\\n- 从 $n$ 个物品组中选物品,每组至多选一个物品(可以不选),要求体积总和至多为 $k$,求物品价值总和的最大值。
\\n⚠注意:对于本题来说,由于元素值都是非负数,且一定可以选 $k$ 个硬币,所以「至多」和「恰好」计算出来的结果是一样的。为方便写代码这里用至多。
\\n一、记忆化搜索
\\n类似 0-1 背包,定义 $\\\\textit{dfs}(i,j)$ 表示从 $\\\\textit{piles}[0]$ 到 $\\\\textit{piles}[i]$ 中,选体积之和至多为 $j$ 的物品时,物品价值之和的最大值。
\\n枚举第 $i$ 组的所有物品(枚举前缀和),设当前物品体积为 $w$,价值为 $v$,那么问题变成从前 $i-1$ 个物品组中,选体积之和至多为 $j-w$ 的物品时,物品价值之和的最大值,即 $\\\\textit{dfs}(i-1,j-w)$,加上 $v$ 得到 $\\\\textit{dfs}(i,j)$。
\\n所有情况取最大值,得
\\n$$
\\n
\\n\\\\textit{dfs}(i,j) = \\\\max_{(v,w)}\\\\textit{dfs}(i-1,j-w) + v
\\n$$如果该组不选物品,则上式中的 $v=w=0$。
\\n递归边界:$\\\\textit{dfs}(-1,j)=0$。
\\n递归入口:$\\\\textit{dfs}(n-1,k)$,这是原问题,也是答案。
\\n关于记忆化搜索的原理,请看 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n本题由于元素值都是正数,所以可以把 $\\\\textit{memo}$ 数组初始化成 $0$。
\\n\\nclass Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k: int) -> int:\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i < 0:\\n return 0\\n # 不选这一组中的任何物品\\n res = dfs(i - 1, j)\\n # 枚举选哪个\\n for w, v in enumerate(accumulate(piles[i][:j]), 1):\\n res = max(res, dfs(i - 1, j - w) + v)\\n return res\\n return dfs(len(piles) - 1, k)\\n
\\nclass Solution {\\n public int maxValueOfCoins(List<List<Integer>> piles, int k) {\\n int n = piles.size();\\n int[][] memo = new int[n][k + 1];\\n return dfs(n - 1, k, piles, memo);\\n }\\n\\n private int dfs(int i, int j, List<List<Integer>> piles, int[][] memo) {\\n if (i < 0) {\\n return 0;\\n }\\n if (memo[i][j] != 0) { // 之前计算过\\n return memo[i][j];\\n }\\n // 不选这一组中的任何物品\\n int res = dfs(i - 1, j, piles, memo);\\n // 枚举选哪个\\n int v = 0;\\n for (int w = 0; w < Math.min(j, piles.get(i).size()); w++) {\\n v += piles.get(i).get(w);\\n // w 从 0 开始,物品体积为 w+1\\n res = Math.max(res, dfs(i - 1, j - w - 1, piles, memo) + v);\\n }\\n return memo[i][j] = res;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxValueOfCoins(vector<vector<int>>& piles, int k) {\\n int n = size(piles);\\n vector memo(n, vector<int>(k + 1));\\n auto dfs = [&](this auto&& dfs, int i, int j) -> int {\\n if (i < 0) {\\n return 0;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n // 不选这一组中的任何物品\\n res = dfs(i - 1, j);\\n // 枚举选哪个\\n int v = 0;\\n for (int w = 0; w < min(j, (int) piles[i].size()); w++) {\\n v += piles[i][w];\\n // w 从 0 开始,物品体积为 w+1\\n res = max(res, dfs(i - 1, j - w - 1) + v);\\n }\\n return res;\\n };\\n return dfs(n - 1, k);\\n }\\n};\\n
\\nfunc maxValueOfCoins(piles [][]int, k int) int {\\n n := len(piles)\\n memo := make([][]int, n)\\n for i := range memo {\\n memo[i] = make([]int, k+1)\\n }\\n var dfs func(int, int) int\\n dfs = func(i, j int) (res int) {\\n if i < 0 {\\n return\\n }\\n p := &memo[i][j]\\n if *p != 0 { // 之前计算过\\n return *p\\n }\\n defer func() { *p = res }() // 记忆化\\n\\n // 不选这一组中的任何物品\\n res = dfs(i-1, j)\\n // 枚举选哪个\\n v := 0\\n for w := range min(j, len(piles[i])) {\\n v += piles[i][w]\\n // w 从 0 开始,物品体积为 w+1\\n res = max(res, dfs(i-1, j-w-1)+v)\\n }\\n return\\n }\\n return dfs(n-1, k)\\n}\\n
二、1:1 翻译成递推
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i+1][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是一样的,都表示从 $\\\\textit{piles}[0]$ 到 $\\\\textit{piles}[i]$ 中,选体积之和至多为 $j$ 的物品时,物品价值之和的最大值。这里 $+1$ 是为了把 $\\\\textit{dfs}(-1,j)$ 这个状态也翻译过来,这样我们可以把 $f[0][j]$ 作为初始值。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\n
\\nf[i+1][j] = \\\\max_{(v,w)}f[i][j-w] + v
\\n$$如果该组不选物品,则上式中的 $v=w=0$。
\\n初始值 $f[0][j]=0$,翻译自递归边界 $\\\\textit{dfs}(-1,j)=0$。
\\n答案为 $f[n][k]$,翻译自递归入口 $\\\\textit{dfs}(n-1,k)$。
\\n\\nclass Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k: int) -> int:\\n f = [[0] * (k + 1) for _ in range(len(piles) + 1)]\\n for i, pile in enumerate(piles):\\n for j in range(k + 1):\\n # 不选这一组中的任何物品\\n f[i + 1][j] = f[i][j]\\n # 枚举选哪个\\n for w, v in enumerate(accumulate(pile[:j]), 1):\\n f[i + 1][j] = max(f[i + 1][j], f[i][j - w] + v)\\n return f[-1][k]\\n
\\nclass Solution {\\n public int maxValueOfCoins(List<List<Integer>> piles, int k) {\\n int[][] f = new int[piles.size() + 1][k + 1];\\n for (int i = 0; i < piles.size(); i++) {\\n List<Integer> pile = piles.get(i);\\n for (int j = 0; j <= k; j++) {\\n // 不选这一组中的任何物品\\n f[i + 1][j] = f[i][j];\\n // 枚举选哪个\\n int v = 0;\\n for (int w = 0; w < Math.min(j, pile.size()); w++) {\\n v += pile.get(w);\\n // w 从 0 开始,物品体积为 w+1\\n f[i + 1][j] = Math.max(f[i + 1][j], f[i][j - w - 1] + v);\\n }\\n }\\n }\\n return f[piles.size()][k];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxValueOfCoins(vector<vector<int>>& piles, int k) {\\n vector f(piles.size() + 1, vector<int>(k + 1));\\n for (int i = 0; i < piles.size(); i++) {\\n auto& pile = piles[i];\\n for (int j = 0; j <= k; j++) {\\n // 不选这一组中的任何物品\\n f[i + 1][j] = f[i][j];\\n // 枚举选哪个\\n int v = 0;\\n for (int w = 0; w < min(j, (int) pile.size()); w++) {\\n v += pile[w];\\n // w 从 0 开始,物品体积为 w+1\\n f[i + 1][j] = max(f[i + 1][j], f[i][j - w - 1] + v);\\n }\\n }\\n }\\n return f.back()[k];\\n }\\n};\\n
\\nfunc maxValueOfCoins(piles [][]int, k int) int {\\n f := make([][]int, len(piles)+1)\\n for i := range f {\\n f[i] = make([]int, k+1)\\n }\\n for i, pile := range piles {\\n for j := range k + 1 {\\n // 不选这一组中的任何物品\\n f[i+1][j] = f[i][j]\\n // 枚举选哪个\\n v := 0\\n for w := range min(j, len(pile)) {\\n v += pile[w]\\n // w 从 0 开始,物品体积为 w+1\\n f[i+1][j] = max(f[i+1][j], f[i][j-w-1]+v)\\n }\\n }\\n }\\n return f[len(piles)][k]\\n}\\n
三、空间优化
\\n和 0-1 背包一样,把 $f$ 数组的第一维去掉,第二维倒序枚举。原理请看【基础算法精讲 18】。
\\n优化后 $f[i + 1][j] = f[i][j]$ 变成 $f[j] = f[j]$,可以省略。
\\n此外,循环次数也可以优化。想一想,如果 $\\\\textit{piles}[0]$ 和 $\\\\textit{piles}[1]$ 的大小之和只有 $5$,而 $k=100$,那么是否需要从 $j=100$ 开始倒序枚举呢?
\\n\\nclass Solution:\\n def maxValueOfCoins(self, piles: List[List[int]], k: int) -> int:\\n f = [0] * (k + 1)\\n sum_n = 0\\n for pile in piles:\\n n = len(pile)\\n for i in range(1, n):\\n pile[i] += pile[i - 1] # 提前计算 pile 的前缀和\\n sum_n = min(sum_n + n, k)\\n for j in range(sum_n, 0, -1): # 优化:j 从前 i 个栈的大小之和开始枚举\\n # w 从 0 开始,物品体积为 w+1\\n f[j] = max(f[j], max(f[j - w - 1] + pile[w] for w in range(min(n, j))))\\n return f[k]\\n
\\nclass Solution {\\n public int maxValueOfCoins(List<List<Integer>> piles, int k) {\\n int[] f = new int[k + 1];\\n int sumN = 0;\\n for (List<Integer> Pile : piles) {\\n Integer[] pile = Pile.toArray(Integer[]::new); // 转成数组处理更快更方便\\n int n = pile.length;\\n for (int i = 1; i < n; i++) {\\n pile[i] += pile[i - 1]; // 提前计算 pile 的前缀和\\n }\\n sumN = Math.min(sumN + n, k);\\n for (int j = sumN; j > 0; j--) { // 优化:j 从前 i 个栈的大小之和开始枚举\\n for (int w = 0; w < Math.min(n, j); w++) {\\n f[j] = Math.max(f[j], f[j - w - 1] + pile[w]); // w 从 0 开始,物品体积为 w+1\\n }\\n }\\n }\\n return f[k];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int maxValueOfCoins(vector<vector<int>>& piles, int k) {\\n vector<int> f(k + 1);\\n int sum_n = 0;\\n for (auto& pile : piles) {\\n partial_sum(pile.begin(), pile.end(), pile.begin()); // 提前计算 pile 的前缀和\\n int n = pile.size();\\n sum_n = min(sum_n + n, k);\\n for (int j = sum_n; j; j--) { // 优化:j 从前 i 个栈的大小之和开始枚举\\n for (int w = 0; w < min(n, j); w++) {\\n f[j] = max(f[j], f[j - w - 1] + pile[w]); // w 从 0 开始,物品体积为 w+1\\n }\\n }\\n }\\n return f[k];\\n }\\n};\\n
\\nfunc maxValueOfCoins(piles [][]int, k int) int {\\n f := make([]int, k+1)\\n sumN := 0\\n for _, pile := range piles {\\n n := len(pile)\\n for i := 1; i < n; i++ {\\n pile[i] += pile[i-1] // 提前计算 pile 的前缀和\\n }\\n sumN = min(sumN+n, k)\\n for j := sumN; j > 0; j-- { // 优化:j 从前 i 个栈的大小之和开始枚举\\n for w, v := range pile[:min(n, j)] {\\n f[j] = max(f[j], f[j-w-1]+v) // w 从 0 开始,物品体积为 w+1\\n }\\n }\\n }\\n return f[k]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(Lk)$。将外层循环与最内层循环合并,即为每个栈的大小之和,记作 $\\\\textit{L}$。再算上中间这层 $\\\\mathcal{O}(k)$ 的循环,时间复杂度为 $\\\\mathcal{O}(Lk)$。
\\n- 空间复杂度:$\\\\mathcal{O}(k)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"分析 对于一个栈(数组),我们只能移除其前缀。注意题目说数组 $\\\\textit{piles}[i]$ 从左到右表示栈顶到栈底。\\n\\n对每个栈求前缀和 $s$,其中 $s[w]$ 表示一个体积为 $w+1$,价值为 $s[w]$ 的物品。\\n\\n问题转化成:\\n\\n从 $n$ 个物品组中选物品,每组至多选一个物品(可以不选),要求体积总和至多为 $k$,求物品价值总和的最大值。\\n\\n⚠注意:对于本题来说,由于元素值都是非负数,且一定可以选 $k$ 个硬币,所以「至多」和「恰好」计算出来的结果是一样的。为方便写代码这里用至多。\\n\\n一、记忆化搜索\\n\\n类似 0-1 背包,定义…","guid":"https://leetcode.cn/problems/maximum-value-of-k-coins-from-piles//solution/zhuan-hua-cheng-fen-zu-bei-bao-pythongoc-3xnk","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-27T04:07:38.900Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【道哥刷题】改造标准并查集解题","url":"https://leetcode.cn/problems/lexicographically-smallest-equivalent-string//solution/-by-lcfgrn-ionf","content":"解题思路
\\n本题是典型的并查集应用题,一个字符可以与多个字符等价,很容易联想到并查集的union,本题我们将‘a’-“z”26个字符,可以转化为0-25这26个数字。
\\n
\\n由于题目要求:将baseStr转化为字典序最小的等价字符串;那么我们需要在union方法中做一个改造,使得等价的两个字符union时,根节点指向字典序更小的那个字符。\\nif (pRoot < qRoot) {\\n parent[qRoot] = pRoot;\\n} else {\\n parent[pRoot] = qRoot;\\n}\\n
解题步骤:
\\n\\n
\\n- 改造UF的union方法,使得等价的两个字符union时,根节点指向字典序更小的那个字符;
\\n- 同时遍历s1、s2,将等价的字符相连;
\\n- 遍历baseStr,找到每个字符的根,并用sb组合;
\\n- 返回结果。
\\n代码
\\n###java
\\n\\nclass Solution {\\n public String smallestEquivalentString(String s1, String s2, String baseStr) {\\n int n = s1.length();\\n char[] chs1 = s1.toCharArray();\\n char[] chs2 = s2.toCharArray();\\n UF uf = new UF(26);\\n for (int i = 0; i < n; i++) {\\n char ch1 = chs1[i];\\n char ch2 = chs2[i];\\n uf.union(ch1 - \'a\', ch2 - \'a\');\\n }\\n StringBuilder sb = new StringBuilder();\\n char[] baseStrArr = baseStr.toCharArray();\\n for (char c : baseStrArr) {\\n char nc = (char) (uf.findRoot(c - \'a\') + \'a\');\\n sb.append(nc);\\n }\\n return sb.toString();\\n }\\n\\n class UF {\\n private int[] parent;\\n private int count;\\n\\n public UF(int n) {\\n parent = new int[n];\\n for (int i = 0; i < n; i++) {\\n parent[i] = i;\\n }\\n count = n;\\n }\\n \\n public int findRoot(int x) {\\n while(parent[x] != x) {\\n parent[x] = parent[parent[x]];\\n x = parent[x];\\n }\\n return x;\\n }\\n \\n public boolean connected(int p, int q) {\\n int pRoot = findRoot(p);\\n int qRoot = findRoot(q);\\n return pRoot == qRoot;\\n }\\n\\n public void union(int p, int q) {\\n int pRoot = findRoot(p);\\n int qRoot = findRoot(q);\\n if (pRoot == qRoot) {\\n return;\\n }\\n if (pRoot < qRoot) {\\n parent[qRoot] = pRoot;\\n } else {\\n parent[pRoot] = qRoot;\\n }\\n --count;\\n }\\n\\n public int getCount() {\\n return count;\\n }\\n }\\n}\\n
\\n","description":"解题思路 本题是典型的并查集应用题,一个字符可以与多个字符等价,很容易联想到并查集的union,本题我们将‘a’-“z”26个字符,可以转化为0-25这26个数字。\\n 由于题目要求:将baseStr转化为字典序最小的等价字符串;那么我们需要在union方法中做一个改造,使得等价的两个字符union时,根节点指向字典序更小的那个字符。\\n\\nif (pRoot < qRoot) {\\n parent[qRoot] = pRoot;\\n} else {\\n parent[pRoot] = qRoot;\\n}\\n\\n\\n解题步骤:\\n\\n改造UF的union方法…","guid":"https://leetcode.cn/problems/lexicographically-smallest-equivalent-string//solution/-by-lcfgrn-ionf","author":"lcfgrn","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-25T01:29:38.760Z","media":[{"url":"https://pic.leetcode-cn.com/1648172127-BtqEkd-image.png","type":"photo","width":503,"height":304,"blurhash":"LPRMoI%Lxt-;~RWVWFj]Ngt6f6j["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题双解 :「模拟」&「前缀和」","url":"https://leetcode.cn/problems/image-smoother//solution/by-ac_oier-nn3v","content":"
\\n如果您认可道哥的题解,麻烦点赞支持一下。朴素解法
\\n为了方便,我们称每个单元格及其八连通方向单元格所组成的连通块为一个
\\nitem
。数据范围只有 $200$,我们可以直接对每个
\\nitem
进行遍历模拟。代码:
\\n###Java
\\n\\nclass Solution {\\n public int[][] imageSmoother(int[][] img) {\\n int m = img.length, n = img[0].length;\\n int[][] ans = new int[m][n];\\n int[][] dirs = new int[][]{{0,0},{1,0},{-1,0},{0,1},{0,-1},{-1,-1},{-1,1},{1,-1},{1,1}};\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int tot = 0, cnt = 0;\\n for (int[] di : dirs) {\\n int nx = i + di[0], ny = j + di[1];\\n if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;\\n tot += img[nx][ny]; cnt++;\\n }\\n ans[i][j] = tot / cnt;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Python3
\\n\\ndirs = list(product(*[[-1,0,1]] * 2))\\nclass Solution:\\n def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:\\n m, n = len(img), len(img[0])\\n ans = [[0] * n for _ in range(m)]\\n for i, j in product(range(m), range(n)):\\n tot, cnt = 0, 0\\n for di in dirs:\\n if 0 <= (nx := i + di[0]) < m and 0 <= (ny := j + di[1]) < n:\\n tot += img[nx][ny]\\n cnt += 1\\n ans[i][j] = tot // cnt\\n return ans\\n
\\n
\\n- 时间复杂度:$O(m * n * C)$,其中 $C$ 为灰度单位所包含的单元格数量,固定为 $9$
\\n- 空间复杂度:$O(m * n)$
\\n
\\n前缀和
\\n在朴素解法中,对于每个 $ans[i][j]$ 我们都不可避免的遍历 $8$ 联通方向,而利用「前缀和」我们可以对该操作进行优化。
\\n\\n\\n不了解「二维前缀和」的同学可以看前置 🧀: 二维前缀和模板如何记忆
\\n对于某个 $ans[i][j]$ 而言,我们可以直接计算出其所在
\\nitem
的左上角 $(a, b) = (i - 1, j - 1)$ 以及其右下角 $(c, d) = (i + 1, j + 1)$,同时为了防止超出原矩阵,我们需要将 $(a, b)$ 与 $(c, d)$ 对边界分别取max
和min
。当有了合法的 $(a, b)$ 和 $(c, d)$ 后,我们可以直接计算出
\\nitem
的单元格数量(所包含的行列乘积)及item
的单元格之和(前缀和查询),从而算得 $ans[i][j]$。代码:
\\n###Java
\\n\\nclass Solution {\\n public int[][] imageSmoother(int[][] img) {\\n int m = img.length, n = img[0].length;\\n int[][] sum = new int[m + 10][n + 10];\\n for (int i = 1; i <= m; i++) {\\n for (int j = 1; j <= n; j++) {\\n sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + img[i - 1][j - 1];\\n }\\n }\\n int[][] ans = new int[m][n];\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int a = Math.max(0, i - 1), b = Math.max(0, j - 1);\\n int c = Math.min(m - 1, i + 1), d = Math.min(n - 1, j + 1);\\n int cnt = (c - a + 1) * (d - b + 1);\\n int tot = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b];\\n ans[i][j] = tot / cnt;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Python3
\\n\\nclass Solution:\\n def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:\\n m, n = len(img), len(img[0])\\n sum = [[0] * (n + 10) for _ in range(m + 10)]\\n for i, j in product(range(1, m + 1), range(1, n + 1)):\\n sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + img[i - 1][j - 1]\\n ans = [[0] * n for _ in range(m)]\\n for i, j in product(range(m), range(n)):\\n a, b = max(0, i - 1), max(0, j - 1)\\n c, d = min(m - 1, i + 1), min(n - 1, j + 1)\\n cnt = (c - a + 1) * (d - b + 1)\\n tot = sum[c + 1][d + 1] - sum[a][d + 1] - sum[c + 1][b] + sum[a][b]\\n ans[i][j] = tot // cnt\\n return ans\\n
\\n
\\n- 时间复杂度:$O(m * n)$
\\n- 空间复杂度:$O(m * n)$
\\n
\\n同类型加餐
\\n今日份加餐:【面试高频题】难度 1.5/5,一道「桶排序 + 前缀和」优化题 🎉 🎉
\\n或是加练如下「前缀和」题目 🍭🍭
\\n\\n\\n
\\n\\n \\n\\n\\n题目 \\n题解 \\n难度 \\n推荐指数 \\n\\n \\n689. 三个无重叠子数组的最大和 \\nLeetCode 题解链接 \\n困难 \\n🤩🤩🤩 \\n\\n \\n1074. 元素和为目标值的子矩阵数量 \\nLeetCode 题解链接 \\n困难 \\n🤩🤩🤩 \\n\\n \\n1310. 子数组异或查询 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩🤩 \\n\\n \\n1442. 形成两个异或相等数组的三元组数目 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n1738. 找出第 K 大的异或坐标值 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n1749. 任意子数组和的绝对值的最大值 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n\\n1894. 找到需要补充粉笔的学生编号 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩🤩 \\n注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"朴素解法 为了方便,我们称每个单元格及其八连通方向单元格所组成的连通块为一个 item。\\n\\n数据范围只有 $200$,我们可以直接对每个 item 进行遍历模拟。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int[][] imageSmoother(int[][] img) {\\n int m = img.length, n = img[0].length;\\n int[][] ans = new int[m][n];\\n int[][] dirs = new int[][]{{0…","guid":"https://leetcode.cn/problems/image-smoother//solution/by-ac_oier-nn3v","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-23T23:10:32.782Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"图片平滑器","url":"https://leetcode.cn/problems/image-smoother//solution/tu-pian-ping-hua-qi-by-leetcode-solution-9oi5","content":"方法一:遍历
\\n思路和算法
\\n按照题目的要求,我们直接依次计算每一个位置平滑处理后的结果即可。
\\n具体地,对于位置 $(i,j)$,我们枚举其周围的九个单元是否存在,对于存在的单元格,我们统计其数量 $\\\\textit{num}$ 与总和 $\\\\textit{sum}$,那么该位置平滑处理后的结果即为 $\\\\Big\\\\lfloor\\\\dfrac{\\\\textit{sum}}{\\\\textit{num}}\\\\Big\\\\rfloor$。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> imageSmoother(vector<vector<int>>& img) {\\n int m = img.size(), n = img[0].size();\\n vector<vector<int>> ret(m, vector<int>(n));\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int num = 0, sum = 0;\\n for (int x = i - 1; x <= i + 1; x++) {\\n for (int y = j - 1; y <= j + 1; y++) {\\n if (x >= 0 && x < m && y >= 0 && y < n) {\\n num++;\\n sum += img[x][y];\\n }\\n }\\n }\\n ret[i][j] = sum / num;\\n }\\n }\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int[][] imageSmoother(int[][] img) {\\n int m = img.length, n = img[0].length;\\n int[][] ret = new int[m][n];\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int num = 0, sum = 0;\\n for (int x = i - 1; x <= i + 1; x++) {\\n for (int y = j - 1; y <= j + 1; y++) {\\n if (x >= 0 && x < m && y >= 0 && y < n) {\\n num++;\\n sum += img[x][y];\\n }\\n }\\n }\\n ret[i][j] = sum / num;\\n }\\n }\\n return ret;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[][] ImageSmoother(int[][] img) {\\n int m = img.Length, n = img[0].Length;\\n int[][] ret = new int[m][];\\n for (int i = 0; i < m; i++) {\\n ret[i] = new int[n];\\n }\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int num = 0, sum = 0;\\n for (int x = i - 1; x <= i + 1; x++) {\\n for (int y = j - 1; y <= j + 1; y++) {\\n if (x >= 0 && x < m && y >= 0 && y < n) {\\n num++;\\n sum += img[x][y];\\n }\\n }\\n }\\n ret[i][j] = sum / num;\\n }\\n }\\n return ret;\\n }\\n}\\n
###C
\\n\\nint** imageSmoother(int** img, int imgSize, int* imgColSize, int* returnSize, int** returnColumnSizes){\\n int m = imgSize, n = imgColSize[0];\\n int ** ret = (int **)malloc(sizeof(int *) * m);\\n *returnColumnSizes = (int *)malloc(sizeof(int) * m);\\n for (int i = 0; i < m; i ++) {\\n ret[i] = (int *)malloc(sizeof(int) * n);\\n memset(ret[i], 0, sizeof(int) * n);\\n (*returnColumnSizes)[i] = n;\\n }\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n int num = 0, sum = 0;\\n for (int x = i - 1; x <= i + 1; x++) {\\n for (int y = j - 1; y <= j + 1; y++) {\\n if (x >= 0 && x < m && y >= 0 && y < n) {\\n num++;\\n sum += img[x][y];\\n }\\n }\\n }\\n ret[i][j] = sum / num;\\n }\\n }\\n *returnSize = m;\\n return ret;\\n}\\n
###JavaScript
\\n\\nvar imageSmoother = function(img) {\\n const m = img.length, n = img[0].length;\\n const ret = new Array(m).fill(0).map(() => new Array(n).fill(0));\\n for (let i = 0; i < m; i++) {\\n for (let j = 0; j < n; j++) {\\n let num = 0, sum = 0;\\n for (let x = i - 1; x <= i + 1; x++) {\\n for (let y = j - 1; y <= j + 1; y++) {\\n if (x >= 0 && x < m && y >= 0 && y < n) {\\n num++;\\n sum += img[x][y];\\n }\\n }\\n }\\n ret[i][j] = Math.floor(sum / num);\\n }\\n }\\n return ret;\\n};\\n
###Python
\\n\\nclass Solution:\\n def imageSmoother(self, img: List[List[int]]) -> List[List[int]]:\\n m, n = len(img), len(img[0])\\n ans = [[0] * n for _ in range(m)]\\n for i in range(m):\\n for j in range(n):\\n tot, num = 0, 0\\n for x in range(max(i - 1, 0), min(i + 2, m)):\\n for y in range(max(j - 1, 0), min(j + 2, n)):\\n tot += img[x][y]\\n num += 1\\n ans[i][j] = tot // num\\n return ans\\n
###go
\\n\\nfunc imageSmoother(img [][]int) [][]int {\\n m, n := len(img), len(img[0])\\n ans := make([][]int, m)\\n for i := range ans {\\n ans[i] = make([]int, n)\\n for j := range ans[i] {\\n sum, num := 0, 0\\n for _, row := range img[max(i-1, 0):min(i+2, m)] {\\n for _, v := range row[max(j-1, 0):min(j+2, n)] {\\n sum += v\\n num++\\n }\\n }\\n ans[i][j] = sum / num\\n }\\n }\\n return ans\\n}\\n\\nfunc max(a, b int) int {\\n if b > a {\\n return b\\n }\\n return a\\n}\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:遍历 思路和算法\\n\\n按照题目的要求,我们直接依次计算每一个位置平滑处理后的结果即可。\\n\\n具体地,对于位置 $(i,j)$,我们枚举其周围的九个单元是否存在,对于存在的单元格,我们统计其数量 $\\\\textit{num}$ 与总和 $\\\\textit{sum}$,那么该位置平滑处理后的结果即为 $\\\\Big\\\\lfloor\\\\dfrac{\\\\textit{sum}}{\\\\textit{num}}\\\\Big\\\\rfloor$。\\n\\n代码\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n vector- \\n
\\n时间复杂度:$O(mnC^2)$,其中 $m$ 为给定矩阵的行数,$n$ 为给定矩阵的列数,$C=3$ 为过滤器的宽高。我们需要遍历整个矩阵以计算每个位置的值,计算单个位置的值的时间复杂度为 $O(C^2)$。
\\n- \\n
\\n空间复杂度:$O(1)$。注意返回值不计入空间复杂度。
\\n> imageSmoother…","guid":"https://leetcode.cn/problems/image-smoother//solution/tu-pian-ping-hua-qi-by-leetcode-solution-9oi5","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-22T06:07:41.312Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"教你一步步思考 DP:从记忆化搜索到递推到空间优化!(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/by-endlesscheng-pa3v","content":" 一、寻找子问题
\\n在示例 1 中,我们要解决的问题(原问题)是:
\\n\\n
\\n- 有 $2$ 条地毯和 $8$ 块砖,计算没被覆盖的白色砖块的最少数目。
\\n考虑从右往左覆盖砖。讨论最后一块砖是否覆盖(选或不选):
\\n\\n
\\n- 如果不覆盖(不选)最后一块砖,那么需要解决的子问题为:有 $2$ 条地毯和 $8-1=7$ 块砖,计算没被覆盖的白色砖块的最少数目。
\\n- 如果覆盖(选)最后一块砖,那么末尾连续的 $\\\\textit{carpetLen}=2$ 块砖都会被覆盖,需要解决的子问题为:有 $1$ 条地毯和 $8-\\\\textit{carpetLen}=6$ 块砖,计算没被覆盖的白色砖块的最少数目。
\\n由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。
\\n\\n\\n注:从右往左思考,主要是为了方便把递归翻译成递推。从左往右思考也是可以的。
\\n二、状态定义与状态转移方程
\\n根据上面的讨论,我们需要在递归过程中跟踪以下信息:
\\n\\n
\\n- $i$:还剩下 $i$ 条地毯。
\\n- $j$:剩余砖块为 $\\\\textit{floor}[0]$ 到 $\\\\textit{floor}[j]$,即 $j+1$ 块砖。
\\n因此,定义状态为 $\\\\textit{dfs}(i,j)$,表示用 $i$ 条地毯覆盖下标在 $[0,j]$ 中的砖,没被覆盖的白色砖块的最少数目。
\\n接下来,思考如何从一个状态转移到另一个状态。
\\n考虑 $\\\\textit{floor}[j]$ 是否覆盖(选或不选):
\\n\\n
\\n- 不覆盖(不选):接下来要解决的问题是,用 $i$ 条地毯覆盖下标在 $[0,j-1]$ 中的砖,没被覆盖的白色砖块的最少数目,再加上 $\\\\texttt{int}(\\\\textit{floor}[j])$(刚好白色是 $1$),得 $\\\\textit{dfs}(i,j) = \\\\textit{dfs}(i,j-1) + \\\\texttt{int}(\\\\textit{floor}[j])$。
\\n- 覆盖(选):如果 $i>0$,接下来要解决的问题是,用 $i-1$ 条地毯覆盖下标在 $[0,j-\\\\textit{carpetLen}]$ 中的砖,没被覆盖的白色砖块的最少数目,即 $\\\\textit{dfs}(i,j) = \\\\textit{dfs}(i-1,j-\\\\textit{carpetLen})$。
\\n这两种情况取最小值,就得到了 $\\\\textit{dfs}(i,j)$,即
\\n$$
\\n
\\n\\\\textit{dfs}(i,j) =
\\n\\\\begin{cases}
\\n\\\\min(\\\\textit{dfs}(i,j-1) + \\\\texttt{int}(\\\\textit{floor}[j]), \\\\textit{dfs}(i-1,j-\\\\textit{carpetLen})), & i>0 \\\\
\\n\\\\textit{dfs}(i,j-1) + \\\\texttt{int}(\\\\textit{floor}[j]), & i=0 \\\\
\\n\\\\end{cases}
\\n$$递归边界:如果 $j<\\\\textit{carpetLen}\\\\cdot i$,那么 $\\\\textit{dfs}(i,j)=0$,因为剩余砖块可以全部覆盖。
\\n递归入口:$\\\\textit{dfs}(\\\\textit{numCarpets},m-1)$,其中 $m$ 是 $\\\\textit{floor}$ 的长度。这是原问题,也是答案。
\\n三、递归搜索 + 保存递归返回值 = 记忆化搜索
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n\\n
\\n- 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 $\\\\textit{memo}$ 数组中。
\\n- 如果一个状态不是第一次遇到($\\\\textit{memo}$ 中保存的结果不等于 $\\\\textit{memo}$ 的初始值),那么可以直接返回 $\\\\textit{memo}$ 中保存的结果。
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i,j)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。具体请看视频讲解 动态规划入门:从记忆化搜索到递推,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n\\nclass Solution:\\n def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:\\n floor = list(map(int, floor)) # 避免在 dfs 中频繁调用 int()\\n @cache # 缓存装饰器,避免重复计算 dfs 的结果(一行代码实现记忆化)\\n def dfs(i: int, j: int) -> int:\\n if j < carpetLen * i: # 剩余砖块可以全部覆盖\\n return 0\\n if i == 0:\\n return dfs(i, j - 1) + floor[j]\\n return min(dfs(i, j - 1) + floor[j], dfs(i - 1, j - carpetLen))\\n return dfs(numCarpets, len(floor) - 1)\\n
\\nclass Solution {\\n public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {\\n int m = floor.length();\\n int[][] memo = new int[numCarpets + 1][m];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1); // -1 表示没有计算过\\n }\\n return dfs(numCarpets, m - 1, floor.toCharArray(), memo, carpetLen);\\n }\\n\\n private int dfs(int i, int j, char[] floor, int[][] memo, int carpetLen) {\\n if (j < carpetLen * i) { // 剩余砖块可以全部覆盖\\n return 0;\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j];\\n }\\n int res = dfs(i, j - 1, floor, memo, carpetLen) + (floor[j] - \'0\');\\n if (i > 0) {\\n res = Math.min(res, dfs(i - 1, j - carpetLen, floor, memo, carpetLen));\\n }\\n return memo[i][j] = res; // 记忆化\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int m = floor.size();\\n vector memo(numCarpets + 1, vector<int>(m, -1)); // -1 表示没有计算过\\n auto dfs = [&](this auto&& dfs, int i, int j) -> int {\\n if (j < carpetLen * i) { // 剩余砖块可以全部覆盖\\n return 0;\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n if (i == 0) {\\n return res = dfs(i, j - 1) + (floor[j] - \'0\');\\n }\\n return res = min(dfs(i, j - 1) + (floor[j] - \'0\'), dfs(i - 1, j - carpetLen));\\n };\\n return dfs(numCarpets, m - 1);\\n }\\n};\\n
\\nfunc minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {\\n m := len(floor)\\n memo := make([][]int, numCarpets+1)\\n for i := range memo {\\n memo[i] = make([]int, m)\\n for j := range memo[i] {\\n memo[i][j] = -1 // -1 表示没有计算过\\n }\\n }\\n var dfs func(int, int) int\\n dfs = func(i, j int) (res int) {\\n if j < carpetLen*i { // 剩余砖块可以全部覆盖\\n return\\n }\\n p := &memo[i][j]\\n if *p != -1 { // 之前计算过\\n return *p\\n }\\n defer func() { *p = res }() // 记忆化\\n res = dfs(i, j-1) + int(floor[j]-\'0\')\\n if i > 0 {\\n res = min(res, dfs(i-1, j-carpetLen))\\n }\\n return\\n }\\n return dfs(numCarpets, m-1)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$,其中 $m$ 是 $\\\\textit{floor}$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\\\\times$ 单个状态的计算时间。本题状态个数等于 $\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$,单个状态的计算时间为 $\\\\mathcal{O}(1)$,所以总的时间复杂度为 $\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$。
\\n- 空间复杂度:$\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$。保存多少状态,就需要多少空间。
\\n四、1:1 翻译成递推
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i][j]$ 的定义和 $\\\\textit{dfs}(i,j)$ 的定义是完全一样的,都表示用 $i$ 条地毯覆盖下标在 $[0,j]$ 中的砖,没被覆盖的白色砖块的最少数目。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\n
\\nf[i][j] =
\\n\\\\begin{cases}
\\n\\\\min(f[i][j-1] + \\\\texttt{int}(\\\\textit{floor}[j]), f[i-1][j-\\\\textit{carpetLen})], & i>0 \\\\
\\nf[i][j-1] + \\\\texttt{int}(\\\\textit{floor}[j]), & i=0 \\\\
\\n\\\\end{cases}
\\n$$初始值 $f[i][j]=0$,翻译自递归边界 $\\\\textit{dfs}(i,j)=0$。
\\n答案为 $f[\\\\textit{numCarpets}][m-1]$,翻译自递归入口 $\\\\textit{dfs}(\\\\textit{numCarpets},m-1)$。
\\n\\nclass Solution:\\n def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:\\n floor = list(map(int, floor))\\n m = len(floor)\\n f = [[0] * m for _ in range(numCarpets + 1)]\\n f[0] = list(accumulate(floor)) # 单独计算 i=0 的情况,本质是 floor 的前缀和\\n for i in range(1, numCarpets + 1):\\n for j in range(carpetLen * i, m):\\n f[i][j] = min(f[i][j - 1] + floor[j], f[i - 1][j - carpetLen])\\n return f[-1][-1]\\n
\\nclass Solution {\\n public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {\\n char[] s = floor.toCharArray();\\n int m = s.length;\\n int[][] f = new int[numCarpets + 1][m];\\n // 单独计算 i=0 的情况,本质是 s 的前缀和\\n f[0][0] = s[0] - \'0\';\\n for (int j = 1; j < m; j++) {\\n f[0][j] = f[0][j - 1] + (s[j] - \'0\');\\n }\\n for (int i = 1; i <= numCarpets; i++) {\\n for (int j = carpetLen * i; j < m; j++) {\\n f[i][j] = Math.min(f[i][j - 1] + (s[j] - \'0\'), f[i - 1][j - carpetLen]);\\n }\\n }\\n return f[numCarpets][m - 1];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int m = floor.size();\\n vector f(numCarpets + 1, vector<int>(m));\\n // 单独计算 i=0 的情况,本质是 floor 的前缀和\\n f[0][0] = floor[0] - \'0\';\\n for (int j = 1; j < m; j++) {\\n f[0][j] = f[0][j - 1] + (floor[j] - \'0\');\\n }\\n for (int i = 1; i <= numCarpets; i++) {\\n for (int j = carpetLen * i; j < m; j++) {\\n f[i][j] = min(f[i][j - 1] + (floor[j] - \'0\'), f[i - 1][j - carpetLen]);\\n }\\n }\\n return f[numCarpets][m - 1];\\n }\\n};\\n
\\nfunc minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {\\n m := len(floor)\\n f := make([][]int, numCarpets+1)\\n for i := range f {\\n f[i] = make([]int, m)\\n }\\n // 单独计算 i=0 的情况,本质是 floor 的前缀和\\n f[0][0] = int(floor[0] - \'0\')\\n for j := 1; j < m; j++ {\\n f[0][j] = f[0][j-1] + int(floor[j]-\'0\')\\n }\\n for i := 1; i <= numCarpets; i++ {\\n for j := carpetLen * i; j < m; j++ {\\n f[i][j] = min(f[i][j-1]+int(floor[j]-\'0\'), f[i-1][j-carpetLen])\\n }\\n }\\n return f[numCarpets][m-1]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$,其中 $m$ 是 $\\\\textit{floor}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$。
\\n五、空间优化
\\n由于计算 $f[i]$ 只需要知道 $f[i-1]$ 中的数据,我们可以改用两个长为 $m$ 的数组滚动计算。
\\n此外,可以在计算 DP 之前,特判 $\\\\textit{numCarpets}\\\\cdot \\\\textit{carpetLen}\\\\ge m$ 的情况,此时所有砖块都能被覆盖,直接返回 $0$。
\\n\\nclass Solution:\\n def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:\\n m = len(floor)\\n if numCarpets * carpetLen >= m:\\n return 0\\n\\n floor = list(map(int, floor))\\n f = list(accumulate(floor))\\n for i in range(1, numCarpets + 1):\\n nf = [0] * m\\n for j in range(carpetLen * i, m):\\n nf[j] = min(nf[j - 1] + floor[j], f[j - carpetLen])\\n f = nf\\n return f[-1]\\n
\\nclass Solution:\\n def minimumWhiteTiles(self, floor: str, numCarpets: int, carpetLen: int) -> int:\\n m = len(floor)\\n if numCarpets * carpetLen >= m:\\n return 0\\n\\n floor = list(map(int, floor))\\n f = list(accumulate(floor))\\n for i in range(1, numCarpets + 1):\\n nf = [0] * m\\n for j in range(carpetLen * i, m):\\n x = nf[j - 1] + floor[j]\\n y = f[j - carpetLen]\\n nf[j] = x if x < y else y\\n f = nf\\n return f[-1]\\n
\\nclass Solution {\\n public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {\\n int m = floor.length();\\n if (numCarpets * carpetLen >= m) {\\n return 0;\\n }\\n\\n char[] s = floor.toCharArray();\\n int[] f = new int[m];\\n f[0] = s[0] - \'0\';\\n for (int j = 1; j < m; j++) {\\n f[j] = f[j - 1] + (s[j] - \'0\');\\n }\\n for (int i = 1; i <= numCarpets; i++) {\\n int[] nf = new int[m];\\n for (int j = carpetLen * i; j < m; j++) {\\n nf[j] = Math.min(nf[j - 1] + (s[j] - \'0\'), f[j - carpetLen]);\\n }\\n f = nf;\\n }\\n return f[m - 1];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int m = floor.size();\\n if (numCarpets * carpetLen >= m) {\\n return 0;\\n }\\n\\n vector<int> f(m);\\n f[0] = floor[0] - \'0\';\\n for (int j = 1; j < m; j++) {\\n f[j] = f[j - 1] + (floor[j] - \'0\');\\n }\\n for (int i = 1; i <= numCarpets; i++) {\\n vector<int> nf(m);\\n for (int j = carpetLen * i; j < m; j++) {\\n nf[j] = min(nf[j - 1] + (floor[j] - \'0\'), f[j - carpetLen]);\\n }\\n f = move(nf);\\n }\\n return f[m - 1];\\n }\\n};\\n
\\nfunc minimumWhiteTiles(floor string, numCarpets, carpetLen int) int {\\n m := len(floor)\\n if numCarpets*carpetLen >= m {\\n return 0\\n }\\n\\n f := make([]int, m)\\n f[0] = int(floor[0] - \'0\')\\n for j := 1; j < m; j++ {\\n f[j] = f[j-1] + int(floor[j]-\'0\')\\n }\\n for i := 1; i <= numCarpets; i++ {\\n nf := make([]int, m)\\n for j := carpetLen * i; j < m; j++ {\\n nf[j] = min(nf[j-1]+int(floor[j]-\'0\'), f[j-carpetLen])\\n }\\n f = nf\\n }\\n return f[m-1]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(\\\\textit{numCarpets}\\\\cdot m)$,其中 $m$ 是 $\\\\textit{floor}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(m)$。
\\n更多相似题目,见 动态规划题单 中的「§6.3 约束划分个数」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"一、寻找子问题 在示例 1 中,我们要解决的问题(原问题)是:\\n\\n有 $2$ 条地毯和 $8$ 块砖,计算没被覆盖的白色砖块的最少数目。\\n\\n考虑从右往左覆盖砖。讨论最后一块砖是否覆盖(选或不选):\\n\\n如果不覆盖(不选)最后一块砖,那么需要解决的子问题为:有 $2$ 条地毯和 $8-1=7$ 块砖,计算没被覆盖的白色砖块的最少数目。\\n如果覆盖(选)最后一块砖,那么末尾连续的 $\\\\textit{carpetLen}=2$ 块砖都会被覆盖,需要解决的子问题为:有 $1$ 条地毯和 $8-\\\\textit{carpetLen}=6$ 块砖…","guid":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/by-endlesscheng-pa3v","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-20T01:23:31.873Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 + 一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/by-endlesscheng-yfyf","content":"首先计算插入之前的 $\\\\textit{pattern}$ 子序列的个数,然后计算因为插入字母额外增加的 $\\\\textit{pattern}$ 子序列的个数。
\\n设 $x=\\\\textit{pattern}[0],\\\\ y=\\\\textit{pattern}[1]$。
\\n遍历 $\\\\textit{text}$ 统计答案:遇到 $y$ 时,如果左边出现了 $3$ 个 $x$,那么就意味着我们找到了 $3$ 个 $\\\\textit{pattern}$ 子序列,把 $3$ 加入答案。一般地,在遍历 $\\\\textit{text}$ 的同时,维护 $x$ 的出现次数 $\\\\textit{cntX}$。遇到 $y$ 时,把 $\\\\textit{cntX}$ 加入答案。
\\n然后考虑插入字母。
\\n根据题意,$x$ 插入的位置越靠左,$\\\\textit{pattern}$ 子序列的个数越多;$y$ 插入的位置越靠右,$\\\\textit{pattern}$ 子序列的个数越多。那么 $x$ 应插在 $\\\\textit{text}$ 最左侧,$y$ 应插在 $\\\\textit{text}$ 最右侧。
\\n分类讨论:
\\n\\n
\\n- 把 $x$ 插在 $\\\\textit{text}$ 最左侧:答案额外增加 $\\\\textit{cntY}$,其中 $\\\\textit{cntY}$ 是 $y$ 在 $\\\\textit{text}$ 中的出现次数。
\\n- 把 $y$ 插在 $\\\\textit{text}$ 最右侧:答案额外增加 $\\\\textit{cntX}$,其中 $\\\\textit{cntX}$ 是 $x$ 在 $\\\\textit{text}$ 中的出现次数。
\\n⚠注意:代码没有特判 $x=y$ 的情况,要先更新答案,再更新 $\\\\textit{cntX}$,这可以保证更新答案时 $\\\\textit{cntX}$ 表示的是当前字母左边的 $\\\\textit{x}$ 的出现次数,$\\\\textit{cntX}$ 尚未计入当前字母。
\\n\\nclass Solution:\\n def maximumSubsequenceCount(self, text: str, pattern: str) -> int:\\n x, y = pattern\\n ans = cnt_x = cnt_y = 0\\n for c in text:\\n if c == y:\\n ans += cnt_x\\n cnt_y += 1\\n if c == x:\\n cnt_x += 1\\n return ans + max(cnt_x, cnt_y)\\n
\\nclass Solution {\\n public long maximumSubsequenceCount(String text, String pattern) {\\n char x = pattern.charAt(0);\\n char y = pattern.charAt(1);\\n long ans = 0;\\n int cntX = 0;\\n int cntY = 0;\\n for (char c : text.toCharArray()) {\\n if (c == y) {\\n ans += cntX;\\n cntY++;\\n }\\n if (c == x) {\\n cntX++;\\n }\\n }\\n return ans + Math.max(cntX, cntY);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long maximumSubsequenceCount(string text, string pattern) {\\n char x = pattern[0], y = pattern[1];\\n long long ans = 0;\\n int cnt_x = 0, cnt_y = 0;\\n for (char c : text) {\\n if (c == y) {\\n ans += cnt_x;\\n cnt_y++;\\n }\\n if (c == x) {\\n cnt_x++;\\n }\\n }\\n return ans + max(cnt_x, cnt_y);\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nlong long maximumSubsequenceCount(char* text, char* pattern) {\\n char x = pattern[0], y = pattern[1];\\n long long ans = 0;\\n int cnt_x = 0, cnt_y = 0;\\n for (int i = 0; text[i]; i++) {\\n if (text[i] == y) {\\n ans += cnt_x;\\n cnt_y++;\\n }\\n if (text[i] == x) {\\n cnt_x++;\\n }\\n }\\n return ans + MAX(cnt_x, cnt_y);\\n}\\n
\\nfunc maximumSubsequenceCount(text, pattern string) (ans int64) {\\n x, y := pattern[0], pattern[1]\\n cntX, cntY := 0, 0\\n for i := range text {\\n c := text[i]\\n if c == y {\\n ans += int64(cntX)\\n cntY++\\n }\\n if c == x {\\n cntX++\\n }\\n }\\n return ans + int64(max(cntX, cntY))\\n}\\n
\\nvar maximumSubsequenceCount = function(text, pattern) {\\n const [x, y] = pattern;\\n let ans = 0, cntX = 0, cntY = 0;\\n for (const c of text) {\\n if (c === y) {\\n ans += cntX;\\n cntY++;\\n }\\n if (c === x) {\\n cntX++;\\n }\\n }\\n return ans + Math.max(cntX, cntY);\\n};\\n
\\nimpl Solution {\\n pub fn maximum_subsequence_count(text: String, pattern: String) -> i64 {\\n let pattern = pattern.as_bytes();\\n let x = pattern[0];\\n let y = pattern[1];\\n let mut ans = 0i64;\\n let mut cnt_x = 0;\\n let mut cnt_y = 0;\\n for c in text.bytes() {\\n if c == y {\\n ans += cnt_x as i64;\\n cnt_y += 1;\\n }\\n if c == x {\\n cnt_x += 1;\\n }\\n }\\n ans + cnt_x.max(cnt_y) as i64\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{text}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n思考题
\\n如果 $\\\\textit{pattern}$ 的长度是 $3$ 呢?是 $m$ 呢?
\\n欢迎在评论区分享你的思路/代码。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"首先计算插入之前的 $\\\\textit{pattern}$ 子序列的个数,然后计算因为插入字母额外增加的 $\\\\textit{pattern}$ 子序列的个数。 设 $x=\\\\textit{pattern}[0],\\\\ y=\\\\textit{pattern}[1]$。\\n\\n遍历 $\\\\textit{text}$ 统计答案:遇到 $y$ 时,如果左边出现了 $3$ 个 $x$,那么就意味着我们找到了 $3$ 个 $\\\\textit{pattern}$ 子序列,把 $3$ 加入答案。一般地,在遍历 $\\\\textit{text}$ 的同时,维护 $x$ 的出现次数…","guid":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/by-endlesscheng-yfyf","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-20T01:00:17.803Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++,贪心 + 计数","url":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/by-liu-xiang-3-th3s","content":"思路:
\\n\\n
\\n- 最多的子字符串, p[0]放在text开始或者p[1]放在text的结尾;
\\n- 对于字符串text,
\\n
\\np[1]出现时, 前面出现过的p[0]都可以组成p, 因此ans += cnt0, cnt1++;
\\np[0]出现时, cnt0++;- 考虑插入字符的情况, ans += max(cnt0, cnt1);
\\n- 对于p[0] == p[1], 循环中的两个if可以处理;
\\n\\n","description":"思路: 最多的子字符串, p[0]放在text开始或者p[1]放在text的结尾;\\n对于字符串text,\\n p[1]出现时, 前面出现过的p[0]都可以组成p, 因此ans += cnt0, cnt1++;\\n p[0]出现时, cnt0++;\\n考虑插入字符的情况, ans += max(cnt0, cnt1);\\n对于p[0] == p[1], 循环中的两个if可以处理;\\nclass Solution {\\npublic:\\n long long maximumSubsequenceCount(string text, string pattern) {…","guid":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/by-liu-xiang-3-th3s","author":"liu-xiang-3","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-20T00:06:04.966Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"wqs 二分 O(n log n)","url":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/wqs-er-fen-on-log-n-by-zerotrac2-cp7j","content":"class Solution {\\npublic:\\n long long maximumSubsequenceCount(string text, string pattern) {\\n int cnt0 = 0;\\n int cnt1 = 0;\\n long long ans = 0;\\n for (auto &ch : text) {\\n if (ch == pattern[1]) {\\n ans += cnt0;\\n cnt1++;\\n }\\n if (ch == pattern[0]) {\\n cnt0++;\\n }\\n }\\n return ans + max(cnt0, cnt1);\\n }\\n};\\n
容易发现当 k 增加时,覆盖的 1 的数量的增量是单调不增的,因此覆盖的 1 的数量关于 k 是一个上凸函数,可以使用 wqs 二分。
\\n贴两个链接,感兴趣的可以学一学。
\\n\\n\\n###C++
\\n\\n","description":"容易发现当 k 增加时,覆盖的 1 的数量的增量是单调不增的,因此覆盖的 1 的数量关于 k 是一个上凸函数,可以使用 wqs 二分。 贴两个链接,感兴趣的可以学一学。\\n\\nwqs 二分学习笔记\\n\\n一种基于 wqs 二分的优秀做法\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int n = floor.size();\\n vectorclass Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int n = floor.size();\\n vector<int> pre(n);\\n pre[0] = (floor[0] == \'1\');\\n for (int i = 1; i < n; ++i) {\\n pre[i] = pre[i - 1] + (floor[i] == \'1\');\\n if (i >= carpetLen) {\\n pre[i] -= (floor[i - carpetLen] == \'1\');\\n }\\n }\\n\\n vector<int> f(n), g(n);\\n int l = 0, r = n, ans = 0;\\n while (l <= r) {\\n f[0] = g[0] = 0;\\n int mid = (l + r) / 2;\\n for (int i = 0; i < n; ++i) {\\n if (i != 0) {\\n f[i] = f[i - 1];\\n g[i] = g[i - 1];\\n }\\n if (i >= carpetLen) {\\n int use = f[i - carpetLen] + pre[i] - mid;\\n if (use > f[i]) {\\n f[i] = use;\\n g[i] = g[i - carpetLen] + 1;\\n }\\n }\\n else {\\n int use = pre[i] - mid;\\n if (use > f[i]) {\\n f[i] = use;\\n g[i] = 1;\\n }\\n }\\n }\\n \\n if (g[n - 1] <= numCarpets) {\\n ans = f[n - 1] + mid * numCarpets;\\n r = mid - 1;\\n }\\n else {\\n l = mid + 1;\\n }\\n }\\n\\n int cnt1 = 0;\\n for (char ch: floor) {\\n cnt1 += (ch == \'1\');\\n }\\n return cnt1 - ans;\\n }\\n};\\n
pre(n);…","guid":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/wqs-er-fen-on-log-n-by-zerotrac2-cp7j","author":"zerotrac2","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-19T17:37:43.208Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心 && DP C++","url":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/tan-xin-by-cyber-space-iwvd","content":" dp[i][j] 表示考虑前面长度为 j 的地板中,用了 i 条地毯,最后剩余最少白色砖块。
\\n
\\n状态转移方程:\\n
\\n- 当 i == 0 时,此时不用地毯, dp[0][j] 表示前 j 个砖块中白色砖块数目。
\\n- 当 i != 0 时,dp[i][j] = min(dp[i][j - 1] + count[j], dp[i - 1][j - carpetLen]);
\\n
\\n其中 count[j] 表示第 j 块地板是否为白色砖块,\\n
\\n- dp[i][j-1] + count[j] 表示前 j - 1 块地板用了i 条地毯,剩余最少的白色砖块,加上第 j 块地板是否为白色砖块。(即第 j 个位置不用地毯)
\\n- dp[i - 1][j - carpetLen] 表示在下标 j 这里用了地毯,那么 dp[i][j] 就和前 (j - carpetLen) 块地板中用了 i - 1 条地毯剩余的白色砖块数目一致。(即第 j 个位置用了地毯)
\\n时间复杂度: O(nm)
\\n
\\n空间复杂度: O(nm)
\\n其中 n 为 floor 长度, m 为numcarpets代码如下:
\\n\\n","description":"dp[i][j] 表示考虑前面长度为 j 的地板中,用了 i 条地毯,最后剩余最少白色砖块。 状态转移方程:\\n\\n当 i == 0 时,此时不用地毯, dp[0][j] 表示前 j 个砖块中白色砖块数目。\\n当 i != 0 时,dp[i][j] = min(dp[i][j - 1] + count[j], dp[i - 1][j - carpetLen]);\\n 其中 count[j] 表示第 j 块地板是否为白色砖块,\\ndp[i][j-1] + count[j] 表示前 j - 1 块地板用了i 条地毯,剩余最少的白色砖块,加上第 j 块地板是否为白色砖…","guid":"https://leetcode.cn/problems/minimum-white-tiles-after-covering-with-carpets//solution/tan-xin-by-cyber-space-iwvd","author":"Cyber-Space","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-19T16:13:39.019Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心思想 一次遍历 不需额外空间","url":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/jian-yi-jie-fa-by-zohn-z-fxvm","content":"class Solution {\\npublic:\\n int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) {\\n int len = floor.size();\\n vector<vector<int>> dp(numCarpets + 1, vector<int>(len, 0));\\n vector<int> count(len); //第j块地板是否为白色\\n dp[0][0] = floor[0] == \'1\' ? 1 : 0;\\n for(int i = 1; i < len; ++i) {\\n dp[0][i] = dp[0][i - 1];\\n if(floor[i] == \'1\') dp[0][i]++, count[i] = 1;\\n }\\n for(int i = 1; i <= numCarpets; ++i) {\\n for(int j = 0; j < len; ++j) {\\n if(j < carpetLen) dp[i][j] = 0; //少于carpetLen块砖块用了地毯最终都会剩余0块白色砖块\\n else dp[i][j] = min(dp[i][j - 1] + count[j], dp[i - 1][j - carpetLen]);\\n }\\n }\\n return dp[numCarpets][len - 1];\\n }\\n};\\n
若pattern=\\"ac\\",那么认为在text的开始添加\'a\'或者在text的结尾添加\'c\'这两种情况能得到最大值。
\\n
\\n遍历字符串,并记录初始子序列数量sum,以及\'a\'和\'c\'的数量。\\n
\\n- 在开始处添加\'a\',则子序列数量=初始子序列数量+\'c\'的数量。
\\n- 在结尾处添加\'c\',则子序列数量=初始子序列数量+\'a\'的数量。
\\n###java
\\n\\n","description":"若pattern=\\"ac\\",那么认为在text的开始添加\'a\'或者在text的结尾添加\'c\'这两种情况能得到最大值。 遍历字符串,并记录初始子序列数量sum,以及\'a\'和\'c\'的数量。\\n\\n在开始处添加\'a\',则子序列数量=初始子序列数量+\'c\'的数量。\\n在结尾处添加\'c\',则子序列数量=初始子序列数量+\'a\'的数量。\\n\\n###java\\n\\nclass Solution {\\n public long maximumSubsequenceCount(String text, String pattern) {\\n char front…","guid":"https://leetcode.cn/problems/maximize-number-of-subsequences-in-a-string//solution/jian-yi-jie-fa-by-zohn-z-fxvm","author":"zohn-z","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-19T16:10:10.472Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"完成旅途的最少时间","url":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/wan-cheng-lu-tu-de-zui-shao-shi-jian-by-uxyrp","content":"class Solution {\\n public long maximumSubsequenceCount(String text, String pattern) {\\n char front = pattern.charAt(0), back = pattern.charAt(1);\\n long sum = 0;\\n int fcnt = 0, bcnt = 0;\\n // 从后向前遍历,统计pattern[0]和pattenr[1]的数量以及初始子序列数量。\\n for(int i = text.length() - 1; i >= 0; i--) {\\n char c = text.charAt(i);\\n if(c == front) {\\n sum += bcnt;\\n fcnt++;\\n } \\n if(c == back){\\n bcnt++;\\n }\\n }\\n // 返回初始子序列数量+新增的子序列数量\\n return sum + Math.max(bcnt, fcnt);\\n }\\n}\\n
方法一:二分查找转化为判定问题
\\n提示 $1$
\\n当时间增加时,所有公交车完成旅途的总数一定不会减少。
\\n思路与算法
\\n根据 提示 $1$,「花费 $t$ 时间能否完成 $\\\\textit{totalTrips}$ 趟旅途」这个判定问题如果对于某个 $t$ 成立,那么它对于 $[t, \\\\infty)$ 区间内的所有整数均成立。这也就说明这个判定问题对于花费时间 $t$ 具有二值性。因此我们可以通过二分查找确定使得该判定问题成立的最小的 $t$。
\\n由于我们至少需要 $1$ 时间来至少完成一趟旅途,因此二分查找的下界为 $1$。而对于二分查找的上界,出于方便计算的考虑,我们可以将「花费时间最长的公交车完成 $\\\\textit{totalTrips}$ 趟旅途的时间」作为二分查找的上界。
\\n对于花费 $t$ 时间对应的判定问题,我们引入辅助函数 $\\\\textit{check}(t)$ 来判断。
\\n在辅助函数 $\\\\textit{check}(t)$ 中,我们用 $\\\\textit{cnt}$ 统计所有公交车完成旅途数量的总和。随后,我们遍历 $\\\\textit{time}$ 数组的所有元素,对于其中花费为 $\\\\textit{period}$ 的公交车,它在 $t$ 时间内完成旅途的数目即为 $\\\\lfloor t / \\\\textit{period} \\\\rfloor$,其中 $\\\\lfloor x \\\\rfloor$ 表示对 $x$ 向下取整。最终,我们判断 $\\\\textit{cnt}$ 是否大于等于 $\\\\textit{totalTrips}$,并将该答案作为辅助函数的返回值。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n long long minimumTime(vector<int>& time, int totalTrips) {\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n auto check = [&](long long t) -> bool {\\n long long cnt = 0;\\n for (int period: time) {\\n cnt += t / period;\\n }\\n return cnt >= totalTrips;\\n };\\n \\n // 二分查找下界与上界\\n long long l = 1;\\n long long r = (long long) totalTrips * *max_element(time.begin(), time.end());\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n long long mid = l + (r - l) / 2;\\n if (check(mid)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public long minimumTime(int[] time, int totalTrips) {\\n // 二分查找下界与上界\\n long l = 1;\\n long r = (long) totalTrips * Arrays.stream(time).max().orElse(0);\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n long mid = l + (r - l) / 2;\\n if (check(mid, time, totalTrips)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n }\\n\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n public boolean check(long t, int[] time, int totalTrips) {\\n long cnt = 0;\\n for (int period : time) {\\n cnt += t / period;\\n }\\n return cnt >= totalTrips;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public long MinimumTime(int[] time, int totalTrips) {\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n bool Check(long t) {\\n long cnt = 0;\\n foreach (int period in time) {\\n cnt += t / period;\\n }\\n return cnt >= totalTrips;\\n }\\n\\n // 二分查找下界与上界\\n long l = 1;\\n long r = (long) totalTrips * time.Max();\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n long mid = l + (r - l) / 2;\\n if (Check(mid)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n }\\n}\\n
###Go
\\n\\nfunc minimumTime(time []int, totalTrips int) int64 {\\n // 二分查找下界与上界\\nl, r := int64(1), int64(totalTrips) * int64(time[0])\\nfor _, period := range time {\\nif period > int(r / int64(totalTrips)) {\\nr = int64(totalTrips) * int64(period)\\n}\\n}\\n// 二分查找寻找满足要求的最小的 t\\nfor l < r {\\nmid := l + (r - l) / 2\\nif check(mid, time, totalTrips) {\\nr = mid\\n} else {\\nl = mid + 1\\n}\\n}\\nreturn l\\n}\\n\\n// 判断 t 时间内是否可以完成 totalTrips 趟旅途\\nfunc check(t int64, time []int, totalTrips int) bool {\\nvar cnt int64 = 0\\nfor _, period := range time {\\ncnt += t / int64(period)\\n}\\nreturn cnt >= int64(totalTrips)\\n}\\n
###Python
\\n\\nclass Solution:\\n def minimumTime(self, time: List[int], totalTrips: int) -> int:\\n # 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n def check(t: int) -> bool:\\n cnt = 0\\n for period in time:\\n cnt += t // period\\n return cnt >= totalTrips\\n \\n # 二分查找下界与上界\\n l = 1\\n r = totalTrips * max(time)\\n # 二分查找寻找满足要求的最小的 t\\n while l < r:\\n mid = l + (r - l) // 2\\n if check(mid):\\n r = mid\\n else:\\n l = mid + 1\\n return l\\n
###C
\\n\\n// 判断 t 时间内是否可以完成 totalTrips 趟旅途\\nbool check(long long t, int* time, int timeSize, int totalTrips) {\\n long long cnt = 0;\\n for (int i = 0; i < timeSize; i++) {\\n cnt += t / time[i];\\n }\\n return cnt >= totalTrips;\\n}\\n\\nlong long minimumTime(int* time, int timeSize, int totalTrips) {\\n // 二分查找下界与上界\\n long long l = 1;\\n long long r = (long long) totalTrips * time[0];\\n for (int i = 1; i < timeSize; i++) {\\n if (time[i] > r / totalTrips) {\\n r = (long long) totalTrips * time[i];\\n }\\n }\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n long long mid = l + (r - l) / 2;\\n if (check(mid, time, timeSize, totalTrips)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n}\\n
###JavaScript
\\n\\nvar minimumTime = function(time, totalTrips) {\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n const check = (t) => {\\n let cnt = 0;\\n for (const period of time) {\\n cnt += Math.floor(t / period);\\n }\\n return cnt >= totalTrips;\\n };\\n \\n // 二分查找下界与上界\\n let l = 1;\\n let r = totalTrips * Math.max(...time);\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n const mid = Math.floor((l + r) / 2);\\n if (check(mid)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n};\\n
###TypeScript
\\n\\nfunction minimumTime(time: number[], totalTrips: number): number {\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n const check = (t: number): boolean => {\\n let cnt = 0;\\n for (const period of time) {\\n cnt += Math.floor(t / period);\\n }\\n return cnt >= totalTrips;\\n };\\n \\n // 二分查找下界与上界\\n let l = 1;\\n let r = totalTrips * Math.max(...time);\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n const mid = Math.floor((l + r) / 2);\\n if (check(mid)) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn minimum_time(time: Vec<i32>, total_trips: i32) -> i64 {\\n // 二分查找下界与上界\\n let mut l = 1i64;\\n let mut r = total_trips as i64 * *time.iter().max().unwrap() as i64;\\n // 二分查找寻找满足要求的最小的 t\\n while l < r {\\n let mid = l + (r - l) / 2;\\n if Self::check(mid, &time, total_trips) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n l\\n }\\n\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n pub fn check(t: i64, time: &Vec<i32>, total_trips: i32) -> bool {\\n let mut cnt = 0i64;\\n for &period in time {\\n cnt += t / period as i64;\\n }\\n cnt >= total_trips as i64\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func minimumTime(time: Array<Int64>, totalTrips: Int64): Int64 {\\n var maxTime = 0\\n for (t in time) {\\n maxTime = max(maxTime, t)\\n }\\n // 判断 t 时间内是否可以完成 totalTrips 趟旅途\\n func check(t: Int64): Bool {\\n var cnt = 0\\n for (period in time) {\\n cnt += t / period\\n }\\n return cnt >= totalTrips\\n }\\n \\n // 二分查找下界与上界\\n var l = 1\\n var r = totalTrips * maxTime\\n // 二分查找寻找满足要求的最小的 t\\n while (l < r) {\\n let mid = (l + r) / 2\\n if (check(mid)) {\\n r = mid\\n } else {\\n l = mid + 1\\n }\\n }\\n return l\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二分查找转化为判定问题 提示 $1$\\n\\n当时间增加时,所有公交车完成旅途的总数一定不会减少。\\n\\n思路与算法\\n\\n根据 提示 $1$,「花费 $t$ 时间能否完成 $\\\\textit{totalTrips}$ 趟旅途」这个判定问题如果对于某个 $t$ 成立,那么它对于 $[t, \\\\infty)$ 区间内的所有整数均成立。这也就说明这个判定问题对于花费时间 $t$ 具有二值性。因此我们可以通过二分查找确定使得该判定问题成立的最小的 $t$。\\n\\n由于我们至少需要 $1$ 时间来至少完成一趟旅途,因此二分查找的下界为 $1$。而对于二分查找的上界,出于方便计算的考虑…","guid":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/wan-cheng-lu-tu-de-zui-shao-shi-jian-by-uxyrp","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-03-01T03:08:30.552Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"周赛第282场周赛 葡萄城专场T3.6010. 完成旅途的最少时间 排序+二分缩小时间范围进行求解","url":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/zhou-sai-di-282chang-zhou-sai-pu-tao-che-s4eo","content":"- \\n
\\n时间复杂度:$O(n \\\\log(mk))$,其中 $n$ 为 $\\\\textit{time}$ 数组的长度,$m = \\\\textit{totalTrips}$,$k$ 为 $\\\\textit{time}$ 中元素的最大值。我们总共需要进行 $O(\\\\log(mk))$ 次二分查找,每次判断完成旅途数目是否达到要求的时间复杂度均为 $O(n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n\\n\\n📖本篇内容:leetcode每周.周赛第282场周赛 葡萄城专场 ~简单模拟 + 哈希表的应用 + 二分边界求最值
\\n📑 文章专栏:leetcode周赛打卡《周周打卡》
\\n📆 最近更新:2022年2月 20日 leetcode每周.周赛第281场周赛 红海游戏专场 ~简单模拟 + 链表数据结构简单应用 + 优先队列的合理使用
\\n
\\n🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ)
\\n🌇 点赞 👍 收藏 ⭐留言 📝 一键三连 ~关爱程序猿,从你我做起~🙊写在前面
\\n🙊小付来喽,今天接着有更新周赛栏目了哦,今天小付第7次打单周赛
\\n📆第282场周赛——2022-02-27
\\n📝T3.6010. 完成旅途的最少时间
\\n题目
\\n\\n\\n给你一个数组 time ,其中 time[i] 表示第 i 辆公交车完成 一趟旅途 所需要花费的时间。
\\n\\n\\n每辆公交车可以 连续 完成多趟旅途,也就是说,一辆公交车当前旅途完成后,可以 立马开始 下一趟旅途。每辆公交车 独立 运行,也就是说可以同时有多辆公交车在运行且互不影响。
\\n\\n\\n给你一个整数 totalTrips ,表示所有公交车 总共 需要完成的旅途数目。请你返回完成 至少 totalTrips 趟旅途需要花费的 最少 时间。
\\n示例
\\n示例1:
\\n###txt
\\n\\n输入:time = [1,2,3], totalTrips = 5\\n输出:3\\n解释:\\n- 时刻 t = 1 ,每辆公交车完成的旅途数分别为 [1,0,0] 。\\n 已完成的总旅途数为 1 + 0 + 0 = 1 。\\n- 时刻 t = 2 ,每辆公交车完成的旅途数分别为 [2,1,0] 。\\n 已完成的总旅途数为 2 + 1 + 0 = 3 。\\n- 时刻 t = 3 ,每辆公交车完成的旅途数分别为 [3,1,1] 。\\n 已完成的总旅途数为 3 + 1 + 1 = 5 。\\n所以总共完成至少 5 趟旅途的最少时间为 3 。\\n
###txt
\\n\\n输入:time = [2], totalTrips = 1\\n输出:2\\n解释:\\n只有一辆公交车,它将在时刻 t = 2 完成第一趟旅途。\\n所以完成 1 趟旅途的最少时间为 2 。\\n
提示
\\n\\n
1 <= time.length <= 10^5
\\n1 <= time[i], totalTrips <= 10^7
⭐思路 ⭐
\\n本题思路以及考察点:
\\n\\n
\\n- \\n
\\n\\n我们先来理解一下题意,它是需要让我们求出完成
\\n至少 totalTrips
趟旅途需要花费的 最少 时间, 我们可以先将这个车辆行驶时间进行排序
通过计算time[0] * totalTrips
,从而获得得到 旅途花费的 最多 时间,因为当有其他车辆在花费时间最多之内也能跑完则会使得车次跑完旅途的次数+1。代码实现
\\n###java
\\n\\nclass Solution {\\n public long minimumTime(int[] time, int totalTrips) {\\n Arrays.sort(time);\\n long left = 0;\\n // 记录当前最大完成旅途的时间\\n long right = 1L* time[0] * totalTrips ;\\n // 在最小时间和最大时间之间搜索符合条件的时间\\n while (left < right ){\\n long mid = left + (right - left) /2;\\n // 记录当前完成旅途的车\\n long trips = 0;\\n // 遍历每个车次需要完成的时间\\n for(int t : time){\\n if(mid < t){\\n break;\\n }\\n // 记录当前时间能完成的趟数\\n trips += mid / t;\\n }\\n // 如果当前完成的车次已经到达了完成的次数则缩小范围 搜索前面时间范围\\n if(trips >= totalTrips){\\n right = mid;\\n } else {\\n // 反之搜索后面时间范围\\n left = mid + 1;\\n }\\n } \\n return left;\\n }\\n}\\n
执行结果
\\n\\n
🙊写在最后
\\n小付打卡的第7场单周赛 2022-02-27
\\n尽可能把会做的题 都做好 就可以了
\\n本次周赛 对了两道题
\\nT3 超时 T4 没有摸到=-=
\\n所以还是很不错的周赛体验
\\n最后
\\n每天进步点 每天收获点
\\n愿诸君 事业有成 学有所获
\\n如果觉得不错 别忘啦一键三连哦~
\\n\\n","description":"📖本篇内容:leetcode每周.周赛第282场周赛 葡萄城专场 ~简单模拟 + 哈希表的应用 + 二分边界求最值 📑 文章专栏:leetcode周赛打卡《周周打卡》\\n\\n📆 最近更新:2022年2月 20日 leetcode每周.周赛第281场周赛 红海游戏专场 ~简单模拟 + 链表数据结构简单应用 + 优先队列的合理使用\\n 🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技术作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ)\\n 🌇 点赞…","guid":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/zhou-sai-di-282chang-zhou-sai-pu-tao-che-s4eo","author":"alascanfu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-27T06:04:06.283Z","media":[{"url":"https://pic.leetcode-cn.com/1645941845-zCpqlz-file_1645941845953","type":"photo","width":695,"height":325,"blurhash":"LI3vO7j]ahofirj=k9f6g7azaffk"},{"url":"https://pic.leetcode-cn.com/1645941845-tVMnJO-file_1645941845977","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二分答案,附题单(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/er-fen-da-an-python-yi-xing-gao-ding-by-xwvs8","content":"
时间越多,可以完成的旅途也就越多,有单调性,可以二分答案。
\\n由于答案不可能超过让最快的车跑 $\\\\textit{totalTrips}$ 趟所花费的时间,我们可以将
\\n$$
\\n
\\n\\\\textit{totalTrips}\\\\cdot \\\\min(time)
\\n$$作为开区间二分的上界,即让 $\\\\min(time)$ 这辆车跑 $\\\\textit{totalTrips}$ 趟,用时 $\\\\textit{totalTrips}\\\\cdot \\\\min(time)$,这个时间一定能满足题目要求。
\\n开区间二分下界:$\\\\min(time)-1$,这个时间任何车都没法完成一趟旅途 ,一定不满足题目要求。
\\n\\n\\n注:如果你想写闭区间二分,可以把二分下界加一,二分上界减一。
\\n设二分的答案为 $x$,则我们可以完成
\\n$$
\\n
\\n\\\\sum\\\\limits_{i=0}^{n-1} \\\\left\\\\lfloor\\\\dfrac{x}{\\\\textit{time}[i]}\\\\right\\\\rfloor
\\n$$趟旅途,将它与 $\\\\textit{totalTrips}$ 比较,如果比 $\\\\textit{totalTrips}$ 小,说明二分的答案小了,更新二分区间左端点 $\\\\textit{left}$,否则更新二分区间右端点 $\\\\textit{right}$。
\\n关于二分的原理,请看视频【基础算法精讲 04】。
\\n\\nclass Solution:\\n def minimumTime(self, time: List[int], totalTrips: int) -> int:\\n min_t = min(time)\\n left = min_t - 1 # 循环不变量:sum >= totalTrips 恒为 False\\n right = totalTrips * min_t # 循环不变量:sum >= totalTrips 恒为 True\\n while left + 1 < right: # 开区间 (left, right) 不为空\\n mid = (left + right) // 2\\n if sum(mid // t for t in time) >= totalTrips:\\n right = mid # 缩小二分区间为 (left, mid)\\n else:\\n left = mid # 缩小二分区间为 (mid, right)\\n return right # 最小的 True\\n
\\nclass Solution:\\n def minimumTime(self, time: List[int], totalTrips: int) -> int:\\n f = lambda x: sum(x // t for t in time)\\n min_t = min(time)\\n return bisect_left(range(totalTrips * min_t), totalTrips, min_t, key=f)\\n
\\nclass Solution {\\n public long minimumTime(int[] time, int totalTrips) {\\n long minT = Long.MAX_VALUE;\\n for (int t : time) {\\n minT = Math.min(minT, t);\\n }\\n // 循环不变量:check(left) 恒为 false\\n long left = minT - 1;\\n // 循环不变量:check(right) 恒为 true\\n long right = totalTrips * minT;\\n // 开区间 (left, right) 不为空\\n while (left + 1 < right) {\\n long mid = (left + right) / 2;\\n if (check(mid, time, totalTrips)) {\\n // 缩小二分区间为 (left, mid)\\n right = mid;\\n } else {\\n // 缩小二分区间为 (mid, right)\\n left = mid;\\n }\\n }\\n // 此时 left 等于 right-1\\n // check(left-1) = false 且 check(right) = true,所以答案是 right\\n return right;\\n }\\n\\n private boolean check(long x, int[] time, int totalTrips) {\\n long sum = 0;\\n for (int t : time) {\\n sum += x / t;\\n if (sum >= totalTrips) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long minimumTime(vector<int> &time, int totalTrips) {\\n auto check = [&](long long x) -> bool {\\n long long sum = 0;\\n for (int t : time) {\\n sum += x / t;\\n if (sum >= totalTrips) {\\n return true;\\n }\\n }\\n return false;\\n };\\n\\n long long min_t = ranges::min(time);\\n long long left = min_t - 1; // 循环不变量:check(left) 恒为 false\\n long long right = totalTrips * min_t; // 循环不变量:check(right) 恒为 true\\n while (left + 1 < right) { // 开区间 (left, right) 不为空\\n long long mid = (left + right) / 2;\\n (check(mid) ? right : left) = mid;\\n }\\n // 此时 left 等于 right-1\\n // check(left-1) = false 且 check(right) = true,所以答案是 right\\n return right; // 最小的 true\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nbool check(long long x, int* time, int timeSize, int totalTrips) {\\n long long sum = 0;\\n for (int i = 0; i < timeSize; i++) {\\n sum += x / time[i];\\n if (sum >= totalTrips) {\\n return true;\\n }\\n }\\n return false;\\n}\\n\\nlong long minimumTime(int* time, int timeSize, int totalTrips) {\\n long long min_t = LLONG_MAX;\\n for (int i = 0; i < timeSize; i++) {\\n min_t = MIN(min_t, time[i]);\\n }\\n\\n // 循环不变量:check(left) 恒为 false\\n long long left = min_t - 1;\\n // 循环不变量:check(right) 恒为 true\\n long long right = totalTrips * min_t;\\n\\n // 开区间 (left, right) 不为空\\n while (left + 1 < right) {\\n long long mid = (left + right) / 2;\\n if (check(mid, time, timeSize, totalTrips)) {\\n // 缩小二分区间为 (left, mid)\\n right = mid;\\n } else {\\n // 缩小二分区间为 (mid, right)\\n left = mid;\\n }\\n }\\n\\n // 此时 left 等于 right - 1\\n // check(left-1) = false 且 check(right) = true,所以答案是 right\\n return right;\\n}\\n
\\nfunc minimumTime(time []int, totalTrips int) int64 {\\n minT := slices.Min(time)\\n left := minT - 1 // 循环不变量:sum >= totalTrips 恒为 false\\n right := totalTrips * minT // 循环不变量:sum >= totalTrips 恒为 true\\n for left+1 < right { // 开区间 (left, right) 不为空\\n mid := (left + right) / 2\\n sum := 0\\n for _, t := range time {\\n sum += mid / t\\n }\\n if sum >= totalTrips {\\n right = mid // 缩小二分区间为 (left, mid)\\n } else {\\n left = mid // 缩小二分区间为 (mid, right)\\n }\\n }\\n return int64(right) // 最小的 true\\n}\\n
\\nfunc minimumTime(time []int, totalTrips int) int64 {\\n minT := slices.Min(time)\\n return int64(sort.Search(totalTrips*minT, func(x int) bool {\\n sum := 0\\n for _, t := range time {\\n sum += x / t\\n if sum >= totalTrips {\\n return true\\n }\\n }\\n return false\\n }))\\n}\\n
\\nvar minimumTime = function (time, totalTrips) {\\n const minT = Math.min(...time);\\n let left = minT - 1; // 循环不变量:sum >= totalTrips 恒为 false\\n let right = totalTrips * minT; // 循环不变量:sum >= totalTrips 恒为 true\\n while (left + 1 < right) { // 开区间 (left, right) 不为空\\n const mid = Math.floor((left + right) / 2);\\n let sum = 0;\\n for (const t of time) {\\n sum += Math.floor(mid / t);\\n }\\n if (sum >= totalTrips) {\\n right = mid; // 缩小二分区间为 (left, mid)\\n } else {\\n left = mid; // 缩小二分区间为 (mid, right)\\n }\\n }\\n return right; // 最小的 true\\n};\\n
\\nimpl Solution {\\n pub fn minimum_time(time: Vec<i32>, total_trips: i32) -> i64 {\\n let total_trips = total_trips as i64;\\n let min_t = *time.iter().min().unwrap() as i64;\\n let mut left = min_t - 1; // 循环不变量:sum >= total_trips 恒为 false\\n let mut right = total_trips * min_t; // 循环不变量:sum >= total_trips 恒为 true\\n while left + 1 < right { // 开区间 (left, right) 不为空\\n let mid = (left + right) / 2;\\n let mut sum = 0;\\n for &t in &time {\\n sum += mid / t as i64;\\n }\\n if sum >= total_trips {\\n right = mid; // 缩小二分区间为 (left, mid)\\n } else {\\n left = mid; // 缩小二分区间为 (mid, right)\\n }\\n }\\n right // 最小的 true\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log U)$,其中 $n$ 为 $\\\\textit{time}$ 的长度,$U = \\\\textit{totalTrips}\\\\cdot \\\\min(time)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。仅用到若干额外变量。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"时间越多,可以完成的旅途也就越多,有单调性,可以二分答案。 由于答案不可能超过让最快的车跑 $\\\\textit{totalTrips}$ 趟所花费的时间,我们可以将\\n\\n$$\\n \\\\textit{totalTrips}\\\\cdot \\\\min(time)\\n $$\\n\\n作为开区间二分的上界,即让 $\\\\min(time)$ 这辆车跑 $\\\\textit{totalTrips}$ 趟,用时 $\\\\textit{totalTrips}\\\\cdot \\\\min(time)$,这个时间一定能满足题目要求。\\n\\n开区间二分下界:$\\\\min(time)-1$,这个时间任何车都没法完成一趟旅途…","guid":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/er-fen-da-an-python-yi-xing-gao-ding-by-xwvs8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-27T04:14:20.220Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二分[Java]","url":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/er-fen-ji-ke-by-xiaoxi666-0bf9","content":"时间复杂度为O(nlogn)
\\n\\n","description":"时间复杂度为O(nlogn) class Solution {\\n public long minimumTime(int[] time, int totalTrips) {\\n // 其实下界是min(time),我们可以直接将1作为下界,就不用去顺序遍历一次了。因为二分遍历的时间复杂度O(logn)总是小于顺序遍历的时间复杂度O(n),所以我们这样处理虽然可能会导致下界更小,但总体来看更省时间。\\n long l = 1; \\n // 更为准确的上界是 (long)totalTrips * min…","guid":"https://leetcode.cn/problems/minimum-time-to-complete-trips//solution/er-fen-ji-ke-by-xiaoxi666-0bf9","author":"xiaoxi666","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-27T04:09:03.353Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单模拟题","url":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-jz6f","content":"class Solution {\\n public long minimumTime(int[] time, int totalTrips) {\\n // 其实下界是min(time),我们可以直接将1作为下界,就不用去顺序遍历一次了。因为二分遍历的时间复杂度O(logn)总是小于顺序遍历的时间复杂度O(n),所以我们这样处理虽然可能会导致下界更小,但总体来看更省时间。\\n long l = 1; \\n // 更为准确的上界是 (long)totalTrips * min(time)。我们直接用time[0]计算,就不用去顺序遍历一次了。因为二分遍历的时间复杂度O(logn)总是小于顺序遍历的时间复杂度O(n),所以我们这样处理虽然可能会导致上界更大,但总体来看更省时间。\\n long r = (long)totalTrips * time[0];\\n \\n while(l <= r) {\\n long mid = l + ((r - l) >> 1);\\n if (cal(time, mid, totalTrips) < totalTrips) {\\n l = mid + 1;\\n } else {\\n r = mid - 1;\\n }\\n }\\n return l;\\n }\\n \\n // curTime时刻,已经完成的趟数\\n private long cal(int[] time, long curTime, int totalTrips) {\\n int res = 0;\\n for (int t : time) {\\n res += curTime / t;\\n if (res > totalTrips) {\\n return res;\\n }\\n }\\n return res;\\n }\\n\\n}\\n
模拟
\\n数据范围只有 $100$,直接模拟每个球从顶部的某列出发,最终到底底部哪列即可(如果可以到达的话)。
\\n我们使用
\\nr
和c
表示小球当前所处的位置,受重力影响,在不被卡住的情况下,r
会持续自增,直到到达底部,而c
的变化,则是取决于当前挡板grid[r][c]
的方向,若grid[r][c]
为 $1$,则小球的下一个位置为 $(r + 1, c + 1)$;若grid[r][c]
为 $-1$,则下一位置为 $(r + 1, c - 1)$,即可以统一表示为 $(r + 1, c + grid[r][c])$。当且仅当小球在本次移动过程中没被卡住,才能继续移动。即只有 $c + grid[r][c]$ 没有超过矩阵的左右边界(没有被边界卡住),或者 $grid[r][c]$ 和 $grid[r][c + grid[r][c]]$ 同向(不形成夹角),小球方能继续移动。代码:
\\n###Java
\\n\\nclass Solution {\\n int m, n;\\n int[][] g;\\n public int[] findBall(int[][] grid) {\\n g = grid;\\n m = g.length; n = g[0].length;\\n int[] ans = new int[n];\\n for (int i = 0; i < n; i++) ans[i] = getVal(i);\\n return ans;\\n }\\n int getVal(int x) {\\n int r = 0, c = x;\\n while (r < m) {\\n int ne = c + g[r][c];\\n if (ne < 0 || ne >= n) return -1;\\n if (g[r][c] != g[r][ne]) return -1;\\n r++; c = ne;\\n }\\n return c;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(m * n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 数据范围只有 $100$,直接模拟每个球从顶部的某列出发,最终到底底部哪列即可(如果可以到达的话)。\\n\\n我们使用 r 和 c 表示小球当前所处的位置,受重力影响,在不被卡住的情况下,r 会持续自增,直到到达底部,而 c 的变化,则是取决于当前挡板 grid[r][c] 的方向,若 grid[r][c] 为 $1$,则小球的下一个位置为 $(r + 1, c + 1)$;若 grid[r][c] 为 $-1$,则下一位置为 $(r + 1, c - 1)$,即可以统一表示为 $(r + 1, c + grid[r][c…","guid":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-jz6f","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-23T23:01:07.582Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"球会落何处","url":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/qiu-hui-luo-he-chu-by-leetcode-solution-xqop","content":"方法一:模拟
\\n思路
\\n我们依次判断每个球的最终位置。对于每个球,从上至下判断球位置的移动方向。在对应的位置,如果挡板向右偏,则球会往右移动;如果挡板往左偏,则球会往左移动。若移动过程中碰到侧边或者 $\\\\text{V}$ 型,则球会停止移动,卡在箱子里。如果可以完成本层的移动,则继续判断下一层的移动方向,直到落出箱子或者卡住。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def findBall(self, grid: List[List[int]]) -> List[int]:\\n n = len(grid[0])\\n ans = [-1] * n\\n for j in range(n):\\n col = j # 球的初始列\\n for row in grid:\\n dir = row[col]\\n col += dir # 移动球\\n if col < 0 or col == n or row[col] != dir: # 到达侧边或 V 形\\n break\\n else: # 成功到达底部\\n ans[j] = col\\n return ans\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> findBall(vector<vector<int>> &grid) {\\n int n = grid[0].size();\\n vector<int> ans(n);\\n for (int j = 0; j < n; ++j) {\\n int col = j; // 球的初始列\\n for (auto &row : grid) {\\n int dir = row[col];\\n col += dir; // 移动球\\n if (col < 0 || col == n || row[col] != dir) { // 到达侧边或 V 形\\n col = -1;\\n break;\\n }\\n }\\n ans[j] = col; // col >= 0 为成功到达底部\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int[] findBall(int[][] grid) {\\n int n = grid[0].length;\\n int[] ans = new int[n];\\n for (int j = 0; j < n; j++) {\\n int col = j; // 球的初始列\\n for (int[] row : grid) {\\n int dir = row[col];\\n col += dir; // 移动球\\n if (col < 0 || col == n || row[col] != dir) { // 到达侧边或 V 形\\n col = -1;\\n break;\\n }\\n }\\n ans[j] = col; // col >= 0 为成功到达底部\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] FindBall(int[][] grid) {\\n int n = grid[0].Length;\\n int[] ans = new int[n];\\n for (int j = 0; j < n; j++) {\\n int col = j; // 球的初始列\\n foreach (int[] row in grid) {\\n int dir = row[col];\\n col += dir; // 移动球\\n if (col < 0 || col == n || row[col] != dir) { // 到达侧边或 V 形\\n col = -1;\\n break;\\n }\\n }\\n ans[j] = col; // col >= 0 为成功到达底部\\n }\\n return ans;\\n }\\n}\\n
###go
\\n\\nfunc findBall(grid [][]int) []int {\\n n := len(grid[0])\\n ans := make([]int, n)\\n for j := range ans {\\n col := j // 球的初始列\\n for _, row := range grid {\\n dir := row[col]\\n col += dir // 移动球\\n if col < 0 || col == n || row[col] != dir { // 到达侧边或 V 形\\n col = -1\\n break\\n }\\n }\\n ans[j] = col // col >= 0 为成功到达底部\\n }\\n return ans\\n}\\n
###C
\\n\\nint* findBall(int** grid, int gridSize, int* gridColSize, int* returnSize) {\\n int n = gridColSize[0];\\n int * ans = (int *)malloc(sizeof(int) * n);\\n for (int j = 0; j < n; ++j) {\\n int col = j; // 球的初始列\\n for (int i = 0; i < gridSize; i++) {\\n int dir = grid[i][col];\\n col += dir; // 移动球\\n if (col < 0 || col == n || grid[i][col] != dir) { // 到达侧边或 V 形\\n col = -1;\\n break;\\n }\\n }\\n ans[j] = col; // col >= 0 为成功到达底部\\n }\\n *returnSize = n; \\n return ans;\\n}\\n
###JavaScript
\\n\\nvar findBall = function(grid) {\\n const n = grid[0].length;\\n const ans = new Array(n);\\n for (let j = 0; j < n; j++) {\\n let col = j; // 球的初始列\\n for (const row of grid) {\\n const dir = row[col];\\n col += dir; // 移动球\\n if (col < 0 || col === n || row[col] !== dir) { // 到达侧边或 V 形\\n col = -1;\\n break;\\n }\\n }\\n ans[j] = col; // col >= 0 为成功到达底部\\n }\\n return ans;\\n};\\n
复杂度分析
\\n\\n
\\n","description":"方法一:模拟 思路\\n\\n我们依次判断每个球的最终位置。对于每个球,从上至下判断球位置的移动方向。在对应的位置,如果挡板向右偏,则球会往右移动;如果挡板往左偏,则球会往左移动。若移动过程中碰到侧边或者 $\\\\text{V}$ 型,则球会停止移动,卡在箱子里。如果可以完成本层的移动,则继续判断下一层的移动方向,直到落出箱子或者卡住。\\n\\n代码\\n\\n###Python\\n\\nclass Solution:\\n def findBall(self, grid: List[List[int]]) -> List[int]:\\n n = len(grid[0])…","guid":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/qiu-hui-luo-he-chu-by-leetcode-solution-xqop","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-23T02:39:06.048Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【负雪明烛】秒懂系列:详细思路,清晰图解","url":"https://leetcode.cn/problems/push-dominoes//solution/fu-xue-ming-zhu-miao-dong-xi-lie-xiang-x-xkts","content":"- \\n
\\n时间复杂度:$O(m \\\\times n)$,其中 $m$ 和 $n$ 是网格的行数和列数。外循环消耗 $O(n)$,内循环消耗 $O(m)$。
\\n- \\n
\\n空间复杂度:$O(1)$。返回值不计入空间。
\\n大家好,我是 @负雪明烛。点击右上方的「+关注」↗,优质题解不间断!
\\n题目大意
\\n推多米诺骨牌。
\\n
\\n
\\n在起始的时候,都是站着的,然后同时像某些方向推,L
代表向左边推,R
代表向右边推,.
代表不推。
\\n如果
\\n.
左右两边的牌都撞到了自己身上,那么.
就受力平衡所以仍然站着。
\\n另外,很重要的一点,如果一个牌倒在了另外一个已经倒了的牌上,不会给它施加任何力。换句话说,一个推倒了的牌(
\\n\\"L\\"
或\\"R\\"
)只能对另一个站着的牌(\\".\\"
)起作用。解题方法
\\n如果理解了「一个推倒了的牌只能对另一个站着的牌起作用」这句话那么基本上就能做出来这个题了,否则是做不出来的。
\\n
\\n含义是:
\\n\\n
\\n- 两个相邻的被推倒的牌互不影响。
\\n- 一张站立的牌(
\\n\\".\\"
)的最终状态与离其两侧最近的\\"L\\"
或\\"R\\"
有关。所以我们应该找出每个(
\\n\\".\\"
)左右两边最近的两个被推倒了的牌,然后判断这两个牌是什么样子的即可,不用考虑这个区间以外的牌。因为这两张牌被推倒了,而这个区间外的其他牌不会对推倒了的牌起作用。\\n
双指针
\\n可以使用「双指针」的方式寻找
\\n\\".\\"
左右两边距离最近的被推倒的牌,形成\\"X....Y\\"
型的区间。在这两个被推倒了牌形成的区间里,根据左右两端的牌不同,有四种可能性:
\\n\\n\'R......R\' => \'RRRRRRRR\'\\n\'R......L\' => \'RRRRLLLL\' or \'RRRR.LLLL\'\\n\'L......R\' => \'L......R\'\\n\'L......L\' => \'LLLLLLLL\'\\n
使用双指针算法:
\\n\\n
\\n- \\n
l
指向区间的开始(指向\\"L\\"
或者\\"R\\"
);- \\n
r
跳过所有的\\".\\"
,指向区间的结束(也指向\\"L\\"
或者\\"R\\"
)。- 此时区间的形状为
\\n\\"X....Y\\"
,判断这个区间左右端点的\\"X\\"
、\\"Y\\"
是什么,确定中间的\\".\\"
的状态。\\n
代码
\\n由于可能存在输入的
\\ndominoes
的最左边和最右边都是\\".\\"
,那么形成不了\\"X....Y\\"
这样的区间。所以,下面的代码中,给dominoes
最左边添加了一个\\"L\\"
,最右边添加了一个\\"R\\"
,添加的这两个虚拟的牌不会影响dominoes
内部所有的牌的倒向,但是有助于我们形成区间,而且这两个添加的牌,也不会放到最终结果里。\\n
在每个
\\nfor
循环中,向res
添加结果只添加区间的[l, r)
部分,即左闭右开。而且注意当l = 0
的位置,是我们虚拟的牌,不要向res
中添加。代码中的
\\nmid
表示区间内有多少个\\".\\"
。代码如下:
\\n###Python
\\n\\nclass Solution(object):\\n def pushDominoes(self, d):\\n \\"\\"\\"\\n :type dominoes: str\\n :rtype: str\\n \\"\\"\\"\\n d = \\"L\\" + d + \\"R\\"\\n res = []\\n l = 0\\n for r in range(1, len(d)):\\n if d[r] == \'.\':\\n continue\\n mid = r - l - 1\\n if l: # 虚拟的牌不放入结果\\n res.append(d[l])\\n if d[l] == d[r]:\\n res.append(d[l] * mid)\\n elif d[l] == \'L\' and d[r] == \'R\':\\n res.append(\'.\' * mid)\\n else:\\n res.append(\'R\' * (mid // 2) + \'.\' * (mid % 2) + \'L\' * (mid // 2))\\n l = r\\n return \\"\\".join(res)\\n
###C++
\\n\\nclass Solution {\\npublic:\\n string pushDominoes(string dominoes) {\\n dominoes = \\"L\\" + dominoes + \\"R\\";\\n int l = 0;\\n string res = \\"\\";\\n for (int r = 1; r < dominoes.size(); ++r) {\\n if (dominoes[r] == \'.\') {\\n continue;\\n }\\n if (l != 0) { // 虚拟的牌不放入结果\\n res += dominoes[l];\\n }\\n int mid = r - l - 1;\\n if (dominoes[l] == dominoes[r]) {\\n res += string(mid, dominoes[l]);\\n } else if (dominoes[l] == \'L\' && dominoes[r] == \'R\') {\\n res += string(mid, \'.\');\\n } else {\\n res += string(mid / 2, \'R\') + (mid % 2 == 1? \\".\\" : \\"\\") + string(mid / 2, \'L\');\\n }\\n // cout << dominoes[l] << \\" \\" << dominoes[r] << \\" \\" << res << endl;\\n l = r;\\n }\\n return res;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public String pushDominoes(String dominoes) {\\n dominoes = \\"L\\" + dominoes + \\"R\\";\\n int l = 0;\\n StringBuilder res = new StringBuilder();\\n for (int r = 1; r < dominoes.length(); ++r) {\\n if (dominoes.charAt(r) == \'.\') {\\n continue;\\n }\\n if (l != 0) { // 虚拟的牌不放入结果\\n res.append(dominoes.charAt(l));\\n }\\n int mid = r - l - 1;\\n if (dominoes.charAt(l) == dominoes.charAt(r)) {\\n for (int i = 0; i < mid; ++i) {\\n res.append(dominoes.charAt(l));\\n }\\n } else if (dominoes.charAt(l) == \'L\' && dominoes.charAt(r) == \'R\') {\\n for (int i = 0; i < mid; ++i) {\\n res.append(\'.\');\\n }\\n } else {\\n for (int i = 0; i < mid / 2; ++i) {\\n res.append(\'R\');\\n }\\n if (mid % 2 == 1) {\\n res.append(\'.\');\\n }\\n for (int i = 0; i < mid / 2; ++i) {\\n res.append(\'L\');\\n }\\n }\\n l = r;\\n }\\n return res.toString();\\n }\\n}\\n
参考资料:
\\n
\\nhttps://leetcode.com/problems/push-dominoes/discuss/132332/C++JavaPython-Two-Pointers时间复杂度
\\n\\n
\\n- 时间复杂度:$O(N)$,其中 $N$ 是数组长度;
\\n- 空间复杂度:结果不算的话,是 $O(1)$。
\\n总结
\\n\\n
\\n- 重要的永远是题意!理解题意,成功大半!
\\n- 不妨向我一样画个图,能清晰很多!
\\n
\\n我是 @负雪明烛 ,刷算法题 1000 多道,写了 1000 多篇算法题解,收获阅读量 300 万。
\\n
\\n关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。\\n
\\n","description":"大家好,我是 @负雪明烛。点击右上方的「+关注」↗,优质题解不间断! 题目大意\\n\\n推多米诺骨牌。\\n \\n 在起始的时候,都是站着的,然后同时像某些方向推,L代表向左边推,R代表向右边推,.代表不推。\\n\\n\\n如果.左右两边的牌都撞到了自己身上,那么 . 就受力平衡所以仍然站着。\\n\\n\\n另外,很重要的一点,如果一个牌倒在了另外一个已经倒了的牌上,不会给它施加任何力。换句话说,一个推倒了的牌(\\"L\\"或\\"R\\")只能对另一个站着的牌(\\".\\")起作用。\\n\\n解题方法\\n\\n如果理解了「一个推倒了的牌只能对另一个站着的牌起作用」这句话那么基本上就能做出来这个题了,否则是做不出来的。\\n\\n\\n含义是:…","guid":"https://leetcode.cn/problems/push-dominoes//solution/fu-xue-ming-zhu-miao-dong-xi-lie-xiang-x-xkts","author":"fuxuemingzhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-21T01:35:56.176Z","media":[{"url":"https://pic.leetcode-cn.com/1645407132-uQNwgQ-838.%20%E6%8E%A8%E5%A4%9A%E7%B1%B3%E8%AF%BA.001.png","type":"photo","width":1920,"height":1080,"blurhash":"LMS$ZF.8yD-;.7jai{kC%%adjEV@"},{"url":"https://pic.leetcode-cn.com/1645407162-iCWgoh-838.%20%E6%8E%A8%E5%A4%9A%E7%B1%B3%E8%AF%BA.002.png","type":"photo","width":1920,"height":1080,"blurhash":"LNSr=g-pxt-;.8j[j?j[*0kCjGof"},{"url":"https://pic.leetcode-cn.com/1645407172-VWzlZL-838.%20%E6%8E%A8%E5%A4%9A%E7%B1%B3%E8%AF%BA.003.png","type":"photo","width":1920,"height":1080,"blurhash":"LHS$cO_NtR?c_3jEWVj@*0jER5aw"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题双解 :「BFS」&「预处理 + 双指针」","url":"https://leetcode.cn/problems/push-dominoes//solution/gong-shui-san-xie-yi-ti-shuang-jie-bfs-y-z52w","content":"- 在刷题的时候,如果你不知道该怎么刷题,可以看 LeetCode 应该怎么刷?
\\n- 如果你觉得题目太多,想在短时间内快速提高,可以看 LeetCode 最经典的 100 道题。
\\n- 送你一份刷题的代码模板:【LeetCode】代码模板,刷题必会
\\nBFS
\\n推倒骨牌是一个行为传递的过程,可以使用
\\nBFS
来进行模拟。起始将所有不为
\\n.
的骨牌以 $(loc, time, dire)$ 三元组的形式进行入队,三元组所代表的含义为「位置为 $loc$ 的骨牌在 $time$ 时刻受到一个方向为 $dire$ 的力」,然后进行常规的BFS
即可。在受力(入队)时,我们尝试修改骨牌的状态,同时为了解决「一个骨牌同时受到左右推力时,维持站立状态不变」的问题,我们需要在尝试修改骨牌状态后,额外记录下该骨牌的状态修改时间,如果在同一时间内,一块骨牌受力两次(只能是来自左右两个方向的力),需要将该骨牌恢复成竖立状态。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public String pushDominoes(String dominoes) {\\n char[] cs = dominoes.toCharArray();\\n int n = cs.length;\\n int[] g = new int[n];\\n Deque<int[]> d = new ArrayDeque<>();\\n for (int i = 0; i < n; i++) {\\n if (cs[i] == \'.\') continue;\\n int dire = cs[i] == \'L\' ? -1 : 1;\\n d.add(new int[]{i, 1, dire});\\n g[i] = 1;\\n }\\n while (!d.isEmpty()) {\\n int[] info = d.pollFirst();\\n int loc = info[0], time = info[1], dire = info[2];\\n int ne = loc + dire;\\n if (cs[loc] == \'.\' || (ne < 0 || ne >= n)) continue;\\n if (g[ne] == 0) { // 首次受力\\n d.addLast(new int[]{ne, time + 1, dire});\\n g[ne] = time + 1;\\n cs[ne] = dire == -1 ? \'L\' : \'R\';\\n } else if (g[ne] == time + 1) { // 多次受力\\n cs[ne] = \'.\';\\n }\\n }\\n return String.valueOf(cs);\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n预处理 + 双指针
\\n我们知道,如果一块原本竖立的骨牌最终倒下,必然是「受到来自左侧向右的力」或者「受到来自右侧向左的力」。
\\n基于此,我们可以创建两个二维数组
\\nl
和r
分别存储每个位置 $i$ 的左侧和右侧的受力情况,每个的 $l[i]$ 和 $r[i]$ 分别存储「左侧」和「右侧」的最近受力点下标,以及该力的方向。然后枚举所有 $dominoes[i]$ 为
\\n.
的位置,获取其左侧的最近受力点loc1
和受力方向dire1
,以及其右侧的最近受力点loc2
和受力方向dire2
,并进行分情况讨论即可。根据左右侧受力情况修改骨牌状态可通过「双指针」实现。
\\n\\n\\n一些细节:为了避免每个样例都
\\nnew
大数组,可以使用static
优化l
和r
的创建。代码:
\\n###Java
\\n\\nclass Solution {\\n static int N = 100010;\\n static int[][] l = new int[N][2], r = new int[N][2];\\n public String pushDominoes(String dominoes) {\\n char[] cs = dominoes.toCharArray();\\n int n = cs.length;\\n for (int i = 0, j = -1; i < n; i++) {\\n if (cs[i] != \'.\') j = i;\\n l[i] = new int[]{j, j != -1 ? cs[j] : \'.\'};\\n }\\n for (int i = n - 1, j = -1; i >= 0; i--) {\\n if (cs[i] != \'.\') j = i;\\n r[i] = new int[]{j, j != -1 ? cs[j] : \'.\'};\\n }\\n for (int i = 0; i < n; ) {\\n if (cs[i] != \'.\' && ++i >= 0) continue;\\n int j = i;\\n while (j < n && cs[j] == \'.\') j++;\\n j--;\\n int[] a = l[i], b = r[j];\\n int loc1 = a[0], dire1 = a[1], loc2 = b[0], dire2 = b[1];\\n if (loc1 == -1 && loc2 == -1) { // 两侧无力\\n } else if (loc1 == -1) { // 只有右侧有力,且力的方向向左\\n if (dire2 == \'L\') update(cs, i, j, \'L\', \'L\');\\n } else if (loc2 == -1) { // 只有左侧有力,且力的方向向右\\n if (dire1 == \'R\') update(cs, i, j, \'R\', \'R\');\\n } else { // 两侧有力,且两力方向「不同时」反向\\n if (!(dire1 == \'L\' && dire2 == \'R\')) update(cs, i, j, (char)dire1, (char)dire2);\\n }\\n i = j + 1;\\n }\\n return String.valueOf(cs);\\n }\\n void update(char[] cs, int l, int r, char c1, char c2) {\\n for (int p = l, q = r; p <= q; p++, q--) {\\n if (p == q && c1 != c2) continue;\\n cs[p] = c1; cs[q] = c2;\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:预处理
\\nl
和r
的复杂度为 $O(n)$;构造答案复杂度为 $O(n)$。整体复杂度为 $O(n)$- 空间复杂度:$O(n)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"BFS 推倒骨牌是一个行为传递的过程,可以使用 BFS 来进行模拟。\\n\\n起始将所有不为 . 的骨牌以 $(loc, time, dire)$ 三元组的形式进行入队,三元组所代表的含义为「位置为 $loc$ 的骨牌在 $time$ 时刻受到一个方向为 $dire$ 的力」,然后进行常规的 BFS 即可。\\n\\n在受力(入队)时,我们尝试修改骨牌的状态,同时为了解决「一个骨牌同时受到左右推力时,维持站立状态不变」的问题,我们需要在尝试修改骨牌状态后,额外记录下该骨牌的状态修改时间,如果在同一时间内,一块骨牌受力两次(只能是来自左右两个方向的力…","guid":"https://leetcode.cn/problems/push-dominoes//solution/gong-shui-san-xie-yi-ti-shuang-jie-bfs-y-z52w","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-20T23:34:23.132Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"双周赛t1 基于筛法的O(nlogn)解法","url":"https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array//solution/shuang-zhou-sai-t1-ji-yu-shai-fa-de-onlo-ai6k","content":"解题思路
\\n类似于周赛t4 6015. 统计可以被 K 整除的下标对数目
\\n\\n
\\n- 筛法预处理每个因子的倍数有哪些,记录倍数时也要记录他对应的数组里的值
\\n- 遍历每个数,分index=0与index>0的情况
\\n- index=0直接特殊处理即可
\\n- index>0时加上符合题意的配对数
\\nmultiCounter[need][value]
,注意要减去自身重复取的情况,最后结果除以二即可代码
\\n###python3
\\n\\n","description":"解题思路 类似于周赛t4 6015. 统计可以被 K 整除的下标对数目\\n\\n筛法预处理每个因子的倍数有哪些,记录倍数时也要记录他对应的数组里的值\\n遍历每个数,分index=0与index>0的情况\\nindex=0直接特殊处理即可\\nindex>0时加上符合题意的配对数multiCounter[need][value],注意要减去自身重复取的情况,最后结果除以二即可\\n代码\\n\\n###python3\\n\\nfrom collections import defaultdict\\nfrom math import gcd\\nfrom typing import Counter…","guid":"https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array//solution/shuang-zhou-sai-t1-ji-yu-shai-fa-de-onlo-ai6k","author":"981377660LMT","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-20T08:42:28.273Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"推多米诺","url":"https://leetcode.cn/problems/push-dominoes//solution/tui-duo-mi-nuo-by-leetcode-solution-dwgm","content":"from collections import defaultdict\\nfrom math import gcd\\nfrom typing import Counter, List\\n\\nclass Solution:\\n def countPairs(self, nums: List[int], k: int) -> int:\\n n = len(nums)\\n multiCounter = defaultdict(lambda: defaultdict(int))\\n for factor in range(1, n):\\n for multi in range(factor, n, factor):\\n multiCounter[factor][nums[multi]] += 1\\n\\n counter = Counter(nums)\\n res1, res2 = 0, 0\\n for index, value in enumerate(nums):\\n if index == 0:\\n res1 += counter[value] - 1\\n else:\\n need = k // gcd(index, k)\\n res2 += multiCounter[need][value]\\n if index ** 2 % k == 0:\\n res2 -= 1\\n\\n return res1 + res2 // 2\\n
方法一:广度优先搜索
\\n思路
\\n当时间为 $0$ 时,部分骨牌会受到一个初始的向左或向右的力而翻倒。过了 $1$ 秒后,这些翻倒的骨牌会对其周围的骨牌施加一个力。具体表现为:
\\n\\n
\\n- \\n
\\n向左翻倒的骨牌,如果它有直立的左边紧邻的骨牌,则会对该直立的骨牌施加一个向左的力。
\\n- \\n
\\n向右翻倒的骨牌,如果它有直立的右边紧邻的骨牌,则会对该直立的骨牌施加一个向右的力。
\\n接下去需要分析这些 $1$ 秒时受力的骨牌的状态。如果仅受到单侧的力,它们会倒向单侧;如果受到两个力,则会保持平衡。再过 $1$ 秒后,这些新翻倒的骨牌又会对其他直立的骨牌施加力,而不会对正在翻倒或已经翻倒的骨牌施加力。
\\n这样的思路类似于广度优先搜索。我们用一个队列 $q$ 模拟搜索的顺序;数组 $\\\\textit{time}$ 记录骨牌翻倒或者确定不翻倒的时间,翻倒的骨牌不会对正在翻倒或者已经翻倒的骨牌施加力;数组 $\\\\textit{force}$ 记录骨牌受到的力,骨牌仅在受到单侧的力时会翻倒。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def pushDominoes(self, dominoes: str) -> str:\\n n = len(dominoes)\\n q = deque()\\n time = [-1] * n\\n force = [[] for _ in range(n)]\\n for i, f in enumerate(dominoes):\\n if f != \'.\':\\n q.append(i)\\n time[i] = 0\\n force[i].append(f)\\n\\n res = [\'.\'] * n\\n while q:\\n i = q.popleft()\\n if len(force[i]) == 1:\\n res[i] = f = force[i][0]\\n ni = i - 1 if f == \'L\' else i + 1\\n if 0 <= ni < n:\\n t = time[i]\\n if time[ni] == -1:\\n q.append(ni)\\n time[ni] = t + 1\\n force[ni].append(f)\\n elif time[ni] == t + 1:\\n force[ni].append(f)\\n return \'\'.join(res)\\n
###Java
\\n\\nclass Solution {\\n public String pushDominoes(String dominoes) {\\n int n = dominoes.length();\\n Deque<Integer> queue = new ArrayDeque<Integer>();\\n int[] time = new int[n];\\n Arrays.fill(time, -1);\\n List<Character>[] force = new List[n];\\n for (int i = 0; i < n; i++) {\\n force[i] = new ArrayList<Character>();\\n }\\n for (int i = 0; i < n; i++) {\\n char f = dominoes.charAt(i);\\n if (f != \'.\') {\\n queue.offer(i);\\n time[i] = 0;\\n force[i].add(f);\\n }\\n }\\n\\n char[] res = new char[n];\\n Arrays.fill(res, \'.\');\\n while (!queue.isEmpty()) {\\n int i = queue.poll();\\n if (force[i].size() == 1) {\\n char f = force[i].get(0);\\n res[i] = f;\\n int ni = f == \'L\' ? i - 1 : i + 1;\\n if (ni >= 0 && ni < n) {\\n int t = time[i];\\n if (time[ni] == -1) {\\n queue.offer(ni);\\n time[ni] = t + 1;\\n force[ni].add(f);\\n } else if (time[ni] == t + 1) {\\n force[ni].add(f);\\n }\\n }\\n }\\n }\\n return new String(res);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public string PushDominoes(string dominoes) {\\n int n = dominoes.Length;\\n Queue<int> queue = new Queue<int>();\\n int[] time = new int[n];\\n Array.Fill(time, -1);\\n IList<char>[] force = new IList<char>[n];\\n for (int i = 0; i < n; i++) {\\n force[i] = new List<char>();\\n }\\n for (int i = 0; i < n; i++) {\\n char f = dominoes[i];\\n if (f != \'.\') {\\n queue.Enqueue(i);\\n time[i] = 0;\\n force[i].Add(f);\\n }\\n }\\n\\n char[] res = new char[n];\\n Array.Fill(res, \'.\');\\n while (queue.Count > 0) {\\n int i = queue.Dequeue();\\n if (force[i].Count == 1) {\\n char f = force[i][0];\\n res[i] = f;\\n int ni = f == \'L\' ? i - 1 : i + 1;\\n if (ni >= 0 && ni < n) {\\n int t = time[i];\\n if (time[ni] == -1) {\\n queue.Enqueue(ni);\\n time[ni] = t + 1;\\n force[ni].Add(f);\\n } else if (time[ni] == t + 1) {\\n force[ni].Add(f);\\n }\\n }\\n }\\n }\\n return new string(res);\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n string pushDominoes(string dominoes) {\\n int n = dominoes.size();\\n queue<int> q;\\n vector<int> time(n, - 1);\\n vector<string> force(n);\\n for (int i = 0; i < n; i++) {\\n if (dominoes[i] != \'.\') {\\n q.emplace(i);\\n time[i] = 0;\\n force[i].push_back(dominoes[i]);\\n }\\n }\\n\\n string res(n, \'.\');\\n while (!q.empty()) {\\n int i = q.front();\\n q.pop();\\n if (force[i].size() == 1) {\\n char f = force[i][0];\\n res[i] = f;\\n int ni = (f == \'L\') ? (i - 1) : (i + 1);\\n if (ni >= 0 and ni < n) {\\n int t = time[i];\\n if (time[ni] == -1) {\\n q.emplace(ni);\\n time[ni] = t + 1;\\n force[ni].push_back(f);\\n } else if(time[ni] == t + 1) {\\n force[ni].push_back(f);\\n }\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
###C
\\n\\ntypedef struct StListNode {\\n int val;\\n struct StListNode * next;\\n} StListNode;\\n\\ntypedef struct Queue{\\n struct StListNode * head;\\n struct StListNode * tail;\\n int length;\\n} Queue;\\n\\nbool isEmpty(const Queue * obj) {\\n return obj->length == 0;\\n}\\n\\nint length(const Queue * obj) {\\n return obj->length;\\n}\\n\\nbool initQueue(Queue * obj) {\\n obj->head = NULL;\\n obj->tail = NULL;\\n obj->length = 0;\\n return true;\\n}\\n\\nbool pushQueue(Queue * obj, int val) {\\n StListNode * node = (StListNode *)malloc(sizeof(StListNode));\\n node->val = val;\\n node->next = NULL;\\n if (NULL == obj->head) {\\n obj->head = node;\\n obj->tail = node;\\n } else {\\n obj->tail->next = node;\\n obj->tail = obj->tail->next;\\n }\\n obj->length++;\\n return true;\\n}\\n\\nint front(const Queue * obj) {\\n if (obj->length == 0) {\\n return -1;\\n }\\n return obj->head->val;\\n}\\n\\nint popQueue(Queue * obj) {\\n if (obj->length == 0) {\\n return -1;\\n }\\n int res = obj->head->val;\\n StListNode * node = obj->head;\\n obj->head = obj->head->next;\\n obj->length--;\\n free(node);\\n return res;\\n}\\n\\nchar * pushDominoes(char * dominoes){\\n int n = strlen(dominoes);\\n int * time = (int *)malloc(sizeof(int) * n);\\n char * res = (char *)malloc(sizeof(char) * (n + 1));\\n Queue ** force = (Queue **)malloc(sizeof(Queue *) * n);\\n\\n for (int i = 0; i < n; i++) {\\n time[i] = -1;\\n force[i] = (Queue *)malloc(sizeof(Queue));\\n initQueue(force[i]);\\n res[i] = \'.\';\\n }\\n res[n] = \'\\\\0\';\\n Queue qu; \\n initQueue(&qu);\\n for (int i = 0; i < n; i++) {\\n if (dominoes[i] != \'.\') {\\n pushQueue(&qu, i);\\n time[i] = 0;\\n pushQueue(force[i], dominoes[i]);\\n }\\n }\\n\\n while (!isEmpty(&qu)) {\\n int i = popQueue(&qu);\\n if (length(force[i]) == 1) {\\n char f = front(force[i]);\\n res[i] = f;\\n int ni = (f == \'L\') ? (i - 1) : (i + 1);\\n if (ni >= 0 && ni < n) {\\n int t = time[i];\\n if (time[ni] == -1) {\\n pushQueue(&qu, ni);\\n time[ni] = t + 1;\\n pushQueue(force[ni], f);\\n } else if(time[ni] == t + 1) {\\n pushQueue(force[ni], f);\\n }\\n }\\n }\\n }\\n /* free resource */\\n for (int i = 0; i < n; i++) {\\n while (!isEmpty(force[i])) {\\n popQueue(force[i]);\\n }\\n }\\n return res;\\n}\\n
###go
\\n\\nfunc pushDominoes(dominoes string) string {\\n n := len(dominoes)\\n q := []int{}\\n time := make([]int, n)\\n for i := range time {\\n time[i] = -1\\n }\\n force := make([][]byte, n)\\n for i, ch := range dominoes {\\n if ch != \'.\' {\\n q = append(q, i)\\n time[i] = 0\\n force[i] = append(force[i], byte(ch))\\n }\\n }\\n\\n ans := bytes.Repeat([]byte{\'.\'}, n)\\n for len(q) > 0 {\\n i := q[0]\\n q = q[1:]\\n if len(force[i]) > 1 {\\n continue\\n }\\n f := force[i][0]\\n ans[i] = f\\n ni := i - 1\\n if f == \'R\' {\\n ni = i + 1\\n }\\n if 0 <= ni && ni < n {\\n t := time[i]\\n if time[ni] == -1 {\\n q = append(q, ni)\\n time[ni] = t + 1\\n force[ni] = append(force[ni], f)\\n } else if time[ni] == t+1 {\\n force[ni] = append(force[ni], f)\\n }\\n }\\n }\\n return string(ans)\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是 $\\\\textit{dominoes}$ 的长度。每个下标会最多被判断一次状态。
\\n- \\n
\\n空间复杂度:$O(n)$。队列和数组最多各包含 $n$ 个元素。
\\n方法二:模拟
\\n思路
\\n我们可以枚举所有连续的没有被推动的骨牌,根据这段骨牌的两边骨牌(如果有的话)的推倒方向决定这段骨牌的最终状态:
\\n\\n
\\n- 如果两边的骨牌同向,那么这段连续的竖立骨牌会倒向同一方向。
\\n- 如果两边的骨牌相对,那么这段骨牌会向中间倒。
\\n- 如果两边的骨牌相反,那么这段骨牌会保持竖立。
\\n特别地,如果左侧没有被推倒的骨牌,则假设存在一块向左倒的骨牌;如果右侧没有被推倒的骨牌,则假设存在一块向右倒的骨牌。这样的假设不会破坏骨牌的最终状态,并且边界情况也可以落入到上述三种情况中。
\\n我们可以使用两个指针 $i$ 和 $j$ 来枚举所有连续的没有被推动的骨牌,$\\\\textit{left}$ 和 $\\\\textit{right}$ 表示两边骨牌的推倒方向。根据上述三种情况来计算骨牌的最终状态。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def pushDominoes(self, dominoes: str) -> str:\\n s = list(dominoes)\\n n, i, left = len(s), 0, \'L\'\\n while i < n:\\n j = i\\n while j < n and s[j] == \'.\': # 找到一段连续的没有被推动的骨牌\\n j += 1\\n right = s[j] if j < n else \'R\'\\n if left == right: # 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while i < j:\\n s[i] = right\\n i += 1\\n elif left == \'R\' and right == \'L\': # 方向相对,那么就从两侧向中间倒\\n k = j - 1\\n while i < k:\\n s[i] = \'R\'\\n s[k] = \'L\'\\n i += 1\\n k -= 1\\n left = right\\n i = j + 1\\n return \'\'.join(s)\\n
###Java
\\n\\nclass Solution {\\n public String pushDominoes(String dominoes) {\\n char[] s = dominoes.toCharArray();\\n int n = s.length, i = 0;\\n char left = \'L\';\\n while (i < n) {\\n int j = i;\\n while (j < n && s[j] == \'.\') { // 找到一段连续的没有被推动的骨牌\\n j++;\\n }\\n char right = j < n ? s[j] : \'R\';\\n if (left == right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while (i < j) {\\n s[i++] = right;\\n }\\n } else if (left == \'R\' && right == \'L\') { // 方向相对,那么就从两侧向中间倒\\n int k = j - 1;\\n while (i < k) {\\n s[i++] = \'R\';\\n s[k--] = \'L\';\\n }\\n }\\n left = right;\\n i = j + 1;\\n }\\n return new String(s);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public string PushDominoes(string dominoes) {\\n char[] s = dominoes.ToCharArray();\\n int n = s.Length, i = 0;\\n char left = \'L\';\\n while (i < n) {\\n int j = i;\\n while (j < n && s[j] == \'.\') { // 找到一段连续的没有被推动的骨牌\\n j++;\\n }\\n char right = j < n ? s[j] : \'R\';\\n if (left == right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while (i < j) {\\n s[i++] = right;\\n }\\n } else if (left == \'R\' && right == \'L\') { // 方向相对,那么就从两侧向中间倒\\n int k = j - 1;\\n while (i < k) {\\n s[i++] = \'R\';\\n s[k--] = \'L\';\\n }\\n }\\n left = right;\\n i = j + 1;\\n }\\n return new string(s);\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n string pushDominoes(string dominoes) {\\n int n = dominoes.size(), i = 0;\\n char left = \'L\';\\n while (i < n) {\\n int j = i;\\n while (j < n && dominoes[j] == \'.\') { // 找到一段连续的没有被推动的骨牌\\n j++;\\n }\\n char right = j < n ? dominoes[j] : \'R\';\\n if (left == right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while (i < j) {\\n dominoes[i++] = right;\\n }\\n } else if (left == \'R\' && right == \'L\') { // 方向相对,那么就从两侧向中间倒\\n int k = j - 1;\\n while (i < k) {\\n dominoes[i++] = \'R\';\\n dominoes[k--] = \'L\';\\n }\\n }\\n left = right;\\n i = j + 1;\\n }\\n return dominoes;\\n }\\n};\\n
###C
\\n\\nchar * pushDominoes(char * dominoes) {\\n int n = strlen(dominoes), i = 0;\\n char left = \'L\';\\n while (i < n) {\\n int j = i;\\n while (j < n && dominoes[j] == \'.\') { // 找到一段连续的没有被推动的骨牌\\n j++;\\n }\\n char right = j < n ? dominoes[j] : \'R\';\\n if (left == right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while (i < j) {\\n dominoes[i++] = right;\\n }\\n } else if (left == \'R\' && right == \'L\') { // 方向相对,那么就从两侧向中间倒\\n int k = j - 1;\\n while (i < k) {\\n dominoes[i++] = \'R\';\\n dominoes[k--] = \'L\';\\n }\\n }\\n left = right;\\n i = j + 1;\\n }\\n return dominoes;\\n}\\n
###go
\\n\\nfunc pushDominoes(dominoes string) string {\\n s := []byte(dominoes)\\n i, n, left := 0, len(s), byte(\'L\')\\n for i < n {\\n j := i\\n for j < n && s[j] == \'.\' { // 找到一段连续的没有被推动的骨牌\\n j++\\n }\\n right := byte(\'R\')\\n if j < n {\\n right = s[j]\\n }\\n if left == right { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n for i < j {\\n s[i] = right\\n i++\\n }\\n } else if left == \'R\' && right == \'L\' { // 方向相对,那么就从两侧向中间倒\\n k := j - 1\\n for i < k {\\n s[i] = \'R\'\\n s[k] = \'L\'\\n i++\\n k--\\n }\\n }\\n left = right\\n i = j + 1\\n }\\n return string(s)\\n}\\n
###JavaScript
\\n\\nvar pushDominoes = function(dominoes) {\\n const s = [...dominoes];\\n let n = s.length, i = 0;\\n let left = \'L\';\\n while (i < n) {\\n let j = i;\\n while (j < n && s[j] == \'.\') { // 找到一段连续的没有被推动的骨牌\\n j++;\\n }\\n const right = j < n ? s[j] : \'R\';\\n if (left === right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向\\n while (i < j) {\\n s[i++] = right;\\n }\\n } else if (left === \'R\' && right === \'L\') { // 方向相对,那么就从两侧向中间倒\\n let k = j - 1;\\n while (i < k) {\\n s[i++] = \'R\';\\n s[k--] = \'L\';\\n }\\n }\\n left = right;\\n i = j + 1;\\n }\\n return s.join(\'\');\\n};\\n
复杂度分析
\\n\\n
\\n","description":"方法一:广度优先搜索 思路\\n\\n当时间为 $0$ 时,部分骨牌会受到一个初始的向左或向右的力而翻倒。过了 $1$ 秒后,这些翻倒的骨牌会对其周围的骨牌施加一个力。具体表现为:\\n\\n向左翻倒的骨牌,如果它有直立的左边紧邻的骨牌,则会对该直立的骨牌施加一个向左的力。\\n\\n向右翻倒的骨牌,如果它有直立的右边紧邻的骨牌,则会对该直立的骨牌施加一个向右的力。\\n\\n接下去需要分析这些 $1$ 秒时受力的骨牌的状态。如果仅受到单侧的力,它们会倒向单侧;如果受到两个力,则会保持平衡。再过 $1$ 秒后,这些新翻倒的骨牌又会对其他直立的骨牌施加力…","guid":"https://leetcode.cn/problems/push-dominoes//solution/tui-duo-mi-nuo-by-leetcode-solution-dwgm","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-20T02:39:18.429Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"树状数组/线段树/平衡树","url":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/shu-zhuang-shu-zu-xian-duan-shu-ping-hen-knho","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是 $\\\\textit{dominoes}$ 的长度。每个下标会最多会被访问和赋值各一次。
\\n- \\n
\\n空间复杂度:$O(1)$ 或 $O(n)$。某些语言字符串不可变,需要 $O(n)$ 的额外空间。
\\n方法一:树状数组/线段树/平衡树
\\n首先用哈希表记录每个数在数组二中的位置,然后按照数组一的顺序依次处理。
\\n我们考虑以当前数字作为三元组中间数字的好三元组的数目。第一个数字需要是之前已经遍历过的,并且在数组二中的位置比当前数字更靠前的;第三个数字需要是当前还没有遍历过的,并且在数组二中的位置比当前数字更靠后的。这里只对数字的位置有要求,而对数字具体的值没有要求。
\\n如何快速求出满足条件的第一个数字和第三个数字的个数呢?
\\n以 $[4,0,1,3,2]\\\\quad[4,1,0,2,3]$为例,考虑我们的遍历过程:
\\n首先处理的是 4,此时数组二中的出现情况为:
\\n$[4,X,X,X,X]$
\\n我们需要统计的是 4 之前的有值的个数(0 个),以及 4 之后的没有值的个数(4 个)。因此以 4 为中间数字能形成 0 个好三元组。
\\n接下来是 0,此时数组二中的出现情况为:
\\n$[4,X,0,X,X]$
\\n0 之前有值的个数(1 个),0 之后没有值的个数(2 个)。因此以 0 为中间数字能形成 2 个好三元组。
\\n接下来是 1,此时数组二中的出现情况为:
\\n$[4,1,0,X,X]$
\\n1 之前有值的个数(1 个),1 之后没有值的个数(2 个)。因此以 1 为中间数字能形成 2 个好三元组。
\\n接下来是 3,此时数组二中的出现情况为:
\\n$[4,1,0,X,3]$
\\n3 之前有值的个数(3 个),3 之后没有值的个数(0 个)。因此以 3 为中间数字能形成 0 个好三元组。
\\n最后是 2,此时数组二中的出现情况为:
\\n$[4,1,0,2,3]$
\\n2 之前有值的个数(3 个),2 之后没有值的个数(0 个)。因此以 2 为中间数字能形成 0 个好三元组。
\\n最后的答案是 4。
\\n因为我们并不关心数字具体的值,而只关心是否出现过,所以我们实际上可以把数组二的出现情况用一个 0–1 数组来表示:
\\n$[1,0,0,0,0]\\\\rightarrow[1,0,1,0,0]\\\\rightarrow[1,1,1,0,0]\\\\rightarrow[1,1,1,0,1]\\\\rightarrow[1,1,1,1,1]$
\\n这时可以看出,我们用树状数组(或者线段树、平衡树)就能快速更新状态,并求出我们需要的两个数值(左边的 1 的个数和右边的 0 的个数)。
\\n理清思路之后,代码的实现是比较容易的。
\\n\\n
\\n- 时间复杂度$\\\\mathcal{O}(N\\\\log N)$。
\\n- 空间复杂度$\\\\mathcal{O}(N)$。
\\n参考代码(C++)
\\n###cpp
\\n\\n","description":"方法一:树状数组/线段树/平衡树 首先用哈希表记录每个数在数组二中的位置,然后按照数组一的顺序依次处理。\\n\\n我们考虑以当前数字作为三元组中间数字的好三元组的数目。第一个数字需要是之前已经遍历过的,并且在数组二中的位置比当前数字更靠前的;第三个数字需要是当前还没有遍历过的,并且在数组二中的位置比当前数字更靠后的。这里只对数字的位置有要求,而对数字具体的值没有要求。\\n\\n如何快速求出满足条件的第一个数字和第三个数字的个数呢?\\n\\n以 $[4,0,1,3,2]\\\\quad[4,1,0,2,3]$为例,考虑我们的遍历过程:\\n\\n首先处理的是 4,此时数组二中的出现情况为:\\n\\n$[…","guid":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/shu-zhuang-shu-zu-xian-duan-shu-ping-hen-knho","author":"lucifer1004","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-19T16:11:33.126Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(nlogn) 数学做法,枚举右维护左(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array//solution/mo-ni-by-endlesscheng-wegn","content":"template <class T> class FenwickTree {\\n int limit;\\n vector<T> arr;\\n\\n int lowbit(int x) { return x & (-x); }\\n\\npublic:\\n FenwickTree(int limit) {\\n this->limit = limit;\\n arr = vector<T>(limit + 1);\\n }\\n\\n void update(int idx, T delta) {\\n for (; idx <= limit; idx += lowbit(idx))\\n arr[idx] += delta;\\n }\\n\\n T query(int idx) {\\n T ans = 0;\\n for (; idx > 0; idx -= lowbit(idx))\\n ans += arr[idx];\\n return ans;\\n }\\n};\\n\\nclass Solution {\\npublic:\\n long long goodTriplets(vector<int>& nums1, vector<int>& nums2) {\\n int n = nums1.size();\\n FenwickTree<int> occur(n);\\n unordered_map<int, int> pos;\\n for (int i = 0; i < n; ++i)\\n pos[nums2[i]] = i + 1;\\n \\n long long ans = 0;\\n for (int i = 0; i < n; ++i) {\\n int idx = pos[nums1[i]];\\n \\n int left = occur.query(idx);\\n int right = n - idx - (occur.query(n) - occur.query(idx));\\n ans += 1LL * left * right;\\n \\n occur.update(idx, 1);\\n }\\n \\n return ans;\\n }\\n};\\n
如果数组长度 $n=10^5$,$\\\\mathcal{O}(n^2)$ 的暴力枚举就超时了,怎么优化?
\\n从特殊到一般,先考虑所有 $\\\\textit{nums}[i]$ 都相同的情况。此时问题简化成:
\\n\\n
\\n- 统计 $0 \\\\le i < j < n$ 且 $ij$ 能被 $k$ 整除的下标对 $(i, j)$ 的数目。
\\n\\n\\n由于 $0$ 不是因子,单独统计($i=0$ 时,$ij=0$,一定能被 $k$ 整除),所以下面讨论 $1 \\\\le i < j < n$ 的情况。
\\n如果 $k=12$,$j=9$,哪些 $i$ 是满足要求的?
\\n$i$ 如果是 $12$ 的倍数,肯定可以满足要求,因为 $12$ 的倍数一定能被 $12$ 整除。还有其他的 $i$ 吗?
\\n注意到 $j=9$ 是 $3$ 的倍数,而 $12=4\\\\times 3$,那么 $i$ 只需要是 $4$ 的倍数,便可以满足 $ij$ 是 $k$ 的倍数,因为这种情况下 $ij=(4i\')(3j\')=12i\'j\'$ 一定是 $12$ 的倍数。
\\n换句话说,由于 $j$ 和 $k$ 的最大公因子(GCD)是 $3$,所以 $i$ 只需要是 $\\\\dfrac{12}{3}=4$ 的倍数就可以满足要求,即 $i=4,8,12,\\\\cdots$ 都是满足要求的。
\\n一般地,枚举 $j$,那么 $i$ 必须是 $k\'=\\\\dfrac{k}{\\\\text{GCD}(k,j)}$ 的倍数。
\\n回到本题,多了一个元素值必须相同的约束。
\\n遍历数组,设当前元素 $x=\\\\textit{nums}[j]$,我们需要知道左边值为 $x$ 且下标是 $k\'$ 的倍数的数的个数。怎么维护?例如 $\\\\textit{nums}[4]=\\\\textit{nums}[6]=50$,对于 $i=4$,把 $50$ 以及 $4$ 的所有因子 $1,2,4$,组成三个二元组 $(50,1),(50,2),(50,4)$ 加到哈希表中(统计二元组的个数);对于 $i=6$,把 $50$ 以及 $6$ 的所有因子 $1,2,3,6$,组成四个二元组 $(50,1),(50,2),(50,3),(50,6)$ 加到哈希表中。这样如果后面遍历到一个值为 $50$ 的数,且 $k\'=2$,通过查询哈希表中的 $(50,2)$ 的个数,就能知道,左边有两个数,值为 $50$ 且下标是 $2$ 的倍数。
\\n\\n# 预处理每个数的因子\\nMX = 101\\ndivisors = [[] for _ in range(MX)]\\nfor i in range(1, MX):\\n for j in range(i, MX, i):\\n divisors[j].append(i)\\n\\nclass Solution:\\n def countPairs(self, nums: list[int], k: int) -> int:\\n ans = 0\\n cnt = defaultdict(int)\\n for j, x in enumerate(nums): # 枚举 j,计算左边有多少个符合要求的 i\\n if j and x == nums[0]:\\n ans += 1 # 单独统计 i=0 的情况\\n k2 = k // gcd(k, j) # i 必须是 k2 的倍数\\n ans += cnt[(x, k2)]\\n for d in divisors[j]: # j 是 d 的倍数\\n cnt[(x, d)] += 1\\n return ans\\n
\\nclass Solution {\\n private static final int MX = 101;\\n private static final List<Integer>[] divisors = new ArrayList[MX];\\n\\n static {\\n Arrays.setAll(divisors, i -> new ArrayList<>());\\n // 预处理每个数的因子\\n for (int i = 1; i < MX; i++) {\\n for (int j = i; j < MX; j += i) {\\n divisors[j].add(i);\\n }\\n }\\n }\\n\\n public int countPairs(int[] nums, int k) {\\n int ans = 0;\\n Map<Integer, Integer> cnt = new HashMap<>();\\n for (int j = 0; j < nums.length; j++) { // 枚举 j,计算左边有多少个符合要求的 i\\n int x = nums[j];\\n if (j > 0 && x == nums[0]) {\\n ans++; // 单独统计 i=0 的情况\\n }\\n int k2 = k / gcd(k, j); // i 必须是 k2 的倍数\\n // 用位运算把二元组 (x, k2) 合并成一个整数\\n ans += cnt.getOrDefault(k2 << 10 | x, 0);\\n for (int d : divisors[j]) { // j 是 d 的倍数\\n cnt.merge(d << 10 | x, 1, Integer::sum); // cnt[d<<10|x]++\\n }\\n }\\n return ans;\\n }\\n\\n private int gcd(int a, int b) {\\n while (a != 0) {\\n int tmp = b % a;\\n b = a;\\n a = tmp;\\n }\\n return b;\\n }\\n}\\n
\\nconst int MX = 101;\\nvector<int> divisors[MX];\\n\\nauto init = [] {\\n for (int i = 1; i < MX; i++) {\\n for (int j = i; j < MX; j += i) {\\n divisors[j].push_back(i);\\n }\\n }\\n return 0;\\n}();\\n\\nclass Solution {\\npublic:\\n int countPairs(vector<int>& nums, int k) {\\n int ans = 0;\\n unordered_map<int, int> cnt;\\n for (int j = 0; j < nums.size(); j++) { // 枚举 j,计算左边有多少个符合要求的 i\\n int x = nums[j];\\n if (j && x == nums[0]) {\\n ans++; // 单独统计 i=0 的情况\\n }\\n int k2 = k / gcd(k, j); // i 必须是 k2 的倍数\\n ans += cnt[k2 << 10 | x]; // 用位运算把二元组 (x, k2) 合并成一个整数\\n for (int d : divisors[j]) { // j 是 d 的倍数\\n cnt[d << 10 | x]++;\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nconst mx = 101\\nvar divisors [mx][]int\\n\\nfunc init() {\\n // 预处理每个数的因子\\n for i := 1; i < mx; i++ {\\n for j := i; j < mx; j += i {\\n divisors[j] = append(divisors[j], i)\\n }\\n }\\n}\\n\\nfunc countPairs(nums []int, k int) (ans int) {\\n type pair struct{ v, d int }\\n cnt := map[pair]int{}\\n for j, x := range nums { // 枚举 j,计算左边有多少个符合要求的 i\\n if j > 0 && x == nums[0] {\\n ans++ // 单独统计 i=0 的情况\\n }\\n k2 := k / gcd(k, j)\\n ans += cnt[pair{x, k2}] // 统计左边有多少个数,值为 x 且下标是 k2 的倍数\\n for _, d := range divisors[j] { // j 是 d 的倍数\\n cnt[pair{x, d}]++\\n }\\n }\\n return\\n}\\n\\nfunc gcd(a, b int) int { for a != 0 { a, b = b%a, a }; return b }\\n
复杂度分析
\\n由调和级数可得,预处理的时间是 $\\\\mathcal{O}(M\\\\log M)$,其中 $M=100$。
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。每次计算 GCD 需要 $\\\\mathcal{O}(\\\\log \\\\min(k,n))$ 的时间。遍历 $1$ 到 $n-1$ 的所有因子跟预处理是一样的,由调和级数可得,需要 $\\\\mathcal{O}(n\\\\log n)$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(n\\\\log n)$。
\\n相似题目
\\n\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"如果数组长度 $n=10^5$,$\\\\mathcal{O}(n^2)$ 的暴力枚举就超时了,怎么优化? 从特殊到一般,先考虑所有 $\\\\textit{nums}[i]$ 都相同的情况。此时问题简化成:\\n\\n统计 $0 \\\\le i < j < n$ 且 $ij$ 能被 $k$ 整除的下标对 $(i, j)$ 的数目。\\n\\n由于 $0$ 不是因子,单独统计($i=0$ 时,$ij=0$,一定能被 $k$ 整除),所以下面讨论 $1 \\\\le i < j < n$ 的情况。\\n\\n如果 $k=12$,$j=9$,哪些 $i$ 是满足要求的?\\n\\n$i$ 如果是 $12…","guid":"https://leetcode.cn/problems/count-equal-and-divisible-pairs-in-an-array//solution/mo-ni-by-endlesscheng-wegn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-19T16:11:16.078Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"转化为经典问题 (求左侧小于当前元素的个数)","url":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/zhuan-hua-wei-jing-dian-wen-ti-ji-ke-by-b03sy","content":"解法:转化为经典问题(求左侧小于当前元素的个数)+ 树状数组
\\n设 $i < j < k$,如果 $pos2[nums1[i]] < pos2[nums1[j]] < pos2[nums1[k]]$,那么就找到了一个三元组 $(x,y,z),x = nums1[i], y = nums1[j], z = nums1[k]$。令 $f[i] = pos2[nums1[i]]$,问题由此归结为:求满足 $f[i] < f[j] < f[k] (i < j < k)$ 的三元组 $(i, j, k)$ 的数量(因为 $nums1$ 和 $nums2$ 中不包含重复元素,所以三元组 $i,j,k$ 和三元组 $x,y,z$ 是一一对应的)。
\\n我们可以枚举三元组的中间元素 $j$,令 $left[j] =$ 在 $j$ 的左侧小于 $f[j]$ 的元素数量,$right[j] =$ 在 $j$ 右侧大于 $f[j]$ 的数量,那么以 $j$ 为中间的三元组的数量 $=left[j] \\\\times right[j]$。
\\n这类问题是非常典型的 LC 原题 315. 计算右侧小于当前元素的个数,可以采用 官方题解 的树状数组解法,或者归并的解法。这里我采用的是树状数组解法,代码量小,写起来出错几率小。
\\n\\nclass Solution:\\n def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:\\n n = len(nums1)\\n \\n pos2 = dict((nums2[i], i) for i in range(n))\\n f = [pos2[nums1[i]] for i in range(n)]\\n \\n def add(x):\\n while x <= n:\\n t[x] += 1\\n x += (x & (-x))\\n \\n def query(x):\\n res = 0\\n while x:\\n res += t[x]\\n x -= (x & (-x))\\n return res\\n \\n left, right = [0] * n, [0] * n\\n \\n # 计算左侧小于 f[i] 的元素个数\\n t = [0] * (n + 1)\\n for i in range(n):\\n left[i] = query(f[i])\\n add(f[i] + 1)\\n \\n # 计算右侧大于 f[i] 的元素个数\\n t = [0] * (n + 1)\\n for i in range(n-1, -1, -1):\\n right[i] = n - 1 - i - query(f[i] + 1)\\n add(f[i] + 1)\\n \\n res = 0\\n for i in range(n):\\n res += left[i] * right[i]\\n \\n return res\\n
\\n","description":"解法:转化为经典问题(求左侧小于当前元素的个数)+ 树状数组 设 $i < j < k$,如果 $pos2[nums1[i]] < pos2[nums1[j]] < pos2[nums1[k]]$,那么就找到了一个三元组 $(x,y,z),x = nums1[i], y = nums1[j], z = nums1[k]$。令 $f[i] = pos2[nums1[i]]$,问题由此归结为:求满足 $f[i] < f[j] < f[k] (i < j < k)$ 的三元组 $(i, j, k)$ 的数量(因为 $nums1$ 和 $nums2…","guid":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/zhuan-hua-wei-jing-dian-wen-ti-ji-ke-by-b03sy","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-19T16:11:06.982Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"转化成严格递增子序列 + 枚举中间 + 树状数组(Java/Python/C++/Go)","url":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/deng-jie-zhuan-huan-shu-zhuang-shu-zu-by-xmyd","content":"class Solution {\\npublic:\\n void add(vector<int>& A, int pos, int val) {\\n for(; pos < A.size(); pos += (pos & (-pos))) A[pos] += val;\\n }\\n int query(vector<int>& A, int pos) {\\n int res = 0;\\n for(; pos; pos -= (pos & (-pos))) res += A[pos];\\n return res;\\n }\\n long long goodTriplets(vector<int>& nums1, vector<int>& nums2) {\\n int n = nums1.size();\\n vector<int> pos2(n);\\n for(int i = 0; i < nums2.size(); ++i) {\\n pos2[nums2[i]] = i;\\n }\\n \\n // 计算左侧小于该元素的个数\\n vector<int> tr(n + 1, 0), left(n), right(n);\\n for(int i = 0; i < n; ++i) {\\n left[i] = query(tr, pos2[nums1[i]]);\\n add(tr, pos2[nums1[i]] + 1, 1);\\n }\\n \\n fill(tr.begin(), tr.end(), 0);\\n \\n // 计算右侧大于该元素的个数\\n for(int i = n - 1; i >= 0; --i) {\\n right[i] = n - 1 - i - query(tr, pos2[nums1[i]] + 1);\\n add(tr, pos2[nums1[i]] + 1, 1);\\n }\\n \\n long long res = 0;\\n for(int i = 1; i < n - 1; ++i) {\\n res += 1ll * left[i] * right[i];\\n }\\n \\n return res;\\n }\\n};\\n
题意解读
\\n题目本质上是求:$\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度恰好为 $3$ 的公共子序列的个数。
\\n你可能想到了 1143. 最长公共子序列。但本题 $n$ 太大,写 $\\\\mathcal{O}(n^2)$ 的 DP 太慢。
\\n核心思路
\\n本题是排列,所有元素互不相同。如果可以通过某种方法,把 $\\\\textit{nums}_1$ 变成 $[0,1,2,\\\\dots,n-1]$,我们就能把「公共子序列问题」变成「严格递增子序列问题」,后者有更好的性质,可以更快地求解。
\\n此外,本题子序列长度为 $3$,对于 $3$ 个数的问题,通常可以枚举中间那个数。
\\n前置知识:置换
\\n置换是一个排列到另一个排列的双射。
\\n以示例 2 为例,定义如下置换 $P(x)$:
\\n$$
\\n
\\n\\\\begin{pmatrix}
\\nx & 0 & 1 & 2 & 3 & 4 \\\\
\\nP(x) & 1 & 2 & 4 & 3 & 0 \\\\
\\n\\\\end{pmatrix}
\\n$$把 $\\\\textit{nums}_1=[4,0,1,3,2]$ 中的每个元素 $x$ 替换为 $P(x)$,可以得到一个单调递增的排列 $A=[0,1,2,3,4]$。
\\n把 $\\\\textit{nums}_2=[4,1,0,2,3]$ 中的每个元素 $x$ 替换为 $P(x)$,可以得到一个新的排列 $B=[0,2,1,4,3]$。
\\n\\n\\n解释:比如 $1$ 替换成 $P(1)=2$,意思是所有等于 $1$ 的元素($\\\\textit{nums}_1[2]$ 和 $\\\\textit{nums}_2[1]$)都要替换成 $2$。替换后 $A[2]=2$,$B[1]=2$。
\\n在置换之前,$(4,0,3)$ 是两个排列的公共子序列。
\\n在置换之后,$(P(4),P(0),P(3))=(0,1,3)$ 也是两个新的排列的公共子序列。
\\n⚠注意:置换不是排序,是映射(可以理解成重命名),原来的公共子序列在映射后,子序列元素的位置没变,只是数值变了,仍然是公共子序列。所以置换不会改变公共子序列的个数。
\\n思路
\\n把 $\\\\textit{nums}_1$ 置换成排列 $A=[0,1,2,\\\\dots, n-1]$,设这一置换为 $P(x)$。把 $P(x)$ 也应用到 $\\\\textit{nums}_2$ 上,得到排列 $B$。
\\n置换后,我们要找的长为 $3$ 的公共子序列,一定是严格递增的。由于 $A$ 的所有子序列都是严格递增的,我们只需关注 $B$。现在问题变成:
\\n\\n
\\n- $B$ 中有多少个长为 $3$ 的严格递增子序列?
\\n对于长为 $3$ 的严格递增子序列 $(x,y,z)$,枚举中间元素 $y$。现在问题变成:
\\n\\n
\\n- 在 $B$ 中,元素 $y$ 的左侧有多少个比 $y$ 小的数 $x$?右侧有多少个比 $y$ 大的数 $z$?
\\n枚举 $y=B[i]$,设 $i$ 左侧有 $\\\\textit{less}_y$ 个元素比 $y$ 小,那么 $i$ 左侧有 $i-\\\\textit{less}_y$ 个元素比 $y$ 大。在整个排列 $B$ 中,比 $y$ 大的数有 $n-1-y$ 个,减去 $i-\\\\textit{less}_y$,得到 $i$ 右侧有 $n-1-y-(i-\\\\textit{less}_y)$ 个数比 $y$ 大。所以(根据乘法原理)中间元素是 $y$ 的长为 $3$ 的严格递增子序列的个数为
\\n$$
\\n
\\n\\\\textit{less}_y\\\\cdot(n-1-y-(i-\\\\textit{less}_y))
\\n$$枚举 $y=B[i]$,计算上式,加入答案。
\\n如何计算 $\\\\textit{less}_y$?这可以用值域树状数组(或者有序集合)。关于树状数组的原理,请看 带你发明树状数组!附数学证明。
\\n值域树状数组的意思是,把元素值视作下标。添加一个值为 $3$ 的数,就是调用树状数组的 $\\\\texttt{update}(3,1)$。查询小于 $3$ 的元素个数,即小于等于 $2$ 的元素个数,就是调用树状数组的 $\\\\texttt{pre}(2)$。完整的树状数组模板,见 数据结构题单。
\\n由于本题元素值是从 $0$ 开始的,但树状数组的下标是从 $1$ 开始的,所以把元素值转成下标,要加一。
\\n\\nclass FenwickTree:\\n def __init__(self, n: int):\\n self.tree = [0] * (n + 1) # 使用下标 1 到 n\\n\\n # a[i] 增加 val\\n # 1 <= i <= n\\n def update(self, i: int, val: int) -> None:\\n while i < len(self.tree):\\n self.tree[i] += val\\n i += i & -i\\n\\n # 计算前缀和 a[1] + ... + a[i]\\n # 1 <= i <= n\\n def pre(self, i: int) -> int:\\n res = 0\\n while i > 0:\\n res += self.tree[i]\\n i &= i - 1\\n return res\\n\\nclass Solution:\\n def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:\\n n = len(nums1)\\n p = [0] * n\\n for i, x in enumerate(nums1):\\n p[x] = i\\n\\n ans = 0\\n t = FenwickTree(n)\\n for i, y in enumerate(nums2):\\n y = p[y]\\n less = t.pre(y)\\n ans += less * (n - 1 - y - (i - less))\\n t.update(y + 1, 1)\\n return ans\\n
\\nclass Solution:\\n def goodTriplets(self, nums1: List[int], nums2: List[int]) -> int:\\n n = len(nums1)\\n p = [0] * n\\n for i, x in enumerate(nums1):\\n p[x] = i\\n\\n ans = 0\\n sl = SortedList()\\n for i, y in enumerate(nums2):\\n y = p[y]\\n less = sl.bisect_left(y) # sl 的 [0,less-1] 中的数都是小于 y 的,这有 less 个\\n ans += less * (n - 1 - y - (i - less))\\n sl.add(y)\\n return ans\\n
\\nclass FenwickTree {\\n private final int[] tree;\\n\\n public FenwickTree(int n) {\\n tree = new int[n + 1]; // 使用下标 1 到 n\\n }\\n\\n // a[i] 增加 val\\n // 1 <= i <= n\\n public void update(int i, long val) {\\n for (; i < tree.length; i += i & -i) {\\n tree[i] += val;\\n }\\n }\\n\\n // 求前缀和 a[1] + ... + a[i]\\n // 1 <= i <= n\\n public int pre(int i) {\\n int res = 0;\\n for (; i > 0; i &= i - 1) {\\n res += tree[i];\\n }\\n return res;\\n }\\n}\\n\\nclass Solution {\\n public long goodTriplets(int[] nums1, int[] nums2) {\\n int n = nums1.length;\\n int[] p = new int[n];\\n for (int i = 0; i < n; i++) {\\n p[nums1[i]] = i;\\n }\\n\\n long ans = 0;\\n FenwickTree t = new FenwickTree(n);\\n for (int i = 0; i < n - 1; i++) {\\n int y = p[nums2[i]];\\n int less = t.pre(y);\\n ans += (long) less * (n - 1 - y - (i - less));\\n t.update(y + 1, 1);\\n }\\n return ans;\\n }\\n}\\n
\\ntemplate<typename T>\\nclass FenwickTree {\\n vector<T> tree;\\n\\npublic:\\n // 使用下标 1 到 n\\n FenwickTree(int n) : tree(n + 1) {}\\n\\n // a[i] 增加 val\\n // 1 <= i <= n\\n void update(int i, T val) {\\n for (; i < tree.size(); i += i & -i) {\\n tree[i] += val;\\n }\\n }\\n\\n // 求前缀和 a[1] + ... + a[i]\\n // 1 <= i <= n\\n T pre(int i) const {\\n T res = 0;\\n for (; i > 0; i &= i - 1) {\\n res += tree[i];\\n }\\n return res;\\n }\\n};\\n\\nclass Solution {\\npublic:\\n long long goodTriplets(vector<int>& nums1, vector<int>& nums2) {\\n int n = nums1.size();\\n vector<int> p(n);\\n for (int i = 0; i < n; i++) {\\n p[nums1[i]] = i;\\n }\\n\\n long long ans = 0;\\n FenwickTree<int> t(n);\\n for (int i = 0; i < n - 1; i++) {\\n int y = p[nums2[i]];\\n int less = t.pre(y);\\n ans += 1LL * less * (n - 1 - y - (i - less));\\n t.update(y + 1, 1);\\n }\\n return ans;\\n }\\n};\\n
\\ntype fenwick []int\\n\\nfunc newFenwickTree(n int) fenwick {\\nreturn make(fenwick, n+1) // 使用下标 1 到 n\\n}\\n\\n// a[i] 增加 val\\n// 1 <= i <= n\\nfunc (f fenwick) update(i int, val int) {\\nfor ; i < len(f); i += i & -i {\\nf[i] += val\\n}\\n}\\n\\n// 求前缀和 a[1] + ... + a[i]\\n// 1 <= i <= n\\nfunc (f fenwick) pre(i int) (res int) {\\nfor ; i > 0; i &= i - 1 {\\nres += f[i]\\n}\\nreturn\\n}\\n\\nfunc goodTriplets(nums1, nums2 []int) (ans int64) {\\nn := len(nums1)\\np := make([]int, n)\\nfor i, x := range nums1 {\\np[x] = i\\n}\\n\\nt := newFenwickTree(n)\\nfor i, y := range nums2[:n-1] {\\ny = p[y]\\nless := t.pre(y)\\nans += int64(less) * int64(n-1-y-(i-less))\\nt.update(y+1, 1)\\n}\\nreturn\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n)$。其中 $n$ 是 $\\\\textit{nums}_1$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n进阶问题
\\n把 $3$ 改成 $4$ 怎么做?改成 $k$ 怎么做?
\\n欢迎在评论区分享你的思路/代码。
\\n相似题目
\\n\\n
\\n- 1713. 得到子序列的最少操作次数
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"题意解读 题目本质上是求:$\\\\textit{nums}_1$ 和 $\\\\textit{nums}_2$ 的长度恰好为 $3$ 的公共子序列的个数。\\n\\n你可能想到了 1143. 最长公共子序列。但本题 $n$ 太大,写 $\\\\mathcal{O}(n^2)$ 的 DP 太慢。\\n\\n核心思路\\n\\n本题是排列,所有元素互不相同。如果可以通过某种方法,把 $\\\\textit{nums}_1$ 变成 $[0,1,2,\\\\dots,n-1]$,我们就能把「公共子序列问题」变成「严格递增子序列问题」,后者有更好的性质,可以更快地求解。\\n\\n此外,本题子序列长度为 $3$,对于 $3…","guid":"https://leetcode.cn/problems/count-good-triplets-in-an-array//solution/deng-jie-zhuan-huan-shu-zhuang-shu-zu-by-xmyd","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-19T16:08:50.051Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【彤哥来刷题啦】一题两解:记忆化搜索 转 动态规划!","url":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/tong-ge-lai-shua-ti-la-yi-ti-liang-jie-j-y92k","content":"大家好,今天是我坚持写题解的第 197 天!
\\n\\n
方法一、记忆化搜索
\\n今天这道题比较简单。
\\n首先,观察题目给定的数据范围,并不是很大,所以,我们可以先尝试使用暴力搜索来解决。
\\n这里的关键是如何求概率。
\\n通过题意可知,向每个方向前进的概率为 1/8,只要没有走出去都可以增加 1/8 成功的概率,然后,再继续前进即可,把途径的 1/8 的概率相乘就是这一条路径的概率,然后求出所有可行路径的概率相加就是最终的概率。
\\n其实,还有一种算法,我们可以先求出所有可行的路径数,然后,除以 $8^k$ 得到的结果,也是最终的概率。
\\n好了,概率弄清楚了,我们先写第一个暴力版本:
\\n###java
\\n\\nclass Solution {\\n\\n private static final int[][] DIRS = {{1, 2}, {2, 1}, {-1, 2}, {2, -1}, {1, -2}, {-2, 1}, {-1, -2}, {-2, -1}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n return dfs(n, k, row, column);\\n }\\n\\n public double dfs(int n, int k, int i, int j) {\\n if (i < 0 || j < 0 || i >= n || j >= n) {\\n return 0;\\n }\\n if (k == 0) {\\n return 1;\\n }\\n\\n // 每一个方向的概率都是 1/8\\n double ans = 0;\\n for (int[] dir : DIRS) {\\n ans += dfs(n, k - 1, i + dir[0], j + dir[1]) / 8.0;\\n }\\n\\n return ans;\\n }\\n}\\n
这个拿去跑会卡在第 11 个用例,超时了,所以,要想办法优化一下。
\\n优化也很简单,试想一下,当 k 比较大时,棋盘上的同一个位置在剩余 x 次时有可能会重复的到达,所以,我们需要加一个缓存,这也就是记忆化搜索。
\\n还有一种更简单的方法,观察递归方法的签名:
\\npublic double dfs(int n, int k, int i, int j)
,里面有三个可变的因素:i、j、k,所以,声明的缓存也需要有这三个维度。请看代码:
\\n###java
\\n\\nclass Solution {\\n\\n private static final int[][] DIRS = {{1, 2}, {2, 1}, {-1, 2}, {2, -1}, {1, -2}, {-2, 1}, {-1, -2}, {-2, -1}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n // 记忆化搜索\\n double[][][] memo = new double[n][n][k + 1];\\n return dfs(n, k, row, column, memo);\\n }\\n\\n public double dfs(int n, int k, int i, int j, double[][][] memo) {\\n // 走出边界了,这条路不通,概率为0\\n if (i < 0 || j < 0 || i >= n || j >= n) {\\n return 0;\\n }\\n // k 步走完了还没超出边界,这一步的概率是1,还需要乘上前面的 k 个 1/8\\n if (k == 0) {\\n return 1;\\n }\\n\\n // 缓存中存在,直接返回\\n if (memo[i][j][k] != 0) {\\n return memo[i][j][k];\\n }\\n\\n // 每一个方向的概率都是 1/8\\n double ans = 0;\\n for (int[] dir : DIRS) {\\n ans += dfs(n, k - 1, i + dir[0], j + dir[1], memo) / 8.0;\\n }\\n\\n memo[i][j][k] = ans;\\n\\n return ans;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n * n * k)$,最坏情况为走了所有格子,且走了 k 步,虽然每个格子有可能重复到达,但是有缓存可以降低时间复杂度,而暴力的时间复杂度是指数级别的,为 $O(8^k)$,最坏情况是走了 k 步,所有的情况都还在棋盘内。
\\n- 空间复杂度:$O(n * n * k)$。
\\n运行结果如下:
\\n\\n
方法二、动态规划
\\n其实,有了记忆化搜索就很容易写出来动态规划了,对于动态规划不太熟悉的同学,也可以按照我的方法先写记忆化搜索,再转换成动态规划。
\\n我们直接把记忆化搜索的缓存数组改成 dp 数组,再把递归改成迭代,就成了动态规划。
\\n\\n\\n其实,记忆化搜索也是动态规划的一种写法啦。
\\n所以,我们可以这样定义动态规划:
\\n\\n
\\n- 定义:
\\ndp[i][j][k]
表示走 k 步到坐标 [i, j] 位置的概率。- 转移方程:要走到 [i, j] 的位置,必须要先走了 [i, j] 向外八个方向中的一个位置,而且是走了 k-1 步,所以,
\\ndp[i][j][k]
= $\\\\sum_{1}^8(dp[i+d_x][j+d_y][k-1]/8)$。- 注意:记忆化搜索中 k 是从大到小的,动态规划中 k 需要从 0 开始慢慢增大。
\\n请看代码:
\\n###java
\\n\\nclass Solution {\\n\\n private static final int[][] DIRS = {{1, 2}, {2, 1}, {-1, 2}, {2, -1}, {1, -2}, {-2, 1}, {-1, -2}, {-2, -1}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n // 动态规划\\n double[][][] dp = new double[n][n][k + 1];\\n // k 从 0 开始变大\\n for (int kk = 0; kk <= k; kk++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n // 走 0 步就到 [i, j] 的概率为1\\n if (kk == 0) {\\n dp[i][j][kk] = 1;\\n } else {\\n // 八个方向\\n for (int[] dir : DIRS) {\\n // 上一个坐标,这里用 减号 也是可以的\\n int ni = i + dir[0];\\n int nj = j + dir[1];\\n if (ni >= 0 && nj >= 0 && ni < n && nj < n) {\\n dp[i][j][kk] += dp[ni][nj][kk - 1] / 8.0;\\n }\\n }\\n }\\n }\\n }\\n }\\n // 返回走 k 步到 [row, column] 坐标的概率\\n return dp[row][column][k];\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n * n * k)$。
\\n- 空间复杂度:$O(n * n * k)$。
\\n运行结果如下,比记忆化搜索慢是因为动态规划必须要计算所有的 $n* n$ 个格子走 $0-k$ 步的概率,但是,记忆化搜索不需要,一条路径不满足,直接就返回了:
\\n\\n
最后
\\n如果对你有帮助,请点个赞吧,谢谢^^
\\n欢迎关注公号『彤哥来刷题啦』,也欢迎在 leetcode 上『关注我』或者『点我加V』,我拉你进『刷题群』,每日分享通俗易懂的高质量题解~
\\n","description":"大家好,今天是我坚持写题解的第 197 天! 今天这道题比较简单。\\n\\n首先,观察题目给定的数据范围,并不是很大,所以,我们可以先尝试使用暴力搜索来解决。\\n\\n这里的关键是如何求概率。\\n\\n通过题意可知,向每个方向前进的概率为 1/8,只要没有走出去都可以增加 1/8 成功的概率,然后,再继续前进即可,把途径的 1/8 的概率相乘就是这一条路径的概率,然后求出所有可行路径的概率相加就是最终的概率。\\n\\n其实,还有一种算法,我们可以先求出所有可行的路径数,然后,除以 $8^k$ 得到的结果,也是最终的概率。\\n\\n好了,概率弄清楚了,我们先写第一个暴力版本:\\n\\n###java…","guid":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/tong-ge-lai-shua-ti-la-yi-ti-liang-jie-j-y92k","author":"tong-zhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-17T06:34:08.757Z","media":[{"url":"https://pic.leetcode-cn.com/1645079646-FqyQwm-file_1645079646275","type":"photo","width":328,"height":237,"blurhash":"LID,J%smlTXm2]rrI;t7K5WVrXR+"},{"url":"https://pic.leetcode-cn.com/1645079646-FqyQwm-file_1645079646309","type":"photo","width":557,"height":285,"blurhash":"LRR3f=xuxa-p~RWUWEj]Iwt6j?j["},{"url":"https://pic.leetcode-cn.com/1645079646-LWrCAI-file_1645079646100","type":"photo","width":557,"height":277,"blurhash":"LSQ]{Fxuxa%M~RWVa#j]ETs:j?j["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单 DP 运用题","url":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/gong-shui-san-xie-jian-dan-qu-jian-dp-yu-st8l","content":"线性 DP
\\n定义 $f[i][j][p]$ 为从位置 $(i, j)$ 出发,使用步数不超过 $p$ 步,最后仍在棋盘内的概率。
\\n不失一般性考虑 $f[i][j][p]$ 该如何转移,根据题意,移动规则为「八连通」,对下一步的落点 $(nx, ny)$ 进行分情况讨论即可:
\\n\\n
\\n- 由于计算的是仍在棋盘内的概率,因此对于 $(nx, ny)$ 在棋盘外的情况,无须考虑;
\\n- 若下一步的落点 $(nx, ny)$ 在棋盘内,其剩余可用步数为 $p - 1$,则最后仍在棋盘的概率为 $f[nx][ny][p - 1]$,则落点 $(nx, ny)$ 对 $f[i][j][p]$ 的贡献为 $f[nx][ny][p - 1] \\\\times \\\\frac{1}{8}$,其中 $\\\\frac{1}{8}$ 为事件「从 $(i, j)$ 走到 $(nx, ny)$」的概率(八连通移动等概率发生),该事件与「到达 $(nx, ny)$ 后进行后续移动并留在棋盘」为相互独立事件。
\\n最终的 $f[i][j][p]$ 为「八连通」落点的概率之和,即有:
\\n$$
\\n
\\nf[i][j][p] = \\\\sum {f[nx][ny][p - 1] \\\\times \\\\frac{1}{8}}
\\n$$代码:
\\n###Java
\\n\\nclass Solution {\\n int[][] dirs = new int[][]{{-1,-2},{-1,2},{1,-2},{1,2},{-2,1},{-2,-1},{2,1},{2,-1}};\\n public double knightProbability(int n, int k, int row, int column) {\\n double[][][] f = new double[n][n][k + 1];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n f[i][j][0] = 1;\\n }\\n }\\n for (int p = 1; p <= k; p++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n for (int[] d : dirs) {\\n int nx = i + d[0], ny = j + d[1];\\n if (nx < 0 || nx >= n || ny < 0 || ny >= n) continue;\\n f[i][j][p] += f[nx][ny][p - 1] / 8;\\n }\\n }\\n }\\n }\\n return f[row][column][k];\\n }\\n}\\n
\\n
\\n- 时间复杂度:令某个位置可联通的格子数量 $C = 8$,复杂度为 $O(n^2 * k * C)$
\\n- 空间复杂度:$O(n^2 * k)$
\\n
\\n其他「线性 DP」相关内容
\\n题太简单?考虑加练如下「线性 DP」内容 🍭🍭🍭
\\n注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"线性 DP 定义 $f[i][j][p]$ 为从位置 $(i, j)$ 出发,使用步数不超过 $p$ 步,最后仍在棋盘内的概率。\\n\\n不失一般性考虑 $f[i][j][p]$ 该如何转移,根据题意,移动规则为「八连通」,对下一步的落点 $(nx, ny)$ 进行分情况讨论即可:\\n\\n由于计算的是仍在棋盘内的概率,因此对于 $(nx, ny)$ 在棋盘外的情况,无须考虑;\\n若下一步的落点 $(nx, ny)$ 在棋盘内,其剩余可用步数为 $p - 1$,则最后仍在棋盘的概率为 $f[nx][ny][p - 1]$,则落点 $(nx, ny)$ 对 $f[i][j…","guid":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/gong-shui-san-xie-jian-dan-qu-jian-dp-yu-st8l","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-16T22:52:38.785Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++贪心,时间击败97.44%。易于理解。","url":"https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row//solution/ctan-xin-shi-jian-ji-bai-9744-yi-yu-li-j-0vg4","content":"解题思路
\\n翻开上面和下面的第一张牌。考虑这四种贪心策略:
\\n
\\n1.将上面的牌全部变成上面的第一张牌对应的点数;
\\n2.将下面的牌全部变成下面的第一张牌对应的点数;
\\n3.将上面的牌全部变成下面的第一张牌对应的点数;
\\n4.将下面的牌全部变成上面的第一张牌对应的点数。
\\n只要将这四种情况全部考虑到,那么就能求出最终结果。前两种情况是比较好理解的。那么后两种情况又是为什么呢?
\\n
\\n考虑下面两种序列
\\ntops:[1,2,2,2,2,2,2,2]
\\nbottoms:[2,1,1,1,1,1,1,1]
\\n对于这种情况无论是将上面变成1还是将下面变成2都是7次,但是如果将上面第一张就开始交换变成2,那么只需一次就可以完成。同理对于第四种情况,与上面一样。至此,即可开始编写代码,初始时全部为零,后面如果某一种贪心策略不可行,就让他为INT_MAX。这样不会影响我们后面的判断。
\\n代码
\\n###cpp
\\n\\n","description":"解题思路 翻开上面和下面的第一张牌。考虑这四种贪心策略:\\n 1.将上面的牌全部变成上面的第一张牌对应的点数;\\n 2.将下面的牌全部变成下面的第一张牌对应的点数;\\n 3.将上面的牌全部变成下面的第一张牌对应的点数;\\n 4.将下面的牌全部变成上面的第一张牌对应的点数。\\n 只要将这四种情况全部考虑到,那么就能求出最终结果。\\n\\n前两种情况是比较好理解的。那么后两种情况又是为什么呢?\\n 考虑下面两种序列\\n tops:[1,2,2,2,2,2,2,2]\\n bottoms:[2,1,1,1,1,1,1,1]\\n 对于这种情况无论是将上面变成1还是将下面变成2都是7次…","guid":"https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row//solution/ctan-xin-shi-jian-ji-bai-9744-yi-yu-li-j-0vg4","author":"xxc13004","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-16T05:59:55.011Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【彤哥来刷题啦】二分查找!","url":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/tong-ge-lai-shua-ti-la-er-fen-cha-zhao-b-x8dd","content":"class Solution {\\npublic:\\n int minDominoRotations(vector<int>& tops, vector<int>& bottoms) {\\n int n = tops.size();\\n int a = 0, b = 0, c = 0, d = 0;\\n for(int i = 1; i < n; ++i){\\n if(tops[i] == tops[0]) continue;\\n else if(bottoms[i] == tops[0]) ++a;\\n else{\\n a = INT_MAX;\\n break;\\n }\\n }\\n for(int i = 1; i < n; ++i){\\n if(bottoms[i] == bottoms[0]) continue;\\n else if(tops[i] == bottoms[0]) ++b;\\n else{\\n b = INT_MAX;\\n break;\\n }\\n }\\n for(int i = 0; i < n; ++i){\\n if(tops[i] == bottoms[0]) continue;\\n else if(bottoms[i] == bottoms[0]) ++c;\\n else{\\n c = INT_MAX;\\n break;\\n }\\n }\\n for(int i = 0; i < n; ++i){\\n if(bottoms[i] == tops[0]) continue;\\n else if(tops[i] == tops[0]) ++d;\\n else{\\n d = INT_MAX;\\n break;\\n }\\n }\\n int ans = min(min(a, b), min(c, d));\\n return ans == INT_MAX ? -1 : ans;\\n }\\n};\\n
大家好,今天是我坚持写题解的第 194 天!
\\n祝大家情人节快乐~
\\n\\n
方法、二分查找
\\n今天这道题要求我们的时间复杂度为 $O(logn)$,乍一看似乎是使用二分查找。
\\n但是,纯粹地使用二分查找这里并不知道目标值是多少,那么,我们应该如何来二分呢?
\\n观察题目给定的示例1,
\\nnums = [1,1,2,3,3,4,4,8,8]
,题目说明了只有一个数只出现一次,那么,如果我们把这个数补齐到两个,比如,示例1补齐之后,可以得到nums = [1,1,2,2,3,3,4,4,8,8]
,可以发现,每一对 [偶数下标, 奇数下标] 的数肯定是相等的,因此,我们可以利用这条特性来进行二分查找。如果 mid 所在的 [偶数下标, 奇数下标] 的值相等,说明前面半段没有缺失的数,那么,缺失的数肯定在后半段,反之,则在前半段。
\\n这里要进行一些判断:
\\n\\n
\\n- 如果 mid 本身是偶数,那么 mid + 1 就是奇数;
\\n- 如果 mid 本身是奇数,那么 mid - 1 就是偶数。
\\n这两种情况,我们可以使用异或来进行统一,因为 偶数异或1 等于 加1,奇数异或1 等于 减1。
\\n下面分别是不使用异或和使用异或的代码:
\\n不使用异或:
\\n###java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n // 二分查找\\n int left = 0, right = nums.length - 1;\\n while (left < right) {\\n int mid = (left + right) / 2;\\n if (mid % 2 == 0) {\\n if (nums[mid] == nums[mid + 1]) {\\n left = mid + 1;\\n } else {\\n right = mid;\\n }\\n } else {\\n if (nums[mid] == nums[mid - 1]) {\\n left = mid + 1;\\n } else {\\n right = mid;\\n }\\n }\\n }\\n return nums[right];\\n }\\n}\\n
使用异或:
\\n###java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n // 二分查找\\n int left = 0, right = nums.length - 1;\\n while (left < right) {\\n int mid = (left + right) / 2;\\n if (nums[mid] == nums[mid ^ 1]) {\\n left = mid + 1;\\n } else {\\n right = mid;\\n }\\n }\\n return nums[right];\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(logn)$。
\\n- 空间复杂度:$O(1)$。
\\n运行结果如下:
\\n\\n
最后
\\n如果对你有帮助,请点个赞吧,谢谢^^
\\n欢迎关注公号『彤哥来刷题啦』,也欢迎在 leetcode 上『关注我』或者『点我加V』,我拉你进『刷题群』,每日分享通俗易懂的高质量题解~
\\n","description":"大家好,今天是我坚持写题解的第 194 天! 祝大家情人节快乐~\\n\\n今天这道题要求我们的时间复杂度为 $O(logn)$,乍一看似乎是使用二分查找。\\n\\n但是,纯粹地使用二分查找这里并不知道目标值是多少,那么,我们应该如何来二分呢?\\n\\n观察题目给定的示例1,nums = [1,1,2,3,3,4,4,8,8],题目说明了只有一个数只出现一次,那么,如果我们把这个数补齐到两个,比如,示例1补齐之后,可以得到 nums = [1,1,2,2,3,3,4,4,8,8],可以发现,每一对 [偶数下标, 奇数下标] 的数肯定是相等的,因此…","guid":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/tong-ge-lai-shua-ti-la-er-fen-cha-zhao-b-x8dd","author":"tong-zhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-14T03:43:58.215Z","media":[{"url":"https://pic.leetcode-cn.com/1644810237-dinPCd-file_1644810236558","type":"photo","width":510,"height":340,"blurhash":"LHHxTm4nW??FF%OaMyxscbM_Rjx]"},{"url":"https://pic.leetcode-cn.com/1644810237-aFgrlW-file_1644810236131","type":"photo","width":555,"height":275,"blurhash":"LRR3f=xut7%M~RWUWEj]ESs:j@oL"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"骑士在棋盘上的概率","url":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/qi-shi-zai-qi-pan-shang-de-gai-lu-by-lee-2qhk","content":"方法一:动态规划
\\n思路
\\n一个骑士有 $8$ 种可能的走法,骑士会从中以等概率随机选择一种。部分走法可能会让骑士离开棋盘,另外的走法则会让骑士移动到棋盘的其他位置,并且剩余的移动次数会减少 $1$。
\\n定义 $\\\\textit{dp}[\\\\textit{step}][i][j]$ 表示骑士从棋盘上的点 $(i, j)$ 出发,走了 $\\\\textit{step}$ 步时仍然留在棋盘上的概率。特别地,当点 $(i, j)$ 不在棋盘上时,$\\\\textit{dp}[\\\\textit{step}][i][j] = 0$;当点 $(i, j)$ 在棋盘上且 $\\\\textit{step} = 0$ 时,$\\\\textit{dp}[\\\\textit{step}][i][j] = 1$。对于其他情况,$\\\\textit{dp}[\\\\textit{step}][i][j] = \\\\dfrac{1}{8} \\\\times \\\\sum\\\\limits_{\\\\textit{di}, \\\\textit{dj}} \\\\textit{dp}[\\\\textit{step}-1][i+\\\\textit{di}][j+\\\\textit{dj}]$。其中 $(\\\\textit{di}, \\\\textit{dj})$ 表示走法对坐标的偏移量,具体为 $(-2, -1),(-2,1),(2,-1),(2,1),(-1,-2),(-1,2),(1,-2),(1,2)$ 共 $8$ 种。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def knightProbability(self, n: int, k: int, row: int, column: int) -> float:\\n dp = [[[0] * n for _ in range(n)] for _ in range(k + 1)]\\n for step in range(k + 1):\\n for i in range(n):\\n for j in range(n):\\n if step == 0:\\n dp[step][i][j] = 1\\n else:\\n for di, dj in ((-2, -1), (-2, 1), (2, -1), (2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2)):\\n ni, nj = i + di, j + dj\\n if 0 <= ni < n and 0 <= nj < n:\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8\\n return dp[k][row][column]\\n
###Java
\\n\\nclass Solution {\\n static int[][] dirs = {{-2, -1}, {-2, 1}, {2, -1}, {2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}};\\n\\n public double knightProbability(int n, int k, int row, int column) {\\n double[][][] dp = new double[k + 1][n][n];\\n for (int step = 0; step <= k; step++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n if (step == 0) {\\n dp[step][i][j] = 1;\\n } else {\\n for (int[] dir : dirs) {\\n int ni = i + dir[0], nj = j + dir[1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n static int[][] dirs = {new int[]{-2, -1}, new int[]{-2, 1}, new int[]{2, -1}, new int[]{2, 1}, new int[]{-1, -2}, new int[]{-1, 2}, new int[]{1, -2}, new int[]{1, 2}};\\n\\n public double KnightProbability(int n, int k, int row, int column) {\\n double[,,] dp = new double[k + 1, n, n];\\n for (int step = 0; step <= k; step++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n if (step == 0) {\\n dp[step, i, j] = 1;\\n } else {\\n foreach (int[] dir in dirs) {\\n int ni = i + dir[0], nj = j + dir[1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step, i, j] += dp[step - 1, ni, nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k, row, column];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> dirs = {{-2, -1}, {-2, 1}, {2, -1}, {2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}};\\n\\n double knightProbability(int n, int k, int row, int column) {\\n vector<vector<vector<double>>> dp(k + 1, vector<vector<double>>(n, vector<double>(n)));\\n for (int step = 0; step <= k; step++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n if (step == 0) {\\n dp[step][i][j] = 1;\\n } else {\\n for (auto & dir : dirs) {\\n int ni = i + dir[0], nj = j + dir[1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column];\\n }\\n};\\n
###C
\\n\\nstatic int dirs[8][2] = {{-2, -1}, {-2, 1}, {2, -1}, {2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}};\\n\\ndouble knightProbability(int n, int k, int row, int column){\\n double dp[200][30][30];\\n memset(dp, 0, sizeof(dp));\\n for (int step = 0; step <= k; step++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n if (step == 0) {\\n dp[step][i][j] = 1.0;\\n } else {\\n for (int k = 0; k < 8; k++) {\\n int ni = i + dirs[k][0], nj = j + dirs[k][1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column];\\n}\\n
###Go
\\n\\nvar dirs = []struct{ i, j int }{{-2, -1}, {-2, 1}, {2, -1}, {2, 1}, {-1, -2}, {-1, 2}, {1, -2}, {1, 2}}\\n\\nfunc knightProbability(n, k, row, column int) float64 {\\n dp := make([][][]float64, k+1)\\n for step := range dp {\\n dp[step] = make([][]float64, n)\\n for i := 0; i < n; i++ {\\n dp[step][i] = make([]float64, n)\\n for j := 0; j < n; j++ {\\n if step == 0 {\\n dp[step][i][j] = 1\\n } else {\\n for _, d := range dirs {\\n if x, y := i+d.i, j+d.j; 0 <= x && x < n && 0 <= y && y < n {\\n dp[step][i][j] += dp[step-1][x][y] / 8\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column]\\n}\\n
###JavaScript
\\n\\nconst dirs = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2]];\\nvar knightProbability = function(n, k, row, column) {\\n const dp = new Array(k + 1).fill(0).map(() => new Array(n).fill(0).map(() => new Array(n).fill(0)));\\n for (let step = 0; step <= k; step++) {\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j < n; j++) {\\n if (step === 0) {\\n dp[step][i][j] = 1;\\n } else {\\n for (const dir of dirs) {\\n const ni = i + dir[0], nj = j + dir[1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column];\\n};\\n
###TypeScript
\\n\\nconst dirs: number[][] = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2]];\\n\\nfunction knightProbability(n: number, k: number, row: number, column: number): number {\\n const dp: number[][][] = new Array(k + 1).fill(0).map(() => new Array(n).fill(0).map(() => new Array(n).fill(0)));\\n for (let step = 0; step <= k; step++) {\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j < n; j++) {\\n if (step === 0) {\\n dp[step][i][j] = 1;\\n } else {\\n for (const dir of dirs) {\\n const ni = i + dir[0], nj = j + dir[1];\\n if (ni >= 0 && ni < n && nj >= 0 && nj < n) {\\n dp[step][i][j] += dp[step - 1][ni][nj] / 8;\\n }\\n }\\n }\\n }\\n }\\n }\\n return dp[k][row][column];\\n};\\n
###Rust
\\n\\nconst DIRS: [[i32; 2]; 8] = [[-2, -1], [-2, 1], [2, -1], [2, 1], [-1, -2], [-1, 2], [1, -2], [1, 2]];\\n\\nimpl Solution {\\n pub fn knight_probability(n: i32, k: i32, row: i32, column: i32) -> f64 {\\n let n = n as usize;\\n let k = k as usize;\\n let mut dp = vec![vec![vec![0.0; n]; n]; k + 1];\\n\\n for step in 0..= k {\\n for i in 0..n {\\n for j in 0..n {\\n if step == 0 {\\n dp[step][i][j] = 1.0;\\n } else {\\n for dir in &DIRS {\\n let ni = i as i32 + dir[0];\\n let nj = j as i32 + dir[1];\\n if ni >= 0 && ni < n as i32 && nj >= 0 && nj < n as i32 {\\n dp[step][i][j] += dp[step - 1][ni as usize][nj as usize] / 8.0;\\n }\\n }\\n }\\n }\\n }\\n }\\n\\n dp[k][row as usize][column as usize]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:动态规划 思路\\n\\n一个骑士有 $8$ 种可能的走法,骑士会从中以等概率随机选择一种。部分走法可能会让骑士离开棋盘,另外的走法则会让骑士移动到棋盘的其他位置,并且剩余的移动次数会减少 $1$。\\n\\n定义 $\\\\textit{dp}[\\\\textit{step}][i][j]$ 表示骑士从棋盘上的点 $(i, j)$ 出发,走了 $\\\\textit{step}$ 步时仍然留在棋盘上的概率。特别地,当点 $(i, j)$ 不在棋盘上时,$\\\\textit{dp}[\\\\textit{step}][i][j] = 0$;当点 $(i, j)$ 在棋盘上且 $\\\\textit…","guid":"https://leetcode.cn/problems/knight-probability-in-chessboard//solution/qi-shi-zai-qi-pan-shang-de-gai-lu-by-lee-2qhk","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-14T02:21:09.074Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】二段性分析运用题","url":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/gong-shui-san-xie-er-duan-xing-fen-xi-yu-17nv","content":"- \\n
\\n时间复杂度:$O(k \\\\times n ^ 2)$。状态数一共有 $O(k \\\\times n ^ 2)$,每次转移需要考虑 $8$ 种可能的走法,消耗 $O(1)$ 的时间复杂度,总体的时间复杂度是 $O(k \\\\times n ^ 2)$。
\\n- \\n
\\n空间复杂度:$O(k \\\\times n ^ 2)$。状态数一共有 $O(k \\\\times n ^ 2)$,用一个数组来保存。注意到每一步的状态只由前一步决定,空间复杂度可以优化到 $O(n ^ 2)$。
\\n遍历
\\n数据范围为 $10^5$,最简单的方法是以「步长为 $2$」的方式进行从前往后的遍历,找到第一个不符合「与后一个数相等」条件的值即是答案。
\\n或是利用单个元素只有一个(其余成对出现),从头到尾异或一遍,最终结果为单一元素值。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int n = nums.length;\\n for (int i = 0; i < n - 1; i += 2) {\\n if (nums[i] != nums[i + 1]) return nums[i];\\n }\\n return nums[n - 1];\\n }\\n}\\n
###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int ans = 0;\\n for (int i : nums) ans ^= i;\\n return ans;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(1)$
\\n
\\n二分
\\n这道题的「二段性」分析需要一点点「脑筋急转弯」。
\\n由于给定数组有序 且 常规元素总是两两出现,因此如果不考虑“特殊”的单一元素的话,我们有结论:成对元素中的第一个所对应的下标必然是偶数,成对元素中的第二个所对应的下标必然是奇数。
\\n然后再考虑存在单一元素的情况,假如单一元素所在的下标为 $x$,那么下标 $x$ 之前(左边)的位置仍满足上述结论,而下标 $x$ 之后(右边)的位置由于 $x$ 的插入,导致结论翻转。
\\n存在这样的二段性,指导我们根据当前二分点 $mid$ 的奇偶性进行分情况讨论:
\\n\\n
\\n- \\n
\\n$mid$ 为偶数下标:根据上述结论,正常情况下偶数下标的值会与下一值相同,因此如果满足该条件,可以确保 $mid$ 之前并没有插入单一元素。正常情况下,此时应该更新 $l = mid$,否则应当让 $r = mid - 1$,但需要注意这样的更新逻辑,会因为更新 $r$ 时否决 $mid$ 而错过答案,我们可以将否决 $mid$ 的动作放到更新 $l$ 的一侧,即需要将更新逻辑修改为 $l = mid + 1$ 和 $r = mid$ ;
\\n- \\n
\\n$mid$ 为奇数下标:同理,根据上述结论,正常情况下奇数下标的值会与上一值相同,因此如果满足该条件,可以确保 $mid$ 之前并没有插入单一元素,相应的更新 $l$ 和 $r$。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int n = nums.length;\\n int l = 0, r = n - 1;\\n while (l < r) {\\n int mid = l + r >> 1;\\n if (mid % 2 == 0) {\\n if (mid + 1 < n && nums[mid] == nums[mid + 1]) l = mid + 1;\\n else r = mid;\\n } else {\\n if (mid - 1 >= 0 && nums[mid - 1] == nums[mid]) l = mid + 1;\\n else r = mid;\\n }\\n }\\n return nums[r];\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(\\\\log{n})$
\\n- 空间复杂度:$O(1)$
\\n
\\n二分(异或技巧)
\\n不难发现,上述解法对奇偶下标的分情况讨论仅在于当前值 $nums[i]$ 是与 $nums[i + 1]$(当 $i$ 为偶数时)还是 $nums[i - 1]$(当 $i$ 为奇数时)进行比较。
\\n而这样的「连续段 偶奇 两两成对」的组合,适合使用「异或」来找相应的成组对象。
\\n实际上,该技巧广泛地应用在图论存图中:使用邻接表(链式向前星)存无向图时,直接访问「当前边 $e$」所对应的「反向边 $e\'$」。这也是为什么在「链式向前星」中我们只需要使用「单链表」并设定 $idx$ 从 $0$ 开始进行存图即可:能够满足遍历所有出边,同时如果有访问相应反向边的需求,只需要通过
\\ne[i^1]
访问。对这种存图方式不熟悉的同学,可以看前置 🧀:涵盖所有的「存图方式」与「最短路算法(详尽注释)」。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int n = nums.length;\\n int l = 0, r = n - 1;\\n while (l < r) {\\n int mid = l + r >> 1;\\n if (nums[mid] == nums[mid ^ 1]) l = mid + 1;\\n else r = mid;\\n }\\n return nums[r];\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(\\\\log{n})$
\\n- 空间复杂度:$O(1)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"遍历 数据范围为 $10^5$,最简单的方法是以「步长为 $2$」的方式进行从前往后的遍历,找到第一个不符合「与后一个数相等」条件的值即是答案。\\n\\n或是利用单个元素只有一个(其余成对出现),从头到尾异或一遍,最终结果为单一元素值。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int n = nums.length;\\n for (int i = 0; i < n - 1; i += 2) {\\n if…","guid":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/gong-shui-san-xie-er-duan-xing-fen-xi-yu-17nv","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-13T23:17:38.863Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"有序数组中的单一元素","url":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/you-xu-shu-zu-zhong-de-dan-yi-yuan-su-by-y8gh","content":"方法一:全数组的二分查找
\\n思路和算法
\\n假设只出现一次的元素位于下标 $x$,由于其余每个元素都出现两次,因此下标 $x$ 的左边和右边都有偶数个元素,数组的长度是奇数。
\\n由于数组是有序的,因此数组中相同的元素一定相邻。对于下标 $x$ 左边的下标 $y$,如果 $\\\\textit{nums}[y] = \\\\textit{nums}[y + 1]$,则 $y$ 一定是偶数;对于下标 $x$ 右边的下标 $z$,如果 $\\\\textit{nums}[z] = \\\\textit{nums}[z + 1]$,则 $z$ 一定是奇数。由于下标 $x$ 是相同元素的开始下标的奇偶性的分界,因此可以使用二分查找的方法寻找下标 $x$。
\\n初始时,二分查找的左边界是 $0$,右边界是数组的最大下标。每次取左右边界的平均值 $\\\\textit{mid}$ 作为待判断的下标,根据 $\\\\textit{mid}$ 的奇偶性决定和左边或右边的相邻元素比较:
\\n\\n
\\n- \\n
\\n如果 $\\\\textit{mid}$ 是偶数,则比较 $\\\\textit{nums}[\\\\textit{mid}]$ 和 $\\\\textit{nums}[\\\\textit{mid} + 1]$ 是否相等;
\\n- \\n
\\n如果 $\\\\textit{mid}$ 是奇数,则比较 $\\\\textit{nums}[\\\\textit{mid} - 1]$ 和 $\\\\textit{nums}[\\\\textit{mid}]$ 是否相等。
\\n如果上述比较相邻元素的结果是相等,则 $\\\\textit{mid} < x$,调整左边界,否则 $\\\\textit{mid} \\\\ge x$,调整右边界。调整边界之后继续二分查找,直到确定下标 $x$ 的值。
\\n得到下标 $x$ 的值之后,$\\\\textit{nums}[x]$ 即为只出现一次的元素。
\\n细节
\\n利用按位异或的性质,可以得到 $\\\\textit{mid}$ 和相邻的数之间的如下关系,其中 $\\\\oplus$ 是按位异或运算符:
\\n\\n
\\n- \\n
\\n当 $\\\\textit{mid}$ 是偶数时,$\\\\textit{mid} + 1 = \\\\textit{mid} \\\\oplus 1$;
\\n- \\n
\\n当 $\\\\textit{mid}$ 是奇数时,$\\\\textit{mid} - 1 = \\\\textit{mid} \\\\oplus 1$。
\\n因此在二分查找的过程中,不需要判断 $\\\\textit{mid}$ 的奇偶性,$\\\\textit{mid}$ 和 $\\\\textit{mid} \\\\oplus 1$ 即为每次需要比较元素的两个下标。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int low = 0, high = nums.length - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n if (nums[mid] == nums[mid ^ 1]) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int SingleNonDuplicate(int[] nums) {\\n int low = 0, high = nums.Length - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n if (nums[mid] == nums[mid ^ 1]) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int singleNonDuplicate(vector<int>& nums) {\\n int low = 0, high = nums.size() - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n if (nums[mid] == nums[mid ^ 1]) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n};\\n
###C
\\n\\nint singleNonDuplicate(int* nums, int numsSize) {\\n int low = 0, high = numsSize - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n if (nums[mid] == nums[mid ^ 1]) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n}\\n
###go
\\n\\nfunc singleNonDuplicate(nums []int) int {\\n low, high := 0, len(nums)-1\\n for low < high {\\n mid := low + (high-low)/2\\n if nums[mid] == nums[mid^1] {\\n low = mid + 1\\n } else {\\n high = mid\\n }\\n }\\n return nums[low]\\n}\\n
###Python
\\n\\nclass Solution:\\n def singleNonDuplicate(self, nums: List[int]) -> int:\\n low, high = 0, len(nums) - 1\\n while low < high:\\n mid = (low + high) // 2\\n if nums[mid] == nums[mid ^ 1]:\\n low = mid + 1\\n else:\\n high = mid\\n return nums[low]\\n
###JavaScript
\\n\\nvar singleNonDuplicate = function(nums) {\\n let low = 0, high = nums.length - 1;\\n while (low < high) {\\n const mid = Math.floor((high - low) / 2) + low;\\n if (nums[mid] === nums[mid ^ 1]) {\\n low = mid + 1;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n};\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(\\\\log n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要在全数组范围内二分查找,二分查找的时间复杂度是 $O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:偶数下标的二分查找
\\n思路和算法
\\n由于只出现一次的元素所在下标 $x$ 的左边有偶数个元素,因此下标 $x$ 一定是偶数,可以在偶数下标范围内二分查找。二分查找的目标是找到满足 $\\\\textit{nums}[x] \\\\ne \\\\textit{nums}[x + 1]$ 的最小的偶数下标 $x$,则下标 $x$ 处的元素是只出现一次的元素。
\\n初始时,二分查找的左边界是 $0$,右边界是数组的最大偶数下标,由于数组的长度是奇数,因此数组的最大偶数下标等于数组的长度减 $1$。每次取左右边界的平均值 $\\\\textit{mid}$ 作为待判断的下标,如果 $\\\\textit{mid}$ 是奇数则将 $\\\\textit{mid}$ 减 $1$,确保 $\\\\textit{mid}$ 是偶数,比较 $\\\\textit{nums}[\\\\textit{mid}]$ 和 $\\\\textit{nums}[\\\\textit{mid} + 1]$ 是否相等,如果相等则 $\\\\textit{mid} < x$,调整左边界,否则 $\\\\textit{mid} \\\\ge x$,调整右边界。调整边界之后继续二分查找,直到确定下标 $x$ 的值。
\\n得到下标 $x$ 的值之后,$\\\\textit{nums}[x]$ 即为只出现一次的元素。
\\n细节
\\n考虑 $\\\\textit{mid}$ 和 $1$ 按位与运算的结果,其中 $&$ 是按位与运算符:
\\n\\n
\\n- \\n
\\n当 $\\\\textit{mid}$ 是偶数时,$\\\\textit{mid}~&~1 = 0$;
\\n- \\n
\\n当 $\\\\textit{mid}$ 是奇数时,$\\\\textit{mid}~&~1 = 1$。
\\n因此在得到 $\\\\textit{mid}$ 的值之后,将 $\\\\textit{mid}$ 的值减去 $\\\\textit{mid}~&~1$,即可确保 $\\\\textit{mid}$ 是偶数,如果原来的 $\\\\textit{mid}$ 是偶数则值不变,如果原来的 $\\\\textit{mid}$ 是奇数则值减 $1$。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public int singleNonDuplicate(int[] nums) {\\n int low = 0, high = nums.length - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n mid -= mid & 1;\\n if (nums[mid] == nums[mid + 1]) {\\n low = mid + 2;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int SingleNonDuplicate(int[] nums) {\\n int low = 0, high = nums.Length - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n mid -= mid & 1;\\n if (nums[mid] == nums[mid + 1]) {\\n low = mid + 2;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int singleNonDuplicate(vector<int>& nums) {\\n int low = 0, high = nums.size() - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n mid -= mid & 1;\\n if (nums[mid] == nums[mid + 1]) {\\n low = mid + 2;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n }\\n};\\n
###C
\\n\\nint singleNonDuplicate(int* nums, int numsSize) {\\n int low = 0, high = numsSize - 1;\\n while (low < high) {\\n int mid = (high - low) / 2 + low;\\n mid -= mid & 1;\\n if (nums[mid] == nums[mid + 1]) {\\n low = mid + 2;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n}\\n
###go
\\n\\nfunc singleNonDuplicate(nums []int) int {\\n low, high := 0, len(nums)-1\\n for low < high {\\n mid := low + (high-low)/2\\n mid -= mid & 1\\n if nums[mid] == nums[mid+1] {\\n low = mid + 2\\n } else {\\n high = mid\\n }\\n }\\n return nums[low]\\n}\\n
###Python
\\n\\nclass Solution:\\n def singleNonDuplicate(self, nums: List[int]) -> int:\\n low, high = 0, len(nums) - 1\\n while low < high:\\n mid = (low + high) // 2\\n mid -= mid & 1\\n if nums[mid] == nums[mid + 1]:\\n low = mid + 2\\n else:\\n high = mid\\n return nums[low]\\n
###JavaScript
\\n\\nvar singleNonDuplicate = function(nums) {\\n let low = 0, high = nums.length - 1;\\n while (low < high) {\\n let mid = Math.floor((high - low) / 2) + low;\\n mid -= mid & 1;\\n if (nums[mid] === nums[mid + 1]) {\\n low = mid + 2;\\n } else {\\n high = mid;\\n }\\n }\\n return nums[low];\\n};\\n
复杂度分析
\\n\\n
\\n","description":"方法一:全数组的二分查找 思路和算法\\n\\n假设只出现一次的元素位于下标 $x$,由于其余每个元素都出现两次,因此下标 $x$ 的左边和右边都有偶数个元素,数组的长度是奇数。\\n\\n由于数组是有序的,因此数组中相同的元素一定相邻。对于下标 $x$ 左边的下标 $y$,如果 $\\\\textit{nums}[y] = \\\\textit{nums}[y + 1]$,则 $y$ 一定是偶数;对于下标 $x$ 右边的下标 $z$,如果 $\\\\textit{nums}[z] = \\\\textit{nums}[z + 1]$,则 $z$ 一定是奇数。由于下标 $x…","guid":"https://leetcode.cn/problems/single-element-in-a-sorted-array//solution/you-xu-shu-zu-zhong-de-dan-yi-yuan-su-by-y8gh","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-09T02:15:28.984Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"暴力破解的思路详解,附java配方","url":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/bao-li-po-jie-de-si-lu-xiang-jie-fu-java-t437","content":"- \\n
\\n时间复杂度:$O(\\\\log n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。需要在偶数下标范围内二分查找,二分查找的时间复杂度是 $O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n这题真的很麻烦,就是硬做。
\\n
\\n找到所有的可能的移动组合,然后检查是不是有效的组合。审题
\\n三种棋子的鄙视链:
\\n\\n
\\n- 后:无敌,8个方向移动
\\n- 车:水平移动,只有4个行动方向
\\n- 象:对角线移动,4个行动方向
\\n整体策略:
\\n
\\n我们需要记录下每个组合中每个棋子的中转位置和最终位置。
\\n假设一共有n个棋子,我们用一个n*8*8的三维数组来存储每一个棋子的情况。
\\n按照棋子的顺序,从左至右,我们依次遍历棋子的移动情况。
\\n当第一个棋子在完成移动之后,我们递归调用遍历方法,考虑第二个棋子的移动,直到考虑完所有棋子的情况。🌰举个例子:
\\n
\\n假设一共有2个棋子,我们用一个2*8*8的三维数组来存储每一个棋子的情况。
\\n按照棋子的顺序,从左至右,我们依次遍历棋子的移动情况。
\\n当第一个棋子在完成移动之后,我们考虑第二个棋子的移动。
\\n在考虑第二个棋子的移动时,我们需要结合第一个棋子的情况进行判断。具体方法如下。单个棋子移动策略:
\\n
\\n每个棋子准备移动到下一位置时,
\\n首先,需要考虑下一位置是否在棋盘内,
\\n其次,移动后,它有两个选择:\\n
\\n- 停在当前位置,不再移动
\\n
\\n只有当以下条件,对于所有之前棋子都满足时,棋子才可以在这个位置上停留:
\\n• 没有其他棋子在这次移动组合中停留在这个位置上
\\n• 没有其他棋子需要在这一时间后经过这个位置- 继续移动
\\n
\\n若选择继续移动,则需要考虑下一次移动的位置的以下几点:
\\n• 是否有其他棋子同时经过
\\n• 是否有其他棋子早于或等于当前时间停留在这个位置上
\\n如果有以上一点发生,那么当前这个组合就不是一个有效的组合特别地,
\\n
\\n从上面的描述可以看出,中转位置和最终位置,对于后续棋子移动的影响是不一样的。最终位置会阻碍下一棋子对于这一方向的后续所有行动。所以,我们需要在棋盘上区别中转位置和最终位置。这里用到的一个技巧就是,对于最终位置,我们用负数表示,而中转位置能用时间t正数表示。话不多说,来上代码吧
\\n\\nclass Solution {\\n // 所有可能的移动方向,“后”可以向全部方向,“车”可以移动前四个方向,“象”可以移动后四个方向\\n int[][] dirs = new int[][] {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};\\n int[][][] board;\\n\\n public int countCombinations(String[] pieces, int[][] positions) {\\n board = new int[positions.length][8][8];\\n return count_recursive(pieces, positions, 0); // 从第一个棋子的初始位置开始考虑\\n }\\n\\n // 考虑第i个棋子,在前i-1个棋子位置已经确定的情况下,可以移动的组合数量\\n public int count_recursive(String[] pieces, int[][] positions, int i) {\\n if (i >= pieces.length)\\n return 1; // 如果后面没有其它棋子了,那么当前组合的数量只有1\\n\\n int x = positions[i][0] - 1, y = positions[i][1] - 1, res = 0; // 起始位置开始考虑\\n for (int d = 0; d < 8; d++){ // 遍历各个方向\\n if ((d < 4 && pieces[i].equals(\\"bishop\\")) || (d >= 4 && pieces[i].equals(\\"rook\\")))\\n continue;\\n\\n boolean blocked = false; \\n for (int step = res == 0 ? 1 : 2; !blocked; step++) { // 因为起始位置在8个方向中只能考虑一次,所以我们用res作为旗帜,只有第一次移动时会考虑停留在初始位置\\n int nx = x + (step-1) * dirs[d][0]; // 新坐标\\n int ny = y + (step-1) * dirs[d][1];\\n\\n if (nx < 0 || nx >= 8 || ny < 0 || ny >= 8) // 棋盘外了,无效移动,放弃这个方向\\n break;\\n\\n boolean canStop = true;\\n for (int j = 0; j < i; j++) { // 检查前i-1个棋子的在当前位置的情况,判断我们是否可以选择停留或继续前进\\n // 只有当前i-1个棋子都没有停留,并且没有棋子会在之后经过,第i个棋子才可以在当前位置停留\\n canStop = canStop && (board[j][nx][ny] >= 0) && (board[j][nx][ny] < step);\\n // 如果前i-1个棋子在当前时间之前停留在此位置,或者有棋子同时经过这里,第i个棋子就不可以继续前进了\\n blocked = blocked || ((board[j][nx][ny] < 0) && (-board[j][nx][ny] <= step)) || (board[j][nx][ny] == step);\\n }\\n\\n if (!canStop) { // 如果可以停留\\n board[i][nx][ny] = -step; // 用负数标记当前位置为棋子i的停留位\\n res += count_recursive(pieces, positions, i+1); // 考虑下一个棋子的移动的可能性\\n }\\n board[i][nx][ny] = step; // 选择继续移动\\n }\\n board[i] = new int[8][8]; // 当前方向考虑结束,清空棋盘,考虑下一个方向\\n }\\n return res; // 返回可能的总数\\n }\\n}\\n
\\n","description":"这题真的很麻烦,就是硬做。 找到所有的可能的移动组合,然后检查是不是有效的组合。\\n\\n审题\\n\\n三种棋子的鄙视链:\\n\\n后:无敌,8个方向移动\\n车:水平移动,只有4个行动方向\\n象:对角线移动,4个行动方向\\n\\n整体策略:\\n 我们需要记录下每个组合中每个棋子的中转位置和最终位置。\\n 假设一共有n个棋子,我们用一个n*8*8的三维数组来存储每一个棋子的情况。\\n 按照棋子的顺序,从左至右,我们依次遍历棋子的移动情况。\\n 当第一个棋子在完成移动之后,我们递归调用遍历方法,考虑第二个棋子的移动,直到考虑完所有棋子的情况。\\n\\n🌰举个例子:\\n 假设一共有2个棋子,我们用一个2*8…","guid":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/bao-li-po-jie-de-si-lu-xiang-jie-fu-java-t437","author":"esther29","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-02-05T01:38:10.755Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"解决智力问题","url":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/jie-jue-zhi-li-wen-ti-by-leetcode-soluti-ieuq","content":"class Solution {\\n int[][] dirs = new int[][] {{0, 1}, {1, 0}, {0, -1}, {-1, 0}, {1, 1}, {1, -1}, {-1, -1}, {-1, 1}};\\n int[][][] board;\\n\\n public int countCombinations(String[] pieces, int[][] positions) {\\n board = new int[positions.length][8][8];\\n return count_recursive(pieces, positions, 0);\\n }\\n\\n public int count_recursive(String[] pieces, int[][] positions, int i) {\\n if (i >= pieces.length)\\n return 1;\\n int x = positions[i][0] - 1, y = positions[i][1] - 1, res = 0;\\n\\n for (int d = 0; d < 8; d++){\\n if ((d < 4 && pieces[i].equals(\\"bishop\\")) || (d >= 4 && pieces[i].equals(\\"rook\\")))\\n continue;\\n\\n boolean blocked = false; \\n for (int step = res == 0 ? 1 : 2; !blocked; step++) { // ?\\n int nx = x + (step-1) * dirs[d][0];\\n int ny = y + (step-1) * dirs[d][1];\\n\\n if (nx < 0 || nx >= 8 || ny < 0 || ny >= 8) //if goes out of board\\n break;\\n\\n boolean canStop = true;\\n for (int j = 0; j < i; j++) {\\n canStop = canStop && (board[j][nx][ny] >= 0) && (board[j][nx][ny] < step);\\n blocked = blocked || ((board[j][nx][ny] < 0) && (-board[j][nx][ny] <= step)) || (board[j][nx][ny] == step);\\n }\\n\\n if (canStop) {\\n board[i][nx][ny] = -step;\\n res += count_recursive(pieces, positions, i+1);\\n }\\n board[i][nx][ny] = step;\\n }\\n board[i] = new int[8][8];\\n }\\n return res;\\n }\\n}\\n
方法一:动态规划
\\n提示 $1$
\\n我们可以尝试用 $\\\\textit{dp}[i]$ 来表示解决前 $i$ 道题目可以获得的最高分数。根据是否选择解决第 $i$ 道题目,会有以下两种情况:
\\n\\n
\\n- 不解决第 $i$ 道题目,此时 $\\\\textit{dp}[i] = \\\\textit{dp}[i-1]$;
\\n- 解决第 $i$ 道题目,此时要么前面的题目都未解决,要么上一道解决的题目对应的冷冻期已经结束。具体而言:
\\n$$
\\n
\\n\\\\textit{dp}[i] = \\\\textit{points}[i] + \\\\max(0, \\\\max_{j \\\\in [0, i - 1], j + \\\\textit{brainpower}[j] < i} dp[j]).
\\n$$由于每一道题对应的冷冻期都不一样,因此我们很难在不通过遍历 $[0, i - 1]$ 闭区间内的全部下标,以判断对应的冷冻期是否结束的情况下更新 $\\\\textit{dp}[i]$。我们假设题目的总数为 $n$,这样的时间复杂度为 $O(n^2)$,不符合题目要求。
\\n提示 $2$
\\n我们可以从无后效性的角度考虑动态规划「状态」的定义。对于每一道题目,解决与否会影响到后面一定数量题目的结果,但不会影响到前面题目的解决。因此我们可以考虑从反方向定义「状态」,即考虑解决每道题本身及以后的题目可以获得的最高分数。
\\n思路与算法
\\n根据提示 $2$,我们用 $\\\\textit{dp}[i]$ 来表示解决第 $i$ 道题目及以后的题目可以获得的最高分数。同时,我们从后往前遍历题目,并更新 $\\\\textit{dp}$ 数组。类似地,根据是否选择解决第 $i$ 道题目,会有以下两种情况:
\\n\\n
\\n- 不解决第 $i$ 道题目,此时 $\\\\textit{dp}[i] = \\\\textit{dp}[i+1]$;
\\n- 解决第 $i$ 道题目,我们只能解决下标大于 $i + \\\\textit{brainpower}[i]$ 的题目,而此时根据 $\\\\textit{dp}$ 数组的定义,解决这些题目的最高分数为 $dp[i + \\\\textit{brainpower}[i] + 1]$(当 $i \\\\ge n$ 的情况下,我们认为 $dp[i] = 0$)。因此,我们有:
\\n$$
\\n
\\n\\\\textit{dp}[i] = \\\\textit{points}[i] + dp[i + \\\\textit{brainpower}[i] + 1].
\\n$$综合上述两种情况,我们就得出了 $\\\\textit{dp}[i]$ 的状态转移方程:
\\n$$
\\n
\\n\\\\textit{dp}[i] = \\\\max(\\\\textit{dp}[i+1], \\\\textit{points}[i] + dp[i + \\\\textit{brainpower}[i] + 1]).
\\n$$在实际计算中,考虑到 $i \\\\ge n$ 的边界条件,我们在定义 $\\\\textit{dp}$ 数组时,可以预留 $dp[n] = 0$ 用来表示没有做任何题目的分数。那么上面的转移方程变为:
\\n$$
\\n
\\n\\\\textit{dp}[i] = \\\\max(\\\\textit{dp}[i+1], \\\\textit{points}[i] + dp[\\\\min(n, i + \\\\textit{brainpower}[i] + 1)]).
\\n$$最终,$dp[0]$ 即为考试中可以获得的最高分数,我们返回该数值作为答案。
\\n细节
\\n在计算 $\\\\textit{dp}$ 数组时,数组元素的大小有可能超过 $32$ 为有符号整数的上界,因此对于 $\\\\texttt{C++}$ 等语言,我们需要用 $64$ 位整数来存储 $\\\\textit{dp}$ 数组。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n long long mostPoints(vector<vector<int>>& questions) {\\n int n = questions.size();\\n vector<long long> dp(n + 1); // 解决每道题及以后题目的最高分数\\n for (int i = n - 1; i >= 0; --i) {\\n dp[i] = max(dp[i + 1], questions[i][0] + dp[min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def mostPoints(self, questions: List[List[int]]) -> int:\\n n = len(questions)\\n dp = [0] * (n + 1) # 解决每道题及以后题目的最高分数\\n for i in range(n - 1, -1, -1):\\n dp[i] = max(\\n dp[i + 1], questions[i][0] + dp[min(n, i + questions[i][1] + 1)]\\n )\\n return dp[0]\\n\\n
###Java
\\n\\nclass Solution {\\n public long mostPoints(int[][] questions) {\\n int n = questions.length;\\n long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数\\n for (int i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public long MostPoints(int[][] questions) {\\n int n = questions.Length;\\n long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数\\n for (int i = n - 1; i >= 0; i--) {\\n dp[i] = Math.Max(dp[i + 1], questions[i][0] + dp[Math.Min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n }\\n}\\n
###Go
\\n\\nfunc mostPoints(questions [][]int) int64 {\\n n := len(questions)\\n dp := make([]int64, n + 1) // 解决每道题及以后题目的最高分数\\n for i := n - 1; i >= 0; i-- {\\n dp[i] = max(dp[i + 1], int64(questions[i][0]) + dp[min(n, i + questions[i][1] + 1)])\\n }\\n return dp[0]\\n}\\n
###C
\\n\\nlong long max(long long a, long long b) {\\n return a > b ? a : b;\\n}\\n\\nlong long min(long long a, long long b) {\\n return a < b ? a : b;\\n}\\n\\nlong long mostPoints(int** questions, int questionsSize, int* questionsColSize) {\\n long long dp[questionsSize + 1]; // 解决每道题及以后题目的最高分数\\n memset(dp, 0, sizeof(dp));\\n for (int i = questionsSize - 1; i >= 0; --i) {\\n dp[i] = max(dp[i + 1], questions[i][0] + dp[min(questionsSize, i + questions[i][1] + 1)]);\\n }\\n long long result = dp[0];\\n return result;\\n}\\n
###JavaScript
\\n\\nvar mostPoints = function(questions) {\\n const n = questions.length;\\n const dp = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数\\n for (let i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n};\\n
###JavaScript
\\n\\nfunction mostPoints(questions: number[][]): number {\\n const n = questions.length;\\n const dp: number[] = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数\\n for (let i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn most_points(questions: Vec<Vec<i32>>) -> i64 {\\n let n = questions.len();\\n let mut dp = vec![0i64; n + 1]; // 解决每道题及以后题目的最高分数\\n for i in (0..n).rev() {\\n dp[i] = dp[i + 1].max(questions[i][0] as i64 + dp[(n).min(i + questions[i][1] as usize + 1)]);\\n }\\n dp[0]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:动态规划 提示 $1$\\n\\n我们可以尝试用 $\\\\textit{dp}[i]$ 来表示解决前 $i$ 道题目可以获得的最高分数。根据是否选择解决第 $i$ 道题目,会有以下两种情况:\\n\\n不解决第 $i$ 道题目,此时 $\\\\textit{dp}[i] = \\\\textit{dp}[i-1]$;\\n解决第 $i$ 道题目,此时要么前面的题目都未解决,要么上一道解决的题目对应的冷冻期已经结束。具体而言:\\n\\n$$\\n \\\\textit{dp}[i] = \\\\textit{points}[i] + \\\\max(0, \\\\max_{j \\\\in [0, i - 1], j…","guid":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/jie-jue-zhi-li-wen-ti-by-leetcode-soluti-ieuq","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-26T04:25:03.396Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计隐藏数组数目","url":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/tong-ji-yin-cang-shu-zu-shu-mu-by-leetco-t5su","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{questions}$ 数组的长度。即为动态规划计算可以获得的最高分数的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(n)$,即为动态规划数组的空间开销。
\\n方法一:确定隐藏数组上下界的差值
\\n思路与算法
\\n记最终的数组为 $a_0, a_1, \\\\cdots, a_n$。我们可以发现,如果数组 $a$ 满足要求,那么:
\\n$$
\\n
\\na_0 + k, a_1 + k, \\\\cdots, a_n + k
\\n$$也一定满足要求。这里的「要求」指的是相邻元素的差值对应着给定的数组 $\\\\textit{differences}$。
\\n因此我们就可以任意指定 $a_0$,为了方便不妨直接令 $a_0 = 0$,我们就可以还原出数组 $a_0, a_1, \\\\cdots, a_n$ 了。如果我们继续考虑数组元素都在 $[\\\\textit{lower}, \\\\textit{upper}]$ 范围内的要求,不妨记数组中最小的元素为 $a_i$,最大的元素为 $a_j$,显然需要满足:
\\n$$
\\n
\\n\\\\textit{lower} \\\\leq a_i \\\\leq a_j \\\\leq \\\\textit{upper}
\\n$$那么 $a_i$ 的取值下界即为 $\\\\textit{lower}$,上界为 $\\\\textit{upper} - (a_j - a_i)$,即需要保证最大值 $a_j$ 不能超过 $\\\\textit{upper}$。这里的 $a_j - a_i$ 实际上与 $a_i, a_j$ 本身的值无关,它就等于:
\\n$$
\\n
\\n\\\\sum_{k=i}^{j-1} \\\\textit{differences}[k]
\\n$$因此符合要求的隐藏数组的数目即为 $\\\\textit{upper} - (a_j - a_i) - \\\\textit{lower} + 1$,整理可得:
\\n$$
\\n
\\n(\\\\textit{upper} - \\\\textit{lower}) - (a_j - a_i) + 1
\\n$$实际上就是规定的数组元素的区间长度,减去数组元素最大值与最小值的差值,再加上 $1$。我们可以将其看成是一个长度为 $a_j - a_i$ 的小窗口在长度为 $\\\\textit{upper} - \\\\textit{lower}$ 的大窗口中滑动时,能够放置的位置数量。
\\n细节
\\n在还原数组 $a$ 的过程中,我们无需记录整个数组,而是只需要记录最大值和最小值即可。如果某一时刻最大值与最小值的差值大于 $\\\\textit{upper} - \\\\textit{lower}$,我们可以直接返回 $0$。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numberOfArrays(vector<int>& differences, int lower, int upper) {\\n int x = 0, y = 0, cur = 0;\\n for (int d: differences) {\\n cur += d;\\n x = min(x, cur);\\n y = max(y, cur);\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def numberOfArrays(self, differences: List[int], lower: int, upper: int) -> int:\\n x = y = cur = 0\\n for d in differences:\\n cur += d\\n x = min(x, cur)\\n y = max(y, cur)\\n if y - x > upper - lower:\\n return 0\\n return (upper - lower) - (y - x) + 1\\n
###Golang
\\n\\nfunc numberOfArrays(differences []int, lower int, upper int) int {\\nvar x, y, cur int\\nfor _, d := range differences {\\ncur += d\\nx, y = min(x, cur), max(y, cur)\\nif y-x > upper-lower {\\nreturn 0\\n}\\n}\\nreturn (upper - lower) - (y - x) + 1\\n}\\n\\nfunc min(x, y int) int {\\nif x > y {\\nreturn y\\n}\\nreturn x\\n}\\n\\nfunc max(x, y int) int {\\nif x > y {\\nreturn x\\n}\\nreturn y\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int NumberOfArrays(int[] differences, int lower, int upper) {\\n int x = 0, y = 0, cur = 0;\\n foreach (var d in differences) {\\n cur += d;\\n x = Math.Min(x, cur);\\n y = Math.Max(y, cur);\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n }\\n}\\n
###Java
\\n\\nclass Solution {\\n public int numberOfArrays(int[] differences, int lower, int upper) {\\n int x = 0, y = 0, cur = 0;\\n for (int d : differences) {\\n cur += d;\\n x = Math.min(x, cur);\\n y = Math.max(y, cur);\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n }\\n}\\n
###C
\\n\\nint numberOfArrays(int* differences, int differencesSize, int lower, int upper) {\\n int x = 0, y = 0, cur = 0;\\n for (int i = 0; i < differencesSize; i++) {\\n cur += differences[i];\\n x = (x < cur) ? x : cur;\\n y = (y > cur) ? y : cur;\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n}\\n
###JavaScript
\\n\\nvar numberOfArrays = function(differences, lower, upper) {\\n let x = 0, y = 0, cur = 0;\\n for (let d of differences) {\\n cur += d;\\n x = Math.min(x, cur);\\n y = Math.max(y, cur);\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n};\\n
###TypeScript
\\n\\nfunction numberOfArrays(differences: number[], lower: number, upper: number): number {\\n let x = 0, y = 0, cur = 0;\\n for (let d of differences) {\\n cur += d;\\n x = Math.min(x, cur);\\n y = Math.max(y, cur);\\n if (y - x > upper - lower) {\\n return 0;\\n }\\n }\\n return (upper - lower) - (y - x) + 1;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn number_of_arrays(differences: Vec<i32>, lower: i32, upper: i32) -> i32 {\\n let mut x = 0;\\n let mut y = 0;\\n let mut cur = 0;\\n for &d in &differences {\\n cur += d;\\n x = x.min(cur);\\n y = y.max(cur);\\n if y - x > upper - lower {\\n return 0;\\n }\\n }\\n (upper - lower) - (y - x) + 1\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:确定隐藏数组上下界的差值 思路与算法\\n\\n记最终的数组为 $a_0, a_1, \\\\cdots, a_n$。我们可以发现,如果数组 $a$ 满足要求,那么:\\n\\n$$\\n a_0 + k, a_1 + k, \\\\cdots, a_n + k\\n $$\\n\\n也一定满足要求。这里的「要求」指的是相邻元素的差值对应着给定的数组 $\\\\textit{differences}$。\\n\\n因此我们就可以任意指定 $a_0$,为了方便不妨直接令 $a_0 = 0$,我们就可以还原出数组 $a_0, a_1, \\\\cdots, a_n$ 了。如果我们继续考虑数组元素都在 $[\\\\textit…","guid":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/tong-ji-yin-cang-shu-zu-shu-mu-by-leetco-t5su","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-25T03:17:01.384Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Chthollist】双周赛第70场:LC5972:T2:「前缀和 & 区间夹逼」& 「前缀和空间优化」","url":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/chthollist-shuang-zhou-sai-di-70chang-lc-mfq2","content":"- \\n
\\n时间复杂度:$O(n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n前言
\\n大家好,我是新人LeetCoder:「Chthollist」
\\n
\\n正在坚持每日更新LeetCode每日一题,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~
\\n同时也在进行其他专项类型题目的刷题与题解活动,相关资料也会同步到「GitHub」上面 ~
\\n今天是坚持写题解的27天(haha,从21年圣诞节开始的),大家一起加油!
\\n\\n
\\n- 双周赛第70场:LeetCode:5972.统计隐藏数组的数目\\n
\\n\\n
\\n- 时间:2022-01-22
\\n- 力扣难度:Meduim
\\n- 个人难度:Meduim
\\n- 数据结构:数组
\\n- 算法:前缀和、差分、区间求和
\\n\\n
\\n双周赛第70场:LeetCode:5972.统计隐藏数组的数目
\\n1. 题目描述
\\n\\n
\\n- \\n
\\n题目:原题链接
\\n\\n
\\n- 给你一个下标从 0 开始且长度为 n 的整数数组 differences ,它表示一个长度为 n + 1 的隐藏数组相邻元素之间的**差值 **。
\\n- 更正式的表述为:我们将隐藏数组记作 hidden ,那么 differences[i] = hidden[i + 1] - hidden[i] 。
\\n- 同时给你两个整数 lower 和 upper ,它们表示隐藏数组中所有数字的值都在 闭 区间 [lower, upper] 之间。
\\n- 比方说,differences = [1, -3, 4] ,lower = 1 ,upper = 6 ,那么隐藏数组是一个长度为 4 且所有值都在 1 和 6 (包含两者)之间的数组。\\n
\\n\\n
\\n- [3, 4, 1, 5] 和 [4, 5, 2, 6] 都是符合要求的隐藏数组。
\\n- [5, 6, 3, 7] 不符合要求,因为它包含大于 6 的元素。
\\n- [1, 2, 3, 4] 不符合要求,因为相邻元素的差值不符合给定数据。
\\n- 请你返回 符合 要求的隐藏数组的数目。如果没有符合要求的隐藏数组,请返回 0 。
\\n- n == differences.length
\\n- \\n
1 <= n <= 10^5
- \\n
-10^5 <= differences[i] <= 10^5
- \\n
-10^5 <= lower <= upper <= 10^5
- \\n
\\n输入输出规范
\\n\\n
\\n- 输入:差分数组 differences, 区间范围lower、upper
\\n- 输出:符合要求的隐藏数组的个数
\\n- \\n
\\n输入输出示例
\\n\\n
\\n- 输入:differences = [3,-4,5,1,-2], lower = -4, upper = 5
\\n- 输出:4
\\n- 解释:符合要求的隐藏数组为:\\n
\\n\\n
\\n- [-3, 0, -4, 1, 2, 0]
\\n- [-2, 1, -3, 2, 3, 1]
\\n- [-1, 2, -2, 3, 4, 2]
\\n- [0, 3, -1, 4, 5, 3]
\\n
\\n2. 方法一:双指针 & 暴力求解
\\n\\n
\\n- \\n
\\n思路
\\n\\n
\\n- 本题要求解符合要求的隐藏数组的个数,最容易想到的就是从区间下限lower开始,遍历到其上限upper
\\n- 在遍历过程中,计算差分数组当前的前缀和加上此时遍历的循环变量的值是否还在区间内,即表示隐藏数组的其中一个元素是否在区间内,如果在区间内,且计算到了差分数组的最终前缀和,就计数加一
\\n- 如果中间某一次不在区间内,那就表示以本次循环的初始值为隐藏数组的第一个元素是不满足要求的,即跳过本次循环
\\n- 具体而言,就是维护一个计数器变量count、两个指针变量num、start,start表示当前正在讨论的隐藏数组的首元素,num表示隐藏数组的当前元素,此外,再维护一个索引index,表示使用到了差分数组的第几个元素
\\n- 双指针暴力解法的复杂度是 $O(n^2)$,由于本题的数据规模是
\\n10^5
,平方级复杂度为10^10
,,会发生TLE超时- \\n
\\n题解:双指针
\\n\\n###java
\\n\\n","description":"前言 大家好,我是新人LeetCoder:「Chthollist」\\n 正在坚持每日更新LeetCode每日一题,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~\\n 同时也在进行其他专项类型题目的刷题与题解活动,相关资料也会同步到「GitHub」上面 ~\\n 今天是坚持写题解的27天(haha,从21年圣诞节开始的),大家一起加油!\\n\\n双周赛第70场:LeetCode:5972.统计隐藏数组的数目\\n时间:2022-01-22\\n力扣难度:Meduim\\n个人难度:Meduim\\n数据结构:数组\\n算法:前缀和、差分、区间求和…","guid":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/chthollist-shuang-zhou-sai-di-70chang-lc-mfq2","author":"chthollists","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-23T13:27:58.586Z","media":[{"url":"https://pic.leetcode-cn.com/1642944473-AWLXoh-LeetCode%E6%AF%8F%E6%97%A5%E4%B8%80%E9%A2%98.jpg","type":"photo","width":1142,"height":534,"blurhash":"LNINKmn$9ZIp2{ayM{V@57X8xaoe"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【C++】前缀和 + 反推思想","url":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/c-qian-zhui-he-fan-tui-si-xiang-by-ellie-4d0e","content":"> public int numberOfArrays(int[] differences, int lower, int upper) {\\n > if (differences == null || differences.length == 0) return 0;\\n > int n = differences.length;\\n > int index = 0; // 记录差分数组的索引\\n > int count = 0; // 记录个数\\n > int start = lower; // 记录隐藏数组的首元素\\n > for (int num = start; num <= upper; ) {\\n > // 前缀和\\n > num += differences[index];\\n > if (num < lower || num > upper) {\\n > start++;\\n > if (start > upper) break;\\n > num = start; // 新的首元素\\n > index = 0;\\n > continue;\\n > }\\n > index++;\\n > if (index == n) {\\n > count++;\\n > index = 0;\\n > num = start + 1; // 新的首元素\\n > start++; // 新的首元素\\n > }\\n > }\\n > return count;\\n > }\\n > ```\\n\\n* 复杂度分析:n 是数组的长度\\n\\n * 时间复杂度:$O(n^2)$\\n * 空间复杂度:$O(1)$\\n\\n---\\n\\n## 3. 方法二:前缀和 & 区间逼近(夹逼法)\\n\\n* 思想\\n\\n * 由于暴力解法会超时,就需要思考过程中有哪些可以简化的地方\\n * 由于当隐藏数组的**首个元素**确定后,当前隐藏数组就**唯一确定**了,即将问题**简化为求解`hidden[0]`的个数**,而不再是求解隐藏数组中的每一个元素并判断是否在区间内(暴力法)\\n * 从数学的角度分析,我们可以发现对于隐藏数组中的元素 `hidden[j]`而言,其一定满足\\n * $hidden[j] >= lower$\\n * $hidden[j] <= upper$\\n * $hidden[j] = hidden[0]+\\\\sum_{i=0}^jd[i]$\\n\\n * 因此,可以得到`hidden[0]`必须满足的条件,可以**抽象**为**坐标轴**和**区间**的概念,更加容易理解\\n\\n * $hidden[0] >= lower$\\n * $hidden[0] >= lower - \\\\sum_{i=0}^jd[i]$\\n\\n * $hidden[0] <= upper$\\n * $hidden[0] <= upper - \\\\sum_{i=0}^jd[i]$\\n\\n * 为了确保一定同时满足这四个条件,则需要满足:\\n\\n * $hidden[0] >= Max(lower, lower - \\\\sum_{i=0}^jd[i])$\\n * $hidden[0] <= Min(upper, upper - \\\\sum_{i=0}^jd[i]) $\\n\\n * 这两个公式表示的含义就是,遍历整个差分数组,首元素要**大于**每一次(对应隐藏数组每一个元素)**下界中的最大值**,且**小于上界中的最小值**,**不断缩小范围**,**区间逼近**\\n\\n * 此时,就会得到`hidden[0]`的取值上下界,即取值范围,如果下界大于上界,说明不存在符合要求的`hidden[0]`,返回0,否则就返回这个取值范围内的整数数字个数\\n\\n * 注意:运算过程中,**前缀和**可能很大,超出 int,所以需要**使用 long**\\n\\n* 题解:前缀和\\n\\n > \\n###java\\n```java\\n > public int numberOfArrays(int[] differences, int lower, int upper) {\\n > if (differences == null || differences.length == 0) return 0;\\n > int n = differences.length;\\n > long[] sum = new long[n];\\n > sum[0] = differences[0];\\n > // 前缀和\\n > for(int i = 1; i < n; i++) {\\n > sum[i] = sum[i-1] + differences[i];\\n > }\\n > // 上下界\\n > int left = lower, right = upper;\\n > for(int i =0; i < n; i++) {\\n > left = Math.max(left, lower - sum[i]);\\n > right = Math.min(right, upper - sum[i]);\\n > }\\n > if(left > right) return 0;\\n > return right - left + 1;\\n > }\\n > ```\\n\\n* 复杂度分析:n 是数组的长度\\n\\n * 时间复杂度:$O(n)$,两次不嵌套的遍历,线性复杂度\\n * 空间复杂度:$O(n)$,前缀和数组\\n\\n* 思考与优化\\n\\n * 思考:现在的解法中,需要维护一个前缀和数组,即线性的空间复杂度,能否优化为常量空间,且两次遍历能否优化为一次遍历\\n * 优化\\n * 实际上,因为我们只需要求首元素的取值范围,即**在循环中不断选择新的上下界**,实现**区间逼近**,所以我们只需要在求出前缀和的同时,记录边界即可,无需分开操作\\n * 且根据公式可得,**当前前缀和越小,下界(左边界)越大**,而**当前前缀和越大,上界(右边界)越小**,因此,我们只需要在遍历的过程中,**记录前缀和的最大最小值**即可,然后在求区间范围\\n * 具体而言:当前缀和小于0时,当前时刻的下界将大于lower,上界为upper;当前缀和大于0时,当前时刻的下界为lower,上界将小于upper\\n\\n* 题解:前缀和优化\\n\\n > \\n###java\\n```java\\n > public int numberOfArrays(int[] differences, int lower, int upper) {\\n > if (differences == null || differences.length == 0) return 0;\\n > int n = differences.length;\\n > long sum = 0L, min = 0L, max = 0L;\\n > for(int i = 0; i < n; i++) {\\n > sum += differences[i];\\n > min = Math.min(min, sum);\\n > max = Math.max(max, sum);\\n > }\\n > int count = (int) Math.max(0, upper - max - (lower - min) + 1);\\n > return count;\\n > }\\n > ```\\n\\n* 复杂度分析:n 是数组的长度\\n\\n * 时间复杂度:$O(n)$,一次遍历,线性复杂度\\n * 空间复杂度:$O(1)$\\n\\n---\\n\\n## 最后\\n如果本文有所帮助的话,欢迎大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~\\n也希望大家有空的时候光临我的其他平台,上面会更新Java面经、八股文、刷题记录等等,欢迎大家光临交流,谢谢!\\n* 「[个人博客](https://chthollists.github.io/)」\\n* 「[掘金](https://juejin.cn/user/26854211457719)」\\n* 「[简书](https://www.jianshu.com/u/8000305d22b9)」
解题思路
\\n题目提供给了我们
\\ndifferences
数组,我们如果知道隐藏数组第一个元素a[0]
的话,可以通过differences
数组依次推出每一个a
元素,但是枚举a[0]
的值加上依次计算的话时间复杂度是$O(N^2)$的,在数据规模在$10^5$情况下是会TLE的,所以我们需要换一种枚举思路
\\n对于每个d[i]
来说,它都有以下公式:
\\nd[i] = a[i + 1] - a[i]
\\n那么对应每个a[i]
我们都可以推出与a[0]
的关系:
\\na[i] = d[0] + d[1] + ... + d[i - 1] + a[0]
\\n我们希望a[i]
值合法(位于[lower, upper]区间)那么d的前缀和数组加上a[0]
的和就必须位于这个区间内,我们可以由此推导出对于每个数i
对应的a[0]
能取到的合法区间。这个区间为[lower - prefix[i], upper - prefix[i]]
,最后对a[0]
所有的合法区间取最小交集,这个交集区间的长度即使我们的答案代码
\\n###c++
\\n\\n","description":"解题思路 题目提供给了我们differences数组,我们如果知道隐藏数组第一个元素a[0]的话,可以通过differences数组依次推出每一个a元素,但是枚举a[0]的值加上依次计算的话时间复杂度是$O(N^2)$的,在数据规模在$10^5$情况下是会TLE的,所以我们需要换一种枚举思路\\n 对于每个d[i]来说,它都有以下公式:\\n d[i] = a[i + 1] - a[i]\\n 那么对应每个a[i]我们都可以推出与a[0]的关系:\\n a[i] = d[0] + d[1] + ... + d[i - 1] + a[0] \\n 我们希望a[i]值合法(位于…","guid":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/c-qian-zhui-he-fan-tui-si-xiang-by-ellie-4d0e","author":"EllieFeng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-22T16:15:15.519Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"详细数学推导(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/huan-yuan-chai-fen-shu-zu-by-endlesschen-v38b","content":"class Solution {\\npublic:\\n int numberOfArrays(vector<int>& d, int l, int r) {\\n int n = d.size();\\n vector<int> x(n);\\n vector<long long> prefix(n);\\n prefix[0] = d[0];\\n for (int i = 1; i < n; ++i) {\\n prefix[i] = prefix[i - 1] + d[i];\\n }\\n int ans = 0;\\n int mn = l;\\n int mx = r;\\n for (int i = 0; i < n; ++i) {\\n int left = l - prefix[i];\\n int right = r - prefix[i];\\n mn = max(mn, left);\\n mx = min(mx, right);\\n }\\n if (mx < mn) return 0;\\n ans = mx - mn + 1;\\n return ans;\\n }\\n};\\n
下文把 $\\\\textit{hidden}$ 简记为 $a$,把 $\\\\textit{differences}$ 简记为 $d$。
\\n题目给出等式
\\n$$
\\n
\\nd_i = a_{i+1} - a_i
\\n$$移项得
\\n$$
\\n
\\na_{i+1} = a_i + d_i
\\n$$所以有
\\n$$
\\n
\\n\\\\begin{aligned}
\\na_1 &= a_0 + d_0 \\\\
\\na_2 &= a_1 + d_1 = a_0 + d_0 + d_1 \\\\
\\na_3 &= a_2 + d_2 = a_0 + d_0 + d_1 + d_2 \\\\
\\n&\\\\ \\\\ \\\\vdots \\\\
\\na_i &= a_0 + \\\\sum_{i=0}^{i-1} d_i
\\n\\\\end{aligned}
\\n$$计算 $d$ 的 前缀和 数组 $s$,那么有
\\n$$
\\n
\\na_i = a_0 + s_i
\\n$$这意味着,确定了 $a_0$,就确定了整个数组 $a$。所以 $a_0$ 的取值范围的大小就是答案。
\\n题目要求
\\n$$
\\n
\\n\\\\textit{lower}\\\\le a_i\\\\le \\\\textit{upper}
\\n$$等价于
\\n$$
\\n
\\n\\\\textit{lower}\\\\le a_0 + s_i \\\\le \\\\textit{upper}
\\n$$移项得
\\n$$
\\n
\\n\\\\textit{lower} - s_i \\\\le a_0 \\\\le \\\\textit{upper} - s_i
\\n$$这可以得到 $n+1$ 个关于 $a_0$ 不等式,或者说区间 $[\\\\textit{lower} - s_i,\\\\textit{upper} - s_i]$。这 $n+1$ 个区间的交集大小,就是答案。(注意数组 $a$ 的长度是 $n+1$)
\\n区间交集的左端点为 $\\\\max\\\\limits_i {\\\\textit{lower} - s_i} = \\\\textit{lower} - \\\\min\\\\limits_i s_i$。
\\n区间交集的右端点为 $\\\\min\\\\limits_i{ \\\\textit{upper} - s_i }= \\\\textit{upper} - \\\\max\\\\limits_i s_i$。
\\n交集大小为
\\n$$
\\n
\\n\\\\begin{aligned}
\\n& (\\\\textit{upper} - \\\\max_i s_i) - (\\\\textit{lower} - \\\\min_i s_i) + 1 \\\\
\\n={} & \\\\textit{upper} - \\\\textit{lower} - \\\\max_i s_i + \\\\min_i s_i + 1 \\\\
\\n\\\\end{aligned}
\\n$$注意交集可能是空的,上式是负数,所以要和 $0$ 取最大值。最终答案为
\\n$$
\\n
\\n\\\\max(\\\\textit{upper} - \\\\textit{lower} - \\\\max_i s_i + \\\\min_i s_i + 1, 0)
\\n$$代码实现时,$\\\\textit{minS}$ 和 $\\\\textit{maxS}$ 可以初始化成 $0$,这是因为前缀和数组中的 $s_0=0$。
\\n\\nclass Solution:\\n def numberOfArrays(self, differences: List[int], lower: int, upper: int) -> int:\\n min_s = min(accumulate(differences, initial=0)) # 前缀和的最小值\\n max_s = max(accumulate(differences, initial=0)) # 前缀和的最大值\\n return max(upper - lower - max_s + min_s + 1, 0)\\n
\\nclass Solution {\\n public int numberOfArrays(int[] differences, int lower, int upper) {\\n long s = 0, minS = 0, maxS = 0; // s[0] = 0\\n for (int d : differences) {\\n s += d;\\n minS = Math.min(minS, s);\\n maxS = Math.max(maxS, s);\\n }\\n return (int) Math.max(upper - lower - maxS + minS + 1, 0);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int numberOfArrays(vector<int>& differences, int lower, int upper) {\\n long long s = 0, min_s = 0, max_s = 0; // s[0] = 0\\n for (int d : differences) {\\n s += d;\\n min_s = min(min_s, s);\\n max_s = max(max_s, s);\\n }\\n return max(upper - lower - max_s + min_s + 1, 0LL);\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint numberOfArrays(int* differences, int differencesSize, int lower, int upper) {\\n long long s = 0, min_s = 0, max_s = 0; // s[0] = 0\\n for (int i = 0; i < differencesSize; i++) {\\n s += differences[i];\\n min_s = MIN(min_s, s);\\n max_s = MAX(max_s, s);\\n }\\n return MAX(upper - lower - max_s + min_s + 1, 0);\\n}\\n
\\nfunc numberOfArrays(differences []int, lower, upper int) int {\\n var s, minS, maxS int // s[0] = 0\\n for _, d := range differences {\\n s += d\\n minS = min(minS, s)\\n maxS = max(maxS, s)\\n }\\n return max(upper-lower-maxS+minS+1, 0)\\n}\\n
\\nvar numberOfArrays = function(differences, lower, upper) {\\n let s = 0, minS = 0, maxS = 0; // s[0] = 0\\n for (const d of differences) {\\n s += d;\\n minS = Math.min(minS, s);\\n maxS = Math.max(maxS, s);\\n }\\n return Math.max(upper - lower - maxS + minS + 1, 0);\\n};\\n
\\nimpl Solution {\\n pub fn number_of_arrays(differences: Vec<i32>, lower: i32, upper: i32) -> i32 {\\n let mut s = 0; // s[0] = 0\\n let mut min_s = 0;\\n let mut max_s = 0;\\n for d in differences {\\n s += d as i64;\\n min_s = min_s.min(s);\\n max_s = max_s.max(s);\\n }\\n 0.max((upper - lower + 1) as i64 - max_s + min_s) as _\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{differences}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n相似题目
\\n\\n
\\n- 3468. 可行数组的数目 1545
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"下文把 $\\\\textit{hidden}$ 简记为 $a$,把 $\\\\textit{differences}$ 简记为 $d$。 题目给出等式\\n\\n$$\\n d_i = a_{i+1} - a_i\\n $$\\n\\n移项得\\n\\n$$\\n a_{i+1} = a_i + d_i\\n $$\\n\\n所以有\\n\\n$$\\n \\\\begin{aligned}\\n a_1 &= a_0 + d_0 \\\\\\n a_2 &= a_1 + d_1 = a_0 + d_0 + d_1 \\\\\\n a_3 &= a_2 + d_2 = a_0 + d_0 + d_1 + d_2 \\\\\\n &\\\\ \\\\ \\\\vdots \\\\\\n a_i &= a…","guid":"https://leetcode.cn/problems/count-the-hidden-sequences//solution/huan-yuan-chai-fen-shu-zu-by-endlesschen-v38b","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-22T16:13:52.133Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】滑动窗口运用题","url":"https://leetcode.cn/problems/contains-duplicate-ii//solution/gong-shui-san-xie-hua-dong-chuang-kou-yu-q02i","content":"滑动窗口 + 哈希表
\\n整理题意:是否存在长度不超过的 $k + 1$ 窗口,窗口内有相同元素。
\\n我们可以从前往后遍历 $nums$,同时使用
\\nSet
记录遍历当前滑窗内出现过的元素。假设当前遍历的元素为 $nums[i]$:
\\n\\n
\\n- 下标小于等于 $k$(起始滑窗长度还不足 $k + 1$):直接往滑窗加数,即将当前元素加入
\\nSet
中;- 下标大于 $k$:将上一滑窗的左端点元素 $nums[i - k - 1]$ 移除,判断当前滑窗的右端点元素 $nums[i]$ 是否存在
\\nSet
中,若存在,返回True
,否则将当前元素 $nums[i]$ 加入Set
中。重复上述过程,若整个 $nums$ 处理完后仍未找到,返回
\\nFalse
。代码(感谢 @Benhao、@🍭可乐可乐吗 和 @5cm/s 🌸 同学提供的其他语言版本):
\\n###Java
\\n\\nclass Solution {\\n public boolean containsNearbyDuplicate(int[] nums, int k) {\\n int n = nums.length;\\n Set<Integer> set = new HashSet<>();\\n for (int i = 0; i < n; i++) {\\n if (i > k) set.remove(nums[i - k - 1]);\\n if (set.contains(nums[i])) return true;\\n set.add(nums[i]);\\n }\\n return false;\\n }\\n}\\n
###Python3
\\n\\nclass Solution:\\n def containsNearbyDuplicate(self, nums: List[int], k: int) -> bool:\\n n = len(nums)\\n s = set()\\n for i in range(n):\\n if i > k:\\n s.remove(nums[i - k - 1])\\n if nums[i] in s:\\n return True\\n s.add(nums[i])\\n return False\\n
###Go
\\n\\nfunc containsNearbyDuplicate(nums []int, k int) bool {\\n n := len(nums)\\n set := map[int]bool{}\\n for i := 0; i < n; i++ {\\n if i > k {\\n set[nums[i - k - 1]] = false\\n }\\n if set[nums[i]] {\\n return true\\n }\\n set[nums[i]] = true\\n }\\n return false\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n bool containsNearbyDuplicate(vector<int>& nums, int k) {\\n int n = nums.size();\\n unordered_map<int, bool> set;\\n for(int i = 0; i < n; ++i){\\n if(i > k) set[nums[i - k - 1]] = false;\\n if(set[nums[i]]) return true;\\n set[nums[i]] = true;\\n }\\n return false;\\n }\\n};\\n
###JS
\\n\\nvar containsNearbyDuplicate = function(nums, k) {\\n const mp = new Map()\\n for (let i in nums) {\\n if (mp.has(nums[i]) && i - mp.get(nums[i]) <= k) {\\n return true;\\n }\\n mp.set(nums[i], i);\\n }\\n return false;\\n};\\n
###Racket
\\n\\n(define/contract (contains-nearby-duplicate nums k)\\n (-> (listof exact-integer?) exact-integer? boolean?)\\n (define h (make-hasheq))\\n (let loop([nums nums] [i 0])\\n (cond\\n [(empty? nums) false]\\n [(and (hash-has-key? h (car nums)) (>= k (- i (hash-ref h (car nums))))) true]\\n [else\\n (hash-set! h (car nums) i)\\n (loop (cdr nums) (+ i 1))\\n ]\\n )\\n )\\n )\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(k)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"滑动窗口 + 哈希表 整理题意:是否存在长度不超过的 $k + 1$ 窗口,窗口内有相同元素。\\n\\n我们可以从前往后遍历 $nums$,同时使用 Set 记录遍历当前滑窗内出现过的元素。\\n\\n假设当前遍历的元素为 $nums[i]$:\\n\\n下标小于等于 $k$(起始滑窗长度还不足 $k + 1$):直接往滑窗加数,即将当前元素加入 Set 中;\\n下标大于 $k$:将上一滑窗的左端点元素 $nums[i - k - 1]$ 移除,判断当前滑窗的右端点元素 $nums[i]$ 是否存在 Set 中,若存在,返回 True,否则将当前元素 $nums[i]$ 加入 Se…","guid":"https://leetcode.cn/problems/contains-duplicate-ii//solution/gong-shui-san-xie-hua-dong-chuang-kou-yu-q02i","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-18T23:07:55.054Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【解决智力问题】正向 DP","url":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/solving-questions-with-brainpower-by-ika-bnmn","content":"思路
\\n\\n
\\n- 动态规划
\\n- 定义
\\nvector<long long> dp
表示做到第 i 题的时候最高的得分- 状态转移
\\n- 选择跳过,不加此题得分,后续的题继承得分
\\n- 选择做题,加上此题得分,跳过若干题后的下一题继承得分
\\n- 答案为所有可能得分的最大值
\\n答题
\\n\\nclass Solution {\\npublic:\\n long long mostPoints(vector<vector<int>>& questions) {\\n vector<long long> dp(questions.size(), 0);\\n for (int i = 0; i < dp.size(); i++) {\\n int next = i + 1;\\n if (next < dp.size()) {\\n dp[next] = max(dp[next], dp[i]);\\n }\\n\\n next = i + questions[i][1] + 1;\\n dp[i] += questions[i][0];\\n if (next < dp.size()) {\\n dp[next] = max(dp[next], dp[i]);\\n }\\n }\\n auto ans = *max_element(dp.begin(), dp.end());\\n return ans;\\n }\\n};\\n
致谢
\\n感谢您的观看,希望对您有帮助,欢迎热烈的交流!
\\n如果感觉还不错就点个赞吧~
\\n在 我的力扣个人主页 中有我使用的做题助手项目链接,帮助我收集整理题目,可以方便的
\\n","description":"思路 动态规划\\n定义 vectorvisual studio
调试,欢迎关注,stardp 表示做到第 i 题的时候最高的得分\\n状态转移\\n选择跳过,不加此题得分,后续的题继承得分\\n选择做题,加上此题得分,跳过若干题后的下一题继承得分\\n答案为所有可能得分的最大值\\n答题\\nclass Solution {\\npublic:\\n long long mostPoints(vector >& questions) {\\n vector dp(questions.size(), 0);\\n for (int i = 0;…","guid":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/solving-questions-with-brainpower-by-ika-bnmn","author":"ikaruga","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-16T05:03:48.779Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"打家劫舍变形题,查表法 / 刷表法(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/dao-xu-dp-by-endlesscheng-2qkc","content":" 前言
\\n本题其实是 198. 打家劫舍 的变形题:如果选 $\\\\textit{questions}[i]$,那么接下来的 $\\\\textit{brainpower}_i$ 个问题都不能选。打家劫舍那题相当于 $\\\\textit{brainpower}_i=1$。
\\n一、寻找子问题
\\n在示例 1 中,我们要解决的问题(原问题)是:
\\n\\n
\\n- 剩余问题的下标为 $[0,3]$,计算从这些问题中可以获得的最大分数。
\\n讨论 $\\\\textit{questions}[0]$ 选或不选:
\\n\\n
\\n- 如果不选,子问题为:剩余问题的下标为 $[1,3]$,计算从这些问题中可以获得的最大分数。
\\n- 如果选,接下来的 $2$ 个问题都不能选,子问题为:剩余问题的下标为 $[3,3]$,计算从这些问题中可以获得的最大分数。
\\n由于选或不选都会把原问题变成一个和原问题相似的、规模更小的子问题,所以可以用递归解决。
\\n二、状态定义与状态转移方程
\\n根据上面的讨论,定义状态为 $\\\\textit{dfs}(i)$,表示剩余问题的下标为 $[i,n-1]$,计算从这些问题中可以获得的最大分数。其中 $n$ 是 $\\\\textit{questions}$ 的长度。
\\n讨论 $\\\\textit{questions}[i]$ 选或不选:
\\n\\n
\\n- 如果不选,子问题为:剩余问题的下标为 $[i+1,n-1]$,计算从这些问题中可以获得的最大分数,即 $\\\\textit{dfs}(i+1)$。
\\n- 如果选,接下来的 $\\\\textit{brainpower}_i$ 个问题都不能选,子问题为:剩余问题的下标为 $[i+\\\\textit{brainpower}_i+1,n-1]$,计算从这些问题中可以获得的最大分数,即 $\\\\textit{dfs}(i+\\\\textit{brainpower}_i+1)$。
\\n这两种情况取最大值,就得到了 $\\\\textit{dfs}(i)$,即
\\n$$
\\n
\\n\\\\textit{dfs}(i) = \\\\max(\\\\textit{dfs}(i+1),\\\\textit{dfs}(i+\\\\textit{brainpower}_i+1)+\\\\textit{points}_i)
\\n$$\\n\\n对比一下,打家劫舍是 $\\\\textit{dfs}(i) = \\\\max(\\\\textit{dfs}(i+1),\\\\textit{dfs}(i+2)+\\\\textit{nums}_i)$
\\n递归边界:如果 $i\\\\ge n$,那么 $\\\\textit{dfs}(i)=0$。此时没有问题需要解决。
\\n递归入口:$\\\\textit{dfs}(0)$,这是原问题,也是答案。
\\n三、递归搜索 + 保存递归返回值 = 记忆化搜索
\\n考虑到整个递归过程中有大量重复递归调用(递归入参相同)。由于递归函数没有副作用,同样的入参无论计算多少次,算出来的结果都是一样的,因此可以用记忆化搜索来优化:
\\n\\n
\\n- 如果一个状态(递归入参)是第一次遇到,那么可以在返回前,把状态及其结果记到一个 $\\\\textit{memo}$ 数组中。
\\n- 如果一个状态不是第一次遇到($\\\\textit{memo}$ 中保存的结果不等于 $\\\\textit{memo}$ 的初始值),那么可以直接返回 $\\\\textit{memo}$ 中保存的结果。
\\n注意:$\\\\textit{memo}$ 数组的初始值一定不能等于要记忆化的值!例如初始值设置为 $0$,并且要记忆化的 $\\\\textit{dfs}(i)$ 也等于 $0$,那就没法判断 $0$ 到底表示第一次遇到这个状态,还是表示之前遇到过了,从而导致记忆化失效。一般把初始值设置为 $-1$。本题由于 $\\\\textit{points}_i > 0$,所以也可以把初始值设置为 $0$。
\\n\\n\\nPython 用户可以无视上面这段,直接用
\\n@cache
装饰器。具体请看视频讲解 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】,其中包含把记忆化搜索 1:1 翻译成递推的技巧。
\\n\\nclass Solution:\\n def mostPoints(self, questions: List[List[int]]) -> int:\\n @cache # 缓存装饰器,避免重复计算 dfs(一行代码实现记忆化)\\n def dfs(i: int) -> int:\\n if i >= len(questions):\\n return 0\\n return max(dfs(i + 1), dfs(i + questions[i][1] + 1) + questions[i][0])\\n return dfs(0)\\n
\\nclass Solution {\\n public long mostPoints(int[][] questions) {\\n long[] memo = new long[questions.length];\\n return dfs(0, questions, memo);\\n }\\n\\n private long dfs(int i, int[][] questions, long[] memo) {\\n if (i >= memo.length) {\\n return 0;\\n }\\n if (memo[i] > 0) { // 之前计算过\\n return memo[i];\\n }\\n long notChoose = dfs(i + 1, questions, memo);\\n long choose = dfs(i + questions[i][1] + 1, questions, memo) + questions[i][0];\\n return memo[i] = Math.max(notChoose, choose); // 记忆化\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long mostPoints(vector<vector<int>>& questions) {\\n int n = questions.size();\\n vector<long long> memo(n);\\n auto dfs = [&](this auto&& dfs, int i) -> long long {\\n if (i >= n) {\\n return 0;\\n }\\n long long& res = memo[i]; // 注意这里是引用\\n if (res) { // 之前计算过\\n return res;\\n }\\n return res = max(dfs(i + 1), dfs(i + questions[i][1] + 1) + questions[i][0]);\\n };\\n return dfs(0);\\n }\\n};\\n
\\nfunc mostPoints(questions [][]int) int64 {\\n n := len(questions)\\n memo := make([]int64, n)\\n var dfs func(int) int64\\n dfs = func(i int) int64 {\\n if i >= n {\\n return 0\\n }\\n p := &memo[i]\\n if *p == 0 {\\n q := questions[i]\\n *p = max(dfs(i+1), dfs(i+q[1]+1)+int64(q[0]))\\n }\\n return *p\\n }\\n return dfs(0)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{questions}$ 的长度。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\\\\times$ 单个状态的计算时间。本题状态个数等于 $\\\\mathcal{O}(n)$,单个状态的计算时间为 $\\\\mathcal{O}(1)$,所以总的时间复杂度为 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。保存多少状态,就需要多少空间。
\\n四、1:1 翻译成递推
\\n我们可以去掉递归中的「递」,只保留「归」的部分,即自底向上计算。
\\n具体来说,$f[i]$ 的定义和 $\\\\textit{dfs}(i)$ 的定义是完全一样的,都表示剩余问题的下标为 $[i,n-1]$,计算从这些问题中可以获得的最大分数。
\\n相应的递推式(状态转移方程)也和 $\\\\textit{dfs}$ 一样:
\\n$$
\\n
\\nf[i] = \\\\max(f[i+1], f[j] + \\\\textit{points}_i)
\\n$$其中 $j = \\\\min(i+\\\\textit{brainpower}_i+1, n)$,这里把 $i>n$ 的状态都视作 $i=n$。
\\n初始值:$f[n] = 0$,翻译自递归边界 $\\\\textit{dfs}(i\\\\ge n)=0$。
\\n答案为 $f[0]$,翻译自递归入口 $\\\\textit{dfs}(0)$。
\\n答疑
\\n问:如何思考循环顺序?什么时候要正序枚举,什么时候要倒序枚举?
\\n答:这里有一个通用的做法:盯着状态转移方程,想一想,要计算 $f[i]$,必须先把 $f[i+1]$ 和 $f[i+\\\\textit{brainpower}_i+1]$ 算出来,那么只有 $i$ 从大到小枚举才能做到。
\\n\\nclass Solution:\\n def mostPoints(self, questions: List[List[int]]) -> int:\\n n = len(questions)\\n f = [0] * (n + 1)\\n for i in range(n - 1, -1, -1):\\n j = min(i + questions[i][1] + 1, n)\\n f[i] = max(f[i + 1], f[j] + questions[i][0])\\n return f[0]\\n
\\nclass Solution {\\n public long mostPoints(int[][] questions) {\\n int n = questions.length;\\n long[] f = new long[n + 1];\\n for (int i = n - 1; i >= 0; i--) {\\n int j = Math.min(i + questions[i][1] + 1, n);\\n f[i] = Math.max(f[i + 1], f[j] + questions[i][0]);\\n }\\n return f[0];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long mostPoints(vector<vector<int>>& questions) {\\n int n = questions.size();\\n vector<long long> f(n + 1);\\n for (int i = n - 1; i >= 0; i--) {\\n int j = min(i + questions[i][1] + 1, n);\\n f[i] = max(f[i + 1], f[j] + questions[i][0]);\\n }\\n return f[0];\\n }\\n};\\n
\\nfunc mostPoints(questions [][]int) int64 {\\n n := len(questions)\\n f := make([]int64, n+1)\\n for i, q := range slices.Backward(questions) {\\n j := min(i+q[1]+1, n)\\n f[i] = max(f[i+1], f[j]+int64(q[0]))\\n }\\n return f[0]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{questions}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n五、另一种做法:从左往右递推
\\n分析
\\n从左往右递推的困难之处在于,并不好确定该从哪里转移过来:已知当前可以解决的问题是 $i$,那么上一个可以解决的问题是什么?
\\n回顾记忆化搜索的过程:
\\n\\n
\\n- 如果可以解决问题 $i$,但不去解决它,那么下一个可以解决的问题是 $i+1$,即 $i\\\\to i+1$。
\\n- 如果可以解决问题 $i$,并且去解决它,那么下一个可以解决的问题是 $i+\\\\textit{brainpower}_i+1$,即 $i\\\\to i+\\\\textit{brainpower}_i+1$。
\\n换句话说,已知当前可以解决的问题是 $i$,那么下一个可以解决的问题是容易知道的,有两个。
\\n但反过来,已知当前可以解决的问题是 $i$,那么上一个可以解决的问题有哪些?可能有很多,并不好处理(除非建图,预处理所有转移来源)。
\\n思路
\\n对于这种知道该去哪,但不好知道该从哪来的 DP,可以用刷表法:用当前状态,更新未来的(右边的)状态。
\\n\\n\\n与之对比,上面的做法叫查表法:用之前的(右边的)状态,计算当前状态。
\\n定义 $f[i]$ 表示在能解决问题 $i$ 时,解决区间 $[0,i-1]$ 内的问题可以获得的最高分数。
\\n对于问题 $i$:
\\n\\n
\\n- 如果不解决(不选),那么问题 $i+1$ 是能解决的,用 $f[i]$ 更新 $f[i+1]$ 的最大值。
\\n- 如果解决(选),设 $j=\\\\min(i+\\\\textit{brainpower}_i+1,n)$,问题 $j$ 是能解决的,用 $f[i]+\\\\textit{point}_i$ 更新 $f[j]$ 的最大值。
\\n初始值 $f[0]=0$。区间 $[0,-1]$ 是空的,没有问题,得分为 $0$。
\\n答案为 $f[n]$。(把 $n$ 当作一个虚拟的问题)
\\n\\nclass Solution:\\n def mostPoints(self, questions: List[List[int]]) -> int:\\n n = len(questions)\\n f = [0] * (n + 1)\\n for i, (point, brainpower) in enumerate(questions):\\n f[i + 1] = max(f[i + 1], f[i])\\n j = min(i + brainpower + 1, n)\\n f[j] = max(f[j], f[i] + point)\\n return f[n]\\n
\\nclass Solution {\\n public long mostPoints(int[][] questions) {\\n int n = questions.length;\\n long[] f = new long[n + 1];\\n for (int i = 0; i < n; i++) {\\n f[i + 1] = Math.max(f[i + 1], f[i]);\\n int[] q = questions[i];\\n int j = Math.min(i + q[1] + 1, n);\\n f[j] = Math.max(f[j], f[i] + q[0]);\\n }\\n return f[n];\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n long long mostPoints(vector<vector<int>>& questions) {\\n int n = questions.size();\\n vector<long long> f(n + 1);\\n for (int i = 0; i < n; i++) {\\n f[i + 1] = max(f[i + 1], f[i]);\\n auto& q = questions[i];\\n int j = min(i + q[1] + 1, n);\\n f[j] = max(f[j], f[i] + q[0]);\\n }\\n return f[n];\\n }\\n};\\n
\\nfunc mostPoints(questions [][]int) int64 {\\n n := len(questions)\\n f := make([]int64, n+1)\\n for i, q := range questions {\\n f[i+1] = max(f[i+1], f[i])\\n j := min(i+q[1]+1, n)\\n f[j] = max(f[j], f[i]+int64(q[0]))\\n }\\n return f[n]\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{questions}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n更多相似题目,见 动态规划题单 中的「§7.1 一维 DP」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前言 本题其实是 198. 打家劫舍 的变形题:如果选 $\\\\textit{questions}[i]$,那么接下来的 $\\\\textit{brainpower}_i$ 个问题都不能选。打家劫舍那题相当于 $\\\\textit{brainpower}_i=1$。\\n\\n一、寻找子问题\\n\\n在示例 1 中,我们要解决的问题(原问题)是:\\n\\n剩余问题的下标为 $[0,3]$,计算从这些问题中可以获得的最大分数。\\n\\n讨论 $\\\\textit{questions}[0]$ 选或不选:\\n\\n如果不选,子问题为:剩余问题的下标为 $[1,3]$,计算从这些问题中可以获得的最大分数。\\n如果…","guid":"https://leetcode.cn/problems/solving-questions-with-brainpower//solution/dao-xu-dp-by-endlesscheng-2qkc","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-16T04:07:14.479Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"连接两字母单词得到的最长回文串","url":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/lian-jie-liang-zi-mu-dan-ci-de-dao-de-zu-vs99","content":"方法一:贪心 + 哈希表
\\n思路与算法
\\n根据回文串的定义,回文串可以由奇数或者偶数个 $\\\\textit{words}$ 中的单词拼接而成,但必须满足以下条件:
\\n\\n
\\n- \\n
\\n如果数量为奇数,那么位于正中间的单词必须是回文字符串(即两个字符相等);
\\n- \\n
\\n每个单词和反转后对应位置的单词必须互为反转字符串。
\\n根据上面的两个条件,我们可以得出构造最长回文串的规则:
\\n\\n
\\n- \\n
\\n对于两个字符不同的单词,需要尽可能多的成对选择它和它的反转字符串(如有);
\\n- \\n
\\n对于两个字符相同的单词,需要尽可能多的成对选择该单词;
\\n- \\n
\\n如果按照上述条件挑选后,仍然存在未被选择的两个字符相同的单词(此时该字符串只可能有一个未被选择,且该字符串一定在 $\\\\textit{words}$ 中出现奇数次),我们可以任意选择一个。
\\n因此,我们用哈希表统计 $\\\\textit{words}$ 中每个单词的出现次数。随后,我们遍历哈希表的所有元素,并用 $\\\\textit{res}$ 维护可能构成回文字符串的最长长度,同时用初值为 $\\\\texttt{false}$ 的布尔变量 $\\\\textit{mid}$ 判断是否存在可以作为中心单词的、出现奇数次的回文单词。在遍历到字符串 $\\\\textit{word}$ 时,我们首先求出它反转后的字符串 $\\\\textit{rev}$,此时根据 $\\\\textit{word}$ 与 $\\\\textit{rev}$ 的关系,有以下两种情况:
\\n\\n
\\n- \\n
\\n$\\\\textit{word} \\\\not = \\\\textit{rev}$,此时我们需要统计两者在 $\\\\textit{words}$ 出现次数的最小值,即为成对选择的最多数目。假设此时对数为 $n$,则其对最长回文字符串贡献的字符长度为 $4n$,我们将 $\\\\textit{res}$ 加上对应值;
\\n- \\n
\\n$\\\\textit{word} = \\\\textit{rev}$,此时可以构成的对数为 $\\\\lfloor m / 2 \\\\rfloor$,即对最长回文字符串贡献的字符长度为 $4\\\\lfloor m / 2 \\\\rfloor$,我们同样将 $\\\\textit{res}$ 加上对应值。除此以外,我们还需要判断 $\\\\textit{word}$ 的出现次数 $m$ 是否为奇数:
\\n\\n
\\n- \\n
\\n如果 $m$ 为奇数,则存在可以作为中心单词的剩余回文单词,我们将 $\\\\textit{mid}$ 置为 $\\\\texttt{true}$;
\\n- \\n
\\n如果 $m$ 为偶数,则不存在可以作为中心单词的剩余回文单词,我们不改变 $\\\\textit{mid}$ 的取值。
\\n最后,我们根据 $\\\\textit{mid}$ 的取值,判断最长回文串是否含有中心单词。如果 $\\\\textit{mid}$ 为 $\\\\texttt{true}$,则代表含有,我们将 $\\\\textit{res}$ 加上 $2$;反之则没有,我们不进行任何操作。
\\n最后,我们返回 $\\\\textit{res}$ 作为最长回文串的长度。
\\n细节
\\n在遍历哈希表中的每个单词时,为了避免重复计算成对选择的单词,我们只在 $\\\\textit{word}$ 的字典序大于等于 $\\\\textit{rev}$ 时更新 $\\\\textit{res}$。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int longestPalindrome(vector<string>& words) {\\n unordered_map<string, int> freq; // 单词出现次数\\n for (const string& word: words) {\\n ++freq[word];\\n }\\n int res = 0; // 最长回文串长度\\n bool mid = false; // 是否含有中心单词\\n for (const auto& [word, cnt]: freq) {\\n // 遍历出现的单词,并更新长度\\n string rev = string(1, word[1]) + word[0]; // 反转后的单词\\n if (word == rev) {\\n if (cnt % 2 == 1) {\\n mid = true;\\n }\\n res += 2 * (cnt / 2 * 2);\\n }\\n else if (word > rev) { // 避免重复遍历\\n res += 4 * min(freq[word], freq[rev]);\\n }\\n }\\n if (mid) {\\n // 含有中心单词,更新长度\\n res += 2;\\n }\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def longestPalindrome(self, words: List[str]) -> int:\\n freq = Counter(words) # 单词出现次数\\n res = 0 # 最长回文串长度\\n mid = False # 是否含有中心单词\\n for word, cnt in freq.items():\\n # 遍历出现的单词,并更新长度\\n rev = word[1] + word[0] # 反转后的单词\\n if word == rev:\\n if cnt % 2 == 1:\\n mid = True\\n res += 2 * (cnt // 2 * 2)\\n elif word > rev: # 避免重复遍历\\n res += 4 * min(freq[word], freq[rev])\\n if mid:\\n # 含有中心单词,更新长度\\n res += 2\\n return res\\n
###Java
\\n\\nclass Solution {\\n public int longestPalindrome(String[] words) {\\n Map<String, Integer> freq = new HashMap<>();\\n for (String word : words) {\\n freq.put(word, freq.getOrDefault(word, 0) + 1);\\n }\\n int res = 0;\\n boolean mid = false;\\n for (Map.Entry<String, Integer> entry : freq.entrySet()) {\\n String word = entry.getKey();\\n int cnt = entry.getValue();\\n String rev = \\"\\" + word.charAt(1) + word.charAt(0);\\n if (word.equals(rev)) {\\n if (cnt % 2 == 1) {\\n mid = true;\\n }\\n res += 2 * (cnt / 2 * 2);\\n } else if (word.compareTo(rev) > 0) {\\n res += 4 * Math.min(freq.getOrDefault(word, 0), freq.getOrDefault(rev, 0));\\n }\\n }\\n if (mid) {\\n res += 2;\\n }\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int LongestPalindrome(string[] words) {\\n Dictionary<string, int> freq = new Dictionary<string, int>();\\n foreach (string word in words) {\\n freq[word] = freq.GetValueOrDefault(word, 0) + 1;\\n }\\n int res = 0;\\n bool mid = false;\\n foreach (var entry in freq) {\\n string word = entry.Key;\\n int cnt = entry.Value;\\n string rev = \\"\\" + word[1] + word[0];\\n if (word == rev) {\\n if (cnt % 2 == 1) {\\n mid = true;\\n }\\n res += 2 * (cnt / 2 * 2);\\n } else if (string.Compare(word, rev) > 0) {\\n res += 4 * Math.Min(freq.GetValueOrDefault(word, 0), freq.GetValueOrDefault(rev, 0));\\n }\\n }\\n if (mid) {\\n res += 2;\\n }\\n return res;\\n }\\n}\\n
###Go
\\n\\nfunc longestPalindrome(words []string) int {\\n freq := make(map[string]int)\\n for _, word := range words {\\n freq[word]++\\n }\\n res := 0\\n mid := false\\n for word, cnt := range freq {\\n rev := string(word[1]) + string(word[0])\\n if word == rev {\\n if cnt%2 == 1 {\\n mid = true\\n }\\n res += 2 * (cnt / 2 * 2)\\n } else if strings.Compare(word, rev) > 0 {\\n res += 4 * min(freq[word], freq[rev])\\n }\\n }\\n if mid {\\n res += 2\\n }\\n return res\\n}\\n
###C
\\n\\nint longestPalindrome(char** words, int wordsSize) {\\n // 统计单词频率\\n int freq[26 * 26] = {0}; // 26 * 26 表示所有可能的 2 字母组合\\n for (int i = 0; i < wordsSize; i++) {\\n int idx = (words[i][0] - \'a\') * 26 + (words[i][1] - \'a\');\\n freq[idx]++;\\n }\\n int res = 0;\\n int mid = 0;\\n for (int i = 0; i < 26 * 26; i++) {\\n if (freq[i] == 0) continue;\\n int rev = (i % 26) * 26 + (i / 26); // 反转单词的索引\\n if (i == rev) {\\n if (freq[i] % 2 == 1) {\\n mid = 1;\\n }\\n res += 2 * (freq[i] / 2 * 2);\\n } else if (i > rev) {\\n res += 4 * fmin(freq[i], freq[rev]);\\n }\\n }\\n if (mid) {\\n res += 2;\\n }\\n return res;\\n}\\n
###JavaScript
\\n\\nvar longestPalindrome = function(words) {\\n const freq = new Map();\\n for (const word of words) {\\n freq.set(word, (freq.get(word) || 0) + 1);\\n }\\n let res = 0;\\n let mid = false;\\n for (const [word, cnt] of freq.entries()) {\\n const rev = word[1] + word[0];\\n if (word === rev) {\\n if (cnt % 2 === 1) {\\n mid = true;\\n }\\n res += 2 * (Math.floor(cnt / 2) * 2);\\n } else if (word > rev) {\\n res += 4 * Math.min(freq.get(word) || 0, freq.get(rev) || 0);\\n }\\n }\\n if (mid) {\\n res += 2;\\n }\\n return res;\\n}\\n
###TypeScript
\\n\\nfunction longestPalindrome(words: string[]): number {\\n const freq = new Map<string, number>();\\n for (const word of words) {\\n freq.set(word, (freq.get(word) || 0) + 1);\\n }\\n let res = 0;\\n let mid = false;\\n for (const [word, cnt] of freq.entries()) {\\n const rev = word[1] + word[0];\\n if (word === rev) {\\n if (cnt % 2 === 1) {\\n mid = true;\\n }\\n res += 2 * (Math.floor(cnt / 2) * 2);\\n } else if (word > rev) {\\n res += 4 * Math.min(freq.get(word) || 0, freq.get(rev) || 0);\\n }\\n }\\n if (mid) {\\n res += 2;\\n }\\n return res;\\n}\\n
###Rust
\\n\\nuse std::collections::HashMap;\\nuse std::cmp::{min, max};\\n\\nimpl Solution {\\n pub fn longest_palindrome(words: Vec<String>) -> i32 {\\n let mut freq = HashMap::new();\\n for word in &words {\\n *freq.entry(word.clone()).or_insert(0) += 1;\\n }\\n let mut res = 0;\\n let mut mid = false;\\n for (word, cnt) in &freq {\\n let rev = format!(\\"{}{}\\", word.chars().nth(1).unwrap(), word.chars().nth(0).unwrap());\\n if *word == rev {\\n if cnt % 2 == 1 {\\n mid = true;\\n }\\n res += 2 * (cnt / 2 * 2);\\n } else if *word > rev {\\n res += 4 * min(*cnt, *freq.get(&rev).unwrap_or(&0));\\n }\\n }\\n if mid {\\n res += 2;\\n }\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心 + 哈希表 思路与算法\\n\\n根据回文串的定义,回文串可以由奇数或者偶数个 $\\\\textit{words}$ 中的单词拼接而成,但必须满足以下条件:\\n\\n如果数量为奇数,那么位于正中间的单词必须是回文字符串(即两个字符相等);\\n\\n每个单词和反转后对应位置的单词必须互为反转字符串。\\n\\n根据上面的两个条件,我们可以得出构造最长回文串的规则:\\n\\n对于两个字符不同的单词,需要尽可能多的成对选择它和它的反转字符串(如有);\\n\\n对于两个字符相同的单词,需要尽可能多的成对选择该单词;\\n\\n如果按照上述条件挑选后,仍然存在未被选择的两个字符相同的单词…","guid":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/lian-jie-liang-zi-mu-dan-ci-de-dao-de-zu-vs99","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-10T01:54:59.908Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Chthollist】双周赛第69场LeetCode5962:连接单词得到的最长回文串:哈希计数 & 回文单词、奇偶分类讨论","url":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/chthollist-shuang-zhou-sai-di-69chang-le-paij","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为数组 $\\\\textit{words}$ 的长度。即为遍历统计数组各个元素的出现次数以及遍历哈希表计算最长回文串长度的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(\\\\min(n, |\\\\Sigma|^2))$,即为哈希表的空间开销。哈希表的大小受到两方面限制:
\\n\\n
\\n- 哈希表的大小小于等于两字母单词的数量,即 $O(|\\\\Sigma|^2)$;
\\n- 哈希表的大小小于等于 $\\\\textit{words}$ 中单词数量,即 $O(n)$。
\\n\\n
\\n- 双周赛第69场:LeetCode:5962.连接单词得到的最长回文串\\n
\\n\\n
\\n- 时间:2022-01-09
\\n- 力扣难度:Medium
\\n- 个人难度:Medium
\\n- 数据结构:哈希表、字符串
\\n- 算法:哈希计数
\\n
\\n双周赛第69场:LeetCode:5962.连接单词得到的最长回文串
\\n1. 题目描述
\\n\\n
\\n- \\n
\\n题目:原题链接
\\n\\n
\\n- 给你一个字符串数组 words,words 中每个元素都是一个包含两个小写英文字母的单词。
\\n- 请你从 words 中选择一些元素并按任意顺序连接它们,并得到一个尽可能长的回文串,回文串指的是从前往后和从后往前读一样的字符串。
\\n- 每个元素至多只能使用一次,请你返回你能得到的最长回文串的长度。
\\n- 如果没办法得到任何一个回文串,请你返回 0 。
\\n- 注意:每一个word都是两个小写字母
\\n- \\n
\\n输入输出规范
\\n\\n
\\n- 输入:字符串数组words
\\n- 输出:最长的回文串的长度
\\n- \\n
\\n输入输出示例
\\n\\n
\\n- 输入:words = [\\"ab\\",\\"ty\\",\\"yt\\",\\"lc\\",\\"cl\\",\\"ab\\"]
\\n- 输出:8
\\n
\\n2. 方法:哈希计数+奇偶分析
\\n\\n
\\n- \\n
\\n思路:哈希计数+奇偶分析
\\n\\n
\\n- 根据题意可知,字符串数组中,存在两种类型的单词,同时,单词可能会重复,所以需要进行计数,注意单词均为两个小写字母,这可以简化计数方式\\n
\\n\\n
\\n- 本身就是回文串的单词,如:aa,对于这种类型,可以使用
\\nnew int[26]
数组来进行哈希计数- 本身不是回文串的单词,如:ab,对于这种类型,可以使用Map来计数
\\n- 此外,还可以通过二维数组
\\nint[26][26]
来计数,本题解没有使用这种方式,感兴趣的可以尝试下- 除此之外,本题需要根据单词是否回文串分类讨论
\\n- 情况一:回文串单词,此时需要先进行哈希计数(第一次遍历),之后再根据每个存在的单词的个数的奇偶性进行讨论(第二次遍历)\\n
\\n\\n
\\n- 个数为偶数时:可以对称的放到最终的最长回文串的两侧,即此时总长度满足
\\nlen += count * 2
- 个数为奇数时:可以对称的将小于该奇数的最大偶数对称放在两侧,同样有
\\nlen += (count - 1) * 2
,并且额外多出来的一个回文单词,可以放在最长回文串的正中间,即len += 2
- 情况二:非回文串单词,此时可以在进行哈希计数的同时,判断该单词的逆序单词是否存在于哈希表中(一次遍历)\\n
\\n\\n
\\n- 存在时可以将其分别放于最长回文串的两侧,有
\\nlen += 4
- 不存在时将当前单词放入哈希表,等待验证后续单词是否会与其匹配
\\n\\n
\\n
\\n- 判断单词是否回文的方式可以借助
\\nStringBuilder
的reverse()
方法- \\n
\\n复杂度分析:n 是字符串数组的长度,即单词个数
\\n\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n- \\n
\\n题解
\\n\\n###java
\\n\\n","description":"双周赛第69场:LeetCode:5962.连接单词得到的最长回文串 时间:2022-01-09\\n力扣难度:Medium\\n个人难度:Medium\\n数据结构:哈希表、字符串\\n算法:哈希计数\\n1. 题目描述\\n\\n题目:原题链接\\n\\n给你一个字符串数组 words,words 中每个元素都是一个包含两个小写英文字母的单词。\\n请你从 words 中选择一些元素并按任意顺序连接它们,并得到一个尽可能长的回文串,回文串指的是从前往后和从后往前读一样的字符串。\\n每个元素至多只能使用一次,请你返回你能得到的最长回文串的长度。\\n如果没办法得到任何一个回文串…","guid":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/chthollist-shuang-zhou-sai-di-69chang-le-paij","author":"chthollists","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-09T13:41:15.053Z","media":[{"url":"https://pic.leetcode-cn.com/1641735797-xBAaWY-%E5%8F%8C%E5%91%A8%E8%B5%9B69LC5962%E8%BF%9E%E6%8E%A5%E5%8D%95%E8%AF%8D%E7%9A%84%E6%9C%80%E9%95%BF%E5%9B%9E%E6%96%87%E4%B8%B2.png","type":"photo","width":1113,"height":309,"blurhash":"LURfCAz;bo=xtRiwaKoz~oXmaQN{"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Cpp/Java]运用两数之和的思路求解","url":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/javayun-yong-liang-shu-zhi-he-de-si-lu-q-bap5","content":"> public int longestPalindrome(String[] words) {\\n > if (words == null || words.length == 0) return 0;\\n > Map<String, Integer> wordMap = new HashMap<>(); // 存放不是回文串的word\\n > int[] hash = new int[26];\\n > int palindromeLen = 0;\\n > String reverse = \\"\\";\\n > for (String word : words) {\\n > StringBuilder sb = new StringBuilder();\\n > reverse = sb.append(word).reverse().toString();\\n > // 分类讨论,本身就是回文串的word,直接计入最长回文串\\n > if (word.equals(reverse)) { // if (word.charAt(0) == word.charAt(1))\\n > hash[word.charAt(0) - \'a\']++;\\n > } else {\\n > // 本身不是回文串的word,去匹配对应的reverse结构\\n > if (wordMap.get(reverse) != null && wordMap.get(reverse) > 0) {\\n > // 匹配成功,可以添加到最长回文串的两端,len + 2 * len(word)\\n > palindromeLen += 4;\\n > wordMap.put(reverse, wordMap.get(reverse) - 1);\\n > } else {\\n > // 匹配失败,将当前word加入wordMap\\n > wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);\\n > }\\n > }\\n > }\\n > boolean last = false;\\n > for (int count : hash) {\\n > if ((count & 1) == 0) {\\n > palindromeLen += 2 * count; // word.length()\\n > } else {\\n > palindromeLen += 2 * (count - 1); // word.length()\\n > last = true;\\n > }\\n > }\\n > if(last) palindromeLen += 2;\\n > return palindromeLen;\\n > }\\n > ```\\n\\n---\\n\\n## 3. 方法二:优化\\n\\n* 思考与优化\\n\\n * 思考:本题的输入具有一定的限制\\n * 限制一:单词都是两个字母\\n * 限制二:单词都是小写字母\\n * 优化\\n * 对于限制一,此时对放在正中间的回文单词没有要求,如果单词的字母个数没有限制的话,如:a、cc、sss,此时放在正中间的应该是最长的回文单词\\n * 对于限制二,此时可以使用数组哈希,如果单词可以有大小写字母甚至一些其他符号,此时应该使用键值对哈希\\n\\n* 复杂度分析:n 是字符串数组的长度,即单词个数\\n\\n * 时间复杂度:$O(n)$\\n * 空间复杂度:$O(n)$\\n\\n* 题解\\n\\n > \\n###java\\n```java\\n > public int longestPalindrome(String[] words) {\\n > if (words == null || words.length == 0) return 0;\\n > Map<String, Integer> wordMap = new HashMap<>(); // 存放不是回文串的word\\n > Map<String, Integer> palindromeMap = new HashMap<>(); // 存放是回文串的word\\n > int palindromeLen = 0;\\n > String reverse = \\"\\";\\n > for (String word : words) {\\n > StringBuilder sb = new StringBuilder();\\n > reverse = sb.append(word).reverse().toString();\\n > // 分类讨论,本身就是回文串的word,直接添加到map中,后面在进行分析\\n > if (word.equals(reverse)) { // if (word.charAt(0) == word.charAt(1))\\n > palindromeMap.put(word, palindromeMap.getOrDefault(word, 0) + 1);\\n > } else {\\n > // 本身不是回文串的word,去匹配对应的reverse结构\\n > if (wordMap.get(reverse) != null && wordMap.get(reverse) > 0) {\\n > // 匹配成功,可以添加到最长回文串的两端,len + 2 * len(word)\\n > palindromeLen += word.length() << 1;\\n > wordMap.put(reverse, wordMap.get(reverse) - 1);\\n > } else {\\n > // 匹配失败,将当前word加入wordMap\\n > wordMap.put(word, wordMap.getOrDefault(word, 0) + 1);\\n > }\\n > }\\n > }\\n > \\n > // 分析回文word的情况,对应palindromeMap\\n > int maxSingleLen = 0;\\n > Set<Map.Entry<String, Integer>> wordEntries = palindromeMap.entrySet();\\n > for (Map.Entry<String, Integer> wordEntry : wordEntries) {\\n > // 偶数个回文word,直接全部计入最长回文串长度中\\n > if ((wordEntry.getValue() & 1) == 0) {\\n > palindromeLen += wordEntry.getKey().length() * wordEntry.getValue();\\n > }else {\\n > // 奇数个回文word,只能直接将(count - 1)个word计入最长回文串长度中,最后一个与其他单独的word进行长度比较,选择最长的\\n > palindromeLen += wordEntry.getKey().length() * (wordEntry.getValue() - 1);\\n > maxSingleLen = Math.max(maxSingleLen, wordEntry.getKey().length());\\n > }\\n > }\\n > // 将选择的最长的单个回文word计入最长回文串长度中\\n > palindromeLen += maxSingleLen;\\n > return palindromeLen;\\n > }\\n > ```\\n\\n---\\n\\n## 最后\\n新人LeetCoder,发布的题解有些会参考其他大佬的思路(参考资料的链接会放在最下面),欢迎大家关注我 ~ ~ ~\\n如果本文有所帮助的话,希望大家可以给个三连「点赞」&「收藏」&「关注」 ~ ~ ~\\n也希望大家有空的时候光临我的「[个人博客](https://chthollists.github.io/)」。
题意分析
\\n其实这道题本质上和1.两数之和是一样的,也就是本题把两数之和中要寻找的
\\ntarget
变为了回文子串
\\n字符串本身是否为回文子串对我们所构建的最长回文子串有影响
\\n例如:[lc, cl, gg, ll, gg, ll, gg, ccc]
\\n
\\n- 对于本身不为回文字符串的
\\n\\"lc\\"
和\\"cl\\"
可以位于我们最终所构建的字符串的两端(顺序无影响),运用两数之和的思路进行求解即可- 对于本身为回文字符串的三个字符串分别为
\\n\\"gg\\"
,\\"ll\\"
,\\"ccc\\"
\\n\\n
\\n- 如果某个字符串的个数为
\\n1
,则该字符串为添加到最中心部分的字符串,我们需要统计这其中的最长的那个字符串的长度- 如果某个字符串的个数大于等于
\\n2
,则将其两两添加到我们所构建的字符串两端,一次只能分走2
个字符串,一直分下去,直到当该种字符串剩余为1
或0
时为止。- 若剩余个数为
\\n1
,应当以个数为1
时再次进行处理- 基于以上思路和示例,我们所构建的最终的字串为
\\n\\"lcllggcccggllcl\\"
\\n解题思路
\\n\\n
\\n- 创建两个哈希表,一个储存本身不是回文子串的字符串,另一个统计本身为回文字串的字符串
\\n- 遍历数组:若该字符串为非回文串,则检查
\\nmap
中是否有该字符串的反转字符串;若存在,则该反转字符串数量减一,否则储存该字符串。若该字符串为回文字符串,则先存入equal
表中,后续再做处理- 遍历
\\nequal
表,查找其中的每一个字串的数量,若数量为1
,则用max
储存其长度最大值;若大于等于2,则每次减少两个(这里使用除法即可),直到剩余量为1
或0
。剩余为1
时,我们再次按照数量为1
时进行处理- 最终返回值为
\\n两边的回文子串数 + 中间部分的回文子串的最长长度
代码
\\n###java
\\n\\nimport java.util.*;\\n\\nclass Solution {\\n public int longestPalindrome(String[] words) {\\n Map<String, Integer> map = new HashMap<>();\\n Map<String, Integer> equal = new HashMap<>();\\n\\n int ans = 0;\\n\\n for (String word : words) {\\n StringBuilder sb = new StringBuilder();\\n sb.append(word);\\n String reverse = sb.reverse().toString();\\n\\n if (word.equals(reverse)) {\\n equal.put(reverse, equal.getOrDefault(reverse, 0) + 1);\\n continue;\\n }\\n\\n if (map.containsKey(reverse)) {\\n ans += word.length() * 2;\\n\\n if (map.get(reverse) == 1) {\\n map.remove(reverse);\\n } else {\\n map.put(reverse, map.get(reverse) - 1);\\n }\\n } else {\\n map.put(word, map.getOrDefault(word, 0) + 1);\\n }\\n }\\n\\n int max = 0;\\n\\n for (Map.Entry<String, Integer> entry : equal.entrySet()) {\\n if (entry.getValue() == 1) {\\n max = Math.max(max, entry.getKey().length());\\n } else {\\n if (entry.getValue() % 2 == 1) {\\n ans += (entry.getValue() - 1) * entry.getKey().length();\\n max = Math.max(max, entry.getKey().length());\\n } else {\\n ans += entry.getValue() * entry.getKey().length();\\n }\\n }\\n }\\n\\n return ans + max;\\n }\\n}\\n
###C++
\\n\\n","description":"题意分析 其实这道题本质上和1.两数之和是一样的,也就是本题把两数之和中要寻找的target变为了回文子串\\n 字符串本身是否为回文子串对我们所构建的最长回文子串有影响\\n 例如:[lc, cl, gg, ll, gg, ll, gg, ccc]\\n\\n对于本身不为回文字符串的\\"lc\\"和\\"cl\\"可以位于我们最终所构建的字符串的两端(顺序无影响),运用两数之和的思路进行求解即可\\n对于本身为回文字符串的三个字符串分别为\\"gg\\", \\"ll\\", \\"ccc\\"\\n如果某个字符串的个数为1,则该字符串为添加到最中心部分的字符串,我们需要统计这其中的最长的那个字符串的长度\\n如果某个…","guid":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/javayun-yong-liang-shu-zhi-he-de-si-lu-q-bap5","author":"endless_developy","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-09T00:02:00.365Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【利用奇偶性分类讨论】C++","url":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/li-yong-qi-ou-xing-fen-lei-tao-lun-c-by-9gmln","content":"#include \\"bits/stdc++.h\\"\\n\\nusing namespace std;\\n\\n#define fastIO() ios::sync_with_stdio(false),cin.tie(nullptr),cout.tie(nullptr)\\n#define pb push_back\\n#define PF(x) = ((x) * (x))\\n#define LF(x) = ((x) * PF(x))\\n\\ntypedef long long ll;\\ntypedef unsigned long long ull;\\ntypedef long double ld;\\ntypedef pair<int, int> pii;\\ntypedef pair<ll, ll> pll;\\ntypedef unordered_map<int, int> umap;\\ntypedef unsigned int uint;\\n\\nconst double eps = 1e-6;\\nconst int MOD = 1e9 + 7;\\nconst int inf = 0x3f3f3f3f;\\nconst ll infl = 0x3f3f3f3f3f3f3f3fll;\\n\\n\\nclass Solution {\\npublic:\\n int longestPalindrome(vector<string>& words) {\\n map<string, int> map, equal;\\n int ans = 0;\\n\\n for (string word : words) {\\n string demo;\\n demo += word;\\n std::reverse(demo.begin(), demo.end());\\n\\n if (word == demo) {\\n equal[word]++;\\n } else {\\n map[word]++;\\n }\\n }\\n\\n int add = -1;\\n bool flag = false;\\n for (auto it : map) {\\n string pre = it.first, suf;\\n suf += pre;\\n std::reverse(suf.begin(), suf.end());\\n\\n if (map.count(suf)) {\\n int num = min(it.second, map[suf]);\\n ans += num * 4;\\n map[pre] -= num;\\n map[suf] -= num;\\n }\\n\\n }\\n\\n for (auto it : equal) {\\n if (it.second % 2 == 1 && !flag) {\\n add = 2;\\n it.second--;\\n flag = true;\\n }\\n if (it.second >= 2) {\\n ans += it.second / 2 * 4;\\n }\\n }\\n if (add == 2) ans += 2;\\n return ans;\\n }\\n};\\n
思路:
\\n首先需要对出现的字符串进行统计,统计其出现的次数,然后就可以分情况讨论了:
\\n\\n
\\n- \\n
\\n形如 “ab” 的字符串,只有找到 “ba” 才可以放入答案中进行统计,两者出现次数的最小值即可计入答案 $a$ 中。
\\n\\n
\\n- 为什么?因为我们可以往答案字符串的两端各放一个进行叠加即可。
\\n
\\nab xxxxxxx ba
像这样- \\n
\\n形如 “aa” “bb” 的字符串,可以根据它们出现的次数分为奇数个和偶数个讨论:
\\n\\n
\\n- \\n
\\n偶 + 偶 = 偶,很显然,直接统计入答案 $b$ 中即可;
\\n
\\naa bb bb aa / bb aa aa bb
喜欢咋放就咋放~- \\n
\\n偶 + 奇 = 奇,没问题,可以交叉着放,直接统计入答案 $b$ 中即可,但是得出来的序列出现次数变成了一个奇数;
\\n
\\naa bb aa bb aa
- \\n
\\n奇 + 奇 = 偶,这样就不合法了,例如
\\naa bb aa aa
,不存在一种正确的姿势构成回文串,也就是最终的回文串中不能够同时存在两种出现奇数次的形如 “aa” 字符串。那么,我们可以知道,只能够最多存在一种出现次数为奇数次的形如 “aa” 的字符串,所以需要对奇数次出现的次数进行减1来使他们出现的次数变成偶数。我们可以直接统计奇数次出现的次数,记为$c$,最后减去$c-1$即可(因为只能够存在一个),当然如果没有出现奇数次的话就减去0。
\\n代码:
\\n###c++
\\n\\n","description":"思路: 首先需要对出现的字符串进行统计,统计其出现的次数,然后就可以分情况讨论了:\\n\\n形如 “ab” 的字符串,只有找到 “ba” 才可以放入答案中进行统计,两者出现次数的最小值即可计入答案 $a$ 中。\\n\\n为什么?因为我们可以往答案字符串的两端各放一个进行叠加即可。\\n ab xxxxxxx ba像这样\\n\\n形如 “aa” “bb” 的字符串,可以根据它们出现的次数分为奇数个和偶数个讨论:\\n\\n偶 + 偶 = 偶,很显然,直接统计入答案 $b$ 中即可;\\n aa bb bb aa / bb aa aa bb 喜欢咋放就咋放~\\n\\n偶 + 奇 = 奇…","guid":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/li-yong-qi-ou-xing-fen-lei-tao-lun-c-by-9gmln","author":"uthinkufunny","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-08T17:41:35.703Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"构造题:贪心 + 分类讨论(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/gou-zao-tan-xin-fen-lei-tao-lun-by-endle-dqr8","content":"class Solution {\\npublic:\\n int longestPalindrome(vector<string>& words) {\\n // a: 统计 形如 “ab”\\n // b:统计 所有 形如 “aa” 出现的次数(包括 奇数次 和 偶数次)\\n // c:统计 奇数次 “aa” 出现的个数\\n int a = 0, b = 0, c = 0;\\n unordered_map<string, int> ct;\\n for(int i = 0; i < words.size(); ++i) ct[words[i]]++;\\n for(auto it: ct)\\n {\\n if(it.first[0] == it.first[1]){\\n if(it.second & 1){\\n c++;\\n }\\n b += it.second;\\n }\\n else {\\n string to = \\"\\";\\n to += it.first[1];\\n to += it.first[0];\\n if(ct.count(to)){\\n //对于“ab” 可以删除 “ba”减少遍历次数,但是要乘2\\n a += 2 * min(ct[to], it.second);\\n ct.erase(to);\\n }\\n }\\n }\\n return 2 * (b + a - (c <= 1 ? 0 : c - 1));\\n }\\n};\\n
注意输入的字符串长度都是 $2$。
\\n分类讨论
\\n\\n
\\n- 字符串的两个字母不同:假设有 $2$ 个 $\\\\texttt{ab}$ 和 $3$ 个 $\\\\texttt{ba}$,我们可以在回文串的左半边中放 $2$ 个 $\\\\texttt{ab}$,右半边中的对称位置放 $2$ 个 $\\\\texttt{ba}$。剩下 $1$ 个 $\\\\texttt{ba}$ 没法用,舍去。
\\n- 字符串的两个字母相同:\\n
\\n\\n
\\n- 偶数个:假设有 $4$ 个 $\\\\texttt{aa}$,我们可以在回文串的左半边中放 $2$ 个 $\\\\texttt{aa}$,右半边中的对称位置放 $2$ 个 $\\\\texttt{aa}$。
\\n- 奇数个:假设有 $5$ 个 $\\\\texttt{aa}$,我们可以在回文串的左半边中放 $2$ 个 $\\\\texttt{aa}$,右半边中的对称位置放 $2$ 个 $\\\\texttt{aa}$。多出的 $1$ 个 $\\\\texttt{aa}$ 放在回文串的正中间。
\\n算法
\\n首先遍历 $\\\\textit{words}$,统计每个字符串的出现次数,记在 $\\\\textit{cnt}$ 中。为方便下面枚举,可以用一个 $26 \\\\times 26$ 的二维数组统计。
\\n然后计算总个数,也就是回文串由多少个长为 $2$ 的字符串组成。最后把总个数乘以 $2$,就是回文串的长度。
\\n对于两个字母不同的情况,枚举 $0\\\\le i < j < 26$。设 $c$ 为 $\\\\texttt{ij}$ 和 $\\\\texttt{ji}$ 的出现次数的较小者,我们可以在回文串的左半边中放 $c$ 个 $\\\\texttt{ij}$,右半边中的对称位置放 $c$ 个 $\\\\texttt{ji}$,也就是把总个数增加
\\n$$
\\n
\\n\\\\min(\\\\textit{cnt}[i][j], \\\\textit{cnt}[j][i])\\\\cdot 2
\\n$$对于两个字母相同的情况,枚举 $0\\\\le i < 26$。设 $c=\\\\textit{cnt}[i][i]$,分类讨论:
\\n\\n
\\n- 如果 $c$ 是偶数,那么把总个数增加 $c$。
\\n- 如果 $c$ 是奇数,那么把总个数增加 $c-1$。
\\n- 这两种情况可以统一为:把总个数增加 $c - c\\\\bmod 2$。
\\n最后,如果存在奇数 $\\\\textit{cnt}[i][i]$,那么可以把一个 $\\\\texttt{ii}$ 放在回文串的正中间,总个数额外加一。
\\n\\nclass Solution:\\n def longestPalindrome(self, words: List[str]) -> int:\\n cnt = [[0] * 26 for _ in range(26)] # 更快的写法见【Python3 写法二】\\n for w in words:\\n cnt[ord(w[0]) - ord(\'a\')][ord(w[1]) - ord(\'a\')] += 1\\n\\n ans = odd = 0\\n for i in range(26):\\n c = cnt[i][i]\\n ans += c - c % 2 # 保证结果是偶数,也可以写成 c & ~1\\n odd |= c % 2 # 存在出现奇数次的 cnt[i][i]\\n for j in range(i + 1, 26):\\n ans += min(cnt[i][j], cnt[j][i]) * 2\\n return (ans + odd) * 2 # 上面统计的是字符串个数,乘以 2 就是长度\\n
\\nclass Solution:\\n def longestPalindrome(self, words: List[str]) -> int:\\n cnt = Counter(words)\\n\\n ans = odd = 0\\n for w, c in cnt.items():\\n if w[0] == w[1]:\\n ans += c - c % 2\\n odd |= c % 2\\n elif w[0] < w[1]:\\n ans += min(c, cnt[w[::-1]]) * 2\\n return (ans + odd) * 2\\n
\\nclass Solution {\\n public int longestPalindrome(String[] words) {\\n int[][] cnt = new int[26][26];\\n for (String w : words) {\\n cnt[w.charAt(0) - \'a\'][w.charAt(1) - \'a\']++;\\n }\\n\\n int ans = 0;\\n int odd = 0;\\n for (int i = 0; i < 26; i++) {\\n int c = cnt[i][i];\\n ans += c - c % 2; // 保证结果是偶数,也可以写成 c & ~1\\n odd |= c % 2; // 存在出现奇数次的 cnt[i][i]\\n for (int j = i + 1; j < 26; j++) {\\n ans += Math.min(cnt[i][j], cnt[j][i]) * 2;\\n }\\n }\\n return (ans + odd) * 2; // 上面统计的是字符串个数,乘以 2 就是长度\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int longestPalindrome(vector<string>& words) {\\n int cnt[26][26]{};\\n for (auto& w : words) {\\n cnt[w[0] - \'a\'][w[1] - \'a\']++;\\n }\\n\\n int ans = 0, odd = 0;\\n for (int i = 0; i < 26; i++) {\\n int c = cnt[i][i];\\n ans += c - c % 2; // 保证结果是偶数,也可以写成 c & ~1\\n odd |= c % 2; // 存在出现奇数次的 cnt[i][i]\\n for (int j = i + 1; j < 26; j++) {\\n ans += min(cnt[i][j], cnt[j][i]) * 2;\\n }\\n }\\n return (ans + odd) * 2; // 上面统计的是字符串个数,乘以 2 就是长度\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n\\nint longestPalindrome(char** words, int wordsSize) {\\n int cnt[26][26] = {};\\n for (int i = 0; i < wordsSize; i++) {\\n cnt[words[i][0] - \'a\'][words[i][1] - \'a\']++;\\n }\\n\\n int ans = 0, odd = 0;\\n for (int i = 0; i < 26; i++) {\\n int c = cnt[i][i];\\n ans += c - c % 2; // 保证结果是偶数,也可以写成 c & ~1\\n odd |= c % 2; // 存在出现奇数次的 cnt[i][i]\\n for (int j = i + 1; j < 26; j++) {\\n ans += MIN(cnt[i][j], cnt[j][i]) * 2;\\n }\\n }\\n return (ans + odd) * 2; // 上面统计的是字符串个数,乘以 2 就是长度\\n}\\n
\\nfunc longestPalindrome(words []string) (ans int) {\\ncnt := [26][26]int{}\\nfor _, w := range words {\\ncnt[w[0]-\'a\'][w[1]-\'a\']++\\n}\\n\\nodd := 0 // 是否存在出现奇数次的 cnt[i][i]\\nfor i := range cnt {\\nc := cnt[i][i]\\nans += c - c%2 // 保证结果是偶数,也可以写成 c &^ 1\\nodd |= c % 2 // 存在出现奇数次的 cnt[i][i]\\nfor j := i + 1; j < 26; j++ {\\nans += min(cnt[i][j], cnt[j][i]) * 2\\n}\\n}\\nreturn (ans + odd) * 2 // 上面统计的是字符串个数,乘以 2 就是长度 \\n}\\n
\\nvar longestPalindrome = function(words) {\\n const cnt = Array.from({ length: 26 }, () => Array(26).fill(0));\\n const ordA = \'a\'.charCodeAt(0);\\n for (const w of words) {\\n cnt[w.charCodeAt(0) - ordA][w.charCodeAt(1) - ordA]++;\\n }\\n\\n let ans = 0, odd = 0;\\n for (let i = 0; i < 26; i++) {\\n const c = cnt[i][i];\\n ans += c - c % 2; // 保证结果是偶数,也可以写成 c & ~1\\n odd |= c % 2; // 存在出现奇数次的 cnt[i][i]\\n for (let j = i + 1; j < 26; j++) {\\n ans += Math.min(cnt[i][j], cnt[j][i]) * 2;\\n }\\n }\\n return (ans + odd) * 2; // 上面统计的是字符串个数,乘以 2 就是长度\\n};\\n
\\nimpl Solution {\\n pub fn longest_palindrome(words: Vec<String>) -> i32 {\\n let mut cnt = [[0; 26]; 26];\\n for w in words {\\n let w = w.into_bytes();\\n cnt[(w[0] - b\'a\') as usize][(w[1] - b\'a\') as usize] += 1;\\n }\\n\\n let mut ans = 0;\\n let mut odd = 0;\\n for i in 0..26 {\\n let c = cnt[i][i];\\n ans += c - c % 2; // 保证结果是偶数,也可以写成 c & !1\\n odd |= c % 2; // 存在出现奇数次的 cnt[i][i]\\n for j in i + 1..26 {\\n ans += cnt[i][j].min(cnt[j][i]) * 2;\\n }\\n }\\n (ans + odd) * 2 // 上面统计的是字符串个数,乘以 2 就是长度\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n + |\\\\Sigma|^2)$,其中 $n$ 是 $\\\\textit{words}$ 的长度,$|\\\\Sigma|=26$ 是字符集合的大小。
\\n- 空间复杂度:$\\\\mathcal{O}(|\\\\Sigma|^2)$。
\\n更多相似题目,见下面贪心题单的「§3.2 回文串贪心」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"注意输入的字符串长度都是 $2$。 分类讨论\\n字符串的两个字母不同:假设有 $2$ 个 $\\\\texttt{ab}$ 和 $3$ 个 $\\\\texttt{ba}$,我们可以在回文串的左半边中放 $2$ 个 $\\\\texttt{ab}$,右半边中的对称位置放 $2$ 个 $\\\\texttt{ba}$。剩下 $1$ 个 $\\\\texttt{ba}$ 没法用,舍去。\\n字符串的两个字母相同:\\n偶数个:假设有 $4$ 个 $\\\\texttt{aa}$,我们可以在回文串的左半边中放 $2$ 个 $\\\\texttt{aa}$,右半边中的对称位置放 $2$ 个 $\\\\texttt…","guid":"https://leetcode.cn/problems/longest-palindrome-by-concatenating-two-letter-words//solution/gou-zao-tan-xin-fen-lei-tao-lun-by-endle-dqr8","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-08T16:14:06.668Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】动态规划运用题","url":"https://leetcode.cn/problems/cat-and-mouse//solution/gong-shui-san-xie-dong-tai-gui-hua-yun-y-0bx1","content":"动态规划
\\n数据范围只有 $50$,使得本题的难度大大降低。
\\n定义 $f[k][i][j]$ 为当前进行了 $k$ 步,老鼠所在位置为 $i$,猫所在的位置为 $j$ 时,最终的获胜情况($0$ 为平局,$1$ 和 $2$ 分别代表老鼠和猫获胜),起始我们让所有的 $f[k][i][j] = -1$(为无效值),最终答案为 $f[0][1][2]$。
\\n不失一般性的考虑 $f[i][j][k]$ 该如何转移,根据题意,将当前所在位置 $i$ 和 $j$ 结合「游戏结束,判定游戏」的规则来分情况讨论:
\\n\\n
\\n- 若 $i = 0$,说明老鼠位于洞中,老鼠获胜,此时有 $f[k][i][j] = 1$;
\\n- 若 $i = j$,说明两者为与同一位置,猫获胜,此时有 $f[k][i][j] = 2$;
\\n- 考虑如何定义平局,即 $f[k][i][j] = 0$ 的情况?
\\n我们最多有 $n$ 个位置,因此 $(i, j)$ 位置对组合数最多为 $n^2$ 种,然后 $k$ 其实代表的是老鼠先手还是猫先手,共有 $2$ 种可能,因此状态数量数据有明确上界为 $2 * n^2$。
\\n所以我们可以设定 $k$ 的上界为 $2 * n^2$(抽屉原理,走超过 $2 * n^2$ 步,必然会有相同的局面出现过至少两次),但是这样的计算量为 $2 * 50^2 * 50 * 50 = 1.25 * 10^7$,有
\\nTLE
风险,转移过程增加剪枝,可以过。而更小的 $k$ 值需要证明:在最优策略中,相同局面(状态)成环的最大长度的最小值为 $k$。
\\n题目规定轮流移动,且只能按照 $graph$ 中存在的边进行移动。
\\n\\n
\\n- \\n
\\n如果当前 $k$ 为偶数(该回合老鼠移动),此时所能转移所有点为 $f[k + 1][ne][j]$,其中 $ne$ 为 $i$ 所能到达的点。由于采用的是最优移动策略,因此 $f[k][i][j]$ 会优先往 $f[k + 1][ne][j] = 1$ 的点移动(自身获胜),否则往 $f[k + 1][ne][j] = 0$ 的点移动(平局),再否则才是往 $f[k + 1][ne][j] = 2$ 的点移动(对方获胜);
\\n- \\n
\\n同理,如果当前 $k$ 为奇数(该回合猫移动),此时所能转移所有点为 $f[k + 1][i][ne]$,其中 $ne$ 为 $j$ 所能到达的点。由于采用的是最优移动策略,因此 $f[k][i][j]$ 会优先往 $f[k + 1][i][ne] = 2$ 的点移动(自身获胜),否则往 $f[k + 1][i][ne] = 0$ 的点移动(平局),再否则才是往 $f[k + 1][i][ne] = 1$ 的点移动(对方获胜)。
\\n使用该转移优先级进行递推即可,为了方便我们使用「记忆化搜索」的方式来实现动态规划。
\\n注意,该优先级转移其实存在「贪心」决策,但证明这样的决策会使得「最坏情况最好」是相对容易的,而证明存在更小的 $k$ 值才是本题难点。
\\n\\n\\n一些细节:由于本身就要对动规数组进行无效值的初始化,为避免每个样例都
\\nnew
大数组,我们使用static
来修饰大数组,可以有效减少一点时间(不使用static
的话,N
只能取到 $51$)。代码:
\\n###Java
\\n\\nclass Solution {\\n static int N = 55;\\n static int[][][] f = new int[2 * N * N][N][N];\\n int[][] g;\\n int n;\\n public int catMouseGame(int[][] graph) {\\n g = graph;\\n n = g.length;\\n for (int k = 0; k < 2 * n * n; k++) {\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n f[k][i][j] = -1;\\n }\\n }\\n }\\n return dfs(0, 1, 2);\\n }\\n // 0:draw / 1:mouse / 2:cat\\n int dfs(int k, int a, int b) {\\n int ans = f[k][a][b];\\n if (a == 0) ans = 1;\\n else if (a == b) ans = 2;\\n else if (k >= 2 * n * n) ans = 0;\\n else if (ans == -1) {\\n if (k % 2 == 0) { // mouse\\n boolean win = false, draw = false;\\n for (int ne : g[a]) {\\n int t = dfs(k + 1, ne, b);\\n if (t == 1) win = true;\\n else if (t == 0) draw = true;\\n if (win) break;\\n }\\n if (win) ans = 1;\\n else if (draw) ans = 0;\\n else ans = 2;\\n } else { // cat\\n boolean win = false, draw = false;\\n for (int ne : g[b]) {\\n if (ne == 0) continue;\\n int t = dfs(k + 1, a, ne);\\n if (t == 2) win = true;\\n else if (t == 0) draw = true;\\n if (win) break;\\n }\\n if (win) ans = 2;\\n else if (draw) ans = 0;\\n else ans = 1;\\n }\\n }\\n f[k][a][b] = ans;\\n return ans;\\n }\\n}\\n
\\n
\\n- 时间复杂度:状态的数量级为 $n^4$,可以确保每个状态只会被计算一次。复杂度为 $O(n^4)$
\\n- 空间复杂度:$O(n^4)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"动态规划 数据范围只有 $50$,使得本题的难度大大降低。\\n\\n定义 $f[k][i][j]$ 为当前进行了 $k$ 步,老鼠所在位置为 $i$,猫所在的位置为 $j$ 时,最终的获胜情况($0$ 为平局,$1$ 和 $2$ 分别代表老鼠和猫获胜),起始我们让所有的 $f[k][i][j] = -1$(为无效值),最终答案为 $f[0][1][2]$。\\n\\n不失一般性的考虑 $f[i][j][k]$ 该如何转移,根据题意,将当前所在位置 $i$ 和 $j$ 结合「游戏结束,判定游戏」的规则来分情况讨论:\\n\\n若 $i = 0$,说明老鼠位于洞中,老鼠获胜,此时有…","guid":"https://leetcode.cn/problems/cat-and-mouse//solution/gong-shui-san-xie-dong-tai-gui-hua-yun-y-0bx1","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-03T23:45:52.983Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"猫和老鼠","url":"https://leetcode.cn/problems/cat-and-mouse//solution/mao-he-lao-shu-by-leetcode-solution-444x","content":"前言
\\n博弈知识介绍
\\n这道题是博弈问题,猫和老鼠都按照最优策略参与游戏。
\\n在阐述具体解法之前,首先介绍博弈问题中的三个概念:必胜状态、必败状态与必和状态。
\\n\\n
\\n- \\n
\\n对于特定状态,如果游戏已经结束,则根据结束时的状态决定必胜状态、必败状态与必和状态。
\\n\\n
\\n- \\n
\\n如果分出胜负,则该特定状态对于获胜方为必胜状态,对于落败方为必败状态。
\\n- \\n
\\n如果是平局,则该特定状态对于双方都为必和状态。
\\n- \\n
\\n从特定状态开始,如果存在一种操作将状态变成必败状态,则当前玩家可以选择该操作,将必败状态留给对方玩家,因此该特定状态对于当前玩家为必胜状态。
\\n- \\n
\\n从特定状态开始,如果所有操作都会将状态变成必胜状态,则无论当前玩家选择哪种操作,都会将必胜状态留给对方玩家,因此该特定状态对于当前玩家为必败状态。
\\n- \\n
\\n从特定状态开始,如果任何操作都不能将状态变成必败状态,但是存在一种操作将状态变成必和状态,则当前玩家可以选择该操作,将必和状态留给对方玩家,因此该特定状态对于双方玩家都为必和状态。
\\n对于每个玩家,最优策略如下:
\\n\\n
\\n- \\n
\\n争取将必胜状态留给自己,将必败状态留给对方玩家。
\\n- \\n
\\n在自己无法到达必胜状态的情况下,争取将必和状态留给自己。
\\n自顶向下动态规划解法介绍
\\n博弈问题通常可以使用动态规划求解。这道题由于数据规模的原因,动态规划方法不适用,因此只是介绍。
\\n使用三维数组 $\\\\textit{dp}$ 表示状态,$\\\\textit{dp}[\\\\textit{mouse}][\\\\textit{cat}][\\\\textit{turns}]$ 表示从老鼠位于节点 $\\\\textit{mouse}$、猫位于节点 $\\\\textit{cat}$、游戏已经进行了 $\\\\textit{turns}$ 轮的状态开始,猫和老鼠都按照最优策略的情况下的游戏结果。假设图中的节点数是 $n$,则有 $0 \\\\le \\\\textit{mouse}, \\\\textit{cat} < n$。
\\n由于游戏的初始状态是老鼠位于节点 $1$,猫位于节点 $2$,因此 $\\\\textit{dp}[1][2][0]$ 为从初始状态开始的游戏结果。
\\n动态规划的边界条件为可以直接得到游戏结果的状态,包括以下三种状态:
\\n\\n
\\n- \\n
\\n如果 $\\\\textit{mouse} = 0$,老鼠躲入洞里,则老鼠获胜,因此对于任意 $\\\\textit{cat}$ 和 $\\\\textit{turns}$ 都有 $\\\\textit{dp}[0][\\\\textit{cat}][\\\\textit{turns}] = 1$,该状态为老鼠的必胜状态,猫的必败状态。
\\n- \\n
\\n如果 $\\\\textit{cat} = \\\\textit{mouse}$,猫和老鼠占据相同的节点,则猫获胜,因此当 $\\\\textit{cat} = \\\\textit{mouse}$ 时,对于任意 $\\\\textit{mouse}$、$\\\\textit{cat}$ 和 $\\\\textit{turns}$ 都有 $\\\\textit{dp}[\\\\textit{mouse}][\\\\textit{cat}][\\\\textit{turns}] = 2$,该状态为老鼠的必败状态,猫的必胜状态。注意猫不能移动到节点 $0$,因此当 $\\\\textit{mouse} = 0$ 时,一定有 $\\\\textit{cat} \\\\ne \\\\textit{mouse}$。
\\n- \\n
\\n如果 $\\\\textit{turns} \\\\ge 2n(n - 1)$,则是平局,该状态为双方的必和状态。
\\n\\n\\n由于游戏中的每个局面由老鼠的位置、猫的位置和轮到移动的一方三个因素确定,老鼠可能的位置数是 $n$,因此猫可能的位置数是 $n - 1$(由于猫不能移动到节点 $0$),轮到移动的一方有 $2$ 种可能,因此游戏中所有可能的局面数是 $2n(n - 1)$。
\\n根据抽屉原理可知,当游戏进行了 $2n(n - 1)$ 轮时,一定存在至少一个猫和老鼠重复经过的局面。由于猫和老鼠都按照最优策略参与游戏,对于同一个局面,游戏结果是相同的。
\\n考虑该重复经过的局面。从该局面开始,双方按照最优策略移动,结果只能回到该局面,任何一方都无法让己方到达必胜状态,让对方到达必败状态,因此该状态对于双方都不是必胜状态,只能是必和状态。
\\n如果该重复经过的局面和初始局面相同,则初始局面即为双方的必和状态。如果该重复经过的局面和初始局面不同,则从初始局面开始,双方按照最优策略移动,结果只能到达双方的必和状态,任何一方都无法让己方到达必胜状态,让对方到达必败状态,因此初始局面对于双方都不是必胜状态,只能是必和状态。
\\n综上所述,如果游戏进行了 $2n(n - 1)$ 轮还没有任何一方获胜,则是平局。
\\n动态规划的状态转移需要考虑当前玩家所有可能的移动,选择最优策略的移动。
\\n由于老鼠先开始移动,猫后开始移动,因此可以根据游戏已经进行的轮数 $\\\\textit{turns}$ 的奇偶性决定当前轮到的玩家,当 $\\\\textit{turns}$ 是偶数时轮到老鼠移动,当 $\\\\textit{turns}$ 是奇数时轮到猫移动。
\\n如果轮到老鼠移动,则对于老鼠从当前节点移动一次之后可能到达的每个节点,进行如下操作:
\\n\\n
\\n- \\n
\\n如果存在一个节点,老鼠到达该节点之后,老鼠可以获胜,则老鼠到达该节点之后的状态为老鼠的必胜状态,猫的必败状态,因此在老鼠移动之前的当前状态为老鼠的必胜状态。
\\n- \\n
\\n如果老鼠到达任何节点之后的状态都不是老鼠的必胜状态,但是存在一个节点,老鼠到达该节点之后,结果是平局,则老鼠到达该节点之后的状态为双方的必和状态,因此在老鼠移动之前的当前状态为双方的必和状态。
\\n- \\n
\\n如果老鼠到达任何节点之后的状态都不是老鼠的必胜状态或必和状态,则老鼠到达任何节点之后的状态都为老鼠的必败状态,猫的必胜状态,因此在老鼠移动之前的当前状态为老鼠的必败状态。
\\n如果轮到猫移动,则对于猫从当前节点移动一次之后可能到达的每个节点,进行如下操作:
\\n\\n
\\n- \\n
\\n如果存在一个节点,猫到达该节点之后,猫可以获胜,则猫到达该节点之后的状态为猫的必胜状态,老鼠的必败状态,因此在猫移动之前的当前状态为猫的必胜状态。
\\n- \\n
\\n如果猫到达任何节点之后的状态都不是猫的必胜状态,但是存在一个节点,猫到达该节点之后,结果是平局,则猫到达该节点之后的状态为双方的必和状态,因此在猫移动之前的当前状态为双方的必和状态。
\\n- \\n
\\n如果猫到达任何节点之后的状态都不是猫的必胜状态或必和状态,则猫到达任何节点之后的状态都为猫的必败状态,老鼠的必胜状态,因此在猫移动之前的当前状态为猫的必败状态。
\\n实现方面,由于双方移动的策略相似,因此可以使用一个函数实现移动策略,根据游戏已经进行的轮数的奇偶性决定当前轮到的玩家。对于特定玩家的移动,实现方法如下:
\\n\\n
\\n- \\n
\\n如果当前玩家存在一种移动方法到达非必败状态,则用该状态更新游戏结果。
\\n\\n
\\n- \\n
\\n如果该移动方法到达必胜状态,则将当前状态(移动前的状态)设为必胜状态,结束遍历其他可能的移动。
\\n- \\n
\\n如果该移动方法到达必和状态,则将当前状态(移动前的状态)设为必和状态,继续遍历其他可能的移动,因为可能存在到达必胜状态的移动方法。
\\n- \\n
\\n如果当前玩家的任何移动方法都到达必败状态,则将当前状态(移动前的状态)设为必败状态。
\\n由于老鼠可能的位置有 $n$ 个,猫可能的位置有 $n - 1$ 个,游戏轮数最大为 $2n(n - 1)$,因此动态规划的状态数是 $O(n^4)$,对于每个状态需要 $O(n)$ 的时间计算状态值,因此总时间复杂度是 $O(n^5)$,该时间复杂度会超出时间限制,因此自顶向下的动态规划不适用于这道题。以下代码为自顶向下的动态规划的实现,仅供读者参考。
\\n###Java
\\n\\nclass Solution {\\n static final int MOUSE_WIN = 1;\\n static final int CAT_WIN = 2;\\n static final int DRAW = 0;\\n int n;\\n int[][] graph;\\n int[][][] dp;\\n\\n public int catMouseGame(int[][] graph) {\\n this.n = graph.length;\\n this.graph = graph;\\n this.dp = new int[n][n][2 * n * (n - 1)];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n Arrays.fill(dp[i][j], -1);\\n }\\n }\\n return getResult(1, 2, 0);\\n }\\n\\n public int getResult(int mouse, int cat, int turns) {\\n if (turns == 2 * n * (n - 1)) {\\n return DRAW;\\n }\\n if (dp[mouse][cat][turns] < 0) {\\n if (mouse == 0) {\\n dp[mouse][cat][turns] = MOUSE_WIN;\\n } else if (cat == mouse) {\\n dp[mouse][cat][turns] = CAT_WIN;\\n } else {\\n getNextResult(mouse, cat, turns);\\n }\\n }\\n return dp[mouse][cat][turns];\\n }\\n\\n public void getNextResult(int mouse, int cat, int turns) {\\n int curMove = turns % 2 == 0 ? mouse : cat;\\n int defaultResult = curMove == mouse ? CAT_WIN : MOUSE_WIN;\\n int result = defaultResult;\\n int[] nextNodes = graph[curMove];\\n for (int next : nextNodes) {\\n if (curMove == cat && next == 0) {\\n continue;\\n }\\n int nextMouse = curMove == mouse ? next : mouse;\\n int nextCat = curMove == cat ? next : cat;\\n int nextResult = getResult(nextMouse, nextCat, turns + 1);\\n if (nextResult != defaultResult) {\\n result = nextResult;\\n if (result != DRAW) {\\n break;\\n }\\n }\\n }\\n dp[mouse][cat][turns] = result;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n const int MOUSE_WIN = 1;\\n const int CAT_WIN = 2;\\n const int DRAW = 0;\\n int n;\\n int[][] graph;\\n int[,,] dp;\\n\\n public int CatMouseGame(int[][] graph) {\\n this.n = graph.Length;\\n this.graph = graph;\\n this.dp = new int[n, n, 2 * n * (n - 1)];\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < n; j++) {\\n for (int k = 0; k < 2 * n * (n - 1); k++) {\\n dp[i, j, k] = -1;\\n }\\n }\\n }\\n return GetResult(1, 2, 0);\\n }\\n\\n public int GetResult(int mouse, int cat, int turns) {\\n if (turns == 2 * n * (n - 1)) {\\n return DRAW;\\n }\\n if (dp[mouse, cat, turns] < 0) {\\n if (mouse == 0) {\\n dp[mouse, cat, turns] = MOUSE_WIN;\\n } else if (cat == mouse) {\\n dp[mouse, cat, turns] = CAT_WIN;\\n } else {\\n GetNextResult(mouse, cat, turns);\\n }\\n }\\n return dp[mouse, cat, turns];\\n }\\n\\n public void GetNextResult(int mouse, int cat, int turns) {\\n int curMove = turns % 2 == 0 ? mouse : cat;\\n int defaultResult = curMove == mouse ? CAT_WIN : MOUSE_WIN;\\n int result = defaultResult;\\n int[] nextNodes = graph[curMove];\\n foreach (int next in nextNodes) {\\n if (curMove == cat && next == 0) {\\n continue;\\n }\\n int nextMouse = curMove == mouse ? next : mouse;\\n int nextCat = curMove == cat ? next : cat;\\n int nextResult = GetResult(nextMouse, nextCat, turns + 1);\\n if (nextResult != defaultResult) {\\n result = nextResult;\\n if (result != DRAW) {\\n break;\\n }\\n }\\n }\\n dp[mouse, cat, turns] = result;\\n }\\n}\\n
###C++
\\n\\nconst int MOUSE_WIN = 1;\\nconst int CAT_WIN = 2;\\nconst int DRAW = 0;\\nconst int MAXN = 51;\\n\\nclass Solution {\\npublic:\\n int n;\\n int dp[MAXN][MAXN][MAXN*(MAXN-1)*2];\\n vector<vector<int>> graph;\\n \\n int catMouseGame(vector<vector<int>>& graph) {\\n this->n = graph.size();\\n this->graph = graph;\\n memset(dp, -1, sizeof(dp));\\n return getResult(1, 2, 0);\\n }\\n\\n int getResult(int mouse, int cat, int turns) {\\n if (turns == 2 * n * (n - 1)) {\\n return DRAW;\\n }\\n if (dp[mouse][cat][turns] < 0) {\\n if (mouse == 0) {\\n dp[mouse][cat][turns] = MOUSE_WIN;\\n } else if (cat == mouse) {\\n dp[mouse][cat][turns] = CAT_WIN;\\n } else {\\n getNextResult(mouse, cat, turns);\\n }\\n }\\n return dp[mouse][cat][turns];\\n }\\n\\n void getNextResult(int mouse, int cat, int turns) {\\n int curMove = turns % 2 == 0 ? mouse : cat;\\n int defaultResult = curMove == mouse ? CAT_WIN : MOUSE_WIN;\\n int result = defaultResult;\\n for (int next : graph[curMove]) {\\n if (curMove == cat && next == 0) {\\n continue;\\n }\\n int nextMouse = curMove == mouse ? next : mouse;\\n int nextCat = curMove == cat ? next : cat;\\n int nextResult = getResult(nextMouse, nextCat, turns + 1);\\n if (nextResult != defaultResult) {\\n result = nextResult;\\n if (result != DRAW) {\\n break;\\n }\\n }\\n }\\n dp[mouse][cat][turns] = result;\\n }\\n};\\n
###Python
\\n\\nDRAW = 0\\nMOUSE_WIN = 1\\nCAT_WIN = 2\\n\\nclass Solution:\\n def catMouseGame(self, graph: List[List[int]]) -> int:\\n n = len(graph)\\n dp = [[[-1] * (2 * n * (n - 1)) for _ in range(n)] for _ in range(n)]\\n\\n def getResult(mouse: int, cat: int, turns: int) -> int:\\n if turns == 2 * n * (n - 1):\\n return DRAW\\n res = dp[mouse][cat][turns]\\n if res != -1:\\n return res\\n if mouse == 0:\\n res = MOUSE_WIN\\n elif cat == mouse:\\n res = CAT_WIN\\n else:\\n res = getNextResult(mouse, cat, turns)\\n dp[mouse][cat][turns] = res\\n return res\\n\\n def getNextResult(mouse: int, cat: int, turns: int) -> int:\\n curMove = mouse if turns % 2 == 0 else cat\\n defaultRes = MOUSE_WIN if curMove != mouse else CAT_WIN\\n res = defaultRes\\n for next in graph[curMove]:\\n if curMove == cat and next == 0:\\n continue\\n nextMouse = next if curMove == mouse else mouse\\n nextCat = next if curMove == cat else cat\\n nextRes = getResult(nextMouse, nextCat, turns + 1)\\n if nextRes != defaultRes:\\n res = nextRes\\n if res != DRAW:\\n break\\n return res\\n\\n return getResult(1, 2, 0)\\n
###JavaScript
\\n\\nconst MOUSE_WIN = 1;\\nconst CAT_WIN = 2;\\nconst DRAW = 0;\\nvar catMouseGame = function(graph) {\\n const n = graph.length;\\n const dp = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => new Array(2 * n * (n - 1)).fill(-1)));\\n \\n const getResult = (mouse, cat, turns) => {\\n if (turns === 2 * n * (n - 1)) {\\n return DRAW;\\n }\\n let res = dp[mouse][cat][turns];\\n if (res !== -1) {\\n return res;\\n }\\n if (mouse === 0) {\\n res = MOUSE_WIN;\\n } else if (cat === mouse) {\\n res = CAT_WIN;\\n } else {\\n res = getNextResult(mouse, cat, turns);\\n }\\n dp[mouse][cat][turns] = res;\\n return res;\\n }\\n\\n const getNextResult = (mouse, cat, turns) => {\\n const curMove = turns % 2 == 0 ? mouse : cat;\\n const defaultRes = curMove != mouse ? MOUSE_WIN : CAT_WIN;\\n let res = defaultRes;\\n for (const next of graph[curMove]) {\\n if (curMove === cat && next === 0) {\\n continue;\\n }\\n const nextMouse = curMove === mouse ? next : mouse;\\n const nextCat = curMove == cat ? next : cat;\\n const nextRes = getResult(nextMouse, nextCat, turns + 1)\\n if (nextRes !== defaultRes) {\\n res = nextRes;\\n if (res !== DRAW) {\\n break;\\n }\\n } \\n }\\n return res;\\n }\\n\\n return getResult(1, 2, 0);\\n};\\n
###go
\\n\\nconst (\\n draw = 0\\n mouseWin = 1\\n catWin = 2\\n)\\n\\nfunc catMouseGame(graph [][]int) int {\\n n := len(graph)\\n dp := make([][][]int, n)\\n for i := range dp {\\n dp[i] = make([][]int, n)\\n for j := range dp[i] {\\n dp[i][j] = make([]int, n*(n-1)*2)\\n for k := range dp[i][j] {\\n dp[i][j][k] = -1\\n }\\n }\\n }\\n\\n var getResult, getNextResult func(int, int, int) int\\n getResult = func(mouse, cat, turns int) int {\\n if turns == n*(n-1)*2 {\\n return draw\\n }\\n res := dp[mouse][cat][turns]\\n if res != -1 {\\n return res\\n }\\n if mouse == 0 {\\n res = mouseWin\\n } else if cat == mouse {\\n res = catWin\\n } else {\\n res = getNextResult(mouse, cat, turns)\\n }\\n dp[mouse][cat][turns] = res\\n return res\\n }\\n getNextResult = func(mouse, cat, turns int) int {\\n curMove := mouse\\n if turns%2 == 1 {\\n curMove = cat\\n }\\n defaultRes := mouseWin\\n if curMove == mouse {\\n defaultRes = catWin\\n }\\n res := defaultRes\\n for _, next := range graph[curMove] {\\n if curMove == cat && next == 0 {\\n continue\\n }\\n nextMouse, nextCat := mouse, cat\\n if curMove == mouse {\\n nextMouse = next\\n } else if curMove == cat {\\n nextCat = next\\n }\\n nextRes := getResult(nextMouse, nextCat, turns+1)\\n if nextRes != defaultRes {\\n res = nextRes\\n if res != draw {\\n break\\n }\\n }\\n }\\n return res\\n }\\n return getResult(1, 2, 0)\\n}\\n
###C
\\n\\n#define MOUSE_WIN 1\\n#define CAT_WIN 2\\n#define DRAW 0\\n#define MAXN 51\\n\\nint dp[MAXN][MAXN][MAXN*(MAXN-1)*2];\\n\\nint getResult(int mouse, int cat, int turns, const int** graph, const int graphSize, const int* graphColSize) {\\n if (turns == graphSize * (graphSize - 1) * 2) {\\n return DRAW;\\n }\\n if (dp[mouse][cat][turns] < 0) {\\n if (mouse == 0) {\\n dp[mouse][cat][turns] = MOUSE_WIN;\\n } else if (cat == mouse) {\\n dp[mouse][cat][turns] = CAT_WIN;\\n } else {\\n getNextResult(mouse, cat, turns, graph, graphSize, graphColSize);\\n }\\n }\\n return dp[mouse][cat][turns];\\n}\\n\\nvoid getNextResult(int mouse, int cat, int turns, const int** graph, const int graphSize, const int* graphColSize) {\\n int curMove = turns % 2 == 0 ? mouse : cat;\\n int defaultResult = curMove == mouse ? CAT_WIN : MOUSE_WIN;\\n int result = defaultResult;\\n int * nextNodes = graph[curMove];\\n for (int i = 0; i < graphColSize[curMove]; i++) {\\n if (curMove == cat && nextNodes[i] == 0) {\\n continue;\\n }\\n int nextMouse = curMove == mouse ? nextNodes[i] : mouse;\\n int nextCat = curMove == cat ? nextNodes[i] : cat;\\n int nextResult = getResult(nextMouse, nextCat, turns + 1, graph, graphSize, graphColSize);\\n if (nextResult != defaultResult) {\\n result = nextResult;\\n if (result != DRAW) {\\n break;\\n }\\n }\\n }\\n dp[mouse][cat][turns] = result;\\n}\\n\\nint catMouseGame(int** graph, int graphSize, int* graphColSize){\\n memset(dp, -1, sizeof(dp));\\n return getResult(1, 2, 0, graph, graphSize, graphColSize);\\n}\\n
方法一:拓扑排序
\\n思路和算法
\\n自顶向下的动态规划由于判定平局的标准和轮数有关,因此时间复杂度较高。为了降低时间复杂度,需要使用自底向上的方法实现,消除结果和轮数之间的关系。
\\n使用自底向上的方法实现时,游戏中的状态由老鼠的位置、猫的位置和轮到移动的一方三个因素确定。初始时,只有边界情况的胜负结果已知,其余所有状态的结果都初始化为平局。边界情况为直接确定胜负的情况,包括两类情况:老鼠躲入洞里,无论猫位于哪个节点,都是老鼠获胜;猫和老鼠占据相同的节点,无论占据哪个节点,都是猫获胜。
\\n从边界情况出发遍历其他情况。对于当前状态,可以得到老鼠的位置、猫的位置和轮到移动的一方,根据当前状态可知上一轮的所有可能状态,其中上一轮的移动方和当前的移动方相反,上一轮的移动方在上一轮状态和当前状态所在的节点不同。假设当前状态是老鼠所在节点是 $\\\\textit{mouse}$,猫所在节点是 $\\\\textit{cat}$,则根据当前的移动方,可以得到上一轮的所有可能状态:
\\n\\n
\\n- \\n
\\n如果当前的移动方是老鼠,则上一轮的移动方是猫,上一轮状态中老鼠所在节点是 $\\\\textit{mouse}$,猫所在节点可能是 $\\\\textit{graph}[\\\\textit{cat}]$ 中的任意一个节点(除了节点 $0$);
\\n- \\n
\\n如果当前的移动方是猫,则上一轮的移动方是老鼠,上一轮状态中老鼠所在节点可能是 $\\\\textit{graph}[\\\\textit{mouse}]$ 中的任意一个节点,猫所在节点是 $\\\\textit{cat}$。
\\n对于上一轮的每一种可能的状态,如果该状态的结果已知不是平局,则不需要重复计算该状态的结果,只有对结果是平局的状态,才需要计算该状态的结果。对于上一轮的移动方,只有当可以确定上一轮状态是必胜状态或者必败状态时,才更新上一轮状态的结果。
\\n\\n
\\n- \\n
\\n如果上一轮的移动方和当前状态的结果的获胜方相同,由于当前状态为上一轮的移动方的必胜状态,因此上一轮的移动方一定可以移动到当前状态而获胜,上一轮状态为上一轮的移动方的必胜状态。
\\n- \\n
\\n如果上一轮的移动方和当前状态的结果的获胜方不同,则上一轮的移动方需要尝试其他可能的移动,可能有以下三种情况:
\\n\\n
\\n- \\n
\\n如果存在一种移动可以到达上一轮的移动方的必胜状态,则上一轮状态为上一轮的移动方的必胜状态;
\\n- \\n
\\n如果所有的移动都到达上一轮的移动方的必败状态,则上一轮状态为上一轮的移动方的必败状态;
\\n- \\n
\\n如果所有的移动都不能到达上一轮的移动方的必胜状态,但是存在一种移动可以到达上一轮的移动方的必和状态,则上一轮状态为上一轮的移动方的必和状态。
\\n其中,对于必败状态与必和状态的判断依据为上一轮的移动方可能的移动是都到达必败状态还是可以到达必和状态。为了实现必败状态与必和状态的判断,需要记录每个状态的度,初始时每个状态的度为当前玩家在当前位置可以移动到的节点数。对于老鼠而言,初始的度为老鼠所在的节点的相邻节点数;对于猫而言,初始的度为猫所在的节点的相邻且非节点 $0$ 的节点数。
\\n遍历过程中,从当前状态出发遍历上一轮的所有可能状态,如果上一轮状态的结果是平局且上一轮的移动方和当前状态的结果的获胜方不同,则将上一轮状态的度减 $1$。如果上一轮状态的度减少到 $0$,则从上一轮状态出发到达的所有状态都是上一轮的移动方的必败状态,因此上一轮状态也是上一轮的移动方的必败状态。
\\n在确定上一轮状态的结果(必胜或必败)之后,即可从上一轮状态出发,遍历其他结果是平局的状态。当没有更多的状态可以确定胜负结果时,遍历结束,此时即可得到初始状态的结果。
\\n细心的读者可以发现,上述遍历的过程其实是拓扑排序。
\\n证明
\\n必胜状态和必败状态都符合博弈中的最优策略,需要证明的是必和状态的正确性。
\\n遍历结束之后,如果一个状态的结果是平局,则该状态满足以下两个条件:
\\n\\n
\\n- \\n
\\n从该状态出发,任何移动都无法到达该状态的移动方的必胜状态;
\\n- \\n
\\n从该状态出发,存在一种移动可以到达必和状态。
\\n对于标记结果是平局的状态,如果其实际结果是该状态的移动方必胜,则一定存在一个下一轮状态,为当前状态的移动方的必胜状态,在根据下一轮状态的结果标记当前状态的结果时会将当前状态标记为当前状态的移动方的必胜状态,和标记结果是平局矛盾。
\\n对于标记结果是平局的状态,如果其实际结果是该状态的移动方必败,则所有的下一轮状态都为当前状态的移动方的必败状态,在根据下一轮状态的结果标记当前状态的结果时会将当前状态标记为当前状态的移动方的必败状态,和标记结果是平局矛盾。
\\n因此,如果标记的状态是必和状态,则实际结果一定是必和状态。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n static final int MOUSE_TURN = 0, CAT_TURN = 1;\\n static final int DRAW = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\n int[][] graph;\\n int[][][] degrees;\\n int[][][] results;\\n\\n public int catMouseGame(int[][] graph) {\\n int n = graph.length;\\n this.graph = graph;\\n this.degrees = new int[n][n][2];\\n this.results = new int[n][n][2];\\n Queue<int[]> queue = new ArrayDeque<int[]>();\\n for (int i = 0; i < n; i++) {\\n for (int j = 1; j < n; j++) {\\n degrees[i][j][MOUSE_TURN] = graph[i].length;\\n degrees[i][j][CAT_TURN] = graph[j].length;\\n }\\n }\\n for (int node : graph[0]) {\\n for (int i = 0; i < n; i++) {\\n degrees[i][node][CAT_TURN]--;\\n }\\n }\\n for (int j = 1; j < n; j++) {\\n results[0][j][MOUSE_TURN] = MOUSE_WIN;\\n results[0][j][CAT_TURN] = MOUSE_WIN;\\n queue.offer(new int[]{0, j, MOUSE_TURN});\\n queue.offer(new int[]{0, j, CAT_TURN});\\n }\\n for (int i = 1; i < n; i++) {\\n results[i][i][MOUSE_TURN] = CAT_WIN;\\n results[i][i][CAT_TURN] = CAT_WIN;\\n queue.offer(new int[]{i, i, MOUSE_TURN});\\n queue.offer(new int[]{i, i, CAT_TURN});\\n }\\n while (!queue.isEmpty()) {\\n int[] state = queue.poll();\\n int mouse = state[0], cat = state[1], turn = state[2];\\n int result = results[mouse][cat][turn];\\n List<int[]> prevStates = getPrevStates(mouse, cat, turn);\\n for (int[] prevState : prevStates) {\\n int prevMouse = prevState[0], prevCat = prevState[1], prevTurn = prevState[2];\\n if (results[prevMouse][prevCat][prevTurn] == DRAW) {\\n boolean canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn] = result;\\n queue.offer(new int[]{prevMouse, prevCat, prevTurn});\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--;\\n if (degrees[prevMouse][prevCat][prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn] = loseResult;\\n queue.offer(new int[]{prevMouse, prevCat, prevTurn});\\n }\\n }\\n }\\n }\\n }\\n return results[1][2][MOUSE_TURN];\\n }\\n\\n public List<int[]> getPrevStates(int mouse, int cat, int turn) {\\n List<int[]> prevStates = new ArrayList<int[]>();\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n if (prevTurn == MOUSE_TURN) {\\n for (int prev : graph[mouse]) {\\n prevStates.add(new int[]{prev, cat, prevTurn});\\n }\\n } else {\\n for (int prev : graph[cat]) {\\n if (prev != 0) {\\n prevStates.add(new int[]{mouse, prev, prevTurn});\\n }\\n }\\n }\\n return prevStates;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n const int MOUSE_TURN = 0, CAT_TURN = 1;\\n const int DRAW = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\n int[][] graph;\\n int[,,] degrees;\\n int[,,] results;\\n\\n public int CatMouseGame(int[][] graph) {\\n int n = graph.Length;\\n this.graph = graph;\\n this.degrees = new int[n, n, 2];\\n this.results = new int[n, n, 2];\\n Queue<Tuple<int, int, int>> queue = new Queue<Tuple<int, int, int>>();\\n for (int i = 0; i < n; i++) {\\n for (int j = 1; j < n; j++) {\\n degrees[i, j, MOUSE_TURN] = graph[i].Length;\\n degrees[i, j, CAT_TURN] = graph[j].Length;\\n }\\n }\\n foreach (int node in graph[0]) {\\n for (int i = 0; i < n; i++) {\\n degrees[i, node, CAT_TURN]--;\\n }\\n }\\n for (int j = 1; j < n; j++) {\\n results[0, j, MOUSE_TURN] = MOUSE_WIN;\\n results[0, j, CAT_TURN] = MOUSE_WIN;\\n queue.Enqueue(new Tuple<int, int, int>(0, j, MOUSE_TURN));\\n queue.Enqueue(new Tuple<int, int, int>(0, j, CAT_TURN));\\n }\\n for (int i = 1; i < n; i++) {\\n results[i, i, MOUSE_TURN] = CAT_WIN;\\n results[i, i, CAT_TURN] = CAT_WIN;\\n queue.Enqueue(new Tuple<int, int, int>(i, i, MOUSE_TURN));\\n queue.Enqueue(new Tuple<int, int, int>(i, i, CAT_TURN));\\n }\\n while (queue.Count > 0) {\\n Tuple<int, int, int> state = queue.Dequeue();\\n int mouse = state.Item1, cat = state.Item2, turn = state.Item3;\\n int result = results[mouse, cat, turn];\\n IList<Tuple<int, int, int>> prevStates = GetPrevStates(mouse, cat, turn);\\n foreach (Tuple<int, int, int> prevState in prevStates) {\\n int prevMouse = prevState.Item1, prevCat = prevState.Item2, prevTurn = prevState.Item3;\\n if (results[prevMouse, prevCat, prevTurn] == DRAW) {\\n bool canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse, prevCat, prevTurn] = result;\\n queue.Enqueue(new Tuple<int, int, int>(prevMouse, prevCat, prevTurn));\\n } else {\\n degrees[prevMouse, prevCat, prevTurn]--;\\n if (degrees[prevMouse, prevCat, prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse, prevCat, prevTurn] = loseResult;\\n queue.Enqueue(new Tuple<int, int, int>(prevMouse, prevCat, prevTurn));\\n }\\n }\\n }\\n }\\n }\\n return results[1, 2, MOUSE_TURN];\\n }\\n\\n public IList<Tuple<int, int, int>> GetPrevStates(int mouse, int cat, int turn) {\\n IList<Tuple<int, int, int>> prevStates = new List<Tuple<int, int, int>>();\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n if (prevTurn == MOUSE_TURN) {\\n foreach (int prev in graph[mouse]) {\\n prevStates.Add(new Tuple<int, int, int>(prev, cat, prevTurn));\\n }\\n } else {\\n foreach (int prev in graph[cat]) {\\n if (prev != 0) {\\n prevStates.Add(new Tuple<int, int, int>(mouse, prev, prevTurn));\\n }\\n }\\n }\\n return prevStates;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n const int MOUSE_TURN = 0, CAT_TURN = 1;\\n const int DRAW = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\n vector<vector<int>> graph;\\n vector<vector<vector<int>>> degrees;\\n vector<vector<vector<int>>> results;\\n\\n int catMouseGame(vector<vector<int>>& graph) {\\n int n = graph.size();\\n this->graph = graph;\\n this->degrees = vector<vector<vector<int>>>(n, vector<vector<int>>(n, vector<int>(2)));\\n this->results = vector<vector<vector<int>>>(n, vector<vector<int>>(n, vector<int>(2)));\\n queue<tuple<int, int, int>> qu;\\n\\n for (int i = 0; i < n; i++) {\\n for (int j = 1; j < n; j++) {\\n degrees[i][j][MOUSE_TURN] = graph[i].size();\\n degrees[i][j][CAT_TURN] = graph[j].size();\\n }\\n }\\n for (int node : graph[0]) {\\n for (int i = 0; i < n; i++) {\\n degrees[i][node][CAT_TURN]--;\\n }\\n }\\n for (int j = 1; j < n; j++) {\\n results[0][j][MOUSE_TURN] = MOUSE_WIN;\\n results[0][j][CAT_TURN] = MOUSE_WIN;\\n qu.emplace(0, j, MOUSE_TURN);\\n qu.emplace(0, j, CAT_TURN);\\n }\\n for (int i = 1; i < n; i++) {\\n results[i][i][MOUSE_TURN] = CAT_WIN;\\n results[i][i][CAT_TURN] = CAT_WIN;\\n qu.emplace(i, i, MOUSE_TURN);\\n qu.emplace(i, i, CAT_TURN);\\n }\\n while (!qu.empty()) {\\n auto [mouse, cat, turn] = qu.front();\\n qu.pop();\\n int result = results[mouse][cat][turn];\\n vector<tuple<int, int, int>> prevStates = GetPrevStates(mouse, cat, turn);\\n for (auto & [prevMouse, prevCat, prevTurn] : prevStates) {\\n if (results[prevMouse][prevCat][prevTurn] == DRAW) {\\n bool canWin = (result == MOUSE_WIN && prevTurn == MOUSE_TURN) || (result == CAT_WIN && prevTurn == CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn] = result;\\n qu.emplace(prevMouse, prevCat, prevTurn);\\n } else if (--degrees[prevMouse][prevCat][prevTurn] == 0) {\\n int loseResult = prevTurn == MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn] = loseResult;\\n qu.emplace(prevMouse, prevCat, prevTurn);\\n }\\n }\\n }\\n }\\n return results[1][2][MOUSE_TURN];\\n }\\n\\n vector<tuple<int, int, int>> GetPrevStates(int mouse, int cat, int turn) {\\n vector<tuple<int, int, int>> prevStates;\\n int prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n if (prevTurn == MOUSE_TURN) {\\n for (int & prev : graph[mouse]) {\\n prevStates.emplace_back(prev, cat, prevTurn);\\n }\\n } else {\\n for (int & prev : graph[cat]) {\\n if (prev != 0) {\\n prevStates.emplace_back(mouse, prev, prevTurn);\\n }\\n }\\n }\\n return prevStates;\\n }\\n};\\n
###Python
\\n\\nMOUSE_TURN = 0\\nCAT_TURN = 1\\n\\nDRAW = 0\\nMOUSE_WIN = 1\\nCAT_WIN = 2\\n\\nclass Solution:\\n def catMouseGame(self, graph: List[List[int]]) -> int:\\n n = len(graph)\\n degrees = [[[0, 0] for _ in range(n)] for _ in range(n)]\\n results = [[[0, 0] for _ in range(n)] for _ in range(n)]\\n for i in range(n):\\n for j in range(1, n):\\n degrees[i][j][MOUSE_TURN] = len(graph[i])\\n degrees[i][j][CAT_TURN] = len(graph[j])\\n for y in graph[0]:\\n for i in range(n):\\n degrees[i][y][CAT_TURN] -= 1\\n\\n q = deque()\\n for j in range(1, n):\\n results[0][j][MOUSE_TURN] = MOUSE_WIN\\n results[0][j][CAT_TURN] = MOUSE_WIN\\n q.append((0, j, MOUSE_TURN))\\n q.append((0, j, CAT_TURN))\\n for i in range(1, n):\\n results[i][i][MOUSE_TURN] = CAT_WIN\\n results[i][i][CAT_TURN] = CAT_WIN\\n q.append((i, i, MOUSE_TURN))\\n q.append((i, i, CAT_TURN))\\n\\n while q:\\n mouse, cat, turn = q.popleft()\\n result = results[mouse][cat][turn]\\n if turn == MOUSE_TURN:\\n prevStates = [(mouse, prev, CAT_TURN) for prev in graph[cat]]\\n else:\\n prevStates = [(prev, cat, MOUSE_TURN) for prev in graph[mouse]]\\n for prevMouse, prevCat, prevTurn in prevStates:\\n if prevCat == 0:\\n continue\\n if results[prevMouse][prevCat][prevTurn] == DRAW:\\n canWin = result == MOUSE_WIN and prevTurn == MOUSE_TURN or result == CAT_WIN and prevTurn == CAT_TURN\\n if canWin:\\n results[prevMouse][prevCat][prevTurn] = result\\n q.append((prevMouse, prevCat, prevTurn))\\n else:\\n degrees[prevMouse][prevCat][prevTurn] -= 1\\n if degrees[prevMouse][prevCat][prevTurn] == 0:\\n results[prevMouse][prevCat][prevTurn] = CAT_WIN if prevTurn == MOUSE_TURN else MOUSE_WIN\\n q.append((prevMouse, prevCat, prevTurn))\\n return results[1][2][MOUSE_TURN]\\n
###JavaScript
\\n\\nconst MOUSE_TURN = 0, CAT_TURN = 1;\\nconst DRAW = 0, MOUSE_WIN = 1, CAT_WIN = 2;\\nvar catMouseGame = function(graph) {\\n const n = graph.length;\\n degrees = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => new Array(2).fill(0)));\\n results = new Array(n).fill(0).map(() => new Array(n).fill(0).map(() => new Array(2).fill(0)));\\n const queue = [];\\n for (let i = 0; i < n; i++) {\\n for (let j = 1; j < n; j++) {\\n degrees[i][j][MOUSE_TURN] = graph[i].length;\\n degrees[i][j][CAT_TURN] = graph[j].length;\\n }\\n }\\n for (const node of graph[0]) {\\n for (let i = 0; i < n; i++) {\\n degrees[i][node][CAT_TURN]--;\\n }\\n }\\n for (let j = 1; j < n; j++) {\\n results[0][j][MOUSE_TURN] = MOUSE_WIN;\\n results[0][j][CAT_TURN] = MOUSE_WIN;\\n queue.push([0, j, MOUSE_TURN]);\\n queue.push([0, j, CAT_TURN]);\\n }\\n for (let i = 1; i < n; i++) {\\n results[i][i][MOUSE_TURN] = CAT_WIN;\\n results[i][i][CAT_TURN] = CAT_WIN;\\n queue.push([i, i, MOUSE_TURN]);\\n queue.push([i, i, CAT_TURN]);\\n }\\n while (queue.length) {\\n const state = queue.shift();\\n const mouse = state[0], cat = state[1], turn = state[2];\\n const result = results[mouse][cat][turn];\\n const prevStates = getPrevStates(mouse, cat, turn, graph);\\n for (const prevState of prevStates) {\\n let prevMouse = prevState[0], prevCat = prevState[1], prevTurn = prevState[2];\\n if (results[prevMouse][prevCat][prevTurn] === DRAW) {\\n const canWin = (result === MOUSE_WIN && prevTurn === MOUSE_TURN) || (result === CAT_WIN && prevTurn === CAT_TURN);\\n if (canWin) {\\n results[prevMouse][prevCat][prevTurn] = result;\\n queue.push([prevMouse, prevCat, prevTurn]);\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--;\\n if (degrees[prevMouse][prevCat][prevTurn] == 0) {\\n const loseResult = prevTurn === MOUSE_TURN ? CAT_WIN : MOUSE_WIN;\\n results[prevMouse][prevCat][prevTurn] = loseResult;\\n queue.push([prevMouse, prevCat, prevTurn]);\\n }\\n }\\n }\\n }\\n }\\n return results[1][2][MOUSE_TURN];\\n};\\n\\nconst getPrevStates = (mouse, cat, turn, graph) => {\\n const prevStates = [];\\n const prevTurn = turn == MOUSE_TURN ? CAT_TURN : MOUSE_TURN;\\n if (prevTurn === MOUSE_TURN) {\\n for (const prev of graph[mouse]) {\\n prevStates.push([prev, cat, prevTurn]);\\n }\\n } else {\\n for (const prev of graph[cat]) {\\n if (prev != 0) {\\n prevStates.push([mouse, prev, prevTurn]);\\n }\\n }\\n }\\n return prevStates;\\n}\\n
###go
\\n\\nconst (\\n mouseTurn = 0\\n catTurn = 1\\n\\n draw = 0\\n mouseWin = 1\\n catWin = 2\\n)\\n\\nfunc catMouseGame(graph [][]int) int {\\n n := len(graph)\\n degrees := make([][][2]int, n)\\n results := make([][][2]int, n)\\n for i := range degrees {\\n degrees[i] = make([][2]int, n)\\n results[i] = make([][2]int, n)\\n }\\n for i, to := range graph {\\n for j := 1; j < n; j++ {\\n degrees[i][j][mouseTurn] = len(to)\\n degrees[i][j][catTurn] = len(graph[j])\\n }\\n }\\n for _, y := range graph[0] {\\n for i := range degrees {\\n degrees[i][y][catTurn]--\\n }\\n }\\n\\n type state struct{ mouse, cat, turn int }\\n q := []state{}\\n for j := 1; j < n; j++ {\\n results[0][j][mouseTurn] = mouseWin\\n results[0][j][catTurn] = mouseWin\\n q = append(q, state{0, j, mouseTurn}, state{0, j, catTurn})\\n }\\n for i := 1; i < n; i++ {\\n results[i][i][mouseTurn] = catWin\\n results[i][i][catTurn] = catWin\\n q = append(q, state{i, i, mouseTurn}, state{i, i, catTurn})\\n }\\n\\n getPrevStates := func(s state) (prevStates []state) {\\n if s.turn == mouseTurn {\\n for _, prev := range graph[s.cat] {\\n if prev != 0 {\\n prevStates = append(prevStates, state{s.mouse, prev, catTurn})\\n }\\n }\\n } else {\\n for _, prev := range graph[s.mouse] {\\n prevStates = append(prevStates, state{prev, s.cat, mouseTurn})\\n }\\n }\\n return\\n }\\n\\n for len(q) > 0 {\\n s := q[0]\\n q = q[1:]\\n result := results[s.mouse][s.cat][s.turn]\\n for _, p := range getPrevStates(s) {\\n prevMouse, prevCat, prevTurn := p.mouse, p.cat, p.turn\\n if results[prevMouse][prevCat][prevTurn] == draw {\\n canWin := result == mouseWin && prevTurn == mouseTurn || result == catWin && prevTurn == catTurn\\n if canWin {\\n results[prevMouse][prevCat][prevTurn] = result\\n q = append(q, p)\\n } else {\\n degrees[prevMouse][prevCat][prevTurn]--\\n if degrees[prevMouse][prevCat][prevTurn] == 0 {\\n if prevTurn == mouseTurn {\\n results[prevMouse][prevCat][prevTurn] = catWin\\n } else {\\n results[prevMouse][prevCat][prevTurn] = mouseWin\\n }\\n q = append(q, p)\\n }\\n }\\n }\\n }\\n }\\n return results[1][2][mouseTurn]\\n}\\n
复杂度分析
\\n\\n
\\n","description":"前言 博弈知识介绍\\n\\n这道题是博弈问题,猫和老鼠都按照最优策略参与游戏。\\n\\n在阐述具体解法之前,首先介绍博弈问题中的三个概念:必胜状态、必败状态与必和状态。\\n\\n对于特定状态,如果游戏已经结束,则根据结束时的状态决定必胜状态、必败状态与必和状态。\\n\\n如果分出胜负,则该特定状态对于获胜方为必胜状态,对于落败方为必败状态。\\n\\n如果是平局,则该特定状态对于双方都为必和状态。\\n\\n从特定状态开始,如果存在一种操作将状态变成必败状态,则当前玩家可以选择该操作,将必败状态留给对方玩家,因此该特定状态对于当前玩家为必胜状态。\\n\\n从特定状态开始…","guid":"https://leetcode.cn/problems/cat-and-mouse//solution/mao-he-lao-shu-by-leetcode-solution-444x","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2022-01-03T12:37:11.726Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"判断一个括号字符串是否有效","url":"https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid//solution/pan-duan-yi-ge-gua-hao-zi-fu-chuan-shi-f-0s47","content":"- \\n
\\n时间复杂度:$O(n^3)$,其中 $n$ 是图中的节点数。状态数是 $O(n^2)$,对于每个状态需要 $O(n)$ 的时间计算状态值,因此总时间复杂度是 $O(n^3)$。
\\n- \\n
\\n空间复杂度:$O(n^2)$,其中 $n$ 是图中的节点数。需要记录每个状态的度和结果,状态数是 $O(n^2)$。
\\n方法一:数学
\\n思路与算法
\\n我们可以如下定义并计算一个括号字符串的「分数」:
\\n\\n\\n考虑一个初值为 $0$ 的整数,遍历该字符串,如果遇到左括号,则将该整数加上 $1$,如果遇到右括号,则将该整数减去 $1$。最终得到的数值即为括号字符串的分数。
\\n那么,根据有效括号字符串的定义,我们可以利用「分数」的概念给出该定义的等价条件:
\\n\\n\\n该字符串的分数为 $0$,且该字符串任意前缀的分数均大于等于 $0$。
\\n读者可以自行尝试证明上述两个条件的等价性。
\\n我们同样可以用「分数」的概念判断一个部分可变的括号字符串 $s$ 能否变为有效字符串。
\\n为叙述方便,我们首先给出「有效前缀」的定义:
\\n\\n\\n如果括号字符串的某个前缀字符串满足它本身及它的所有前缀的分数均大于等于 $0$,则称该前缀为有效前缀。
\\n可以看出,一个有效括号字符串的任意前缀均为「有效前缀」。
\\n我们可以对字符串 $s$,定义对应的最大分数数组 $\\\\textit{mx}$ 和最小有效分数数组 $\\\\textit{mn}$。具体地:
\\n\\n
\\n- \\n
\\n$\\\\textit{mx}[i + 1]$ 代表前缀 $s[0..i]$ 可以达到的最大分数;
\\n- \\n
\\n$\\\\textit{mn}[i + 1]$ 为前缀 $s[0..i]$ 可以达到的最小分数及作为有效前缀所需的最小分数两者的较大值。
\\n其中「作为有效前缀所需的最小分数」的取值,由于字符串分数的奇偶性一定与字符串长度的奇偶性相同,因此取值会有以下两种情况:
\\n\\n
\\n- \\n
\\n$i$ 为偶数,此时最小分数为 $0$;
\\n- \\n
\\n$i$ 为奇数,此时最小分数为 $1$。
\\n用公式表达,即为 $(i + 1) \\\\bmod 2$。
\\n对于 $i = 0$ 的情况,有 $\\\\textit{mx}[0] = \\\\textit{mn}[0] = 0$。
\\n我们从左至右遍历字符串 $s$,并相应维护 $\\\\textit{mx}$ 和 $\\\\textit{mn}$ 数组。具体地,当遍历到下标 $i$ 时,根据 $\\\\textit{locked}[i]$ 的不同取值,会有以下两种情况:
\\n\\n
\\n- \\n
\\n$\\\\textit{locked}[i] = 1$,此时 $s[i]$ 的取值无法更改。因此 $\\\\textit{mx}[i + 1] = \\\\textit{mx}[i] + \\\\textit{diff}$,其中 $\\\\textit{diff}$ 为 $s[i]$ 的分数。同理,$\\\\textit{mn}[i + 1] = \\\\max(\\\\textit{mn}[i] + \\\\textit{diff}, (i + 1) \\\\bmod 2)$。
\\n- \\n
\\n$\\\\textit{locked}[i] = 0$,此时 $s[i]$ 的取值可以更改。因此 $\\\\textit{mx}[i + 1] = \\\\textit{mx}[i] + 1$,且 $\\\\textit{mn}[i + 1] = \\\\max(\\\\textit{mn}[i] - 1, (i + 1) \\\\bmod 2)$。
\\n在遍历的过程中,如果对于某一下标 $i$,有 $\\\\textit{mx}[i] < \\\\textit{mn}[i]$,那么 $s[0..i]$ 无法通过变换成为有效前缀,也就是说 $s$ 无法通过变换成为有效字符串,此时直接返回 $\\\\texttt{false}$。
\\n当遍历完成后,我们只需要确定 $s$ 是否可以通过变换使得分数为 $0$ 即可。假设 $s$ 的长度为 $n$,这等价于判断 $\\\\textit{mn}[n]$ 是否为 $0$。如果 $\\\\textit{mn}[n] = 0$,则 $s$ 可以通过变换成为有效括号字符串,我们应返回 $\\\\texttt{true}$;反之则不能,应返回 $\\\\texttt{false}$。
\\n细节
\\n由上述的推导过程,我们容易发现,在计算 $\\\\textit{mx}[i + 1]$ 与 $\\\\textit{mn}[i + 1]$ 时,我们并不需要用到整个 $\\\\textit{mx}$ 和 $\\\\textit{mn}$ 数组,只需要 $\\\\textit{mx}[i]$ 与 $\\\\textit{mn}[i]$ 的取值。因此,我们可以用两个同名整数来替代 $\\\\textit{mx}$ 和 $\\\\textit{mn}$ 数组。具体转移如下:
\\n\\n
\\n- \\n
\\n$\\\\textit{mx}$ 和 $\\\\textit{mn}$ 的初值为 $0$;
\\n- \\n
\\n$\\\\textit{locked}[i] = 1$ 时,有 $\\\\textit{mx} = \\\\textit{mx} + \\\\textit{diff}$,$\\\\textit{mn} = \\\\max(\\\\textit{mn} + \\\\textit{diff}, (i + 1) \\\\bmod 2)$;
\\n- \\n
\\n$\\\\textit{locked}[i] = 0$ 时,有 $\\\\textit{mx} = \\\\textit{mx} + 1$,$\\\\textit{mn} = \\\\max(\\\\textit{mn} - 1, (i + 1) \\\\bmod 2)$;
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n bool canBeValid(string s, string locked) {\\n int n = s.size();\\n int mx = 0; // 可以达到的最大分数\\n int mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (int i = 0; i < n; ++i) {\\n if (locked[i] == \'1\') {\\n // 此时对应字符无法更改\\n int diff;\\n if (s[i] == \'(\') {\\n diff = 1;\\n }\\n else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = max(mn + diff, (i + 1) % 2);\\n }\\n else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = max(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def canBeValid(self, s: str, locked: str) -> bool:\\n n = len(s)\\n mx = 0 # 可以达到的最大分数\\n mn = 0 # 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for i in range(n):\\n if locked[i] == \'1\':\\n # 此时对应字符无法更改\\n if s[i] == \'(\':\\n diff = 1\\n else:\\n diff = -1\\n mx += diff\\n mn = max(mn + diff, (i + 1) % 2)\\n else:\\n # 此时对应字符可以更改\\n mx += 1\\n mn = max(mn - 1, (i + 1) % 2)\\n if mx < mn:\\n # 此时该前缀无法变为有效前缀\\n return False\\n # 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0\\n
###Java
\\n\\nclass Solution {\\n public boolean canBeValid(String s, String locked) {\\n int n = s.length();\\n int mx = 0; // 可以达到的最大分数\\n int mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (int i = 0; i < n; ++i) {\\n if (locked.charAt(i) == \'1\') {\\n // 此时对应字符无法更改\\n int diff;\\n if (s.charAt(i) == \'(\') {\\n diff = 1;\\n } else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = Math.max(mn + diff, (i + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = Math.max(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public bool CanBeValid(string s, string locked) {\\n int n = s.Length;\\n int mx = 0; // 可以达到的最大分数\\n int mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (int i = 0; i < n; ++i) {\\n if (locked[i] == \'1\') {\\n // 此时对应字符无法更改\\n int diff;\\n if (s[i] == \'(\') {\\n diff = 1;\\n } else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = Math.Max(mn + diff, (i + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = Math.Max(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0;\\n }\\n}\\n
###Go
\\n\\nfunc canBeValid(s string, locked string) bool {\\n n := len(s)\\n mx := 0 // 可以达到的最大分数\\n mn := 0 // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for i := 0; i < n; i++ {\\n if locked[i] == \'1\' {\\n // 此时对应字符无法更改\\n var diff int\\n if s[i] == \'(\' {\\n diff = 1\\n } else {\\n diff = -1\\n }\\n mx += diff\\n mn = int(math.Max(float64(mn+diff), float64((i+1)%2)))\\n } else {\\n // 此时对应字符可以更改\\n mx++\\n mn = int(math.Max(float64(mn-1), float64((i+1)%2)))\\n }\\n if mx < mn {\\n // 此时该前缀无法变为有效前缀\\n return false\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0\\n}\\n
###C
\\n\\nbool canBeValid(char* s, char* locked) {\\n int n = strlen(s);\\n int mx = 0; // 可以达到的最大分数\\n int mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (int i = 0; i < n; ++i) {\\n if (locked[i] == \'1\') {\\n // 此时对应字符无法更改\\n int diff;\\n if (s[i] == \'(\') {\\n diff = 1;\\n } else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = fmax(mn + diff, (i + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = fmax(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn == 0;\\n}\\n
###JavaScript
\\n\\nvar canBeValid = function(s, locked) {\\n let n = s.length;\\n let mx = 0; // 可以达到的最大分数\\n let mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (let i = 0; i < n; ++i) {\\n if (locked[i] === \'1\') {\\n // 此时对应字符无法更改\\n let diff;\\n if (s[i] === \'(\') {\\n diff = 1;\\n } else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = Math.max(mn + diff, (i + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = Math.max(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn === 0;\\n};\\n
###TypeScript
\\n\\nfunction canBeValid(s: string, locked: string): boolean {\\n const n = s.length;\\n let mx = 0; // 可以达到的最大分数\\n let mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (let i = 0; i < n; ++i) {\\n if (locked[i] === \'1\') {\\n // 此时对应字符无法更改\\n let diff: number;\\n if (s[i] === \'(\') {\\n diff = 1;\\n } else {\\n diff = -1;\\n }\\n mx += diff;\\n mn = Math.max(mn + diff, (i + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n ++mx;\\n mn = Math.max(mn - 1, (i + 1) % 2);\\n }\\n if (mx < mn) {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n return mn === 0;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn can_be_valid(s: String, locked: String) -> bool {\\n let n = s.len();\\n let mut mx = 0; // 可以达到的最大分数\\n let mut mn = 0; // 可以达到的最小分数 与 最小有效前缀对应分数 的较大值\\n for (i, (sc, lc)) in s.chars().zip(locked.chars()).enumerate() {\\n if lc == \'1\' {\\n // 此时对应字符无法更改\\n let diff = if sc == \'(\' { 1 } else { -1 };\\n mx += diff;\\n mn = std::cmp::max(mn + diff, (i as i32 + 1) % 2);\\n } else {\\n // 此时对应字符可以更改\\n mx += 1;\\n mn = std::cmp::max(mn - 1, (i as i32 + 1) % 2);\\n }\\n if mx < mn {\\n // 此时该前缀无法变为有效前缀\\n return false;\\n }\\n }\\n // 最终确定 s 能否通过变换使得分数为 0(成为有效字符串)\\n mn == 0\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:数学 思路与算法\\n\\n我们可以如下定义并计算一个括号字符串的「分数」:\\n\\n考虑一个初值为 $0$ 的整数,遍历该字符串,如果遇到左括号,则将该整数加上 $1$,如果遇到右括号,则将该整数减去 $1$。最终得到的数值即为括号字符串的分数。\\n\\n那么,根据有效括号字符串的定义,我们可以利用「分数」的概念给出该定义的等价条件:\\n\\n该字符串的分数为 $0$,且该字符串任意前缀的分数均大于等于 $0$。\\n\\n读者可以自行尝试证明上述两个条件的等价性。\\n\\n我们同样可以用「分数」的概念判断一个部分可变的括号字符串 $s$ 能否变为有效字符串。\\n\\n为叙述方便,我们首先给出…","guid":"https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid//solution/pan-duan-yi-ge-gua-hao-zi-fu-chuan-shi-f-0s47","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-27T05:19:33.458Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题双解 :「排序 + 双指针」&「桶排 + 前缀和」","url":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/gong-shui-san-xie-yi-ti-shuang-jie-pai-x-maa8","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $s$ 的长度。即为遍历字符串维护 $\\\\textit{mx}$ 和 $\\\\textit{mn}$ 并判断 $s$ 能否变为有效字符串的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n排序 + 双指针
\\n从三个不发送好友请求的条件来看,以 $y$ 的角度来说,可总结为:年龄比我小的不考虑(同龄的可以),年龄比我大可以考虑,但是不能超过一定范围则不考虑。
\\n即对于一个确定的 $y$ 而言,会发送好友请求的 $x$ 范围为连续段:
\\n\\n
随着 $y$ 的逐渐增大,对应的 $x$ 连续段的左右边界均逐渐增大(数轴上均往右移动)。
\\n因此,我们可以先对 $ages$ 进行排序,枚举每个 $y = ages[k]$,同时使用 $i$ 和 $j$ 维护左右区间,$[i, j)$ 代表在 $ages$ 上会往 $y = ages[k]$ 发送请求的 $x$ 连续段,统计每个 $y = ages[k]$ 的 $x$ 有多少个即是答案,同时需要注意在 $[i, j)$ 范围内是包含 $y = ages[k]$ 自身,统计区间长度时需要进行 $-1$ 操作。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int numFriendRequests(int[] ages) {\\n Arrays.sort(ages);\\n int n = ages.length, ans = 0;\\n for (int k = 0, i = 0, j = 0; k < n; k++) {\\n while (i < k && !check(ages[i], ages[k])) i++;\\n if (j < k) j = k;\\n while (j < n && check(ages[j], ages[k])) j++;\\n if (j > i) ans += j - i - 1;\\n }\\n return ans;\\n }\\n boolean check(int x, int y) {\\n if (y <= 0.5 * x + 7) return false;\\n if (y > x) return false;\\n if (y > 100 && x < 100) return false; \\n return true;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n\\\\log{n})$
\\n- 空间复杂度:$O(\\\\log{n})$
\\n
\\n桶排序 + 前缀和
\\n在解法一中,复杂度的上界在于「双轴快排」,利用本题数据范围
\\n1 <= ages[i] <= 120
,值域较小,我们可以通过「桶排序」的方式进行排序优化。假设对 $ages$ 进行桶排后得到的数组为 $nums$,其中 $cnt = nums[i]$ 的含义为在 $ages$ 中年龄为 $i$ 的人有 $cnt$ 个。
\\n同时,我们发现在解法一中,我们枚举 $y = ages[k]$,并使用 $i$ 和 $j$ 两个指针寻找连续的 $x$ 段的过程,$x$ 会始终停留于值与 $y = ages[k]$ 相等的最小下标处,而对于桶排数组而言,当前位置就是最小合法 $x$ 值(与 $y$ 相等),因此我们只需要找到最大合法 $x$ 值的位置即可(对应解法一的 $j$ 位置)。
\\n同样,最大 $x$ 的位置在桶排数组中也是随着 $y$ 的增大(右移)逐渐增大(右移)。
\\n剩下的问题在于,如何统计桶排数组中连续段下标的和为多少(有多少个合法 $x$ 值),这可以直接在桶排数组应用前缀和即可。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n int N = 130;\\n public int numFriendRequests(int[] ages) {\\n int[] nums = new int[N];\\n for (int i : ages) nums[i]++;\\n for (int i = 1; i < N; i++) nums[i] += nums[i - 1];\\n int ans = 0;\\n for (int y = 1, x = 1; y < N; y++) {\\n int a = nums[y] - nums[y - 1]; // 有 a 个 y\\n if (a == 0) continue;\\n if (x < y) x = y;\\n while (x < N && check(x, y)) x++;\\n int b = nums[x - 1] - nums[y - 1] - 1; // [y, x) 为合法的 x 范围,对于每个 y 而言,有 b 个 x\\n if (b > 0) ans += b * a;\\n }\\n return ans;\\n }\\n boolean check(int x, int y) {\\n if (y <= 0.5 * x + 7) return false;\\n if (y > x) return false;\\n if (y > 100 && x < 100) return false; \\n return true;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $C$ 为年龄值域大小,对于本题 $C$ 固定为 $130$。复杂度为 $O(\\\\max(n, C))$
\\n- 空间复杂度:$O(C)$
\\n
\\n同类型加餐
\\n今日份加餐:【面试高频题】难度 1.5/5,一道「桶排序 + 前缀和」优化题 🎉 🎉
\\n或是加练如下「前缀和」题目 🍭🍭
\\n\\n\\n
\\n\\n \\n\\n\\n题目 \\n题解 \\n难度 \\n推荐指数 \\n\\n \\n689. 三个无重叠子数组的最大和 \\nLeetCode 题解链接 \\n困难 \\n🤩🤩🤩 \\n\\n \\n1074. 元素和为目标值的子矩阵数量 \\nLeetCode 题解链接 \\n困难 \\n🤩🤩🤩 \\n\\n \\n1310. 子数组异或查询 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩🤩 \\n\\n \\n1442. 形成两个异或相等数组的三元组数目 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n1738. 找出第 K 大的异或坐标值 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n1749. 任意子数组和的绝对值的最大值 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩 \\n\\n \\n\\n1894. 找到需要补充粉笔的学生编号 \\nLeetCode 题解链接 \\n中等 \\n🤩🤩🤩🤩 \\n注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"排序 + 双指针 从三个不发送好友请求的条件来看,以 $y$ 的角度来说,可总结为:年龄比我小的不考虑(同龄的可以),年龄比我大可以考虑,但是不能超过一定范围则不考虑。\\n\\n即对于一个确定的 $y$ 而言,会发送好友请求的 $x$ 范围为连续段:\\n\\n随着 $y$ 的逐渐增大,对应的 $x$ 连续段的左右边界均逐渐增大(数轴上均往右移动)。\\n\\n因此,我们可以先对 $ages$ 进行排序,枚举每个 $y = ages[k]$,同时使用 $i$ 和 $j$ 维护左右区间,$[i, j)$ 代表在 $ages$ 上会往 $y = ages[k]$ 发送请求的 $x…","guid":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/gong-shui-san-xie-yi-ti-shuang-jie-pai-x-maa8","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-26T23:27:49.862Z","media":[{"url":"https://pic.leetcode-cn.com/1640558862-McjmOR-image.png","type":"photo","width":1456,"height":598,"blurhash":"LiO|z_xuoMxu%2fPofj[~9j[ayoL"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python/Java/JavaScript/Go] 逻辑推导 + 前缀和统计","url":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/pythonjavajavascriptgo-luo-ji-tui-dao-qi-bn03","content":"解题思路
\\n###python3
\\n\\n# 根据题目我们知道加好友的条件需要满足如下布尔表达式:\\n# ! ( (ages[y] <= 0.5 * ages[x] + 7) || (ages[y] > ages[x]) || (ages[y] > 100 && ages[x] < 100) )\\n# 化简可得:\\n# ages[y] > 0.5 * ages[x] + 7 && ages[y] <= ages[x] && (ages[y] <= 100 || ages[x] >= 100)\\n# 也就是x和y满足以下两者之一,x就可以向y发送好友请求:\\n# 1. ages[y] <= ages[x] < (ages[y] - 7) * 2 && ages[y] <= 100\\n# 2. 0.5 * ages[x] + 7 < ages[y] <= ages[x] && ages[x] >= 100\\n
我们使用第二个表达式即可,因为ages[x]小于100时,ages[y]小于等于ages[x]必然就满足ages[y] <= 100,所以只要满足另一半布尔表达式的要求。
\\n
\\n根据这个布尔表达式我们知道,x可以添加好友的y是一个范围,可以使用前缀和快速统计一个范围内的人数,从而求解答案。代码
\\n###python3
\\n\\nclass Solution:\\n def numFriendRequests(self, ages: List[int]) -> int:\\n cnts = [0] * (max(ages) + 1)\\n for age in ages:\\n cnts[age] += 1\\n presum = [0] + list(accumulate(cnts))\\n return sum(cnts[age] * max(0, presum[age + 1] - presum[age//2 + 8] - 1) for age in set(ages))\\n
###Java
\\n\\nclass Solution {\\n public int numFriendRequests(int[] ages) {\\n int[] cnts = new int[120], presum = new int[121];\\n for(int age: ages)\\n cnts[age - 1]++;\\n for(int i=1;i<121;i++)\\n presum[i] = presum[i-1] + cnts[i-1];\\n int ans = 0;\\n for(int age: ages)\\n ans += Math.max(0, presum[age] - presum[age/2 + 7] - 1);\\n return ans;\\n }\\n}\\n
###JavaScript
\\n\\n/**\\n * @param {number[]} ages\\n * @return {number}\\n */\\nvar numFriendRequests = function(ages) {\\n const cnts = new Array(120), presum = new Array(121)\\n cnts.fill(0)\\n presum.fill(0)\\n for(const age of ages)\\n cnts[age-1]++\\n for(let i=1;i<121;i++)\\n presum[i] = presum[i-1] + cnts[i-1]\\n let ans = 0\\n for(const age of ages)\\n ans += Math.max(0, presum[age] - presum[Math.floor(age/2) + 7] - 1)\\n return ans\\n};\\n
###Go
\\n\\n","description":"解题思路 ###python3\\n\\n# 根据题目我们知道加好友的条件需要满足如下布尔表达式:\\n# ! ( (ages[y] <= 0.5 * ages[x] + 7) || (ages[y] > ages[x]) || (ages[y] > 100 && ages[x] < 100) )\\n# 化简可得:\\n# ages[y] > 0.5 * ages[x] + 7 && ages[y] <= ages[x] && (ages[y] <= 100 || ages[x] >= 100)\\n# 也就是x和y满足以下两者之一,x就可以向y发送好友请求:\\n# 1…","guid":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/pythonjavajavascriptgo-luo-ji-tui-dao-qi-bn03","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-26T23:04:52.835Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一次遍历(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid//solution/zheng-fan-liang-ci-bian-li-by-endlessche-z8ac","content":"func numFriendRequests(ages []int) (ans int) {\\n cnts, presum := make([]int, 120), make([]int, 121)\\n for _, age := range ages {\\n cnts[age - 1]++\\n }\\n for i:=1;i<121;i++{\\n presum[i] = presum[i-1] + cnts[i-1];\\n }\\n for _, age := range ages{\\n if v := presum[age] - presum[age/2+7] - 1; v > 0{\\n ans += v\\n }\\n }\\n return\\n}\\n
首先,介绍处理有效括号字符串的通用思路。
\\n在有效括号字符串的任意一个前缀中,左括号的数量都大于等于右括号的数量。例如有效括号字符串 $\\\\texttt{\\"(())()\\"}$,它的前缀有 $\\\\texttt{\\"(()\\"}$,$\\\\texttt{\\"(())(\\"}$ 等,都满足这一性质。为什么?因为对于有效括号字符串的前缀来说,每个右括号的左边,必然有与之匹配的左括号,但左括号不一定有与之匹配的右括号。
\\n根据这一性质,从左到右遍历字符串 $s$,统计未匹配的左括号的个数 $c$:遇到左括号就把 $c$ 加一,遇到右括号就把 $c$ 减一(匹配一个左括号)。如果任何时刻 $c$ 都不为负数,且最终 $c=0$,那么 $s$ 就是有效括号字符串。
\\n例如 $s=\\\\texttt{(())()}$,遍历 $s$ 的过程中,$c$ 是这样变化的
\\n$$
\\n
\\n1\\\\to 2\\\\to 1\\\\to 0\\\\to 1\\\\to 0
\\n$$本题可以修改括号,把可以修改的括号视作 $\\\\texttt{?}$,例如示例 1 的 $s=\\\\texttt{?)?)??}$。
\\n第一个例子
\\n如果 $s=\\\\texttt{??????}$,考察在遍历 $s$ 的过程中,未匹配的左括号的个数 $c$ 如何变化。
\\n注意左括号可以让 $c$ 加一,右括号可以让 $c$ 减一(但不能是负数)。
\\n\\n\\n
\\n\\n \\n\\n\\n$i$ \\n$s_i$ \\n$c$ 的取值范围 \\n备注 \\n\\n \\n$0$ \\n$\\\\texttt{?}$ \\n${1}$ \\n只能是左括号 \\n\\n \\n$1$ \\n$\\\\texttt{?}$ \\n${0,2}$ \\n可以是左括号也可以是右括号 \\n\\n \\n$2$ \\n$\\\\texttt{?}$ \\n${1,3}$ \\n同上(注意 $c$ 不能是负数) \\n\\n \\n$3$ \\n$\\\\texttt{?}$ \\n${0,2,4}$ \\n同上 \\n\\n \\n$4$ \\n$\\\\texttt{?}$ \\n${1,3,5}$ \\n同上 \\n\\n \\n\\n$5$ \\n$\\\\texttt{?}$ \\n${0,2,4,6}$ \\n同上 \\n最终 $c$ 能变成 $0$,说明我们可以把 $s$ 变成有效括号字符串,比如 $\\\\texttt{(())()}$。
\\n第二个例子
\\n如果 $s=\\\\texttt{??())?))}$,考察在遍历 $s$ 的过程中,未匹配的左括号的个数 $c$ 如何变化。
\\n\\n\\n
\\n\\n \\n\\n\\n$i$ \\n$s_i$ \\n$c$ 的取值范围 \\n备注 \\n\\n \\n$0$ \\n$\\\\texttt{?}$ \\n${1}$ \\n只能是左括号 \\n\\n \\n$1$ \\n$\\\\texttt{?}$ \\n${0,2}$ \\n可以是左括号也可以是右括号 \\n\\n \\n$2$ \\n$\\\\texttt{(}$ \\n${1,3}$ \\n所有 $c$ 加一 \\n\\n \\n$3$ \\n$\\\\texttt{)}$ \\n${0,2}$ \\n所有 $c$ 减一 \\n\\n \\n$4$ \\n$\\\\texttt{)}$ \\n${1}$ \\n所有 $c$ 减一,去掉负数 \\n\\n \\n$5$ \\n$\\\\texttt{?}$ \\n${0,2}$ \\n可以是左括号也可以是右括号 \\n\\n \\n$6$ \\n$\\\\texttt{)}$ \\n${1}$ \\n所有 $c$ 减一,去掉负数 \\n\\n \\n\\n$7$ \\n$\\\\texttt{)}$ \\n${0}$ \\n所有 $c$ 减一 \\n最终 $c$ 能变成 $0$,说明我们可以把 $s$ 变成有效括号字符串。本例只有一种方案,即 $\\\\texttt{((())())}$。
\\n第三个例子
\\n如果 $s=\\\\texttt{?))?}$,考察在遍历 $s$ 的过程中,未匹配的左括号的个数 $c$ 如何变化。
\\n\\n\\n
\\n\\n \\n\\n\\n$i$ \\n$s_i$ \\n$c$ 的取值范围 \\n备注 \\n\\n \\n$0$ \\n$\\\\texttt{?}$ \\n${1}$ \\n只能是左括号 \\n\\n \\n$1$ \\n$\\\\texttt{)}$ \\n${0}$ \\n所有 $c$ 减一 \\n\\n \\n\\n$2$ \\n$\\\\texttt{)}$ \\n${}$ \\n所有 $c$ 减一,去掉负数 \\n遍历到 $s_2$ 的时候,$c$ 的取值范围为空,说明无法把 $\\\\texttt{?))?}$ 变成有效括号字符串。
\\n第四个例子
\\n如果 $s=\\\\texttt{?((?}$,考察在遍历 $s$ 的过程中,未匹配的左括号的个数 $c$ 如何变化。
\\n\\n\\n
\\n\\n \\n\\n\\n$i$ \\n$s_i$ \\n$c$ 的取值范围 \\n备注 \\n\\n \\n$0$ \\n$\\\\texttt{?}$ \\n${1}$ \\n只能是左括号 \\n\\n \\n$1$ \\n$\\\\texttt{(}$ \\n${2}$ \\n所有 $c$ 加一 \\n\\n \\n$2$ \\n$\\\\texttt{(}$ \\n${3}$ \\n所有 $c$ 加一 \\n\\n \\n\\n$3$ \\n$\\\\texttt{?}$ \\n${2,4}$ \\n可以是左括号也可以是右括号 \\n由于最终 $c$ 的取值范围不包含 $0$,我们无法把 $\\\\texttt{?((?}$ 变成有效括号字符串。
\\n总结
\\n如果最终 $c$ 的取值范围不为空,且包含 $0$,那么可以把 $s$ 变成有效括号字符串。
\\n我们可以用代码模拟上述流程。但是,真的需要维护一个集合吗?
\\n注意到,集合中的数都是连续的奇数或者连续的偶数(可以用数学归纳法证明),不存在 ${1,5,7}$ 这种情况。所以只需要维护集合中的最小值和最大值。
\\n算法
\\n遍历 $s$ 的过程中,维护 $c$ 的取值范围中的最小值 $\\\\textit{mn}$ 和最大值 $\\\\textit{mx}$:
\\n\\n
\\n- 如果 $s_i=\\\\texttt{(}$,那么把 $\\\\textit{mn}$ 和 $\\\\textit{mx}$ 都加一。
\\n- 如果 $s_i=\\\\texttt{)}$,那么把 $\\\\textit{mn}$ 和 $\\\\textit{mx}$ 都减一。如果 $\\\\textit{mx}<0$,去掉负数后集合为空,说明无法把 $s$ 变成有效括号字符串。如果 $\\\\textit{mn}<0$,那么把 $\\\\textit{mn}$ 改成 $1$。比如 ${0,2,4}$ 都减一变成 ${-1,1,3}$,去掉负数变成 ${1,3}$,其中最小的数是 $1$。
\\n- 如果 $s_i=\\\\texttt{?}$,这个问号可以是左括号,把 $\\\\textit{mn}$ 减一;也可以是右括号,把 $\\\\textit{mx}$ 加一。如果 $\\\\textit{mn}<0$,那么把 $\\\\textit{mn}$ 改成 $1$。
\\n如果最终 $\\\\textit{mn}=0$,说明最终 $c$ 能变成 $0$,我们可以把 $s$ 变成有效括号字符串。
\\n细节:如果 $s$ 的长度是奇数,那么一定有个单独的括号无法匹配,直接返回 $\\\\texttt{false}$。
\\n\\nclass Solution:\\n def canBeValid(self, s: str, locked: str) -> bool:\\n if len(s) % 2:\\n return False\\n mn = mx = 0\\n for b, lock in zip(s, locked):\\n if lock == \'1\': # 不能改\\n d = 1 if b == \'(\' else -1\\n mx += d\\n if mx < 0: # c 不能为负\\n return False\\n mn += d\\n else: # 可以改\\n mx += 1 # 改成右括号\\n mn -= 1 # 改成左括号\\n if mn < 0: # c 不能为负\\n mn = 1 # 此时 c 的取值范围都是奇数,最小的奇数是 1\\n return mn == 0 # 说明最终 c 能是 0\\n
\\nclass Solution {\\n public boolean canBeValid(String s, String locked) {\\n if (s.length() % 2 > 0) {\\n return false;\\n }\\n int mn = 0;\\n int mx = 0;\\n for (int i = 0; i < s.length(); i++) {\\n if (locked.charAt(i) == \'1\') { // 不能改\\n int d = s.charAt(i) == \'(\' ? 1 : -1;\\n mx += d;\\n if (mx < 0) { // c 不能为负\\n return false;\\n }\\n mn += d;\\n } else { // 可以改\\n mx++; // 改成右括号\\n mn--; // 改成左括号\\n }\\n if (mn < 0) { // c 不能为负\\n mn = 1; // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n return mn == 0; // 说明最终 c 能是 0\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool canBeValid(string s, string locked) {\\n if (s.size() % 2) {\\n return false;\\n }\\n int mn = 0, mx = 0;\\n for (int i = 0; i < s.size(); i++) {\\n if (locked[i] == \'1\') { // 不能改\\n int d = s[i] == \'(\' ? 1 : -1;\\n mx += d;\\n if (mx < 0) { // c 不能为负\\n return false;\\n }\\n mn += d;\\n } else { // 可以改\\n mx++; // 改成右括号\\n mn--; // 改成左括号\\n }\\n if (mn < 0) { // c 不能为负\\n mn = 1; // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n return mn == 0; // 说明最终 c 能是 0\\n }\\n};\\n
\\nbool canBeValid(char* s, char* locked) {\\n int mn = 0, mx = 0;\\n for (int i = 0; locked[i]; i++) {\\n if (locked[i] == \'1\') { // 不能改\\n int d = s[i] == \'(\' ? 1 : -1;\\n mx += d;\\n if (mx < 0) { // c 不能为负\\n return false;\\n }\\n mn += d;\\n } else { // 可以改\\n mx++; // 改成右括号\\n mn--; // 改成左括号\\n }\\n if (mn < 0) { // c 不能为负\\n mn = 1; // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n return mn == 0; // 说明最终 c 能是 0\\n}\\n
\\nfunc canBeValid(s, locked string) bool {\\n if len(s)%2 > 0 {\\n return false\\n }\\n mn, mx := 0, 0\\n for i, lock := range locked {\\n if lock == \'1\' { // 不能改\\n d := 1 - int(s[i]%2*2) // 左括号是 1,右括号是 -1\\n mx += d\\n if mx < 0 { // c 不能为负\\n return false\\n }\\n mn += d\\n } else { // 可以改\\n mx++ // 改成右括号\\n mn-- // 改成左括号\\n }\\n if mn < 0 { // c 不能为负\\n mn = 1 // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n return mn == 0 // 说明最终 c 能是 0\\n}\\n
\\nvar canBeValid = function(s, locked) {\\n if (s.length % 2 > 0) {\\n return false;\\n }\\n let mn = 0, mx = 0;\\n for (let i = 0; i < s.length; i++) {\\n if (locked[i] === \'1\') { // 不能改\\n const d = s[i] === \'(\' ? 1 : -1;\\n mx += d;\\n if (mx < 0) { // c 不能为负\\n return false;\\n }\\n mn += d;\\n } else { // 可以改\\n mx++; // 改成右括号\\n mn--; // 改成左括号\\n }\\n if (mn < 0) { // c 不能为负\\n mn = 1; // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n return mn === 0; // 说明最终 c 能是 0\\n};\\n
\\nimpl Solution {\\n pub fn can_be_valid(s: String, locked: String) -> bool {\\n if s.len() % 2 > 0 {\\n return false;\\n }\\n let mut mn = 0;\\n let mut mx = 0;\\n for (b, lock) in s.bytes().zip(locked.bytes()) {\\n if lock == b\'1\' { // 不能改\\n let d = if b == b\'(\' { 1 } else { -1 };\\n mx += d;\\n if mx < 0 { // c 不能为负\\n return false;\\n }\\n mn += d;\\n } else { // 可以改\\n mx += 1; // 改成右括号\\n mn -= 1; // 改成左括号\\n }\\n if mn < 0 { // c 不能为负\\n mn = 1; // 此时 c 的取值范围都是奇数,最小的奇数是 1\\n }\\n }\\n mn == 0 // 说明最终 c 能是 0\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面数据结构题单中的「§3.4 合法括号字符串」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"首先,介绍处理有效括号字符串的通用思路。 在有效括号字符串的任意一个前缀中,左括号的数量都大于等于右括号的数量。例如有效括号字符串 $\\\\texttt{\\"(())()\\"}$,它的前缀有 $\\\\texttt{\\"(()\\"}$,$\\\\texttt{\\"(())(\\"}$ 等,都满足这一性质。为什么?因为对于有效括号字符串的前缀来说,每个右括号的左边,必然有与之匹配的左括号,但左括号不一定有与之匹配的右括号。\\n\\n根据这一性质,从左到右遍历字符串 $s$,统计未匹配的左括号的个数 $c$:遇到左括号就把 $c$ 加一,遇到右括号就把 $c$ 减一(匹配一个左括号…","guid":"https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid//solution/zheng-fan-liang-ci-bian-li-by-endlessche-z8ac","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-26T09:33:29.505Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前后各遍历一次,分别判断左括号和右括号","url":"https://leetcode.cn/problems/check-if-a-parentheses-string-can-be-valid//solution/qian-hou-ge-bian-li-yi-ci-fen-bie-pan-du-w5nu","content":"首先排除掉字符个数为奇数的情况,再从头遍历一次不可修改的右括号,从尾遍历一次不可修改的左括号,看是否能构成有效字符串就行了。
\\n\\n","description":"首先排除掉字符个数为奇数的情况,再从头遍历一次不可修改的右括号,从尾遍历一次不可修改的左括号,看是否能构成有效字符串就行了。 class Solution {\\npublic:\\n bool canBeValid(string s, string locked) {\\n int n=s.size(),l=0,r=0;\\n if(n%2==1) return false;\\n for(int i=0;iclass Solution {\\npublic:\\n bool canBeValid(string s, string locked) {\\n int n=s.size(),l=0,r=0;\\n if(n%2==1) return false;\\n for(int i=0;i<n;i++){\\n if(locked[i]==\'1\'&&s[i]==\')\'){\\n r++;\\n if(i+1-r<r) return false;\\n }\\n }\\n for(int i=n-1;i>=0;i--){\\n if(locked[i]==\'1\'&&s[i]==\'(\'){\\n l++;\\n if(n-i-l<l) return false;\\n }\\n }\\n return true;\\n }\\n};\\n
方法一:排序 + 双指针\\n 思路与算法
\\n观察题目中给定的三个条件:
\\n\\n
\\n- \\n
\\n$\\\\textit{ages}[y] \\\\leq 0.5 \\\\times \\\\textit{ages}[x] + 7$
\\n- \\n
\\n$\\\\textit{ages}[y] > \\\\textit{ages}[x]$
\\n- \\n
\\n$\\\\textit{ages}[y] > 100 \\\\wedge \\\\textit{ages}[x] < 100$
\\n可以发现,条件 $3$ 是蕴含在条件 $2$ 中的,即如果满足条件 $3$ 那么一定满足条件 $2$。因此,我们当条件 $1$ 和 $2$ 均不满足时,用户 $x$ 就会向用户 $y$ 发送好友请求,也就是用户 $y$ 需要满足:
\\n$$
\\n
\\n0.5 \\\\times \\\\textit{ages}[x] + 7 < \\\\textit{ages}[y] \\\\leq \\\\textit{ages}[x]
\\n$$当 $\\\\textit{ages}[x] \\\\leq 14$ 时,不存在满足要求的 $\\\\textit{ages}[y]$。因此我们只需要考虑 $\\\\textit{ages}[x] \\\\geq 15$ 的情况,此时满足要求的 $\\\\textit{ages}[y]$ 的范围为 $\\\\big( 0.5 \\\\times \\\\textit{ages}[x] + 7, \\\\textit{ages}[x] \\\\big]$。
\\n当 $\\\\textit{ages}[x]$ 增加时,上述区间的左右边界均单调递增,因此如果我们将数组 $\\\\textit{ages}$ 进行升序排序,那么就可以在遍历 $\\\\textit{ages}[x]$ 的同时,使用两个指针 $\\\\textit{left}$ 和 $\\\\textit{right}$ 维护满足要求的 $\\\\textit{ages}[y]$ 的左右边界。当 $x$ 向后移动一个位置时:
\\n\\n
\\n- \\n
\\n如果左边界指针 $\\\\textit{left}$ 指向的元素不满足 $\\\\textit{ages}[\\\\textit{left}] > 0.5 \\\\times \\\\textit{ages}[x] + 7$,那么就将左边界向后移动一个位置;
\\n- \\n
\\n如果右边界指针 $\\\\textit{right}$ 指向的下一个元素满足 $\\\\textit{ages}[\\\\textit{right} + 1] \\\\leq \\\\textit{ages}[x]$,那么就将右边界向后移动一个位置。
\\n这样一来,$[\\\\textit{left}, \\\\textit{right}]$ 就是满足年龄要求的 $y$ 的下标。需要注意的是,$x$ 本身一定在 $[\\\\textit{left}, \\\\textit{right}]$ 区间内,因此 $x$ 发送的好友请求数,即为 $[\\\\textit{left}, \\\\textit{right}]$ 区间的长度减去 $1$。
\\n我们将每一个 $x$ 对应的 $[\\\\textit{left}, \\\\textit{right}]$ 区间长度减去 $1$ 进行累加,就可以得到最终的答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numFriendRequests(vector<int>& ages) {\\n int n = ages.size();\\n sort(ages.begin(), ages.end());\\n int left = 0, right = 0, ans = 0;\\n for (int age: ages) {\\n if (age < 15) {\\n continue;\\n }\\n while (ages[left] <= 0.5 * age + 7) {\\n ++left;\\n }\\n while (right + 1 < n && ages[right + 1] <= age) {\\n ++right;\\n }\\n ans += right - left;\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int numFriendRequests(int[] ages) {\\n int n = ages.length;\\n Arrays.sort(ages);\\n int left = 0, right = 0, ans = 0;\\n for (int age : ages) {\\n if (age < 15) {\\n continue;\\n }\\n while (ages[left] <= 0.5 * age + 7) {\\n ++left;\\n }\\n while (right + 1 < n && ages[right + 1] <= age) {\\n ++right;\\n }\\n ans += right - left;\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int NumFriendRequests(int[] ages) {\\n int n = ages.Length;\\n Array.Sort(ages);\\n int left = 0, right = 0, ans = 0;\\n foreach (int age in ages) {\\n if (age < 15) {\\n continue;\\n }\\n while (ages[left] <= 0.5 * age + 7) {\\n ++left;\\n }\\n while (right + 1 < n && ages[right + 1] <= age) {\\n ++right;\\n }\\n ans += right - left;\\n }\\n return ans;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def numFriendRequests(self, ages: List[int]) -> int:\\n n = len(ages)\\n ages.sort()\\n left = right = ans = 0\\n for age in ages:\\n if age < 15:\\n continue\\n while ages[left] <= 0.5 * age + 7:\\n left += 1\\n while right + 1 < n and ages[right + 1] <= age:\\n right += 1\\n ans += right - left\\n return ans\\n
###go
\\n\\nfunc numFriendRequests(ages []int) (ans int) {\\n sort.Ints(ages)\\n left, right := 0, 0\\n for _, age := range ages {\\n if age < 15 {\\n continue\\n }\\n for ages[left]*2 <= age+14 {\\n left++\\n }\\n for right+1 < len(ages) && ages[right+1] <= age {\\n right++\\n }\\n ans += right - left\\n }\\n return\\n}\\n
###C
\\n\\nstatic int cmp(const void * pa, const void * pb) {\\n return *(int *)pa - *(int *)pb;\\n}\\n\\nint numFriendRequests(int* ages, int agesSize){\\n qsort(ages, agesSize, sizeof(int), cmp);\\n int left = 0, right = 0, ans = 0;\\n for (int i = 0; i < agesSize; ++i) {\\n if (ages[i] < 15) {\\n continue;\\n }\\n while (ages[left] <= 0.5 * ages[i] + 7) {\\n ++left;\\n }\\n while (right + 1 < agesSize && ages[right + 1] <= ages[i]) {\\n ++right;\\n }\\n ans += right - left;\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar numFriendRequests = function(ages) {\\n const n = ages.length;\\n ages.sort((a, b) => a - b);\\n let left = 0, right = 0, ans = 0;\\n for (const age of ages) {\\n if (age < 15) {\\n continue;\\n }\\n while (ages[left] <= 0.5 * age + 7) {\\n ++left;\\n }\\n while (right + 1 < n && ages[right + 1] <= age) {\\n ++right;\\n }\\n ans += right - left;\\n }\\n return ans;\\n};\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n \\\\log n)$。排序需要的时间为 $O(n \\\\log n)$,遍历所有的 $\\\\textit{ages}[x]$ 以及使用双指针维护答案区间的时间复杂度为 $O(n)$。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$,即为排序需要使用的栈空间。
\\n方法二:计数排序 + 前缀和
\\n思路与算法
\\n注意到题目中给定的年龄在 $[1, 120]$ 的范围内,因此我们可以使用计数排序代替普通的排序。
\\n记 $\\\\textit{cnt}[\\\\textit{age}]$ 表示年龄为 $\\\\textit{age}$ 的用户数,那么每一个年龄为 $\\\\textit{age}~(\\\\textit{age} \\\\geq 15)$ 的用户发送好友的请求数量即为:
\\n$$
\\n
\\n\\\\left( \\\\sum_{i=\\\\lfloor 0.5 \\\\times \\\\textit{age} + 8 \\\\rfloor}^\\\\textit{age} \\\\textit{cnt}[i] \\\\right) - 1
\\n$$这里的 $\\\\lfloor \\\\cdot \\\\rfloor$ 表示向下取整,$-1$ 表示减去自身,与方法一相同。
\\n为了快速计算上式,我们可以使用数组 $\\\\textit{pre}$ 存储数组 $\\\\textit{cnt}$ 的前缀和,即:
\\n$$
\\n
\\n\\\\textit{pre}[\\\\textit{age}] = \\\\sum_{i=1}^\\\\textit{age} \\\\textit{cnt}[i]
\\n$$这样一来,上式就可以简化为:
\\n$$
\\n
\\n(\\\\textit{pre}[\\\\textit{age}] - \\\\textit{pre}[\\\\lfloor 0.5 \\\\times \\\\textit{age} + 7 \\\\rfloor]) - 1
\\n$$我们就可以在 $O(1)$ 的时间内计算出一个年龄为 $\\\\textit{age}$ 的用户发送好友的请求数量,将其乘以 $\\\\textit{cnt}[\\\\textit{age}]$ 并累加就可以得到最终的答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numFriendRequests(vector<int>& ages) {\\n vector<int> cnt(121);\\n for (int age: ages) {\\n ++cnt[age];\\n }\\n vector<int> pre(121);\\n for (int i = 1; i <= 120; ++i) {\\n pre[i] = pre[i - 1] + cnt[i];\\n }\\n int ans = 0;\\n for (int i = 15; i <= 120; ++i) {\\n if (cnt[i]) {\\n int bound = i * 0.5 + 8;\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);\\n }\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int numFriendRequests(int[] ages) {\\n int[] cnt = new int[121];\\n for (int age : ages) {\\n ++cnt[age];\\n }\\n int[] pre = new int[121];\\n for (int i = 1; i <= 120; ++i) {\\n pre[i] = pre[i - 1] + cnt[i];\\n }\\n int ans = 0;\\n for (int i = 15; i <= 120; ++i) {\\n if (cnt[i] > 0) {\\n int bound = (int) (i * 0.5 + 8);\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int NumFriendRequests(int[] ages) {\\n int[] cnt = new int[121];\\n foreach (int age in ages) {\\n ++cnt[age];\\n }\\n int[] pre = new int[121];\\n for (int i = 1; i <= 120; ++i) {\\n pre[i] = pre[i - 1] + cnt[i];\\n }\\n int ans = 0;\\n for (int i = 15; i <= 120; ++i) {\\n if (cnt[i] > 0) {\\n int bound = (int) (i * 0.5 + 8);\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);\\n }\\n }\\n return ans;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def numFriendRequests(self, ages: List[int]) -> int:\\n cnt = [0] * 121\\n for age in ages:\\n cnt[age] += 1\\n pre = [0] * 121\\n for i in range(1, 121):\\n pre[i] = pre[i - 1] + cnt[i]\\n \\n ans = 0\\n for i in range(15, 121):\\n if cnt[i] > 0:\\n bound = int(i * 0.5 + 8)\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1)\\n return ans\\n
###go
\\n\\nfunc numFriendRequests(ages []int) (ans int) {\\n const mx = 121\\n var cnt, pre [mx]int\\n for _, age := range ages {\\n cnt[age]++\\n }\\n for i := 1; i < mx; i++ {\\n pre[i] = pre[i-1] + cnt[i]\\n }\\n for i := 15; i < mx; i++ {\\n if cnt[i] > 0 {\\n bound := i/2 + 8\\n ans += cnt[i] * (pre[i] - pre[bound-1] - 1)\\n }\\n }\\n return\\n}\\n
###C
\\n\\nint numFriendRequests(int* ages, int agesSize){\\n int * cnt = (int *)malloc(sizeof(int) * 121);\\n int * pre = (int *)malloc(sizeof(int) * 121);\\n memset(cnt, 0, sizeof(int) * 121);\\n memset(pre, 0, sizeof(int) * 121);\\n for (int i = 0; i < agesSize; ++i) {\\n ++cnt[ages[i]];\\n }\\n for (int i = 1; i <= 120; ++i) {\\n pre[i] = pre[i - 1] + cnt[i];\\n }\\n int ans = 0;\\n for (int i = 15; i <= 120; ++i) {\\n if (cnt[i]) {\\n int bound = i * 0.5 + 8;\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);\\n }\\n }\\n free(cnt);\\n free(pre);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar numFriendRequests = function(ages) {\\n const cnt = new Array(121).fill(0);\\n for (const age of ages) {\\n ++cnt[age];\\n }\\n const pre = new Array(121).fill(0);\\n for (let i = 1; i <= 120; ++i) {\\n pre[i] = pre[i - 1] + cnt[i];\\n }\\n let ans = 0;\\n for (let i = 15; i <= 120; ++i) {\\n if (cnt[i] > 0) {\\n const bound = Math.floor(i * 0.5 + 8);\\n ans += cnt[i] * (pre[i] - pre[bound - 1] - 1);\\n }\\n }\\n return ans;\\n};\\n
复杂度分析
\\n\\n
\\n","description":"方法一:排序 + 双指针 思路与算法\\n\\n观察题目中给定的三个条件:\\n\\n$\\\\textit{ages}[y] \\\\leq 0.5 \\\\times \\\\textit{ages}[x] + 7$\\n\\n$\\\\textit{ages}[y] > \\\\textit{ages}[x]$\\n\\n$\\\\textit{ages}[y] > 100 \\\\wedge \\\\textit{ages}[x] < 100$\\n\\n可以发现,条件 $3$ 是蕴含在条件 $2$ 中的,即如果满足条件 $3$ 那么一定满足条件 $2$。因此,我们当条件 $1$ 和 $2$ 均不满足时,用户 $x$ 就会向用户…","guid":"https://leetcode.cn/problems/friends-of-appropriate-ages//solution/gua-ling-de-peng-you-by-leetcode-solutio-v7yk","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-24T03:25:02.570Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【微扰理论】贪心 + 堆 or TreeMap| 每次找最邻近过期的苹果吃 | 红黑树更简洁","url":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/wei-rao-li-lun-tan-xin-dui-mei-ci-zhao-z-txr2","content":"\\n- \\n
\\n时间复杂度:$O(n + C)$,其中 $C$ 是用户年龄的范围,本题中 $C = 120$。计数排序需要 $O(n)$ 的时间,计算前缀和以及统计答案需要 $O(C)$ 的时间。
\\n- \\n
\\n空间复杂度:$O(C)$,即为计数排序以及前缀和数组需要使用的空间。
\\n大家好,我是微扰理论;目前正在连载国服每日一题题解。想加入 emqx 和微扰酱一起工作可以直接联系我哦👆~
\\n祝大家平安夜快乐! 今天「微扰酱」应该也可以升到lv6啦,算是完成一个年底前的小flag,开心;谢谢一直来读我题解的大家的陪伴,才能坚持到现在;提前祝大家周末愉快啦!
\\n补充一种基于TreeMap的更简单的写法 | 大家先看下面的解题思路,再回来看这种解法
\\n由于TreeMap可以直接在过期日期上关联苹果数量;所以代码会简单的多。而我们只需要将取堆顶元素的操作换成取map第一个元素的操作即可。 底层是红黑树;所有操作也是O(logn)的时间复杂度。
\\n
\\n代码如下:###cpp
\\n\\nclass Solution {\\npublic:\\n int eatenApples(vector<int>& apples, vector<int>& days) {\\n int ans = 0;\\n int d = 0;\\n\\n map<int, int> m; // (expire, cnt)\\n\\n while (d < days.size() || !m.empty()) {\\n if (d < days.size()) m[days[d]+d-1] += apples[d];\\n \\n // 尝试从map中取出一个最接近过期但是没有过期的苹果\\n while(!m.empty()) {\\n if (m.begin()->first < d || m.begin()->second == 0) m.erase(m.begin()->first);\\n else {\\n // 如果找到了 我们就吃掉它\\n ans++;\\n // 苹果数要减1\\n m.begin()->second--;\\n break;\\n }\\n }\\n d++;\\n }\\n\\n return ans;\\n }\\n};\\n
解题思路
\\n突然想起来很久之前跟室友讨论如何喝冰箱里的牛奶;室友说自己非常在意体验感,每次都会挑最新鲜的喝;
\\n
\\n但这样,临近保质期的时候,可能就容易有牛奶过期;可能是因为贫穷;「微扰酱」的做法是每次挑选最临近过期日期的牛奶喝,(你猜已经过期的牛奶我会不会扔),这样的贪心策略,可以保证我们尽可能喝更多的牛奶;也就是本题的关键想法。题目要求我们一天只能吃一个苹果,用变量
\\nd
记录天数。
\\n开一个堆,每天都将新长的苹果放入堆中,按照苹果过期的时间排序,过期时间可以用today+days[d]-1
算出来;由于一次可能长出多个苹果,为了避免多次插入同一元素的开销,我们入队的时候同时记录一下某天过期的苹果的数量。
\\n然后从堆中取出一个最接近过期的苹果,有两种情况:\\n
\\n- 已经腐烂;我们直接丢弃;重新取一个。
\\n- 还没腐烂,我们就吃它了;堆中的其他苹果至少不会比这个苹果腐烂的早。
\\n由于堆里存的是可能是多个苹果,如果一次没有吃完,我们将同一天过期的苹果数量-1,也就是吃掉一个,最后再放回堆即可。
\\n如果堆已经没有元素,还没到N天,我们继续等待树上可能长得新苹果。如果已经到了N天,我们把堆中所有苹果吃完就结束计算。
\\n代码
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int eatenApples(vector<int>& apples, vector<int>& days) {\\n int ans = 0;\\n int d = 0;\\n\\n priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> q;\\n\\n while (d < days.size() || !q.empty()) {\\n if (d < days.size()) q.push(make_pair(days[d]+d-1, apples[d]));\\n auto apple = q.top();\\n // 跳过所有已经过期的苹果\\n while (apple.first < d) {\\n q.pop();\\n if (q.size() == 0) break;\\n apple = q.top();\\n }\\n // 如果没有没过期的苹果,继续下一天的等待\\n if (q.size() == 0) { d++; continue; }\\n // 如果有可以吃的苹果;我们要把这一天过期的苹果数量减1\\n if (apple.second > 0) {\\n q.pop();\\n apple.second--;\\n // 如果苹果还有剩余,我们把它放回堆中\\n if (apple.second > 0) q.push(apple);\\n ans++;\\n }\\n d++;\\n }\\n\\n return ans;\\n }\\n};\\n
关于我
\\n微扰酱18年毕业于上海交通大学,是一个在阿里、字节、腾讯都工作过的工程师。如果觉得题解对你有帮助,可以点个赞支持一下我哦!
\\n
\\n✅ 也欢迎联系我,一起打卡刷题,冲。
\\n✅「微扰酱」正在整理常用 算法模板,欢迎来提需求。也欢迎大家留言预定配图~
\\n
\\nflag区
\\n12.31日之前完成前500所有题;大约还差40道hard。 欢迎大家一起来打卡哦~ 解释一下:我还是有在补题的,只是没有都写题解哦。
\\n\\n\\n
\\n","description":"大家好,我是微扰理论;目前正在连载国服每日一题题解。想加入 emqx 和微扰酱一起工作可以直接联系我哦👆~\\n\\n祝大家平安夜快乐! 今天「微扰酱」应该也可以升到lv6啦,算是完成一个年底前的小flag,开心;谢谢一直来读我题解的大家的陪伴,才能坚持到现在;提前祝大家周末愉快啦!\\n\\n补充一种基于TreeMap的更简单的写法 | 大家先看下面的解题思路,再回来看这种解法\\n\\n由于TreeMap可以直接在过期日期上关联苹果数量;所以代码会简单的多。而我们只需要将取堆顶元素的操作换成取map第一个元素的操作即可。 底层是红黑树;所有操作也是O(logn…","guid":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/wei-rao-li-lun-tan-xin-dui-mei-ci-zhao-z-txr2","author":"wfnuser","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-24T01:44:27.389Z","media":[{"url":"https://pic.leetcode-cn.com/1642401540-VfPxoS-file_1642401536561","type":"photo","width":105,"height":20,"blurhash":"LKQ$wO%2WU$%}rofWVkCzVe-j]oL"},{"url":"https://pic.leetcode-cn.com/1641890108-zTFrPQ-file_1641890104117","type":"photo","width":129,"height":20,"blurhash":"LID0Got7WBj[xuj[WBof00WBofj["},{"url":"https://pic.leetcode-cn.com/1641890107-TJrViW-file_1641890104103","type":"photo","width":125,"height":20,"blurhash":"LACP;V%NIA%N-;ayRjRj00IU%MM{"},{"url":"https://pic.leetcode-cn.com/1641890108-KiCpre-file_1641890104115","type":"photo","width":115,"height":20,"blurhash":"LBCZbE?bD%-;-;WBRjj[00IUxuM{"},{"url":"https://pic.leetcode-cn.com/1639625488-cIIUZz-image.png","type":"photo","width":690,"height":690,"blurhash":"LMDIjrI[0i={-SNKI@-SVsbHbasl"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶の相信科学系列】经典优先队列(堆)贪心运用题","url":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-hfdy0","content":"\\n \\n\\n\\n日期 \\n题号 \\n题解 \\n\\n \\n2021.12.6 \\n297. 二叉树的序列化与反序列化 \\n层序遍历 \\n\\n \\n2021.12.6 \\n431. 将 N 叉树编码为二叉树 \\nm-ary to binary \\n\\n \\n2021.12.7 \\n158. 用 Read4 读取 N 个字符 II \\n模拟缓冲区 \\n\\n \\n2021.12.8 \\n329. 矩阵中的最长递增路径 \\ndfs+memo \\n\\n \\n2021.12.10 \\n44. 通配符匹配 \\ndfs+memo \\n\\n \\n2021.12.20 \\n432. 全O(1)数据结构 \\n十字链表 \\n\\n \\n2021.12.20 \\n381.O(1) 时间插入、删除和获取随机元素 - 允许重复 \\nlist+hashmap+vector \\n\\n \\n2021.12.21 \\n480. 滑动窗口中位数 \\n红黑树 \\n\\n \\n2021.12.21 \\n224. 基本计算器 \\n栈 \\n\\n \\n2021.12.21 \\n410. 分割数组的最大值 \\n二分查找 \\n\\n \\n2021.12.22 \\n126. 单词接龙II \\nbfs+dfs \\n\\n \\n2021.12.23 \\n132. 分割回文串II \\n预处理+DP \\n\\n \\n2021.12.23 \\n296. 最佳的碰头地点 \\n双指针 \\n\\n \\n\\n2021.12.24 \\n330. 按要求补齐数组 \\n贪心 \\n贪心 + 优先队列(堆)
\\n这题是一道经典的结合优先队列(堆)的贪心题,与结合排序的贪心题一样,属于最为常见的贪心题型。
\\n直觉上,我们会觉得「优先吃掉最快过期的苹果」会是最优,而这个维护苹果过期的过程,可以使用「小根堆」来实现。
\\n苹果数量很大,但产生苹果的天数最多为 $2 * 10^4$,因此我们以二元组
\\n(最后食用日期, 当日产生苹果数量)
的形式存入「小根堆」进行维护。具体的,我们可以按照如下逻辑进行模拟(令 $n$ 为数组长度,$time$ 为当前时间,$ans$ 为吃到的苹果数量):
\\n\\n
\\n- 首先,如果「$time < n$」或者「堆不为空」,说明「还有苹果未被生成」或者「未必吃掉」,继续模拟;
\\n- 在当日模拟中,如果「$time < n$」,说明当天有苹果生成,先将苹果 以二元组 $(time + days[time] - 1, apples[time])$ 形式加入小根堆中;\\n
\\n\\n\\n其中二元组表示 $(最后食用日期, 当日产生苹果数量)$,同时需要过滤 $apples[time] = 0$ 的情况。
\\n- 然后尝试从堆中取出「最后食用日期」最早「可食用」的苹果
\\ncur
,如果堆顶元素已过期,则抛弃;- 如果吃掉
\\ncur
一个苹果后,仍有剩余,并且最后时间大于当前时间(尚未过期),将cur
重新入堆;- 循环上述逻辑,直到所有苹果出堆。
\\n直观感受往往会骗人,我们使用「归纳法 + 反证法」证明上述猜想的正确性
\\n假设使用上述吃法得到的苹果序列为 $(a_1, a_2, ... ,a_n)$,而真实最优解对应序列为 $(b_1, b_2, ..., b_m)$。
\\n我们目的是证明 $n = m$,即吃掉苹果数量一致(贪心解是最优解的具体方案之一)。
\\n起始,我们先证明边界成立,即 $b_1$ 可以为 $a_1$。
\\n首先该替换操作必然不会使最优序列变长,否则就违背了最优解是长度最长的合法序列这一前提。
\\n由于贪心解中每次总是取「过期时间最早的」苹果来吃,因此有 $a_1$ 过期时间小于等于 $b_1$ 过期时间,需要证明如果将最优解中的 $b_1$ 替换为 $a_1$,并不会影响整个序列的长度。
\\n重点在与该操作是否会使得最优序列长度变短,这需要分情况讨论:
\\n\\n
\\n- \\n
\\n$a_1$ 不存在与 $(b_2, b_3, ..., b_m)$ 当中,此时直接用 $a_1$ 替换 $b_1$ 自然不会对后面的序列产生影响,也就是说替换后,最优序列合法性仍有保证,同时长度不变,结果不会变差;
\\n- \\n
\\n$a_1$ 为 $(b_2, b_3, ..., b_m)$ 中的某个,假设为 $b_i = a_1$,由于 $b_i/a_1$ 在贪心解中的先出堆(过期时间最早),因此必然也有 $b_i/a_1$ 过期时间早于等于 $b_1$ 的过期时间,此时将 $b_1$ 放到 $b_i$ 的位置仍是合法(过期时间比 $b_i/a_1$ 要晚),即将 $b_1$ 和 $b_i/a_1$ 交换位置,最优序列合法性仍有保证,同时长度不变,结果不会变差。
\\n当贪心解和最优解的首个位置决策相同(吃掉的苹果一样),下一位置决策所面临的条件完全相同,归纳推理所依赖的结构没有发生改变,上述分析同样成立。
\\n也就是说,上述推理可以推广到最优解序列中的任意位置。
\\n至此,我们证明出了最优解可以调整为我们的贪心序列,同时长度不变,即贪心解为最优解的具体方案之一。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n PriorityQueue<int[]> q = new PriorityQueue<>((a,b)->a[0]-b[0]);\\n int n = apples.length, time = 0, ans = 0;\\n while (time < n || !q.isEmpty()) {\\n if (time < n && apples[time] > 0) q.add(new int[]{time + days[time] - 1, apples[time]});\\n while (!q.isEmpty() && q.peek()[0] < time) q.poll();\\n if (!q.isEmpty()) {\\n int[] cur = q.poll();\\n if (--cur[1] > 0 && cur[0] > time) q.add(cur);\\n ans++;\\n }\\n time++;\\n }\\n return ans;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $n$ 为数组长度,最多有 $n$ 组苹果入堆/出堆。复杂度为 $O(n\\\\log{n})$
\\n- 空间复杂度:$O(n)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"贪心 + 优先队列(堆) 这题是一道经典的结合优先队列(堆)的贪心题,与结合排序的贪心题一样,属于最为常见的贪心题型。\\n\\n直觉上,我们会觉得「优先吃掉最快过期的苹果」会是最优,而这个维护苹果过期的过程,可以使用「小根堆」来实现。\\n\\n苹果数量很大,但产生苹果的天数最多为 $2 * 10^4$,因此我们以二元组 (最后食用日期, 当日产生苹果数量) 的形式存入「小根堆」进行维护。\\n\\n具体的,我们可以按照如下逻辑进行模拟(令 $n$ 为数组长度,$time$ 为当前时间,$ans$ 为吃到的苹果数量):\\n\\n首先,如果「$time < n$」或者「堆不为空」,说明…","guid":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-hfdy0","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-23T23:31:44.009Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python/Java/JavaScript/Go] 最小堆贪心","url":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/pythonjavajavascriptgo-zui-xiao-dui-tan-3jflb","content":"解题思路
\\n核心思路就是每次要吃最先腐烂的苹果,那么要用最小堆按腐烂时间排序,同时记录消耗掉的苹果数(没有苹果了就删除)
\\nPS:
\\n
\\n兜兜转转刷题一年啦,从只会一点儿Python和Java到现在比较熟练的状态了,再接再厉。也毕业上岸了,希望大家都加油和好运,找到心仪的工作~
\\n好怀念,这是我最早那会儿开始刷题参加周赛时的题目,那时候这题我还错了7、8次到结束也没做出来呢。\\n
代码
\\n按天数循环写在一起
\\n###Python3
\\n\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n pq, i, ans = [], 0, 0\\n while i < len(apples) or pq:\\n while pq and pq[0][0] <= i:\\n heapq.heappop(pq)\\n if i < len(apples) and apples[i]:\\n heapq.heappush(pq, [i + days[i], apples[i]])\\n if pq:\\n pq[0][1]-=1\\n ans += 1\\n if not pq[0][1]:\\n heapq.heappop(pq)\\n i += 1\\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[0]-b[0]);\\n int i=0,ans=0,n=apples.length;\\n while(i < n || !pq.isEmpty()){\\n while(!pq.isEmpty() && pq.peek()[0] <= i)\\n pq.poll();\\n if(i < n && apples[i] > 0)\\n pq.add(new int[]{i+days[i], apples[i]});\\n if(!pq.isEmpty()){\\n ans++;\\n if(--pq.peek()[1] == 0)\\n pq.poll(); \\n }\\n i++;\\n }\\n return ans;\\n }\\n}\\n
###JavaScript
\\n\\n/**\\n * @param {number[]} apples\\n * @param {number[]} days\\n * @return {number}\\n */\\nvar eatenApples = function(apples, days) {\\n const pq = new PriorityQueue(), n = apples.length\\n let i = 0, ans = 0\\n while(i < n || pq.size > 0){\\n while(pq.size > 0 && pq.peek()[0] <= i)\\n pq.poll()\\n if(i < n && apples[i] > 0)\\n pq.offer([i + days[i], apples[i]])\\n if(pq.size > 0){\\n ans++\\n if(--pq.peek()[1]==0)\\n pq.poll()\\n }\\n i++\\n }\\n return ans\\n};\\n\\n\\nclass PriorityQueue {\\n constructor(\\n compare = (a, b) => a[0] < b[0] \\n ){\\n this.data = []\\n this.size = 0\\n this.compare = compare\\n }\\n\\n peek() {\\n return this.size === 0 ? null : this.data[0] \\n }\\n\\n offer(val) {\\n this.data.push(val)\\n this._shifUp(this.size++)\\n }\\n\\n poll() {\\n if(this.size === 0) { return null }\\n this._swap(0, --this.size)\\n this._shifDown(0)\\n return this.data.pop()\\n }\\n\\n _parent(index) {\\n return index - 1 >> 1\\n }\\n \\n _child(index) {\\n return (index << 1) + 1\\n }\\n\\n _shifDown(index) {\\n while(this._child(index) < this.size) {\\n let child = this._child(index)\\n if(child + 1 < this.size \\n && this.compare(this.data[child + 1], this.data[child])) {\\n child = child + 1\\n }\\n if(this.compare(this.data[index], this.data[child])){\\n break\\n }\\n this._swap(index, child)\\n index = child\\n }\\n }\\n\\n _shifUp(index) {\\n while(this._parent(index) >= 0 \\n && this.compare(this.data[index], this.data[this._parent(index)])) {\\n this._swap(index, this._parent(index))\\n index = this._parent(index)\\n }\\n }\\n\\n _swap(a, b) {\\n [this.data[a], this.data[b]] = [this.data[b], this.data[a]]\\n }\\n}\\n
###Go
\\n\\nfunc eatenApples(apples []int, days []int) int {\\n pq := &IntHeap{}\\n i, ans := 0, 0\\n for i < len(apples) || pq.Len() > 0{\\n for pq.Len() > 0 && (*pq)[0][0] <= i {\\n heap.Pop(pq)\\n }\\n if i < len(apples) && apples[i] > 0{\\n heap.Push(pq, []int{i + days[i], apples[i]})\\n }\\n if pq.Len() > 0 {\\n ans++\\n (*pq)[0][1]--\\n if (*pq)[0][1] == 0{\\n heap.Pop(pq)\\n }\\n }\\n i++\\n }\\n return ans\\n}\\n\\ntype IntHeap [][]int\\nfunc (h IntHeap) Len() int{return len(h)}\\nfunc (h IntHeap) Less(i, j int) bool{return h[i][0] < h[j][0]}\\nfunc (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i]}\\nfunc (h *IntHeap) Pop() interface{} {\\n old := *h\\n n := len(old)\\n x := old[n - 1]\\n *h = old[0 : n - 1]\\n return x\\n}\\nfunc (h *IntHeap) Push(x interface{}) {\\n *h = append(*h, x.([]int))\\n}\\n
天数结束后直接依次统计 (不需要再一天天遍历剩下的天)
\\n###Python3
\\n\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n pq, i, ans = [], 0, 0\\n while i < len(apples):\\n while pq and pq[0][0] <= i:\\n heapq.heappop(pq)\\n if apples[i]:\\n heapq.heappush(pq, [i + days[i], apples[i]])\\n if pq:\\n pq[0][1]-=1\\n if not pq[0][1]:\\n heapq.heappop(pq)\\n ans += 1\\n i += 1\\n while pq:\\n cur = heapq.heappop(pq)\\n d = min(cur[0] - i, cur[1])\\n i += d\\n ans += d\\n while pq and pq[0][0] <= i:\\n heapq.heappop(pq)\\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[0]-b[0]);\\n int i=0,ans=0,n=apples.length;\\n for(;i<n;i++){\\n while(!pq.isEmpty() && pq.peek()[0] <= i)\\n pq.poll();\\n if(apples[i] > 0)\\n pq.add(new int[]{i+days[i], apples[i]});\\n if(!pq.isEmpty()){\\n ans++;\\n if(--pq.peek()[1] == 0)\\n pq.poll(); \\n }\\n }\\n while(!pq.isEmpty()){\\n int[] cur = pq.poll();\\n int diff = Math.min(cur[0] - i, cur[1]);\\n i += diff;\\n ans += diff;\\n while(!pq.isEmpty() && pq.peek()[0] <= i)\\n pq.poll();\\n }\\n return ans;\\n }\\n}\\n
###JavaScript
\\n\\n/**\\n * @param {number[]} apples\\n * @param {number[]} days\\n * @return {number}\\n */\\nvar eatenApples = function(apples, days) {\\n const pq = new PriorityQueue(), n = apples.length\\n let i = 0, ans = 0\\n for(;i<n;i++){\\n while(pq.size > 0 && pq.peek()[0] <= i)\\n pq.poll()\\n if(apples[i] > 0)\\n pq.offer([i + days[i], apples[i]])\\n if(pq.size > 0){\\n ans++\\n if(--pq.peek()[1]==0)\\n pq.poll()\\n }\\n }\\n while(pq.size > 0){\\n const cur = pq.poll()\\n const d = Math.min(cur[0] - i, cur[1])\\n i += d\\n ans += d\\n while(pq.size > 0 && pq.peek()[0] <= i)\\n pq.poll()\\n }\\n return ans\\n};\\n\\n\\nclass PriorityQueue {\\n constructor(\\n compare = (a, b) => a[0] < b[0] \\n ){\\n this.data = []\\n this.size = 0\\n this.compare = compare\\n }\\n\\n peek() {\\n return this.size === 0 ? null : this.data[0] \\n }\\n\\n offer(val) {\\n this.data.push(val)\\n this._shifUp(this.size++)\\n }\\n\\n poll() {\\n if(this.size === 0) { return null }\\n this._swap(0, --this.size)\\n this._shifDown(0)\\n return this.data.pop()\\n }\\n\\n _parent(index) {\\n return index - 1 >> 1\\n }\\n \\n _child(index) {\\n return (index << 1) + 1\\n }\\n\\n _shifDown(index) {\\n while(this._child(index) < this.size) {\\n let child = this._child(index)\\n if(child + 1 < this.size \\n && this.compare(this.data[child + 1], this.data[child])) {\\n child = child + 1\\n }\\n if(this.compare(this.data[index], this.data[child])){\\n break\\n }\\n this._swap(index, child)\\n index = child\\n }\\n }\\n\\n _shifUp(index) {\\n while(this._parent(index) >= 0 \\n && this.compare(this.data[index], this.data[this._parent(index)])) {\\n this._swap(index, this._parent(index))\\n index = this._parent(index)\\n }\\n }\\n\\n _swap(a, b) {\\n [this.data[a], this.data[b]] = [this.data[b], this.data[a]]\\n }\\n}\\n
###Go
\\n\\n","description":"解题思路 核心思路就是每次要吃最先腐烂的苹果,那么要用最小堆按腐烂时间排序,同时记录消耗掉的苹果数(没有苹果了就删除)\\n\\nPS:\\n 兜兜转转刷题一年啦,从只会一点儿Python和Java到现在比较熟练的状态了,再接再厉。也毕业上岸了,希望大家都加油和好运,找到心仪的工作~\\n 好怀念,这是我最早那会儿开始刷题参加周赛时的题目,那时候这题我还错了7、8次到结束也没做出来呢。\\n\\n代码\\n\\n按天数循环写在一起\\n\\n###Python3\\n\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List…","guid":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/pythonjavajavascriptgo-zui-xiao-dui-tan-3jflb","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-23T22:44:32.054Z","media":[{"url":"https://pic.leetcode-cn.com/1640272572-SjcXbk-image.png","type":"photo","width":990,"height":1480,"blurhash":"LASF;M~Vrq^+_Ng3kVozS2bbf+kC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"吃苹果的最大数目","url":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/chi-ping-guo-de-zui-da-shu-mu-by-leetcod-93ka","content":"func eatenApples(apples []int, days []int) int {\\n pq := &IntHeap{}\\n i, ans := 0, 0\\n for i < len(apples){\\n for pq.Len() > 0 && (*pq)[0][0] <= i {\\n heap.Pop(pq)\\n }\\n if apples[i] > 0{\\n heap.Push(pq, []int{i + days[i], apples[i]})\\n }\\n if pq.Len() > 0 {\\n ans++\\n (*pq)[0][1]--\\n if (*pq)[0][1] == 0{\\n heap.Pop(pq)\\n }\\n }\\n i++\\n }\\n for pq.Len() > 0 {\\n cur := heap.Pop(pq).([]int)\\n if v := cur[0] - i; v < cur[1] {\\n i += v\\n ans += v\\n }else{\\n i += cur[1]\\n ans += cur[1]\\n }\\n for pq.Len() > 0 && (*pq)[0][0] <= i {\\n heap.Pop(pq)\\n }\\n }\\n return ans\\n}\\n\\ntype IntHeap [][]int\\nfunc (h IntHeap) Len() int{return len(h)}\\nfunc (h IntHeap) Less(i, j int) bool{return h[i][0] < h[j][0]}\\nfunc (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i]}\\nfunc (h *IntHeap) Pop() interface{} {\\n old := *h\\n n := len(old)\\n x := old[n - 1]\\n *h = old[0 : n - 1]\\n return x\\n}\\nfunc (h *IntHeap) Push(x interface{}) {\\n *h = append(*h, x.([]int))\\n}\\n
方法一:贪心 + 优先队列
\\n为了将吃苹果的数目最大化,应该使用贪心策略,在尚未腐烂的苹果中优先选择腐烂日期最早的苹果。
\\n为了得到腐烂日期最早的苹果,可以使用优先队列存储每个苹果的腐烂日期,优先队列中最小的元素(即最早的腐烂日期)会最先被取出。由于数组 $\\\\textit{apples}$ 和 $\\\\textit{days}$ 的长度 $n$ 最大为 $2 \\\\times 10^4$,两个数组中的每个元素最大为 $2 \\\\times 10^4$,因此苹果的总数最大可达 $(2 \\\\times 10^4) \\\\times (2 \\\\times 10^4) = 4 \\\\times 10^8$。如果直接使用优先队列存储每个苹果的腐烂日期,则会导致优先队列中的元素个数过多,时间复杂度和空间复杂度过高,因此需要使用优化的表示法。可以在优先队列中存储二元组,每个二元组表示苹果的腐烂日期和在该日期腐烂的苹果个数,则优先队列中的元素个数最多为 $n$ 个(即数组长度),可以显著降低时间复杂度和空间复杂度。
\\n计算吃苹果的最大数目分成两个阶段,第一阶段是第 $0$ 天到第 $n - 1$ 天,即天数在数组下标范围内,第二阶段是第 $n$ 天及以后,即天数在数组下标范围外。
\\n在第一阶段,由于每天树上都可能长出苹果,因此需要对每一天分别计算是否能吃到苹果,并更新优先队列。具体做法如下:
\\n\\n
\\n- \\n
\\n将优先队列中的所有腐烂日期小于等于当前日期的元素取出,这些元素表示已经腐烂的苹果,无法食用;
\\n- \\n
\\n根据 $\\\\textit{days}$ 和 $\\\\textit{apples}$ 的当前元素计算当天长出的苹果的腐烂日期和数量,如果数量大于 $0$,则将腐烂日期和数量加入优先队列;
\\n- \\n
\\n如果优先队列不为空,则当天可以吃 $1$ 个苹果,将优先队列的队首元素的数量减 $1$,如果队首元素的数量变成 $0$ 则将队首元素取出。
\\n在第二阶段,由于树上不会再长出苹果,因此只需要考虑能吃到的苹果数量。由于优先队列中的每个元素的数量可能很大,因此需要根据当前日期和优先队列的队首元素的腐烂日期和数量计算能吃到的苹果数量。
\\n假设当前日期是第 $i$ 天,首先将优先队列中的所有腐烂日期小于等于 $i$ 的元素取出,如果优先队列不为空,则根据优先队列的队首元素计算能吃到的苹果数量。假设优先队列的队首元素的腐烂日期是 $x$,数量是 $y$,其中 $x > i$,则有 $y$ 个苹果,距离腐烂还有 $x - i$ 天,因此能吃到的苹果数量是 $\\\\textit{curr} = \\\\min(x - i, y)$。将优先队列的队首元素 $(x, y)$ 取出并将日期增加 $\\\\textit{curr}$,重复上述操作直到优先队列为空,即可得到吃苹果的最大数目。
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
,
,
>
###Java
\\n\\nclass Solution {\\n public int eatenApples(int[] apples, int[] days) {\\n int ans = 0;\\n PriorityQueue<int[]> pq = new PriorityQueue<int[]>((a, b) -> a[0] - b[0]);\\n int n = apples.length;\\n int i = 0;\\n while (i < n) {\\n while (!pq.isEmpty() && pq.peek()[0] <= i) {\\n pq.poll();\\n }\\n int rottenDay = i + days[i];\\n int count = apples[i];\\n if (count > 0) {\\n pq.offer(new int[]{rottenDay, count});\\n }\\n if (!pq.isEmpty()) {\\n int[] arr = pq.peek();\\n arr[1]--;\\n if (arr[1] == 0) {\\n pq.poll();\\n }\\n ans++;\\n }\\n i++;\\n }\\n while (!pq.isEmpty()) {\\n while (!pq.isEmpty() && pq.peek()[0] <= i) {\\n pq.poll();\\n }\\n if (pq.isEmpty()) {\\n break;\\n }\\n int[] arr = pq.poll();\\n int curr = Math.min(arr[0] - i, arr[1]);\\n ans += curr;\\n i += curr;\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\ntypedef pair<int,int> pii;\\n\\nclass Solution {\\npublic:\\n int eatenApples(vector<int>& apples, vector<int>& days) {\\n int ans = 0;\\n priority_queue<pii, vector<pii>, greater<pii>> pq;\\n int n = apples.size();\\n int i = 0;\\n while (i < n) {\\n while (!pq.empty() && pq.top().first <= i) {\\n pq.pop();\\n }\\n int rottenDay = i + days[i];\\n int count = apples[i];\\n if (count > 0) {\\n pq.emplace(rottenDay, count);\\n }\\n if (!pq.empty()) {\\n pii curr = pq.top();\\n pq.pop();\\n curr.second--;\\n if (curr.second != 0) { \\n pq.emplace(curr.first, curr.second);\\n }\\n ans++;\\n }\\n i++;\\n }\\n while (!pq.empty()) {\\n while (!pq.empty() && pq.top().first <= i) {\\n pq.pop();\\n }\\n if (pq.empty()) {\\n break;\\n }\\n pii curr = pq.top();\\n pq.pop();\\n int num = min(curr.first - i, curr.second);\\n ans += num;\\n i += num;\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def eatenApples(self, apples: List[int], days: List[int]) -> int:\\n ans = 0\\n pq = []\\n i = 0\\n while i < len(apples):\\n while pq and pq[0][0] <= i:\\n heappop(pq)\\n if apples[i]:\\n heappush(pq, [i + days[i], apples[i]])\\n if pq:\\n pq[0][1] -= 1\\n if pq[0][1] == 0:\\n heappop(pq)\\n ans += 1\\n i += 1\\n while pq:\\n while pq and pq[0][0] <= i:\\n heappop(pq)\\n if len(pq) == 0:\\n break\\n p = heappop(pq)\\n num = min(p[0] - i, p[1])\\n ans += num\\n i += num\\n return ans\\n
###Go
\\n\\nfunc eatenApples(apples, days []int) (ans int) {\\n h := hp{}\\n i := 0\\n for ; i < len(apples); i++ {\\n for len(h) > 0 && h[0].end <= i {\\n heap.Pop(&h)\\n }\\n if apples[i] > 0 {\\n heap.Push(&h, pair{i + days[i], apples[i]})\\n }\\n if len(h) > 0 {\\n h[0].left--\\n if h[0].left == 0 {\\n heap.Pop(&h)\\n }\\n ans++\\n }\\n }\\n for len(h) > 0 {\\n for len(h) > 0 && h[0].end <= i {\\n heap.Pop(&h)\\n }\\n if len(h) == 0 {\\n break\\n }\\n p := heap.Pop(&h).(pair)\\n num := min(p.end-i, p.left)\\n ans += num\\n i += num\\n }\\n return\\n}\\n\\ntype pair struct{ end, left int }\\ntype hp []pair\\n\\nfunc (h hp) Len() int { return len(h) }\\nfunc (h hp) Less(i, j int) bool { return h[i].end < h[j].end }\\nfunc (h hp) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\nfunc (h *hp) Push(v interface{}) { *h = append(*h, v.(pair)) }\\nfunc (h *hp) Pop() interface{} { a := *h; v := a[len(a)-1]; *h = a[:len(a)-1]; return v }\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int EatenApples(int[] apples, int[] days) {\\n int ans = 0;\\n PriorityQueue<int[], int> pq = new PriorityQueue<int[], int>();\\n int n = apples.Length;\\n int i = 0;\\n while (i < n) {\\n while (pq.Count > 0 && pq.Peek()[0] <= i) {\\n pq.Dequeue();\\n }\\n int rottenDay = i + days[i];\\n int count = apples[i];\\n if (count > 0) {\\n pq.Enqueue(new int[]{rottenDay, count}, rottenDay);\\n }\\n if (pq.Count > 0) {\\n rottenDay = pq.Peek()[0];\\n count = pq.Peek()[1];\\n pq.Dequeue();\\n count--;\\n if (count != 0) { \\n pq.Enqueue(new int[]{rottenDay, count}, rottenDay); \\n }\\n ans++;\\n }\\n i++;\\n }\\n\\n while (pq.Count > 0) {\\n while (pq.Count > 0 && pq.Peek()[0] <= i) {\\n pq.Dequeue();\\n }\\n if (pq.Count == 0) {\\n break;\\n }\\n int rottenDay = pq.Peek()[0];\\n int count = pq.Peek()[1];\\n pq.Dequeue();\\n int num = Math.Min(rottenDay - i, count);\\n ans += num;\\n i += num;\\n }\\n\\n return ans;\\n }\\n}\\n
###C
\\n\\n#define MIN_QUEUE_SIZE 64\\n\\ntypedef struct Element {\\n int data[2];\\n} Element;\\n\\ntypedef bool (*compare)(const void *, const void *);\\n\\ntypedef struct PriorityQueue {\\n Element *arr;\\n int capacity;\\n int queueSize;\\n compare lessFunc;\\n} PriorityQueue;\\n\\nElement *createElement(int x, int y) {\\n Element *obj = (Element *)malloc(sizeof(Element));\\n obj->data[0] = x;\\n obj->data[1] = y;\\n return obj;\\n}\\n\\nstatic bool less(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] > e2->data[0] || \\\\\\n (e1->data[0] == e2->data[0] && \\\\\\n e1->data[1] > e2->data[1]);\\n}\\n\\nstatic bool greater(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] < e2->data[0];\\n}\\n\\nstatic void memswap(void *m1, void *m2, size_t size){\\n unsigned char *a = (unsigned char*)m1;\\n unsigned char *b = (unsigned char*)m2;\\n while (size--) {\\n *b ^= *a ^= *b ^= *a;\\n a++;\\n b++;\\n }\\n}\\n\\nstatic void swap(Element *arr, int i, int j) {\\n memswap(&arr[i], &arr[j], sizeof(Element));\\n}\\n\\nstatic void down(Element *arr, int size, int i, compare cmpFunc) {\\n for (int k = 2 * i + 1; k < size; k = 2 * k + 1) {\\n if (k + 1 < size && cmpFunc(&arr[k], &arr[k + 1])) {\\n k++;\\n }\\n if (cmpFunc(&arr[k], &arr[(k - 1) / 2])) {\\n break;\\n }\\n swap(arr, k, (k - 1) / 2);\\n }\\n}\\n\\nPriorityQueue *createPriorityQueue(compare cmpFunc) {\\n PriorityQueue *obj = (PriorityQueue *)malloc(sizeof(PriorityQueue));\\n obj->capacity = MIN_QUEUE_SIZE;\\n obj->arr = (Element *)malloc(sizeof(Element) * obj->capacity);\\n obj->queueSize = 0;\\n obj->lessFunc = cmpFunc;\\n return obj;\\n}\\n\\nvoid heapfiy(PriorityQueue *obj) {\\n for (int i = obj->queueSize / 2 - 1; i >= 0; i--) {\\n down(obj->arr, obj->queueSize, i, obj->lessFunc);\\n }\\n}\\n\\nvoid enQueue(PriorityQueue *obj, Element *e) {\\n if (obj->queueSize == obj->capacity) {\\n obj->capacity *= 2;\\n obj->arr = realloc(obj->arr, sizeof(Element) * obj->capacity);\\n }\\n memcpy(&obj->arr[obj->queueSize], e, sizeof(Element));\\n for (int i = obj->queueSize; i > 0 && obj->lessFunc(&obj->arr[(i - 1) / 2], &obj->arr[i]); i = (i - 1) / 2) {\\n swap(obj->arr, i, (i - 1) / 2);\\n }\\n obj->queueSize++;\\n}\\n\\nElement* deQueue(PriorityQueue *obj) {\\n swap(obj->arr, 0, obj->queueSize - 1);\\n down(obj->arr, obj->queueSize - 1, 0, obj->lessFunc);\\n Element *e = &obj->arr[obj->queueSize - 1];\\n obj->queueSize--;\\n return e;\\n}\\n\\nbool isEmpty(const PriorityQueue *obj) {\\n return obj->queueSize == 0;\\n}\\n\\nElement* front(const PriorityQueue *obj) {\\n if (obj->queueSize == 0) {\\n return NULL;\\n } else {\\n return &obj->arr[0];\\n }\\n}\\n\\nvoid clear(PriorityQueue *obj) {\\n obj->queueSize = 0;\\n}\\n\\nint size(const PriorityQueue *obj) {\\n return obj->queueSize;\\n}\\n\\nvoid freeQueue(PriorityQueue *obj) {\\n free(obj->arr);\\n free(obj);\\n}\\n\\nint eatenApples(int* apples, int applesSize, int* days, int daysSize) {\\n int ans = 0;\\n PriorityQueue *pq = createPriorityQueue(less);\\n int n = applesSize;\\n int i = 0;\\n struct Element node;\\n\\n while (i < n) {\\n while (!isEmpty(pq) && front(pq)->data[0] <= i) {\\n deQueue(pq);\\n }\\n int rottenDay = i + days[i];\\n int count = apples[i];\\n if (count > 0) {\\n node.data[0] = rottenDay;\\n node.data[1] = count;\\n enQueue(pq, &node);\\n }\\n if (!isEmpty(pq)) {\\n rottenDay = front(pq)->data[0];\\n count = front(pq)->data[1];\\n deQueue(pq);\\n count--;\\n if (count != 0) { \\n node.data[0] = rottenDay;\\n node.data[1] = count;\\n enQueue(pq, &node); \\n }\\n ans++;\\n }\\n i++;\\n }\\n\\n while (!isEmpty(pq)) {\\n while (!isEmpty(pq) && front(pq)->data[0] <= i) {\\n deQueue(pq);\\n }\\n if (isEmpty(pq)) {\\n break;\\n }\\n int rottenDay = front(pq)->data[0];\\n int count = front(pq)->data[1];\\n deQueue(pq);\\n int num = fmin(rottenDay - i, count);\\n ans += num;\\n i += num;\\n }\\n freeQueue(pq);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar eatenApples = function(apples, days) {\\n let ans = 0;\\n let pq = new MinPriorityQueue();\\n const n = apples.length;\\n let i = 0;\\n while (i < n) {\\n while (!pq.isEmpty() && pq.front().element[0] <= i) {\\n pq.dequeue();\\n }\\n let rottenDay = i + days[i];\\n let count = apples[i];\\n if (count > 0) {\\n pq.enqueue([rottenDay, count], rottenDay);\\n }\\n if (!pq.isEmpty()) {\\n [rottenDay, count] = pq.dequeue().element;\\n count--;\\n if (count != 0) { \\n pq.enqueue([rottenDay, count], rottenDay); \\n }\\n ans++;\\n }\\n i++;\\n }\\n\\n while (!pq.isEmpty()) {\\n while (!pq.isEmpty() && pq.front().element[0] <= i) {\\n pq.dequeue();\\n }\\n if (pq.isEmpty()) {\\n break;\\n }\\n [rottenDay, count] = pq.dequeue().element;\\n const num = Math.min(rottenDay - i, count);\\n ans += num;\\n i += num;\\n }\\n\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction eatenApples(apples: number[], days: number[]): number {\\n let ans = 0;\\n let pq = new MinPriorityQueue();\\n const n = apples.length;\\n let i = 0;\\n while (i < n) {\\n while (!pq.isEmpty() && pq.front().element[0] <= i) {\\n pq.dequeue();\\n }\\n let rottenDay = i + days[i];\\n let count = apples[i];\\n if (count > 0) {\\n pq.enqueue([rottenDay, count], rottenDay);\\n }\\n if (!pq.isEmpty()) {\\n [rottenDay, count] = pq.dequeue().element;\\n count--;\\n if (count != 0) { \\n pq.enqueue([rottenDay, count], rottenDay); \\n }\\n ans++;\\n }\\n i++;\\n }\\n\\n while (!pq.isEmpty()) {\\n while (!pq.isEmpty() && pq.front().element[0] <= i) {\\n pq.dequeue();\\n }\\n if (pq.isEmpty()) {\\n break;\\n }\\n let [rottenDay, count] = pq.dequeue().element;\\n const num = Math.min(rottenDay - i, count);\\n ans += num;\\n i += num;\\n }\\n\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::collections::BinaryHeap;\\nuse std::cmp::Reverse;\\n\\nimpl Solution {\\n pub fn eaten_apples(apples: Vec<i32>, days: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut pq = BinaryHeap::new();\\n let n = apples.len();\\n let mut i = 0;\\n \\n while i < n {\\n while let Some(Reverse((rotten_day, count))) = pq.peek() {\\n if *rotten_day <= i as i32 {\\n pq.pop();\\n } else {\\n break;\\n }\\n }\\n let rotten_day = i as i32 + days[i];\\n let count = apples[i];\\n if count > 0 {\\n pq.push(Reverse((rotten_day, count)));\\n }\\n if let Some(Reverse((rotten_day, mut count))) = pq.pop() {\\n count -= 1;\\n if count > 0 {\\n pq.push(Reverse((rotten_day, count)));\\n }\\n ans += 1;\\n }\\n i += 1;\\n }\\n\\n while let Some(Reverse((rotten_day, mut count))) = pq.pop() {\\n if rotten_day <= i as i32 {\\n continue;\\n }\\n let num = std::cmp::min(rotten_day - i as i32, count);\\n ans += num;\\n i += num as usize;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心 + 优先队列 为了将吃苹果的数目最大化,应该使用贪心策略,在尚未腐烂的苹果中优先选择腐烂日期最早的苹果。\\n\\n为了得到腐烂日期最早的苹果,可以使用优先队列存储每个苹果的腐烂日期,优先队列中最小的元素(即最早的腐烂日期)会最先被取出。由于数组 $\\\\textit{apples}$ 和 $\\\\textit{days}$ 的长度 $n$ 最大为 $2 \\\\times 10^4$,两个数组中的每个元素最大为 $2 \\\\times 10^4$,因此苹果的总数最大可达 $(2 \\\\times 10^4) \\\\times (2 \\\\times 10^4) = 4…","guid":"https://leetcode.cn/problems/maximum-number-of-eaten-apples//solution/chi-ping-guo-de-zui-da-shu-mu-by-leetcod-93ka","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-23T03:27:31.562Z","media":[{"url":"https://assets.leetcode.cn/solution-static/1705/1.png","type":"photo","width":2000,"height":1143,"blurhash":"LSAw3NIVIWWBM|s:ofj[02%Lxtof"},{"url":"https://assets.leetcode.cn/solution-static/1705/2.png","type":"photo","width":2000,"height":1143,"blurhash":"LRAmr6tSIqxtM{aeoeWC01V?xsM}"},{"url":"https://assets.leetcode.cn/solution-static/1705/3.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.leetcode.cn/solution-static/1705/4.png","type":"photo","width":2000,"height":1143,"blurhash":"LN97t$M_M{V@M{ofofj]4Txuxuoz"},{"url":"https://assets.leetcode.cn/solution-static/1705/5.png","type":"photo","width":2000,"height":1143,"blurhash":"LN97t$M_M{V@M{ofofj]4Txuxuoz"},{"url":"https://assets.leetcode.cn/solution-static/1705/6.png","type":"photo","width":2000,"height":1143,"blurhash":"LM8g{aMyV@aeM{ofj[j[4TxuogkC"},{"url":"https://assets.leetcode.cn/solution-static/1705/7.png","type":"photo","width":2000,"height":1143,"blurhash":"LRAmr6tSIqxtM{aeoeWC01RixsM}"},{"url":"https://assets.leetcode.cn/solution-static/1705/8.png","type":"photo","width":0,"height":0,"blurhash":""},{"url":"https://assets.leetcode.cn/solution-static/1705/9.png","type":"photo","width":2000,"height":1143,"blurhash":"LM8g{aIVV@aeM{ofj[j[4Txuogof"},{"url":"https://assets.leetcode.cn/solution-static/1705/10.png","type":"photo","width":2000,"height":1143,"blurhash":"LN97t$M_M{RjM{ofofj]4Txuxuoz"},{"url":"https://assets.leetcode.cn/solution-static/1705/11.png","type":"photo","width":2000,"height":1143,"blurhash":"LN97t#M_M{RjM{ofofj]4Txuxuoz"},{"url":"https://assets.leetcode.cn/solution-static/1705/12.png","type":"photo","width":2000,"height":1143,"blurhash":"LM8g{aIVV@aeM{ofj[j[4Txuogof"},{"url":"https://assets.leetcode.cn/solution-static/1705/13.png","type":"photo","width":2000,"height":1143,"blurhash":"LN9tMvE3WAoHM{s.j[a}01-nogWZ"},{"url":"https://assets.leetcode.cn/solution-static/1705/14.png","type":"photo","width":2000,"height":1143,"blurhash":"LSAw3MIVIVWBM|ofofj[02%Lxtof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"向字符串添加空格","url":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/xiang-zi-fu-chuan-tian-jia-kong-ge-by-le-4yso","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是数组 $\\\\textit{apples}$ 和 $\\\\textit{days}$ 的长度。优先队列中最多有 $n$ 个元素,每个元素加入优先队列和从优先队列中取出各一次,每次操作的时间复杂度是 $O(\\\\log n)$,因此总时间复杂度是 $O(n \\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{apples}$ 和 $\\\\textit{days}$ 的长度。空间复杂度主要取决于优先队列,优先队列中的元素个数不会超过 $n$。
\\n方法一:双指针
\\n思路与算法
\\n我们可以使用两个指针分别遍历字符串 $s$ 和数组 $\\\\textit{spaces}$。记前者的指针为 $i$,后者的指针为 $\\\\textit{ptr}$:当 $\\\\textit{spaces}[\\\\textit{ptr}]$ 恰好与 $i$ 相等时,我们在答案字符串的末尾放入一个空格,并将 $\\\\textit{ptr}$ 向右移动一个位置。
\\n此外,我们还需要在答案字符串的末尾放入 $s[i]$,并将 $i$ 向右移动一个位置。在两个指针全部遍历完成后,我们就得到了修改后的字符串。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n string addSpaces(string s, vector<int>& spaces) {\\n int n = s.size();\\n string ans;\\n ans.reserve(n + spaces.size());\\n \\n int ptr = 0;\\n for (int i = 0; i < n; ++i) {\\n if (ptr < spaces.size() && spaces[ptr] == i) {\\n ans.push_back(\' \');\\n ++ptr;\\n }\\n ans.push_back(s[i]);\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def addSpaces(self, s: str, spaces: List[int]) -> str:\\n ans = list()\\n\\n ptr = 0\\n for i, ch in enumerate(s):\\n if ptr < len(spaces) and spaces[ptr] == i:\\n ans.append(\\" \\")\\n ptr += 1\\n ans.append(ch)\\n \\n return \\"\\".join(ans)\\n
复杂度分析
\\n\\n
\\n","description":"方法一:双指针 思路与算法\\n\\n我们可以使用两个指针分别遍历字符串 $s$ 和数组 $\\\\textit{spaces}$。记前者的指针为 $i$,后者的指针为 $\\\\textit{ptr}$:当 $\\\\textit{spaces}[\\\\textit{ptr}]$ 恰好与 $i$ 相等时,我们在答案字符串的末尾放入一个空格,并将 $\\\\textit{ptr}$ 向右移动一个位置。\\n\\n此外,我们还需要在答案字符串的末尾放入 $s[i]$,并将 $i$ 向右移动一个位置。在两个指针全部遍历完成后,我们就得到了修改后的字符串。\\n\\n代码\\n\\n###C++\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/xiang-zi-fu-chuan-tian-jia-kong-ge-by-le-4yso","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T05:14:59.780Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ 模拟","url":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/c-mo-ni-by-liyinxin-867l","content":"- \\n
\\n时间复杂度:$O(n + m)$,其中 $n$ 是字符串 $s$ 的长度,$m$ 是数组 $\\\\textit{spaces}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$ 或 $O(n + m)$,取决于使用的语言中字符串是否可以修改。
\\n解题思路
\\n暴力模拟,先根据spaces将整个字符串划分为k个子字符串,然后拼接就好。不过需要注意的一点是最后一部分要判断下是不是已经加入了。
\\n代码
\\n###cpp
\\n\\n","description":"解题思路 暴力模拟,先根据spaces将整个字符串划分为k个子字符串,然后拼接就好。不过需要注意的一点是最后一部分要判断下是不是已经加入了。\\n\\n代码\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n string addSpaces(string s, vectorclass Solution {\\npublic:\\n string addSpaces(string s, vector<int>& spaces) {\\n int n = spaces.size();\\n if(n == 0)return s;\\n vector<string> res;\\n int index = 0;\\n for(int i = 0; i < n;++i){\\n if(spaces[i] == 0)res.push_back(\\" \\");\\n else{\\n res.push_back(s.substr(index,spaces[i]-index));\\n index = spaces[i];\\n }\\n }\\n n = s.size();\\n res.push_back(s.substr(index,n-index));\\n //cout<<s.substr(index,1)<<endl;\\n n = res.size();\\n string ans;\\n if(res[0] != \\" \\")ans = res[0];\\n for(int i = 1;i < n;++i){\\n ans += \\" \\" + res[i];\\n }\\n return ans;\\n }\\n};\\n
& spaces) {\\n int n = spaces.size();\\n if(n == 0)return s;\\n vector res;\\n int index = 0…","guid":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/c-mo-ni-by-liyinxin-867l","author":"liyinxin","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T05:10:16.125Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Nick周赛系列】简单4分题~","url":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/nickzhou-sai-xi-lie-jian-dan-4fen-ti-by-foi88","content":" 4分题,轻松拿下
\\n\\n
方法一:模拟
\\n核心思路:简单模拟每次向新字符串中 添加spaces个分割子字符串即可,每次添加后增加一个空格
\\n
\\n利用一个变量存储分割位置的下标,实时更新!
\\n比较简单,没有什么难点!执行效率图
\\n\\n
代码
\\n###java
\\n\\n","description":"4分题,轻松拿下 方法一:模拟\\n\\n核心思路:简单模拟每次向新字符串中 添加spaces个分割子字符串即可,每次添加后增加一个空格\\n 利用一个变量存储分割位置的下标,实时更新!\\n 比较简单,没有什么难点!\\n\\n执行效率图\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public static String addSpaces(String s, int[] spaces) {\\n StringBuilder stringBuilder = new StringBuilder();\\n int start = 0;…","guid":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/nickzhou-sai-xi-lie-jian-dan-4fen-ti-by-foi88","author":"nickBean","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T04:11:03.950Z","media":[{"url":"https://pic.leetcode-cn.com/1639887036-fPFMUd-3.png","type":"photo","width":1000,"height":1000,"blurhash":"LQPi;Z%M~p-:x[a{t7of?aj[E1WC"},{"url":"https://pic.leetcode-cn.com/1639886967-ZQGNOC-image.png","type":"photo","width":1074,"height":468,"blurhash":"LYQ,Xgxuxa%M~RayWEj]9$ofocj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最短代码不解释","url":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/zui-duan-dai-ma-bu-jie-shi-by-freeyourmi-i8f4","content":"class Solution {\\n public static String addSpaces(String s, int[] spaces) {\\n StringBuilder stringBuilder = new StringBuilder();\\n int start = 0;\\n for (int space : spaces){\\n stringBuilder.append(s.substring(start,space));\\n //新增spaces.size()个空格!\\n stringBuilder.append(\\" \\");\\n start = space;\\n }\\n //最后这一步不能忘记了!还要新增最后一个空格后的字符串!\\n stringBuilder.append(s.substring(start));\\n return stringBuilder.toString();\\n }\\n}\\n
\\n","description":"class Solution: def addSpaces(self, s: str, spaces: List[int]) -> str:\\n return \' \'.join(s[i: j] for i, j in zip([0] + spaces, spaces + [len(s)]))","guid":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/zui-duan-dai-ma-bu-jie-shi-by-freeyourmi-i8f4","author":"FreeYourMind","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T04:08:40.925Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:双指针 / 按照空格分割(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/go-mo-ni-by-endlesscheng-8lv2","content":"class Solution:\\n def addSpaces(self, s: str, spaces: List[int]) -> str:\\n return \' \'.join(s[i: j] for i, j in zip([0] + spaces, spaces + [len(s)]))\\n
方法一:双指针
\\n初始化 $j=0$。遍历 $s$,把 $s[i]$ 加到答案中。如果 $\\\\textit{spaces}[j]$ 等于当前下标 $i$,那么先加空格,再加 $s[i]$,然后把 $j$ 加一。
\\n\\nclass Solution:\\n def addSpaces(self, s: str, spaces: List[int]) -> str:\\n ans = []\\n j = 0\\n for i, c in enumerate(s):\\n if j < len(spaces) and spaces[j] == i:\\n ans.append(\' \')\\n j += 1\\n ans.append(c)\\n return \'\'.join(ans)\\n
\\nclass Solution {\\n public String addSpaces(String s, int[] spaces) {\\n StringBuilder ans = new StringBuilder(s.length() + spaces.length);\\n int j = 0;\\n for (int i = 0; i < s.length(); i++) {\\n if (j < spaces.length && spaces[j] == i) {\\n ans.append(\' \');\\n j++;\\n }\\n ans.append(s.charAt(i));\\n }\\n return ans.toString();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string addSpaces(string s, vector<int>& spaces) {\\n string ans;\\n int j = 0;\\n for (int i = 0; i < s.size(); i++) {\\n if (j < spaces.size() && spaces[j] == i) {\\n ans += \' \';\\n j++;\\n }\\n ans += s[i];\\n }\\n return ans;\\n }\\n};\\n
\\nfunc addSpaces(s string, spaces []int) string {\\nans := make([]byte, 0, len(s)+len(spaces))\\nj := 0\\nfor i, c := range s {\\nif j < len(spaces) && spaces[j] == i {\\nans = append(ans, \' \')\\nj++\\n}\\nans = append(ans, byte(c))\\n}\\nreturn string(ans)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。注意 $\\\\textit{spaces}$ 的长度不超过 $n$。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$ 或 $\\\\mathcal{O}(1)$。
\\n方法二:按照空格分割
\\n\\nclass Solution:\\n def addSpaces(self, s: str, spaces: List[int]) -> str:\\n spaces.append(len(s)) # 这样可以在循环中处理最后一段\\n ans = [s[:spaces[0]]]\\n for p, q in pairwise(spaces):\\n ans.append(\' \')\\n ans.append(s[p: q])\\n return \'\'.join(ans)\\n
\\nclass Solution {\\n public String addSpaces(String s, int[] spaces) {\\n StringBuilder ans = new StringBuilder(s.length() + spaces.length);\\n ans.append(s, 0, spaces[0]);\\n for (int i = 1; i < spaces.length; i++) {\\n ans.append(\' \');\\n ans.append(s, spaces[i - 1], spaces[i]);\\n }\\n ans.append(\' \');\\n ans.append(s, spaces[spaces.length - 1], s.length());\\n return ans.toString();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string addSpaces(string s, vector<int>& spaces) {\\n spaces.push_back(s.length()); // 这样可以在循环中处理最后一段\\n string ans(s, 0, spaces[0]);\\n for (int i = 1; i < spaces.size(); i++) {\\n ans += \' \';\\n ans.append(s, spaces[i - 1], spaces[i] - spaces[i - 1]);\\n }\\n return ans;\\n }\\n};\\n
\\nfunc addSpaces(s string, spaces []int) string {\\nspaces = append(spaces, len(s)) // 这样可以在循环中处理最后一段\\nans := make([]byte, 0, len(s)+len(spaces))\\nans = append(ans, s[:spaces[0]]...)\\nfor i := 1; i < len(spaces); i++ {\\nans = append(ans, \' \')\\nans = append(ans, s[spaces[i-1]:spaces[i]]...)\\n}\\nreturn string(ans)\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$ 或 $\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n","description":"方法一:双指针 初始化 $j=0$。遍历 $s$,把 $s[i]$ 加到答案中。如果 $\\\\textit{spaces}[j]$ 等于当前下标 $i$,那么先加空格,再加 $s[i]$,然后把 $j$ 加一。\\n\\nclass Solution:\\n def addSpaces(self, s: str, spaces: List[int]) -> str:\\n ans = []\\n j = 0\\n for i, c in enumerate(s):\\n if j < len(spaces) and…","guid":"https://leetcode.cn/problems/adding-spaces-to-a-string//solution/go-mo-ni-by-endlesscheng-8lv2","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T04:08:00.370Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单出度入度统计模拟题","url":"https://leetcode.cn/problems/find-the-town-judge//solution/gong-shui-san-xie-jian-dan-chu-du-ru-du-5ms57","content":"- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n模拟
\\n今天起晚了 🤣
\\n令 $m$ 为
\\ntrust
数组长度,对于每个 $trust[i] = (a, b)$ 而言,看作是从 $a$ 指向 $b$ 的有向边。遍历
\\ntrust
,统计每个节点的「入度」和「出度」:若存在 $a -> b$,则 $a$ 节点「出度」加一,$b$ 节点「入度」加一。最后遍历所有点,若存在「入度」数量为 $n - 1$,且「出度」数量为 $0$ 的节点即是法官。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int findJudge(int n, int[][] trust) {\\n int[] in = new int[n + 1], out = new int[n + 1];\\n for (int[] t : trust) {\\n int a = t[0], b = t[1];\\n in[b]++; out[a]++;\\n }\\n for (int i = 1; i <= n; i++) {\\n if (in[i] == n - 1 && out[i] == 0) return i;\\n }\\n return -1;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(m + n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n其他「模拟」相关内容
\\n题太简单?不如来学习热乎的 结合「二叉树」的图论搜索问题 🍭🍭🍭
\\n\\n
\\n- 常规 BFS 搜索题
\\n- 多源 BFS
\\n- 双向 BFS
\\n- 双向 BFS Ⅱ
\\n- 双向 BFS Ⅲ
\\n- 结合「二叉树」的图论搜索问题
\\n- 灵活运用多种搜索方式
\\n- 灵活运用多种搜索方式 Ⅱ
\\n考虑加练如下「模拟」内容 🤣
\\n注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 今天起晚了 🤣\\n\\n令 $m$ 为 trust 数组长度,对于每个 $trust[i] = (a, b)$ 而言,看作是从 $a$ 指向 $b$ 的有向边。\\n\\n遍历 trust,统计每个节点的「入度」和「出度」:若存在 $a -> b$,则 $a$ 节点「出度」加一,$b$ 节点「入度」加一。\\n\\n最后遍历所有点,若存在「入度」数量为 $n - 1$,且「出度」数量为 $0$ 的节点即是法官。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int findJudge(int n, int[][] trust) {…","guid":"https://leetcode.cn/problems/find-the-town-judge//solution/gong-shui-san-xie-jian-dan-chu-du-ru-du-5ms57","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-19T00:24:42.313Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"找到小镇的法官","url":"https://leetcode.cn/problems/find-the-town-judge//solution/zhao-dao-xiao-zhen-de-fa-guan-by-leetcod-0dcg","content":"预备知识
\\n本题需要用到有向图中节点的入度和出度的概念。在有向图中,一个节点的入度是指向该节点的边的数量;而一个节点的出度是从该节点出发的边的数量。
\\n方法一:计算各节点的入度和出度
\\n思路及解法
\\n题干描述了一个有向图。每个人是图的节点,$\\\\textit{trust}$ 的元素 $\\\\textit{trust}[i]$ 是图的有向边,从 $\\\\textit{trust}[i][0]$ 指向 $\\\\textit{trust}[i][1]$。我们可以遍历 $\\\\textit{trust}$,统计每个节点的入度和出度,存储在 $\\\\textit{inDegrees}$ 和 $\\\\textit{outDegrees}$ 中。
\\n根据题意,在法官存在的情况下,法官不相信任何人,每个人(除了法官外)都信任法官,且只有一名法官。因此法官这个节点的入度是 $n-1$, 出度是 $0$。
\\n我们可以遍历每个节点的入度和出度,如果找到一个符合条件的节点,由于题目保证只有一个法官,我们可以直接返回结果;如果不存在符合条件的点,则返回 $-1$。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def findJudge(self, n: int, trust: List[List[int]]) -> int:\\n inDegrees = Counter(y for _, y in trust)\\n outDegrees = Counter(x for x, _ in trust)\\n return next((i for i in range(1, n + 1) if inDegrees[i] == n - 1 and outDegrees[i] == 0), -1)\\n
###Java
\\n\\nclass Solution {\\n public int findJudge(int n, int[][] trust) {\\n int[] inDegrees = new int[n + 1];\\n int[] outDegrees = new int[n + 1];\\n for (int[] edge : trust) {\\n int x = edge[0], y = edge[1];\\n ++inDegrees[y];\\n ++outDegrees[x];\\n }\\n for (int i = 1; i <= n; ++i) {\\n if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {\\n return i;\\n }\\n }\\n return -1;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int FindJudge(int n, int[][] trust) {\\n int[] inDegrees = new int[n + 1];\\n int[] outDegrees = new int[n + 1];\\n foreach (int[] edge in trust) {\\n int x = edge[0], y = edge[1];\\n ++inDegrees[y];\\n ++outDegrees[x];\\n }\\n for (int i = 1; i <= n; ++i) {\\n if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {\\n return i;\\n }\\n }\\n return -1;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int findJudge(int n, vector<vector<int>>& trust) {\\n vector<int> inDegrees(n + 1);\\n vector<int> outDegrees(n + 1);\\n for (auto& edge : trust) {\\n int x = edge[0], y = edge[1];\\n ++inDegrees[y];\\n ++outDegrees[x];\\n }\\n for (int i = 1; i <= n; ++i) {\\n if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {\\n return i;\\n }\\n }\\n return -1;\\n }\\n};\\n
###C
\\n\\nint findJudge(int n, int** trust, int trustSize, int* trustColSize){\\n int* inDegrees = (int *)malloc(sizeof(int)*(n+1));\\n int* outDegrees = (int *)malloc(sizeof(int)*(n+1));\\n memset(inDegrees, 0, sizeof(int)*(n+1));\\n memset(outDegrees, 0, sizeof(int)*(n+1));\\n for (int i = 0; i < trustSize; ++i) {\\n int x = trust[i][0], y = trust[i][1];\\n ++inDegrees[y];\\n ++outDegrees[x];\\n }\\n for (int i = 1; i <= n; ++i) {\\n if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {\\n return i;\\n }\\n }\\n return -1;\\n}\\n
###JavaScript
\\n\\nvar findJudge = function(n, trust) {\\n const inDegrees = new Array(n + 1).fill(0);\\n const outDegrees = new Array(n + 1).fill(0);\\n for (const edge of trust) {\\n const x = edge[0], y = edge[1];\\n ++inDegrees[y];\\n ++outDegrees[x];\\n }\\n for (let i = 1; i <= n; ++i) {\\n if (inDegrees[i] === n - 1 && outDegrees[i] === 0) {\\n return i;\\n }\\n }\\n return -1;\\n};\\n
###go
\\n\\nfunc findJudge(n int, trust [][]int) int {\\n inDegrees := make([]int, n+1)\\n outDegrees := make([]int, n+1)\\n for _, t := range trust {\\n inDegrees[t[1]]++\\n outDegrees[t[0]]++\\n }\\n for i := 1; i <= n; i++ {\\n if inDegrees[i] == n-1 && outDegrees[i] == 0 {\\n return i\\n }\\n }\\n return -1\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func findJudge(n: Int64, trust: Array<Array<Int64>>): Int64 {\\n let inDegrees = Array<Int64>(n + 1, item: 0)\\n let outDegrees = Array<Int64>(n + 1, item: 0)\\n for (t in trust) {\\n inDegrees[t[1]] += 1\\n outDegrees[t[0]] += 1\\n }\\n for (i in 1..n + 1) {\\n if (inDegrees[i] == n - 1 && outDegrees[i] == 0) {\\n return i\\n }\\n }\\n return -1\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"预备知识 本题需要用到有向图中节点的入度和出度的概念。在有向图中,一个节点的入度是指向该节点的边的数量;而一个节点的出度是从该节点出发的边的数量。\\n\\n方法一:计算各节点的入度和出度\\n\\n思路及解法\\n\\n题干描述了一个有向图。每个人是图的节点,$\\\\textit{trust}$ 的元素 $\\\\textit{trust}[i]$ 是图的有向边,从 $\\\\textit{trust}[i][0]$ 指向 $\\\\textit{trust}[i][1]$。我们可以遍历 $\\\\textit{trust}$,统计每个节点的入度和出度,存储在 $\\\\textit{inDegrees}$ 和 $…","guid":"https://leetcode.cn/problems/find-the-town-judge//solution/zhao-dao-xiao-zhen-de-fa-guan-by-leetcod-0dcg","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-17T09:29:20.251Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"找出 3 位偶数","url":"https://leetcode.cn/problems/finding-3-digit-even-numbers//solution/zhao-chu-3-wei-ou-shu-by-leetcode-soluti-hptf","content":"- \\n
\\n时间复杂度:$O(n+m)$,其中 $m$ 是 $\\\\textit{trust}$ 的长度。首先需要遍历 $\\\\textit{trust}$ 计算出 $\\\\textit{inDegrees}$ 和 $\\\\textit{outDegrees}$,然后需要遍历 $\\\\textit{inDegrees}$ 和 $\\\\textit{outDegrees}$ 来确定法官。
\\n- \\n
\\n空间复杂度:$O(n)$。记录各个节点的入度和出度需要 $O(n)$ 的空间。
\\n方法一:枚举数组中的元素组合
\\n思路与算法
\\n我们可以从数组中枚举目标整数的三个整数位,判断组成的整数是否满足以下条件:
\\n\\n
\\n- \\n
\\n整数为偶数;
\\n- \\n
\\n整数不包含前导零(即整数不小于 $100$);
\\n- \\n
\\n三个整数位对应的数组下标不能重复。
\\n为了避免重复,我们用一个哈希集合来维护符合要求的 $3$ 位偶数,如果枚举产生的整数满足上述三个条件,则我们将该整数加入哈希集合。
\\n最终,我们将该哈希集合内的元素放入数组中,按照递增顺序排序并返回。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> findEvenNumbers(vector<int>& digits) {\\n unordered_set<int> nums; // 目标偶数集合\\n int n = digits.size();\\n // 遍历三个数位的下标\\n for (int i = 0; i < n; ++i){\\n for (int j = 0; j < n; ++j){\\n for (int k = 0; k < n; ++k){\\n // 判断是否满足目标偶数的条件\\n if (i == j || j == k || i == k){\\n continue;\\n }\\n int num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 == 0){\\n nums.insert(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n vector<int> res;\\n for (const int num: nums){\\n res.push_back(num);\\n }\\n sort(res.begin(), res.end());\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n nums = set() # 目标偶数集合\\n n = len(digits)\\n # 遍历三个数位的下标\\n for i in range(n):\\n for j in range(n):\\n for k in range(n):\\n # 判断是否满足目标偶数的条件\\n if i == j or j == k or i == k:\\n continue\\n num = digits[i] * 100 + digits[j] * 10 + digits[k]\\n if num >= 100 and num % 2 == 0:\\n nums.add(num)\\n # 转化为升序排序的数组\\n res = sorted(list(nums))\\n return res\\n
###Java
\\n\\npublic class Solution {\\n public int[] findEvenNumbers(int[] digits) {\\n Set<Integer> nums = new HashSet<>();\\n int n = digits.length;\\n // 遍历三个数位的下标\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < n; ++j) {\\n for (int k = 0; k < n; ++k) {\\n // 判断是否满足目标偶数的条件\\n if (i == j || j == k || i == k) {\\n continue;\\n }\\n int num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 == 0) {\\n nums.add(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n List<Integer> res = new ArrayList<>(nums);\\n Collections.sort(res);\\n int[] result = new int[res.size()];\\n for (int i = 0; i < res.size(); ++i) {\\n result[i] = res.get(i);\\n }\\n return result;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] FindEvenNumbers(int[] digits) {\\n HashSet<int> nums = new HashSet<int>();\\n int n = digits.Length;\\n // 遍历三个数位的下标\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < n; ++j) {\\n for (int k = 0; k < n; ++k) {\\n // 判断是否满足目标偶数的条件\\n if (i == j || j == k || i == k) {\\n continue;\\n }\\n int num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 == 0) {\\n nums.Add(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n List<int> res = nums.ToList();\\n res.Sort();\\n return res.ToArray();\\n }\\n}\\n
###Go
\\n\\nfunc findEvenNumbers(digits []int) []int {\\n nums := make(map[int]bool)\\n n := len(digits)\\n // 遍历三个数位的下标\\n for i := 0; i < n; i++ {\\n for j := 0; j < n; j++ {\\n for k := 0; k < n; k++ {\\n // 判断是否满足目标偶数的条件\\n if i == j || j == k || i == k {\\n continue\\n }\\n num := digits[i] * 100 + digits[j] * 10 + digits[k]\\n if num >= 100 && num % 2 == 0 {\\n nums[num] = true\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n res := make([]int, 0, len(nums))\\n for num := range nums {\\n res = append(res, num)\\n }\\n sort.Ints(res)\\n return res\\n}\\n
###C
\\n\\ntypedef struct {\\n int key;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nint compare(const void* a, const void* b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint* findEvenNumbers(int* digits, int digitsSize, int* returnSize) {\\n HashItem *nums = NULL; // 目标偶数集合\\n int n = digitsSize;\\n // 遍历三个数位的下标\\n for (int i = 0; i < n; ++i){\\n for (int j = 0; j < n; ++j){\\n for (int k = 0; k < n; ++k){\\n // 判断是否满足目标偶数的条件\\n if (i == j || j == k || i == k){\\n continue;\\n }\\n int num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 == 0){\\n hashAddItem(&nums, num);\\n }\\n }\\n }\\n }\\n\\n // 转化为升序排序的数组\\n int numsSize = HASH_COUNT(nums);\\n *returnSize = numsSize;\\n int *res = (int *)malloc(sizeof(int) * numsSize);\\n int pos = 0;\\n for (HashItem *pEntry = nums; pEntry; pEntry = pEntry->hh.next) {\\n res[pos++] = pEntry->key;\\n }\\n qsort(res, numsSize, sizeof(int), compare);\\n return res;\\n}\\n
###JavaScript
\\n\\nvar findEvenNumbers = function(digits) {\\n const nums = new Set();\\n const n = digits.length;\\n // 遍历三个数位的下标\\n for (let i = 0; i < n; ++i) {\\n for (let j = 0; j < n; ++j) {\\n for (let k = 0; k < n; ++k) {\\n // 判断是否满足目标偶数的条件\\n if (i === j || j === k || i === k) {\\n continue;\\n }\\n const num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 === 0) {\\n nums.add(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n const res = Array.from(nums).sort((a, b) => a - b);\\n return res;\\n}\\n
###TypeScript
\\n\\nfunction findEvenNumbers(digits: number[]): number[] {\\n const nums = new Set<number>();\\n const n = digits.length;\\n // 遍历三个数位的下标\\n for (let i = 0; i < n; ++i) {\\n for (let j = 0; j < n; ++j) {\\n for (let k = 0; k < n; ++k) {\\n // 判断是否满足目标偶数的条件\\n if (i === j || j === k || i === k) {\\n continue;\\n }\\n const num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if (num >= 100 && num % 2 === 0) {\\n nums.add(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n const res = Array.from(nums).sort((a, b) => a - b);\\n return res;\\n}\\n
###Rust
\\n\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn find_even_numbers(digits: Vec<i32>) -> Vec<i32> {\\n let mut nums = HashSet::new();\\n let n = digits.len();\\n // 遍历三个数位的下标\\n for i in 0..n {\\n for j in 0..n {\\n for k in 0..n {\\n // 判断是否满足目标偶数的条件\\n if i == j || j == k || i == k {\\n continue;\\n }\\n let num = digits[i] * 100 + digits[j] * 10 + digits[k];\\n if num >= 100 && num % 2 == 0 {\\n nums.insert(num);\\n }\\n }\\n }\\n }\\n // 转化为升序排序的数组\\n let mut res: Vec<i32> = nums.into_iter().collect();\\n res.sort();\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^3 + M \\\\log M)$,其中 $M = \\\\min(n^3, 10^k)$ 代表符合要求偶数的数量, $n$ 为 $\\\\textit{digits}$ 的长度,$k$ 为目标偶数的位数。枚举所有元素组合的时间复杂度为 $O(n^3)$,对符合要求偶数集合排序的时间复杂度为 $O(M \\\\log M)$。
\\n- \\n
\\n空间复杂度:$O(M)$,即为符合要求整数的哈希集合的空间开销。
\\n方法二:遍历所有可能的 $3$ 位偶数
\\n思路与算法
\\n我们也可以从小到大遍历所有 $3$ 位偶数(即 $[100, 999]$ 闭区间内的所有偶数),并判断对应的三个整数位是否为 $\\\\textit{digits}$ 数组中三个不同元素。如果是,则该偶数为目标偶数;反之亦然。
\\n具体地,我们首先用哈希表 $\\\\textit{freq}$ 维护 $\\\\textit{digits}$ 数组中每个数出现的次数。在遍历偶数时,我们同样用哈希表 $\\\\textit{freq}_1$ 维护每个偶数中每个数位出现的次数。此时,该偶数能够被数组中不重复元素表示的充要条件即为:
\\n$\\\\textit{freq}_1$ 中每个元素的出现次数都不大于它在 $\\\\textit{freq}$ 中的出现次数。
\\n我们按照上述条件判断每个偶数是否为目标偶数,并按顺序统计这些偶数。最终,我们返回目标偶数的数组作为答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> findEvenNumbers(vector<int>& digits) {\\n vector<int> res; // 目标偶数数组\\n unordered_map<int, int> freq; // 整数数组中各数字的出现次数\\n for (const int digit: digits){\\n ++freq[digit];\\n }\\n // 枚举所有三位偶数,维护整数中各数位的出现次数并比较判断是否为目标偶数\\n for (int i = 100; i < 1000; i += 2){\\n unordered_map<int, int> freq1;\\n int tmp = i;\\n while (tmp){\\n ++freq1[tmp%10];\\n tmp /= 10;\\n }\\n if (all_of(freq1.begin(), freq1.end(), [&](const auto& x){\\n return freq[x.first] >= freq1[x.first]; \\n })){\\n res.push_back(i);\\n }\\n }\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n res = [] # 目标偶数数组\\n freq = Counter(digits) # 整数数组中各数字的出现次数\\n # 枚举所有三位偶数,维护整数中各数位的出现次数并比较判断是否为目标偶数\\n for i in range(100, 1000, 2):\\n freq1 = Counter([int(d) for d in str(i)])\\n if all(freq[d] >= freq1[d] for d in freq1.keys()):\\n res.append(i)\\n return res\\n
###Java
\\n\\nclass Solution {\\n public int[] findEvenNumbers(int[] digits) {\\n List<Integer> res = new ArrayList<>();\\n Map<Integer, Integer> freq = new HashMap<>();\\n // 统计数字数组中各数字的出现次数\\n for (int d : digits) {\\n freq.put(d, freq.getOrDefault(d, 0) + 1);\\n }\\n // 枚举所有三位偶数\\n for (int i = 100; i < 1000; i += 2) {\\n Map<Integer, Integer> freq1 = new HashMap<>();\\n int num = i;\\n // 统计当前偶数的每一位数字的出现次数\\n while (num > 0) {\\n int d = num % 10;\\n freq1.put(d, freq1.getOrDefault(d, 0) + 1);\\n num /= 10;\\n }\\n // 检查是否满足条件\\n boolean isValid = true;\\n for (Map.Entry<Integer, Integer> entry : freq1.entrySet()) {\\n if (freq.getOrDefault(entry.getKey(), 0) < entry.getValue()) {\\n isValid = false;\\n break;\\n }\\n }\\n if (isValid) {\\n res.add(i);\\n }\\n }\\n // 转换为数组\\n int[] result = new int[res.size()];\\n for (int j = 0; j < res.size(); j++) {\\n result[j] = res.get(j);\\n }\\n return result;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] FindEvenNumbers(int[] digits) {\\n List<int> res = new List<int>();\\n Dictionary<int, int> freq = new Dictionary<int, int>();\\n // 统计数字数组中各数字的出现次数\\n foreach (int d in digits) {\\n freq[d] = freq.ContainsKey(d) ? freq[d] + 1 : 1;\\n }\\n // 枚举所有三位偶数\\n for (int i = 100; i < 1000; i += 2) {\\n Dictionary<int, int> freq1 = new Dictionary<int, int>();\\n int num = i;\\n // 统计当前偶数的每一位数字的出现次数\\n while (num > 0) {\\n int d = num % 10;\\n freq1[d] = freq1.ContainsKey(d) ? freq1[d] + 1 : 1;\\n num /= 10;\\n }\\n // 检查是否满足条件\\n bool isValid = true;\\n foreach (var entry in freq1) {\\n if (!freq.ContainsKey(entry.Key) || freq[entry.Key] < entry.Value) {\\n isValid = false;\\n break;\\n }\\n }\\n if (isValid) {\\n res.Add(i);\\n }\\n }\\n return res.ToArray();\\n }\\n}\\n
###Go
\\n\\nfunc findEvenNumbers(digits []int) []int {\\n res := []int{}\\n freq := make(map[int]int)\\n // 统计数字数组中各数字的出现次数\\n for _, d := range digits {\\n freq[d]++\\n }\\n // 枚举所有三位偶数\\n for i := 100; i < 1000; i += 2 {\\n freq1 := make(map[int]int)\\n num := i\\n // 统计当前偶数的每一位数字的出现次数\\n for num > 0 {\\n d := num % 10\\n freq1[d]++\\n num /= 10\\n }\\n // 检查是否满足条件\\n isValid := true\\n for d, count := range freq1 {\\n if freq[d] < count {\\n isValid = false\\n break\\n }\\n }\\n if isValid {\\n res = append(res, i)\\n }\\n }\\n return res\\n}\\n
###C
\\n\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n hashAddItem(obj, key, val);\\n } else {\\n pEntry->val = val;\\n }\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nint compare(const void* a, const void* b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint* findEvenNumbers(int* digits, int digitsSize, int* returnSize) {\\n int *res = (int *)malloc(sizeof(int) * 500); // 目标偶数数组\\n HashItem *freq = NULL; // 整数数组中各数字的出现次数\\n for (int i = 0; i < digitsSize; i++) {\\n int digit = digits[i];\\n hashSetItem(&freq, digit, hashGetItem(&freq, digit, 0) + 1);\\n }\\n // 枚举所有三位偶数,维护整数中各数位的出现次数并比较判断是否为目标偶数\\n int pos = 0;\\n for (int i = 100; i < 1000; i += 2){\\n int freq1[10] = {0};\\n int tmp = i;\\n while (tmp){\\n ++freq1[tmp % 10];\\n tmp /= 10;\\n }\\n // 检查是否满足条件\\n bool isValid = true;\\n for (int j = 0; j < 10; j++) {\\n if (freq1[j] == 0) {\\n continue;\\n }\\n if (!hashFindItem(&freq, j) || hashGetItem(&freq, j, 0) < freq1[j]) {\\n isValid = false;\\n break;\\n }\\n }\\n if (isValid) {\\n res[pos++] = i;\\n }\\n }\\n *returnSize = pos;\\n return res;\\n}\\n
###JavaScript
\\n\\nvar findEvenNumbers = function(digits) {\\n const res = [];\\n const freq = new Map();\\n // 统计数字数组中各数字的出现次数\\n for (const d of digits) {\\n freq.set(d, (freq.get(d) || 0) + 1);\\n }\\n // 枚举所有三位偶数\\n for (let i = 100; i < 1000; i += 2) {\\n const freq1 = new Map();\\n let num = i;\\n // 统计当前偶数的每一位数字的出现次数\\n while (num > 0) {\\n const d = num % 10;\\n freq1.set(d, (freq1.get(d) || 0) + 1);\\n num = Math.floor(num / 10);\\n }\\n // 检查是否满足条件\\n let isValid = true;\\n for (const [d, count] of freq1.entries()) {\\n if ((freq.get(d) || 0) < count) {\\n isValid = false;\\n break;\\n }\\n }\\n if (isValid) {\\n res.push(i);\\n }\\n }\\n return res;\\n}\\n
###TypeScript
\\n\\nfunction findEvenNumbers(digits: number[]): number[] {\\n const res: number[] = [];\\n const freq = new Map<number, number>();\\n // 统计数字数组中各数字的出现次数\\n for (const d of digits) {\\n freq.set(d, (freq.get(d) || 0) + 1);\\n }\\n // 枚举所有三位偶数\\n for (let i = 100; i < 1000; i += 2) {\\n const freq1 = new Map<number, number>();\\n let num = i;\\n // 统计当前偶数的每一位数字的出现次数\\n while (num > 0) {\\n const d = num % 10;\\n freq1.set(d, (freq1.get(d) || 0) + 1);\\n num = Math.floor(num / 10);\\n }\\n // 检查是否满足条件\\n let isValid = true;\\n for (const [d, count] of freq1.entries()) {\\n if ((freq.get(d) || 0) < count) {\\n isValid = false;\\n break;\\n }\\n }\\n if (isValid) {\\n res.push(i);\\n }\\n }\\n return res;\\n}\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn find_even_numbers(digits: Vec<i32>) -> Vec<i32> {\\n let mut res = Vec::new();\\n let mut freq = HashMap::new();\\n // 统计数字数组中各数字的出现次数\\n for &d in &digits {\\n *freq.entry(d).or_insert(0) += 1;\\n }\\n // 枚举所有三位偶数\\n for i in (100..1000).step_by(2) {\\n let mut freq1 = HashMap::new();\\n let mut num = i;\\n // 统计当前偶数的每一位数字的出现次数\\n while num > 0 {\\n let d = num % 10;\\n *freq1.entry(d).or_insert(0) += 1;\\n num /= 10;\\n }\\n // 检查是否满足条件\\n let mut is_valid = true;\\n for (&d, &count) in &freq1 {\\n if freq.get(&d).unwrap_or(&0) < &count {\\n is_valid = false;\\n break;\\n }\\n }\\n if is_valid {\\n res.push(i);\\n }\\n }\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举数组中的元素组合 思路与算法\\n\\n我们可以从数组中枚举目标整数的三个整数位,判断组成的整数是否满足以下条件:\\n\\n整数为偶数;\\n\\n整数不包含前导零(即整数不小于 $100$);\\n\\n三个整数位对应的数组下标不能重复。\\n\\n为了避免重复,我们用一个哈希集合来维护符合要求的 $3$ 位偶数,如果枚举产生的整数满足上述三个条件,则我们将该整数加入哈希集合。\\n\\n最终,我们将该哈希集合内的元素放入数组中,按照递增顺序排序并返回。\\n\\n代码\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n vector- \\n
\\n时间复杂度:$O(k\\\\cdot10^k)$,其中 $k$ 为目标偶数的位数。即为枚举所有给定位数偶数的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(1)$,输出数组不计入空间复杂度。
\\n解题思路\\n 因为题目的答案只是三位数,所以我们外循环可以枚举所有的三位数偶数,对于每一个偶数统计它每一位的计数,如果该计数超过了题目给的digit数组的计数则不满足,所有位数都满足的放入答案,因为是从100枚举至998的,所以答案必定有序。
\\n代码
\\n###cpp
\\n\\n","description":"解题思路 因为题目的答案只是三位数,所以我们外循环可以枚举所有的三位数偶数,对于每一个偶数统计它每一位的计数,如果该计数超过了题目给的digit数组的计数则不满足,所有位数都满足的放入答案,因为是从100枚举至998的,所以答案必定有序。\\n\\n代码\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<int> findEvenNumbers(vector<int>& d) {\\n int nums[10] = {0};\\n for (auto &c : d) {\\n nums[c]++; // 统计原有的每一个数字[0-9]的计数\\n }\\n vector<int> ans;\\n for (int i = 100; i <= 998; i += 2) {\\n int x = i;\\n int cnt[10] = {0}; // 统计目前判断的偶数的每一位数字[0-9]的计数\\n bool flag = true;\\n while (x) {\\n int c = x % 10;\\n cnt[c]++;\\n if (cnt[c] > nums[c]) {\\n flag = false;\\n break;\\n }\\n x /= 10;\\n }\\n if (flag) {\\n ans.push_back(i);\\n }\\n } \\n return ans; \\n }\\n};\\n
findEvenNumbers(vector & d) {\\n int nums[10] = {0};\\n for (auto &c : d) {\\n nums[c]…","guid":"https://leetcode.cn/problems/finding-3-digit-even-numbers//solution/c-mei-ju-suo-you-san-wei-shu-ou-shu-dui-cilpk","author":"EllieFeng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-05T05:35:42.150Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:暴力枚举 / 回溯(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/finding-3-digit-even-numbers//solution/mei-ju-suo-you-san-wei-shu-ou-shu-by-end-8n7d","content":" 方法一:暴力枚举
\\n枚举所有三位偶数 $i=100,102,104,\\\\ldots,998$。
\\n统计 $i$ 中的每个数字 $d=0,1,2,\\\\ldots,9$ 的出现次数。比如 $i=666$,其中 $d=6$ 出现了 $3$ 次,但如果 $\\\\textit{digits}$ 中只有 $2$ 个 $6$,那么 $i$ 无法由 $\\\\textit{digits}$ 中的数字组成,不能加到答案中。
\\n为了知道 $\\\\textit{digits}$ 中的数字个数,可以在枚举 $i$ 之前,统计 $\\\\textit{digits}$ 中的每个数字的个数。
\\n\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n cnt = Counter(digits)\\n ans = []\\n # 枚举所有三位数偶数\\n for i in range(100, 1000, 2):\\n # digits 有充足的数字组成 i\\n if Counter(map(int, str(i))) <= cnt:\\n ans.append(i)\\n return ans\\n
\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n cnt = Counter(digits)\\n return [i for i in range(100, 1000, 2) if Counter(map(int, str(i))) <= cnt]\\n
\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n cnt = [0] * 10\\n for d in digits:\\n cnt[d] += 1\\n\\n ans = []\\n for i in range(100, 1000, 2): # 枚举所有三位数偶数 i\\n c = [0] * 10\\n x = i\\n while x > 0:\\n x, d = divmod(x, 10)\\n c[d] += 1\\n # 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if c[d] > cnt[d]:\\n break\\n else: # 没有中途 break\\n ans.append(i)\\n return ans\\n
\\nclass Solution {\\n public int[] findEvenNumbers(int[] digits) {\\n int[] cnt = new int[10];\\n for (int d : digits) {\\n cnt[d]++;\\n }\\n\\n List<Integer> ans = new ArrayList<>();\\n next:\\n for (int i = 100; i < 1000; i += 2) { // 枚举所有三位数偶数 i\\n int[] c = new int[10];\\n for (int x = i; x > 0; x /= 10) { // 枚举 i 的每一位 d\\n int d = x % 10;\\n // 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if (++c[d] > cnt[d]) { \\n continue next; // 枚举下一个偶数\\n }\\n }\\n ans.add(i);\\n }\\n return ans.stream().mapToInt(i -> i).toArray();\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> findEvenNumbers(vector<int>& digits) {\\n int cnt[10]{};\\n for (int d : digits) {\\n cnt[d]++;\\n }\\n\\n vector<int> ans;\\n for (int i = 100; i < 1000; i += 2) { // 枚举所有三位数偶数 i\\n int c[10]{};\\n bool ok = true;\\n for (int x = i; x > 0; x /= 10) { // 枚举 i 的每一位 d\\n int d = x % 10;\\n // 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if (++c[d] > cnt[d]) { \\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n ans.push_back(i);\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nfunc findEvenNumbers(digits []int) (ans []int) {\\n cnt := [10]int{}\\n for _, d := range digits {\\n cnt[d]++\\n }\\n\\nnext:\\n for i := 100; i < 1000; i += 2 { // 枚举所有三位数偶数 i\\n c := [10]int{}\\n for x := i; x > 0; x /= 10 { // 枚举 i 的每一位 d\\n d := x % 10\\n c[d]++\\n // 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if c[d] > cnt[d] {\\n continue next // 枚举下一个偶数\\n }\\n }\\n ans = append(ans, i)\\n }\\n return\\n}\\n
\\nvar findEvenNumbers = function(digits) {\\n const cnt = Array(10).fill(0);\\n for (const d of digits) {\\n cnt[d]++;\\n }\\n\\n const ans = [];\\n for (let i = 100; i < 1000; i += 2) { // 枚举所有三位数偶数 i\\n const c = Array(10).fill(0);\\n let ok = true;\\n for (let x = i; x > 0; x = Math.floor(x / 10)) { // 枚举 i 的每一位 d\\n const d = x % 10;\\n // 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if (++c[d] > cnt[d]) {\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n ans.push(i);\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn find_even_numbers(digits: Vec<i32>) -> Vec<i32> {\\n let mut cnt = [0; 10];\\n for d in digits {\\n cnt[d as usize] += 1;\\n }\\n\\n let mut ans = vec![];\\n for i in (100..1000).step_by(2) { // 枚举所有三位数偶数 i\\n let mut c = [0; 10];\\n let mut x = i;\\n let mut ok = true;\\n while x > 0 {\\n let d = x % 10;\\n c[d] += 1;\\n // 如果 i 中 d 的个数比 digits 中的还多,那么 i 无法由 digits 中的数字组成\\n if c[d] > cnt[d] {\\n ok = false;\\n break;\\n }\\n x /= 10;\\n }\\n if ok {\\n ans.push(i as i32);\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n + k 10^k)$,其中 $n$ 是 $\\\\textit{digits}$ 的长度,$k=3$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。返回值不计入。$\\\\textit{cnt}$ 的大小视作 $\\\\mathcal{O}(1)$。
\\n方法二:回溯
\\n前置知识:回溯【基础算法精讲 14】
\\n换一个角度,枚举百位数填什么,十位数填什么,个位数填什么。
\\n\\n
\\n- 百位数不能填 $0$。
\\n- 十位数随便填。
\\n- 个位数只能填偶数。
\\n用回溯实现上述枚举,注意填的数字个数必须有剩余,或者说不能超过 $\\\\textit{digits}$ 中的数字个数。
\\n\\nclass Solution:\\n def findEvenNumbers(self, digits: List[int]) -> List[int]:\\n cnt = [0] * 10\\n for d in digits:\\n cnt[d] += 1\\n\\n ans = []\\n # i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n def dfs(i: int, x: int):\\n if i == 3:\\n ans.append(x)\\n return\\n for d, c in enumerate(cnt):\\n if c > 0 and (i == 0 and d > 0 or i == 1 or i == 2 and d % 2 == 0):\\n cnt[d] -= 1 # 消耗一个数字 d\\n dfs(i + 1, x * 10 + d)\\n cnt[d] += 1 # 复原\\n dfs(0, 0)\\n return ans\\n
\\nclass Solution {\\n public int[] findEvenNumbers(int[] digits) {\\n int[] cnt = new int[10];\\n for (int d : digits) {\\n cnt[d]++;\\n }\\n\\n List<Integer> ans = new ArrayList<>();\\n dfs(0, 0, cnt, ans);\\n return ans.stream().mapToInt(i -> i).toArray();\\n }\\n\\n // i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n private void dfs(int i, int x, int[] cnt, List<Integer> ans) {\\n if (i == 3) {\\n ans.add(x);\\n return;\\n }\\n for (int d = 0; d < 10; d++) {\\n if (cnt[d] > 0 && (i == 0 && d > 0 || i == 1 || i == 2 && d % 2 == 0)) {\\n cnt[d]--; // 消耗一个数字 d\\n dfs(i + 1, x * 10 + d, cnt, ans);\\n cnt[d]++; // 复原\\n }\\n }\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> findEvenNumbers(vector<int>& digits) {\\n int cnt[10]{};\\n for (int d : digits) {\\n cnt[d]++;\\n }\\n\\n vector<int> ans;\\n // i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n auto dfs = [&](this auto&& dfs, int i, int x) -> void {\\n if (i == 3) {\\n ans.push_back(x);\\n return;\\n }\\n for (int d = 0; d < 10; d++) {\\n if (cnt[d] > 0 && (i == 0 && d > 0 || i == 1 || i == 2 && d % 2 == 0)) {\\n cnt[d]--; // 消耗一个数字 d\\n dfs(i + 1, x * 10 + d);\\n cnt[d]++; // 复原\\n }\\n }\\n };\\n dfs(0, 0);\\n return ans;\\n }\\n};\\n
\\nfunc findEvenNumbers(digits []int) (ans []int) {\\n cnt := make([]int, 10)\\n for _, d := range digits {\\n cnt[d]++\\n }\\n\\n // i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n var dfs func(i, x int)\\n dfs = func(i, x int) {\\n if i == 3 {\\n ans = append(ans, x)\\n return\\n }\\n for d, c := range cnt {\\n if c > 0 && (i == 0 && d > 0 || i == 1 || i == 2 && d%2 == 0) {\\n cnt[d]-- // 消耗一个数字 d\\n dfs(i+1, x*10+d)\\n cnt[d]++ // 复原\\n }\\n }\\n }\\n dfs(0, 0)\\n return\\n}\\n
\\nvar findEvenNumbers = function(digits) {\\n const cnt = Array(10).fill(0);\\n for (const d of digits) {\\n cnt[d]++;\\n }\\n\\n const ans = [];\\n // i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n function dfs(i, x) {\\n if (i === 3) {\\n ans.push(x);\\n return;\\n }\\n for (let d = 0; d < 10; d++) {\\n if (cnt[d] > 0 && (i === 0 && d > 0 || i === 1 || i === 2 && d % 2 === 0)) {\\n cnt[d]--; // 消耗一个数字 d\\n dfs(i + 1, x * 10 + d);\\n cnt[d]++; // 复原\\n }\\n }\\n }\\n dfs(0, 0);\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn find_even_numbers(digits: Vec<i32>) -> Vec<i32> {\\n let mut cnt = [0; 10];\\n for d in digits {\\n cnt[d as usize] += 1;\\n }\\n\\n // i=0 百位,i=1 十位,i=2 个位,x 表示当前正在构造的数字\\n fn dfs(i: usize, x: i32, cnt: &mut [i32; 10], ans: &mut Vec<i32>) {\\n if i == 3 {\\n ans.push(x);\\n return;\\n }\\n for d in 0..10 {\\n if cnt[d] > 0 && (i == 0 && d > 0 || i == 1 || i == 2 && d % 2 == 0) {\\n cnt[d] -= 1; // 消耗一个数字 d\\n dfs(i + 1, x * 10 + d as i32, cnt, ans);\\n cnt[d] += 1; // 复原\\n }\\n }\\n }\\n let mut ans = vec![];\\n dfs(0, 0, &mut cnt, &mut ans);\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n + 10^k)$,其中 $n$ 是 $\\\\textit{digits}$ 的长度,$k=3$。搜索树是一棵高度为 $k$ 的 $10$ 叉树,有 $\\\\mathcal{O}(10^k)$ 个节点,遍历这棵树需要 $\\\\mathcal{O}(10^k)$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(k)$。返回值不计入。$\\\\textit{cnt}$ 数组的大小视作 $\\\\mathcal{O}(1)$。递归需要 $\\\\mathcal{O}(k)$ 的栈空间。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:暴力枚举 枚举所有三位偶数 $i=100,102,104,\\\\ldots,998$。\\n\\n统计 $i$ 中的每个数字 $d=0,1,2,\\\\ldots,9$ 的出现次数。比如 $i=666$,其中 $d=6$ 出现了 $3$ 次,但如果 $\\\\textit{digits}$ 中只有 $2$ 个 $6$,那么 $i$ 无法由 $\\\\textit{digits}$ 中的数字组成,不能加到答案中。\\n\\n为了知道 $\\\\textit{digits}$ 中的数字个数,可以在枚举 $i$ 之前,统计 $\\\\textit{digits}$ 中的每个数字的个数。\\n\\nclass…","guid":"https://leetcode.cn/problems/finding-3-digit-even-numbers//solution/mei-ju-suo-you-san-wei-shu-ou-shu-by-end-8n7d","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-12-05T04:07:10.221Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"带限制的单源最短路径问题","url":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/dai-xian-zhi-de-dan-yuan-zui-duan-lu-jin-wzuz","content":"解题思路
\\n这类题一般是 dijkstra 模板 + memo 控制再入堆的条件:如果还没看过这个点,或者当前的限制条件比之前更优,则加入堆
\\n代码
\\n###python3
\\n\\n","description":"解题思路 这类题一般是 dijkstra 模板 + memo 控制再入堆的条件:如果还没看过这个点,或者当前的限制条件比之前更优,则加入堆\\n\\n代码\\n\\n###python3\\n\\nfrom typing import List, Tuple\\nfrom heapq import heappop, heappush\\n\\nCost, ID, Time = int, int, int\\n# edges[i] = [xi, yi, timei] 表示城市 xi 和 yi 之间有一条双向道路,耗费时间为 timei 分钟\\n# 你在城市 0 ,你想要在 maxTime 分钟以内…","guid":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/dai-xian-zhi-de-dan-yuan-zui-duan-lu-jin-wzuz","author":"981377660LMT","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-29T10:35:29.542Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"哈希表+二分(两种常用二分总结)","url":"https://leetcode.cn/problems/range-frequency-queries//solution/ha-xi-biao-er-fen-si-chong-chang-yong-er-nwop","content":"from typing import List, Tuple\\nfrom heapq import heappop, heappush\\n\\nCost, ID, Time = int, int, int\\n# edges[i] = [xi, yi, timei] 表示城市 xi 和 yi 之间有一条双向道路,耗费时间为 timei 分钟\\n# 你在城市 0 ,你想要在 maxTime 分钟以内 (包含 maxTime 分钟)到达城市 n - 1\\n# 请你返回完成旅行的 最小费用 ,如果无法在 maxTime 分钟以内完成旅行,请你返回 -1 。\\n\\n# 带限制的单源最短路径问题\\n\\n\\nclass Solution:\\n def minCost(self, maxTime: int, edges: List[List[int]], passingFees: List[int]) -> int:\\n n = len(passingFees)\\n adjList = [[] for _ in range(n)]\\n for u, v, t in edges:\\n adjList[u].append((v, t))\\n adjList[v].append((u, t))\\n\\n times = dict()\\n pq: List[Tuple[Cost, ID, Time]] = [(passingFees[0], 0, 0)]\\n\\n while pq:\\n cur_cost, cur_id, cur_time = heappop(pq)\\n if cur_time > maxTime:\\n continue\\n if cur_id == n - 1:\\n return cur_cost\\n if cur_id not in times or times[cur_id] > cur_time:\\n times[cur_id] = cur_time\\n for next_id, next_time in adjList[cur_id]:\\n heappush(pq, (cur_cost + passingFees[next_id], next_id, cur_time + next_time))\\n \\n return -1\\n\\n
解题思路
\\n对于数组中每一个不同的数,都对应多个位置,我们需要从中统计出符合区间范围的位置的个数。
\\n
\\n很容易想到使用哈希表来存储每个数在数组中的位置下标,将其排序,然后通过二分查找区间范围端点[left, right]
在哈希表中对应数组的下标[l, r]
,将其作差,得到答案l - r
。常用二分模板总结
\\n四种位置
\\n\\n
查找第一个等于target的元素(lower_bound)
\\n\\nwhile(l < r){\\n int mid = (l + r) >> 1;\\n if(target <= a[mid]) r = mid;\\n else l = mid + 1;\\n }\\n return l;\\n
查找第一个大于target的元素(upper_bound)
\\n\\nwhile(l < r){\\n int mid = (l + r) >> 1;\\n if(target >= a[mid]) l = mid + 1;\\n else r = mid;\\n }\\n return l;\\n
最后边界都是
\\nl == r
,返回l
或r
都可以。
\\n如果要找左边界的两个值,关键点是
\\ntarget == a[mid]
的时候,要缩小右端点,即r = (mid) or (mid - 1)
。
\\n找右侧的值,
\\nmid = (l + r) >> 1
和l = mid + 1
是配套使用的,这决定了到边界的时候我们要让左端点l
向上顶,遇到右端点r
,最后得到的是右侧的数值。同理,如果要找右边界的两个值,相等的时候,缩左边界即可。
\\n
\\n找左侧的值,用mid = (l + r + 1) >> 1
和r = mid - 1
。代码
\\n###java
\\n\\nclass RangeFreqQuery {\\n\\n Map<Integer, List<Integer>> map = new HashMap<>();\\n public RangeFreqQuery(int[] arr) {\\n int n = arr.length;\\n for(int i = 0; i < n; i++){\\n List<Integer> tmp = map.getOrDefault(arr[i], new ArrayList<>());\\n tmp.add(i);\\n map.put(arr[i], tmp);\\n }\\n }\\n \\n public int query(int left, int right, int value) {\\n if(!map.containsKey(value)) return 0;\\n int l = lower_bound(map.get(value), left);\\n int r = upper_bound(map.get(value), right);\\n return r - l;\\n }\\n public int lower_bound(List<Integer> a, int target){\\n int n = a.size();\\n int l = 0, r = n;\\n while(l < r){\\n int mid = (l + r) >> 1;\\n if(target <= a.get(mid)) r = mid;\\n else l = mid + 1;\\n }\\n return l;\\n }\\n public int upper_bound(List<Integer> a, int target){\\n int n = a.size();\\n int l = 0, r = n;\\n while(l < r){\\n int mid = (l + r) >> 1;\\n if(target >= a.get(mid)) l = mid + 1;\\n else r = mid;\\n }\\n return l;\\n }\\n}\\n
###c++
\\n\\n","description":"解题思路 对于数组中每一个不同的数,都对应多个位置,我们需要从中统计出符合区间范围的位置的个数。\\n 很容易想到使用哈希表来存储每个数在数组中的位置下标,将其排序,然后通过二分查找区间范围端点[left, right]在哈希表中对应数组的下标[l, r],将其作差,得到答案l - r。\\n\\n常用二分模板总结\\n\\n四种位置\\n\\n查找第一个等于target的元素(lower_bound)\\n\\n while(l < r){\\n int mid = (l + r) >> 1;\\n if(target <= a[mid]) r = mid;…","guid":"https://leetcode.cn/problems/range-frequency-queries//solution/ha-xi-biao-er-fen-si-chong-chang-yong-er-nwop","author":"LCS_0215","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-22T05:44:10.797Z","media":[{"url":"https://pic.leetcode-cn.com/1637558132-HPTSMO-%E5%9B%BE%E7%89%87.png","type":"photo","width":1370,"height":744,"blurhash":"LES6JP^*xayC?bxat6WB~XXSNG$l"},{"url":"https://pic.leetcode-cn.com/1637559648-wcZaEC-%E5%9B%BE%E7%89%87.png","type":"photo","width":1594,"height":889,"blurhash":"LBSY:N~q%3~W~WofR*of^,V@M_Nb"},{"url":"https://pic.leetcode-cn.com/1637559656-NcaCBW-%E5%9B%BE%E7%89%87.png","type":"photo","width":1421,"height":854,"blurhash":"LBR{up~W%Nu2~qkBWBs:-qW.RjxH"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java 二分","url":"https://leetcode.cn/problems/range-frequency-queries//solution/java-er-fen-by-merickbao-2-phux","content":"class RangeFreqQuery {\\npublic:\\n unordered_map<int, vector<int>> mp;\\n RangeFreqQuery(vector<int>& arr) {\\n int n = arr.size();\\n for(int i = 0; i < n; i++){\\n mp[arr[i]].push_back(i);\\n }\\n }\\n int query(int left, int right, int value) {\\n int l = lower_bound(mp[value].begin(), mp[value].end(), left) - mp[value].begin();\\n int r = upper_bound(mp[value].begin(), mp[value].end(), right) - mp[value].begin();\\n return r - l;\\n }\\n};\\n
解题思路
\\n我们可以有序的记录每个值对应的所有下标,然后在查询时时,就可以对下标进行二分,找到左右端点的下标
\\na,b
,这样每次查询的频率就为b - a + 1
;
\\n由于$1 <= arr[i] <= 10^{4}$,所以可以用一个长度为10000的数组来代替哈希表,来存储每个值对应的所有下标。
\\n时间复杂度:$O(NlogN)$
\\n空间复杂度:$O(VM)$,V为值域大小,M为值对应的下标个数
\\n代码
\\n###java
\\n\\n","description":"解题思路 我们可以有序的记录每个值对应的所有下标,然后在查询时时,就可以对下标进行二分,找到左右端点的下标a,b,这样每次查询的频率就为b - a + 1;\\n 由于$1 <= arr[i] <= 10^{4}$,所以可以用一个长度为10000的数组来代替哈希表,来存储每个值对应的所有下标。\\n 时间复杂度:$O(NlogN)$\\n 空间复杂度:$O(VM)$,V为值域大小,M为值对应的下标个数\\n\\n\\n代码\\n\\n###java\\n\\nclass RangeFreqQuery {\\n Listclass RangeFreqQuery {\\n List<List<Integer>> all = new ArrayList<>();\\n\\n public RangeFreqQuery(int[] arr) {\\n for (int i = 0; i <= 10000; i++) {\\n all.add(new ArrayList<>());\\n }\\n for (int i = 0; i < arr.length; i++) {\\n // 下标是按顺序加入的,所以是有序的,所以后面可以直接进行二分查找\\n all.get(arr[i]).add(i);\\n }\\n }\\n \\n public int query(int left, int right, int value) {\\n if (all.get(value).size() == 0) return 0;\\n // 当前值对应的下标集合\\n List<Integer> now = all.get(value);\\n // 第一次二分找左端点下标\\n int a = binarySearch(now, 0, now.size() - 1, left);\\n // 不存在这样的左端点\\n if (now.get(a) > right || now.get(a) < left) return 0;\\n\\n // 第二次二分,找右端点的下标\\n int b = binarySearch(now, a, now.size() - 1, right);\\n if (now.get(b) > right) {\\n b--;\\n }\\n return b - a + 1;\\n }\\n\\n // 找到大于等于target的第一个位置\\n public int binarySearch(List<Integer> nums, int l , int r, int target) {\\n while (l < r) {\\n int mid = (r - l) / 2 + l;\\n if (nums.get(mid) < target) {\\n l = mid + 1;\\n } else {\\n r = mid;\\n }\\n }\\n return l;\\n }\\n}\\n\\n/**\\n * Your RangeFreqQuery object will be instantiated and called as such:\\n * RangeFreqQuery obj = new RangeFreqQuery(arr);\\n * int param_1 = obj.query(left,right,value);\\n */\\n
> all = new ArrayList<>();…","guid":"https://leetcode.cn/problems/range-frequency-queries//solution/java-er-fen-by-merickbao-2-phux","author":"merickbao-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-21T04:23:24.979Z","media":[{"url":"https://pic.leetcode-cn.com/1637468527-KuacrS-%E6%88%AA%E5%B1%8F2021-11-21%2012.22.04.png","type":"photo","width":551,"height":167,"blurhash":"LCRpB]_2%M?b~qoeah%3-:WAM|WC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简洁写法:统计位置+二分查找(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/range-frequency-queries//solution/tong-ji-wei-zhi-er-fen-wei-zhi-by-endles-8l9u","content":"
对于 $\\\\textit{arr}$ 中的每个数,计算其在 $\\\\textit{arr}$ 中的出现位置(下标)。例如 $\\\\textit{arr}=[1,2,1,1,2,2]$,其中数字 $2$ 的下标为 $[1,4,5]$。
\\n知道了下标,那么对于 $\\\\texttt{query}$ 来说,问题就变成了:
\\n\\n
\\n- 下标列表中,满足 $\\\\textit{left}\\\\le i \\\\le \\\\textit{right}$ 的下标 $i$ 的个数。
\\n例如 $\\\\texttt{query}(3,5,2)$,由于数字 $2$ 的下标列表 $[1,4,5]$ 中的下标 $4$ 和 $5$ 都在区间 $[3,5]$ 中,所以返回 $2$。
\\n把下标列表记作数组 $a$,由于 $a$ 是有序数组,我们可以用二分查找快速求出:
\\n\\n
\\n- $a$ 中的第一个 $\\\\ge \\\\textit{left}$ 的数的下标,设其为 $p$。如果不存在,则 $p$ 等于 $a$ 的长度。
\\n- $a$ 中的第一个 $> \\\\textit{right}$ 的数的下标,设其为 $q$。如果不存在,则 $q$ 等于 $a$ 的长度。
\\n$a$ 中的下标在 $[p,q-1]$ 内的数都是满足要求的,这有 $q-p$ 个。特别地,如果 $a$ 中没有满足要求的下标,那么 $q-p=0$,这仍然是正确的。
\\n关于二分查找的原理,请看视频讲解:二分查找 红蓝染色法【基础算法精讲 04】
\\n\\nclass RangeFreqQuery:\\n def __init__(self, arr: List[int]):\\n pos = defaultdict(list)\\n for i, x in enumerate(arr):\\n pos[x].append(i)\\n self.pos = pos\\n\\n def query(self, left: int, right: int, value: int) -> int:\\n a = self.pos[value]\\n return bisect_right(a, right) - bisect_left(a, left)\\n
\\nclass RangeFreqQuery {\\n private final Map<Integer, List<Integer>> pos = new HashMap<>();\\n\\n public RangeFreqQuery(int[] arr) {\\n for (int i = 0; i < arr.length; i++) {\\n pos.computeIfAbsent(arr[i], k -> new ArrayList<>()).add(i);\\n }\\n }\\n\\n public int query(int left, int right, int value) {\\n List<Integer> a = pos.get(value);\\n if (a == null) {\\n return 0;\\n }\\n // > right 等价于 >= right+1\\n return lowerBound(a, right + 1) - lowerBound(a, left);\\n }\\n\\n // 开区间写法\\n // 请看 https://www.bilibili.com/video/BV1AP41137w7/\\n private int lowerBound(List<Integer> a, int target) {\\n // 开区间 (left, right)\\n int left = -1;\\n int right = a.size();\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // a[left] < target\\n // a[right] >= target\\n int mid = (left + right) >>> 1;\\n if (a.get(mid) < target) {\\n left = mid; // 范围缩小到 (mid, right)\\n } else {\\n right = mid; // 范围缩小到 (left, mid)\\n }\\n }\\n return right; // right 是最小的满足 a[right] >= target 的下标\\n }\\n}\\n
\\nclass RangeFreqQuery {\\n unordered_map<int, vector<int>> pos;\\n\\npublic:\\n RangeFreqQuery(vector<int>& arr) {\\n for (int i = 0; i < arr.size(); i++) {\\n pos[arr[i]].push_back(i);\\n }\\n }\\n\\n int query(int left, int right, int value) {\\n // 不推荐写 a = pos[value],如果 value 不在 pos 中会插入 value\\n auto it = pos.find(value);\\n if (it == pos.end()) {\\n return 0;\\n }\\n auto& a = it->second;\\n return ranges::upper_bound(a, right) - ranges::lower_bound(a, left);\\n }\\n};\\n
\\ntype RangeFreqQuery map[int][]int\\n\\nfunc Constructor(arr []int) RangeFreqQuery {\\n pos := map[int][]int{}\\n for i, x := range arr {\\n pos[x] = append(pos[x], i)\\n }\\n return pos\\n}\\n\\nfunc (pos RangeFreqQuery) Query(left, right, value int) int {\\n a := pos[value]\\n // > right 等价于 >= right+1\\n return sort.SearchInts(a, right+1) - sort.SearchInts(a, left)\\n}\\n
\\ntype RangeFreqQuery map[int][]int\\n\\nfunc Constructor(arr []int) RangeFreqQuery {\\n pos := map[int][]int{}\\n for i, x := range arr {\\n pos[x] = append(pos[x], i)\\n }\\n return pos\\n}\\n\\nfunc (pos RangeFreqQuery) Query(left, right, value int) int {\\n a := pos[value]\\n p := sort.SearchInts(a, left)\\n return sort.SearchInts(a[p:], right+1) // 二分长度更短\\n}\\n
\\nvar RangeFreqQuery = function(arr) {\\n this.pos = {};\\n for (let i = 0; i < arr.length; i++) {\\n if (this.pos[arr[i]] === undefined) {\\n this.pos[arr[i]] = [];\\n }\\n this.pos[arr[i]].push(i);\\n }\\n};\\n\\nRangeFreqQuery.prototype.query = function(left, right, value) {\\n const a = this.pos[value];\\n if (a === undefined) {\\n return 0;\\n }\\n // > right 等价于 >= right+1\\n return lowerBound(a, right + 1) - lowerBound(a, left);\\n};\\n\\n// 见 https://www.bilibili.com/video/BV1AP41137w7/\\nvar lowerBound = function(a, target) {\\n let left = -1, right = a.length; // 开区间 (left, right)\\n while (left + 1 < right) { // 区间不为空\\n const mid = Math.floor((left + right) / 2);\\n if (a[mid] >= target) {\\n right = mid; // 范围缩小到 (left, mid)\\n } else {\\n left = mid; // 范围缩小到 (mid, right)\\n }\\n }\\n return right;\\n}\\n
\\nuse std::collections::HashMap;\\n\\nstruct RangeFreqQuery {\\n pos: HashMap<i32, Vec<usize>>,\\n}\\n\\nimpl RangeFreqQuery {\\n fn new(arr: Vec<i32>) -> Self {\\n let mut pos = HashMap::new();\\n for (i, &x) in arr.iter().enumerate() {\\n pos.entry(x).or_insert(vec![]).push(i);\\n }\\n Self { pos }\\n }\\n\\n fn query(&self, left: i32, right: i32, value: i32) -> i32 {\\n if let Some(a) = self.pos.get(&value) {\\n let p = a.partition_point(|&i| i < left as usize);\\n let q = a.partition_point(|&i| i <= right as usize);\\n (q - p) as _\\n } else {\\n 0\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:初始化 $\\\\mathcal{O}(n)$,其中 $n$ 为 $\\\\textit{arr}$ 的长度。每次 $\\\\texttt{query}$ 需要 $\\\\mathcal{O}(\\\\log n)$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"对于 $\\\\textit{arr}$ 中的每个数,计算其在 $\\\\textit{arr}$ 中的出现位置(下标)。例如 $\\\\textit{arr}=[1,2,1,1,2,2]$,其中数字 $2$ 的下标为 $[1,4,5]$。 知道了下标,那么对于 $\\\\texttt{query}$ 来说,问题就变成了:\\n\\n下标列表中,满足 $\\\\textit{left}\\\\le i \\\\le \\\\textit{right}$ 的下标 $i$ 的个数。\\n\\n例如 $\\\\texttt{query}(3,5,2)$,由于数字 $2$ 的下标列表 $[1,4,5]$ 中的下标 $4$ 和 $5…","guid":"https://leetcode.cn/problems/range-frequency-queries//solution/tong-ji-wei-zhi-er-fen-wei-zhi-by-endles-8l9u","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-21T04:07:30.307Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"买票需要的时间","url":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/mai-piao-xu-yao-de-shi-jian-by-leetcode-jnfxx","content":"方法一:计算每个人需要的时间
\\n思路与算法
\\n为了计算第 $k$ 个人买完票所需的时间,我们可以首先计算在这个过程中每个人买票所需要的时间,再对这些时间求和得到答案。
\\n我们可以对每个人的下标 $i$ 分类讨论:
\\n\\n
\\n- \\n
\\n如果这个人初始在第 $k$ 个人的前方,或者这个人恰好为第 $k$ 个人,即 $i \\\\le k$,此时在第 $k$ 个人买完票之前他最多可以购买 $\\\\textit{tickets}[k]$ 张。考虑到他想要购买的票数,那么他买票所需时间即为 $\\\\min(\\\\textit{tickets}[k], \\\\textit{tickets}[i])$;
\\n- \\n
\\n如果这个人初始在第 $k$ 个人的后方,即 $i > k$,此时在第 $k$ 个人买完票之前他最多可以购买 $\\\\textit{tickets}[k] - 1$ 张。考虑到他想要购买的票数,那么他买票所需时间即为 $\\\\min(\\\\textit{tickets}[k] - 1, \\\\textit{tickets}[i])$。
\\n我们遍历每个人的下标,按照上述方式计算并维护每个人买票所需时间之和,即可得到第 $k$ 个人买完票所需的时间,我们返回该值作为答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int timeRequiredToBuy(vector<int>& tickets, int k) {\\n int n = tickets.size();\\n int res = 0;\\n for (int i = 0; i < n; ++i){\\n // 遍历计算每个人所需时间\\n if (i <= k){\\n res += min(tickets[i], tickets[k]);\\n }\\n else{\\n res += min(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int timeRequiredToBuy(int[] tickets, int k) {\\n int n = tickets.length;\\n int res = 0;\\n for (int i = 0; i < n; i++) {\\n // 遍历计算每个人所需时间\\n if (i <= k) {\\n res += Math.min(tickets[i], tickets[k]);\\n } else {\\n res += Math.min(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def timeRequiredToBuy(self, tickets: List[int], k: int) -> int:\\n n = len(tickets)\\n res = 0\\n for i in range(n):\\n # 遍历计算每个人所需时间\\n if i <= k:\\n res += min(tickets[i], tickets[k])\\n else:\\n res += min(tickets[i], tickets[k] - 1)\\n return res\\n
###JavaScript
\\n\\nvar timeRequiredToBuy = function(tickets, k) {\\n const n = tickets.length;\\n let res = 0;\\n for (let i = 0; i < n; i++) {\\n // 遍历计算每个人所需时间\\n if (i <= k) {\\n res += Math.min(tickets[i], tickets[k]);\\n } else {\\n res += Math.min(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n};\\n
###TypeScript
\\n\\nfunction timeRequiredToBuy(tickets: number[], k: number): number {\\n const n = tickets.length;\\n let res = 0;\\n for (let i = 0; i < n; i++) {\\n // 遍历计算每个人所需时间\\n if (i <= k) {\\n res += Math.min(tickets[i], tickets[k]);\\n } else {\\n res += Math.min(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n};\\n
###Go
\\n\\nfunc timeRequiredToBuy(tickets []int, k int) int {\\n n := len(tickets)\\n res := 0\\n for i := 0; i < n; i++ {\\n // 遍历计算每个人所需时间\\n if i <= k {\\n res += min(tickets[i], tickets[k])\\n } else {\\n res += min(tickets[i], tickets[k] - 1)\\n }\\n }\\n return res\\n}\\n\\nfunc min(a, b int) int {\\n if a < b {\\n return a\\n }\\n return b\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int TimeRequiredToBuy(int[] tickets, int k) {\\n int n = tickets.Length;\\n int res = 0;\\n for (int i = 0; i < n; i++) {\\n // 遍历计算每个人所需时间\\n if (i <= k) {\\n res += Math.Min(tickets[i], tickets[k]);\\n } else {\\n res += Math.Min(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n }\\n}\\n
###C
\\n\\nint timeRequiredToBuy(int* tickets, int ticketsSize, int k) {\\n int n = ticketsSize;\\n int res = 0;\\n for (int i = 0; i < n; ++i){\\n // 遍历计算每个人所需时间\\n if (i <= k){\\n res += fmin(tickets[i], tickets[k]);\\n }\\n else{\\n res += fmin(tickets[i], tickets[k] - 1);\\n }\\n }\\n return res;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn time_required_to_buy(tickets: Vec<i32>, k: i32) -> i32 {\\n let n = tickets.len() as i32;\\n let mut res = 0;\\n for i in 0..n {\\n // 遍历计算每个人所需时间\\n if i <= k.try_into().unwrap() {\\n res += std::cmp::min(tickets[i as usize], tickets[k as usize]);\\n } else {\\n res += std::cmp::min(tickets[i as usize], tickets[k as usize] - 1);\\n }\\n }\\n res\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func timeRequiredToBuy(tickets: Array<Int64>, k: Int64): Int64 {\\n let n = tickets.size\\n var res = 0\\n for (i in 0..n) {\\n // 遍历计算每个人所需时间\\n if (i <= k) {\\n res += min(tickets[i], tickets[k])\\n } else {\\n res += min(tickets[i], tickets[k] - 1)\\n }\\n }\\n return res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:计算每个人需要的时间 思路与算法\\n\\n为了计算第 $k$ 个人买完票所需的时间,我们可以首先计算在这个过程中每个人买票所需要的时间,再对这些时间求和得到答案。\\n\\n我们可以对每个人的下标 $i$ 分类讨论:\\n\\n如果这个人初始在第 $k$ 个人的前方,或者这个人恰好为第 $k$ 个人,即 $i \\\\le k$,此时在第 $k$ 个人买完票之前他最多可以购买 $\\\\textit{tickets}[k]$ 张。考虑到他想要购买的票数,那么他买票所需时间即为 $\\\\min(\\\\textit{tickets}[k], \\\\textit{tickets}[i])$;\\n\\n如…","guid":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/mai-piao-xu-yao-de-shi-jian-by-leetcode-jnfxx","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-15T03:49:20.616Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"每一个查询的最大美丽值","url":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/mei-yi-ge-cha-xun-de-zui-da-mei-li-zhi-b-d8jw","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{tickets}$ 的长度。即为遍历数组计算买票所需总时间的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法一:排序 + 二分查找
\\n思路与算法
\\n我们假设物品总数为 $n$,对于单次查询,一种朴素的方法是遍历整个数组,寻找价格符合要求的物品并维护其最大美丽值,但这样的时间复杂度为 $O(n)$,假设查询总数为 $q$,则总时间复杂度为 $O(nq)$,这样的复杂度无法通过本题。因此我们需要优化单次查询的时间复杂度。
\\n我们可以将单次查询的过程分为两步:
\\n\\n
\\n- \\n
\\n第一步,寻找到所有价格小于等于查询价格的物品;
\\n- \\n
\\n第二步,求出这些物品中的最大美丽值。
\\n对于第一步,我们可以将 $\\\\textit{items}$ 数组按照物品的价格升序排序,此时一定存在一个下标(可能不合法),下标大于它的物品(可能不存在)的价格高于查询价格,下标小于等于它的物品(可能不存在)的价格不高于查询价格。我们可以在 $O(n\\\\log n)$ 的时间内完成上述预处理,同时我们可以通过二分查找在 $O(\\\\log n)$ 的时间复杂度中查找到该下标。
\\n对于第二步,在第一步的基础上,我们将排序后数组中每个物品的美丽值($\\\\textit{items}[i][1]$)修改为下标小于等于 $i$ 的物品的最大美丽值。这一过程可以在 $O(n)$ 的时间复杂度下,通过从左至右的一次遍历完成。这样,当我们找到第一步对应的下标时,如果下标合法,则该下标对应的修改后的美丽值即为价格小于等于查询价格中物品的最大美丽值,我们将该值作为查询值;如果下标不合法,那么说明所有物品的价格均高于查询价格,此时查询到结果应当为 $0$。
\\n根据上文的分析,我们首先对 $\\\\textit{items}$ 数组按照物品的价格升序排序,并遍历数组,将下标为 $i$ 物品的美丽值修改为下标小于等于 $i$ 的物品的最大美丽值。随后,对于每个查询,我们用函数 $\\\\textit{query}(q)$ 求出价格小于等于 $q$ 的物品的最大美丽值。
\\n在函数 $\\\\textit{query}(q)$ 中,我们首先通过二分查找求出价格不高于 $q$ 的物品的下标最大值,如果该值合法,则返回 $\\\\textit{items}$ 数组中该下标对应的修改后的美丽值作为查询结果;如果该值不合法,则返回 $0$ 作为查询结果。
\\n我们按顺序处理查询,记录查询结果,并将最终的结果返回作为答案。
\\n细节
\\n在排序时,我们只需要将价格设为排序的唯一关键字,因为二分查找得到的下标(如果合法)一定为相同价格中最大的,因此该下标对应的修改后的美丽值一定为价格小于等于查询价格中物品的最大美丽值。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {\\n // 将物品按价格升序排序\\n sort(items.begin(), items.end(), [](auto&& item1, auto&& item2) {\\n return item1[0] < item2[0]; \\n });\\n int n = items.size();\\n // 按定义修改美丽值\\n for (int i = 1; i < n; ++i){\\n items[i][1] = max(items[i][1], items[i-1][1]);\\n }\\n // 二分查找处理查询\\n auto query = [&](int q) -> int{\\n int l = 0, r = n;\\n while (l < r){\\n int mid = l + (r - l) / 2;\\n if (items[mid][0] > q){\\n r = mid;\\n }\\n else{\\n l = mid + 1;\\n }\\n }\\n if (l == 0){\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n }\\n else{\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l-1][1];\\n }\\n };\\n \\n vector<int> res;\\n for (int q: queries){\\n res.push_back(query(q));\\n }\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:\\n # 将物品按价格升序排序\\n items.sort(key=lambda x: x[0])\\n n = len(items)\\n # 按定义修改美丽值\\n for i in range(1, n):\\n items[i][1] = max(items[i][1], items[i-1][1])\\n # 二分查找处理查询\\n def query(q: int) -> int:\\n l, r = 0, n\\n while l < r:\\n mid = l + (r - l) // 2\\n if items[mid][0] > q:\\n r = mid\\n else:\\n l = mid + 1\\n if l == 0:\\n # 此时所有物品价格均大于查询价格\\n return 0\\n else:\\n # 返回小于等于查询价格的物品的最大美丽值\\n return items[l-1][1]\\n \\n res = [query(q) for q in queries]\\n return res\\n
###Java
\\n\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n // 将物品按价格升序排序\\n Arrays.sort(items, (a, b) -> Integer.compare(a[0], b[0]));\\n int n = items.length;\\n // 按定义修改美丽值\\n for (int i = 1; i < n; ++i) {\\n items[i][1] = Math.max(items[i][1], items[i - 1][1]);\\n }\\n // 二分查找处理查询\\n int[] res = new int[queries.length];\\n for (int i = 0; i < queries.length; i++) {\\n res[i] = query(items, queries[i]);\\n }\\n return res;\\n }\\n \\n private int query(int[][] items, int q) {\\n int l = 0, r = items.length;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n if (items[mid][0] > q) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if (l == 0) {\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1];\\n }\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] MaximumBeauty(int[][] items, int[] queries) {\\n // 将物品按价格升序排序\\n Array.Sort(items, (a, b) => a[0].CompareTo(b[0]));\\n int n = items.Length;\\n // 按定义修改美丽值\\n for (int i = 1; i < n; ++i) {\\n items[i][1] = Math.Max(items[i][1], items[i - 1][1]);\\n }\\n // 二分查找处理查询\\n int[] res = new int[queries.Length];\\n for (int i = 0; i < queries.Length; i++) {\\n res[i] = Query(items, queries[i]);\\n }\\n return res;\\n }\\n\\n private int Query(int[][] items, int q) {\\n int l = 0, r = items.Length;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n if (items[mid][0] > q) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if (l == 0) {\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1];\\n }\\n }\\n}\\n
###Go
\\n\\nfunc maximumBeauty(items [][]int, queries []int) []int {\\n // 将物品按价格升序排序\\n sort.Slice(items, func(i, j int) bool {\\n return items[i][0] < items[j][0]\\n })\\n n := len(items)\\n // 按定义修改美丽值\\n for i := 1; i < n; i++ {\\n if items[i][1] < items[i-1][1] {\\n items[i][1] = items[i-1][1]\\n }\\n }\\n // 二分查找处理查询\\n res := make([]int, len(queries))\\n for i, q := range queries {\\n res[i] = query(items, q)\\n }\\n return res\\n}\\n\\nfunc query(items [][]int, q int) int {\\n l, r := 0, len(items)\\n for l < r {\\n mid := l + (r - l) / 2\\n if items[mid][0] > q {\\n r = mid\\n } else {\\n l = mid + 1\\n }\\n }\\n if l == 0 {\\n // 此时所有物品价格均大于查询价格\\n return 0\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1]\\n }\\n}\\n
###C
\\n\\nint compare(const void *a, const void *b) {\\n return (*(int**)a)[0] - (*(int**)b)[0];\\n}\\n\\nint query(int **items, int itemsSize, int q) {\\n int l = 0, r = itemsSize;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n if (items[mid][0] > q) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if (l == 0) {\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1];\\n }\\n}\\n\\nint* maximumBeauty(int** items, int itemsSize, int* itemsColSize, int* queries, int queriesSize, int* returnSize) {\\n // 将物品按价格升序排序\\n qsort(items, itemsSize, sizeof(items[0]), compare);\\n // 按定义修改美丽值\\n for (int i = 1; i < itemsSize; ++i) {\\n if (items[i][1] < items[i-1][1]) {\\n items[i][1] = items[i-1][1];\\n }\\n }\\n // 二分查找处理查询\\n int* res = (int*)malloc(queriesSize * sizeof(int));\\n for (int i = 0; i < queriesSize; i++) {\\n res[i] = query(items, itemsSize, queries[i]);\\n }\\n *returnSize = queriesSize;\\n return res;\\n}\\n
###JavaScript
\\n\\nvar maximumBeauty = function(items, queries) {\\n // 将物品按价格升序排序\\n items.sort((a, b) => a[0] - b[0]);\\n const n = items.length;\\n // 按定义修改美丽值\\n for (let i = 1; i < n; ++i) {\\n items[i][1] = Math.max(items[i][1], items[i - 1][1]);\\n }\\n // 二分查找处理查询\\n const res = [];\\n for (let q of queries) {\\n res.push(query(items, q, n));\\n }\\n return res;\\n};\\n\\nfunction query(items, q, n) {\\n let l = 0, r = n;\\n while (l < r) {\\n const mid = l + Math.floor((r - l) / 2);\\n if (items[mid][0] > q) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if (l === 0) {\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1];\\n }\\n}\\n
###TypeScript
\\n\\nfunction maximumBeauty(items: number[][], queries: number[]): number[] {\\n // 将物品按价格升序排序\\n items.sort((a, b) => a[0] - b[0]);\\n const n = items.length;\\n // 按定义修改美丽值\\n for (let i = 1; i < n; ++i) {\\n items[i][1] = Math.max(items[i][1], items[i - 1][1]);\\n }\\n // 二分查找处理查询\\n const res: number[] = [];\\n for (let q of queries) {\\n res.push(query(items, q, n));\\n }\\n return res;\\n};\\n\\nfunction query(items: number[][], q: number, n: number): number {\\n let l = 0, r = n;\\n while (l < r) {\\n const mid = l + Math.floor((r - l) / 2);\\n if (items[mid][0] > q) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if (l === 0) {\\n // 此时所有物品价格均大于查询价格\\n return 0;\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n return items[l - 1][1];\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn maximum_beauty(items: Vec<Vec<i32>>, queries: Vec<i32>) -> Vec<i32> {\\n let mut items = items.clone();\\n // 将物品按价格升序排序\\n items.sort_by(|a, b| a[0].cmp(&b[0]));\\n let n = items.len();\\n // 按定义修改美丽值\\n for i in 1..n {\\n items[i][1] = items[i][1].max(items[i - 1][1]);\\n }\\n // 二分查找处理查询\\n queries.into_iter().map(|q| Self::query(&items, q, n)).collect()\\n }\\n\\n fn query(items: &Vec<Vec<i32>>, q: i32, n: usize) -> i32 {\\n let mut l = 0;\\n let mut r = n;\\n while l < r {\\n let mid = l + (r - l) / 2;\\n if items[mid][0] > q {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n if l == 0 {\\n // 此时所有物品价格均大于查询价格\\n 0\\n } else {\\n // 返回小于等于查询价格的物品的最大美丽值\\n items[l - 1][1]\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:排序 + 二分查找 思路与算法\\n\\n我们假设物品总数为 $n$,对于单次查询,一种朴素的方法是遍历整个数组,寻找价格符合要求的物品并维护其最大美丽值,但这样的时间复杂度为 $O(n)$,假设查询总数为 $q$,则总时间复杂度为 $O(nq)$,这样的复杂度无法通过本题。因此我们需要优化单次查询的时间复杂度。\\n\\n我们可以将单次查询的过程分为两步:\\n\\n第一步,寻找到所有价格小于等于查询价格的物品;\\n\\n第二步,求出这些物品中的最大美丽值。\\n\\n对于第一步,我们可以将 $\\\\textit{items}$ 数组按照物品的价格升序排序,此时一定存在一个下标…","guid":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/mei-yi-ge-cha-xun-de-zui-da-mei-li-zhi-b-d8jw","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-14T13:01:07.649Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"你可以安排的最多任务数目","url":"https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign//solution/ni-ke-yi-an-pai-de-zui-duo-ren-wu-shu-mu-p7dm","content":"- \\n
\\n时间复杂度:$O(n\\\\log n + q\\\\log n)$,其中 $n$ 为 $\\\\textit{items}$ 的长度,$q$ 为 $\\\\textit{queries}$ 的长度。对 $\\\\textit{items}$ 数组按价格排序并更新美丽值的时间复杂度为 $O(n\\\\log n)$,单次二分查找查询最大美丽值的时间复杂度为 $O(\\\\log n)$,共需进行 $q$ 次查询。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$,即为排序的栈空间开销。
\\n方法一:二分查找 + 贪心选择工人
\\n提示 $1$
\\n如果我们已经知道「一定」可以完成 $k$ 个任务,那么:
\\n\\n
\\n- \\n
\\n我们可以在 $\\\\textit{tasks}$ 中选择 $k$ 个值最小的任务;
\\n- \\n
\\n我们可以在 $\\\\textit{workers}$ 中选择 $k$ 个值最大的工人。
\\n提示 $2$
\\n如果我们可以完成 $k$ 个任务,并且满足提示 $1$,那么一定可以完成 $k-1$ 个任务,并且可以选择 $k-1$ 个值最小的任务以及 $k-1$ 个值最大的工人,同样满足提示 $1$。
\\n思路与算法
\\n根据提示 $2$,我们就可以使用二分查找的方法找到 $k$ 的上界 $k\'$,使得我们可以完成 $k\'$ 个任务,但不能完成 $k\'+1$ 个任务。我们找到的 $k\'$ 即为答案。
\\n在二分查找的每一步中,当我们得到 $k$ 个值最小的任务以及 $k$ 个值最大的工人后,我们应该如何判断这些任务是否都可以完成呢?
\\n我们可以考虑值最大的那个任务,此时会出现两种情况:
\\n\\n
\\n- \\n
\\n如果有工人的值大于等于该任务的值,那么我们一定不需要使用药丸,并且一定让值最大的工人完成该任务。
\\n\\n\\n证明的思路为:由于我们考虑的是值最大的那个任务,因此所有能完成该任务的工人都能完成剩余的所有任务。因此如果一个值并非最大的工人(无论是否使用药丸)完成该任务,而值最大的工人完成了另一个任务,那么我们将这两个工人完成的任务交换,仍然是可行的。
\\n- \\n
\\n如果所有工人的值都小于该任务的值,那么我们必须使用药丸让一名工人完成任务,并且一定让值最小的工人完成该任务。
\\n\\n\\n这里的值最小指的是在使用药丸能完成任务的前提下,值最小的工人。
\\n\\n\\n证明的思路为:由于我们考虑的是值最大的那个任务,因此所有通过使用药丸能完成该任务的工人都能完成剩余的所有任务。如果一个值并非最小的工人使用药丸完成该任务,而值最小的工人(无论是否使用药丸)完成了另一个任务,那么我们将这两个工人完成的任务交换,仍然是可行的。
\\n因此,我们可以从大到小枚举每一个任务,并使用有序集合维护所有的工人。当枚举到任务的值为 $t$ 时:
\\n\\n
\\n- \\n
\\n如果有序集合中最大的元素大于等于 $t$,那么我们将最大的元素从有序集合中删除。
\\n- \\n
\\n如果有序集合中最大的元素小于 $t$,那么我们在有序集合中找出最小的大于等于 $t - \\\\textit{strength}$ 的元素并删除。
\\n对于这种情况,如果我们没有药丸剩余,或者有序集合中不存在大于等于 $t - \\\\textit{strength}$ 的元素,那么我们就无法完成所有任务。
\\n这样一来,我们就解决了二分查找后判断可行性的问题。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int maxTaskAssign(vector<int>& tasks, vector<int>& workers, int pills, int strength) {\\n int n = tasks.size(), m = workers.size();\\n sort(tasks.begin(), tasks.end());\\n sort(workers.begin(), workers.end());\\n \\n auto check = [&](int mid) -> bool {\\n int p = pills;\\n // 工人的有序集合\\n multiset<int> ws;\\n for (int i = m - mid; i < m; ++i) {\\n ws.insert(workers[i]);\\n }\\n // 从大到小枚举每一个任务\\n for (int i = mid - 1; i >= 0; --i) {\\n // 如果有序集合中最大的元素大于等于 tasks[i]\\n if (auto it = prev(ws.end()); *it >= tasks[i]) {\\n ws.erase(it);\\n }\\n else {\\n if (!p) {\\n return false;\\n }\\n auto rep = ws.lower_bound(tasks[i] - strength);\\n if (rep == ws.end()) {\\n return false;\\n }\\n --p;\\n ws.erase(rep);\\n }\\n }\\n return true;\\n };\\n \\n int left = 1, right = min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid)) {\\n ans = mid;\\n left = mid + 1;\\n }\\n else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nfrom sortedcontainers import SortedList\\n\\nclass Solution:\\n def maxTaskAssign(self, tasks: List[int], workers: List[int], pills: int, strength: int) -> int:\\n n, m = len(tasks), len(workers)\\n tasks.sort()\\n workers.sort()\\n\\n def check(mid: int) -> bool:\\n p = pills\\n # 工人的有序集合\\n ws = SortedList(workers[m - mid:])\\n # 从大到小枚举每一个任务\\n for i in range(mid - 1, -1, -1):\\n # 如果有序集合中最大的元素大于等于 tasks[i]\\n if ws[-1] >= tasks[i]:\\n ws.pop()\\n else:\\n if p == 0:\\n return False\\n rep = ws.bisect_left(tasks[i] - strength)\\n if rep == len(ws):\\n return False\\n p -= 1\\n ws.pop(rep)\\n return True\\n\\n left, right, ans = 1, min(m, n), 0\\n while left <= right:\\n mid = (left + right) // 2\\n if check(mid):\\n ans = mid\\n left = mid + 1\\n else:\\n right = mid - 1\\n \\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {\\n Arrays.sort(tasks);\\n Arrays.sort(workers);\\n int n = tasks.length, m = workers.length;\\n int left = 1, right = Math.min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(tasks, workers, pills, strength, mid)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n\\n private boolean check(int[] tasks, int[] workers, int pills, int strength, int mid) {\\n int p = pills;\\n TreeMap<Integer, Integer> ws = new TreeMap<>();\\n for (int i = workers.length - mid; i < workers.length; ++i) {\\n ws.put(workers[i], ws.getOrDefault(workers[i], 0) + 1);\\n }\\n for (int i = mid - 1; i >= 0; --i) {\\n Integer key = ws.lastKey();\\n if (key >= tasks[i]) {\\n ws.put(key, ws.get(key) - 1);\\n if (ws.get(key) == 0) {\\n ws.remove(key);\\n }\\n } else {\\n if (p == 0) {\\n return false;\\n }\\n key = ws.ceilingKey(tasks[i] - strength);\\n if (key == null) {\\n return false;\\n }\\n ws.put(key, ws.get(key) - 1);\\n if (ws.get(key) == 0) {\\n ws.remove(key);\\n }\\n --p;\\n }\\n }\\n return true;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {\\n Array.Sort(tasks);\\n Array.Sort(workers);\\n int n = tasks.Length, m = workers.Length;\\n int left = 1, right = Math.Min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (Check(tasks, workers, pills, strength, mid)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n\\n private bool Check(int[] tasks, int[] workers, int pills, int strength, int mid) {\\n int p = pills;\\n var ws = new SortedDictionary<int, int>();\\n for (int i = workers.Length - mid; i < workers.Length; ++i) {\\n if (ws.ContainsKey(workers[i])) {\\n ws[workers[i]]++;\\n } else {\\n ws[workers[i]] = 1;\\n }\\n }\\n for (int i = mid - 1; i >= 0; --i) {\\n var lastKey = ws.Keys.Max();\\n if (lastKey >= tasks[i]) {\\n ws[lastKey]--;\\n if (ws[lastKey] == 0) {\\n ws.Remove(lastKey);\\n }\\n } else {\\n if (p == 0) {\\n return false;\\n }\\n var key = ws.Keys.Where(k => k >= tasks[i] - strength).DefaultIfEmpty(-1).First();\\n if (key == -1) {\\n return false;\\n }\\n ws[key]--;\\n if (ws[key] == 0) {\\n ws.Remove(key);\\n }\\n --p;\\n }\\n }\\n return true;\\n }\\n}\\n
###Rust
\\n\\nuse std::collections::BTreeMap;\\n\\nimpl Solution {\\n pub fn max_task_assign(tasks: Vec<i32>, workers: Vec<i32>, pills: i32, strength: i32) -> i32 {\\n let mut tasks = tasks;\\n let mut workers = workers;\\n tasks.sort();\\n workers.sort();\\n let n = tasks.len();\\n let m = workers.len();\\n let (mut left, mut right, mut ans) = (1, m.min(n), 0);\\n\\n while left <= right {\\n let mid = (left + right) / 2;\\n if Self::check(&tasks, &workers, pills, strength, mid) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n ans as i32\\n }\\n\\n fn check(tasks: &[i32], workers: &[i32], pills: i32, strength: i32, mid: usize) -> bool {\\n let mut p = pills;\\n let mut ws = BTreeMap::new();\\n for &w in workers.iter().skip(workers.len() - mid) {\\n *ws.entry(w).or_insert(0) += 1;\\n }\\n for &t in tasks.iter().take(mid).rev() {\\n if let Some((&max_key, _)) = ws.iter().next_back() {\\n if max_key >= t {\\n *ws.get_mut(&max_key).unwrap() -= 1;\\n if ws[&max_key] == 0 {\\n ws.remove(&max_key);\\n }\\n } else {\\n if p == 0 {\\n return false;\\n }\\n if let Some((&key, _)) = ws.range(t - strength..).next() {\\n *ws.get_mut(&key).unwrap() -= 1;\\n if ws[&key] == 0 {\\n ws.remove(&key);\\n }\\n p -= 1;\\n } else {\\n return false;\\n }\\n }\\n }\\n }\\n true\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n \\\\log n + m \\\\log m + \\\\min(m, n) \\\\log^2 \\\\min(m, n))$。
\\n\\n
\\n- \\n
\\n对数组 $\\\\textit{tasks}$ 排序需要 $O(n \\\\log n)$ 的时间;
\\n- \\n
\\n对数组 $\\\\textit{workers}$ 排序需要 $O(m \\\\log m)$ 的时间;
\\n- \\n
\\n二分查找的下界为 $1$,上界为 $m$ 和 $n$ 中的较小值,因此二分查找的次数为 $\\\\log \\\\min(m, n)$。每一次查找需要枚举 $\\\\min(m, n)$ 个任务,并且枚举的过程中需要对工人的有序集合进行删除操作,单次操作时间复杂度为 $\\\\log \\\\min(m, n)$。因此二分查找的总时间复杂度为 $O(\\\\min(m, n) \\\\log^2 \\\\min(m, n))$。
\\n- \\n
\\n空间复杂度:$O(\\\\log n + \\\\log m + \\\\min(m, n))$。
\\n\\n
\\n- \\n
\\n对数组 $\\\\textit{tasks}$ 排序需要 $O(\\\\log n)$ 的栈空间;
\\n- \\n
\\n对数组 $\\\\textit{workers}$ 排序需要 $O(\\\\log m)$ 的栈空间;
\\n- \\n
\\n二分查找中使用的有序集合需要 $O(\\\\min(m, n))$ 的空间。
\\n扩展
\\n可以发现,当我们从大到小枚举每一个任务时,如果我们维护了(在使用药丸的情况下)所有可以完成任务的工人,那么:
\\n\\n
\\n- \\n
\\n如果有工人可以不使用药丸完成该任务,那么我们选择(删除)值最大的工人;
\\n- \\n
\\n如果所有工人都需要使用药丸才能完成该任务,那么我们选择(删除)值最小的工人。
\\n而随着任务值的减少,可以完成任务的工人只增不减,因此我们可以使用一个「双端队列」来维护所有可以(在使用药丸的情况下)所有可以完成任务的工人,此时要么队首的工人被选择(删除),要么队尾的工人被选择(删除),那么单次删除操作的时间复杂度由 $O(\\\\log \\\\min (m, n))$ 降低为 $O(1)$,总时间复杂度降低为:
\\n$$
\\n
\\nO(n \\\\log n + m \\\\log m + \\\\min(m, n) \\\\log \\\\min(m, n)) = O(n \\\\log n + m \\\\log m)
\\n$$###C++
\\n\\nclass Solution {\\npublic:\\n int maxTaskAssign(vector<int>& tasks, vector<int>& workers, int pills, int strength) {\\n int n = tasks.size(), m = workers.size();\\n sort(tasks.begin(), tasks.end());\\n sort(workers.begin(), workers.end());\\n \\n auto check = [&](int mid) -> bool {\\n int p = pills;\\n deque<int> ws;\\n int ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for (int i = mid - 1; i >= 0; --i) {\\n while (ptr >= m - mid && workers[ptr] + strength >= tasks[i]) {\\n ws.push_front(workers[ptr]);\\n --ptr;\\n }\\n if (ws.empty()) {\\n return false;\\n }\\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n else if (ws.back() >= tasks[i]) {\\n ws.pop_back();\\n }\\n else {\\n if (!p) {\\n return false;\\n }\\n --p;\\n ws.pop_front();\\n }\\n }\\n return true;\\n };\\n \\n int left = 1, right = min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid)) {\\n ans = mid;\\n left = mid + 1;\\n }\\n else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nfrom sortedcontainers import SortedList\\n\\nclass Solution:\\n def maxTaskAssign(self, tasks: List[int], workers: List[int], pills: int, strength: int) -> int:\\n n, m = len(tasks), len(workers)\\n tasks.sort()\\n workers.sort()\\n\\n def check(mid: int) -> bool:\\n p = pills\\n ws = deque()\\n ptr = m - 1\\n # 从大到小枚举每一个任务\\n for i in range(mid - 1, -1, -1):\\n while ptr >= m - mid and workers[ptr] + strength >= tasks[i]:\\n ws.appendleft(workers[ptr])\\n ptr -= 1\\n if not ws:\\n return False\\n # 如果双端队列中最大的元素大于等于 tasks[i]\\n elif ws[-1] >= tasks[i]:\\n ws.pop()\\n else:\\n if p == 0:\\n return False\\n p -= 1\\n ws.popleft()\\n return True\\n\\n left, right, ans = 1, min(m, n), 0\\n while left <= right:\\n mid = (left + right) // 2\\n if check(mid):\\n ans = mid\\n left = mid + 1\\n else:\\n right = mid - 1\\n \\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int maxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {\\n int n = tasks.length, m = workers.length;\\n Arrays.sort(tasks);\\n Arrays.sort(workers);\\n int left = 1, right = Math.min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(tasks, workers, pills, strength, mid)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n\\n // 检查是否可以在mid个任务中使用pills和strength\\n private boolean check(int[] tasks, int[] workers, int pills, int strength, int mid) {\\n int p = pills;\\n int m = workers.length;\\n Deque<Integer> ws = new ArrayDeque<>();\\n int ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for (int i = mid - 1; i >= 0; --i) {\\n while (ptr >= m - mid && workers[ptr] + strength >= tasks[i]) {\\n ws.addFirst(workers[ptr]);\\n --ptr;\\n }\\n if (ws.isEmpty()) {\\n return false;\\n } else if (ws.getLast() >= tasks[i]) { \\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n ws.pollLast();\\n } else {\\n if (p == 0) {\\n return false;\\n }\\n --p;\\n ws.pollFirst();\\n }\\n }\\n return true;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxTaskAssign(int[] tasks, int[] workers, int pills, int strength) {\\n int n = tasks.Length, m = workers.Length;\\n Array.Sort(tasks);\\n Array.Sort(workers);\\n \\n Func<int, bool> check = mid => {\\n int p = pills;\\n var ws = new LinkedList<int>();\\n int ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for (int i = mid - 1; i >= 0; --i) {\\n while (ptr >= m - mid && workers[ptr] + strength >= tasks[i]) {\\n ws.AddFirst(workers[ptr]);\\n --ptr;\\n }\\n if (ws.Count == 0) {\\n return false;\\n } else if (ws.Last.Value >= tasks[i]) {\\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n ws.RemoveLast();\\n } else {\\n if (p == 0) {\\n return false;\\n }\\n --p;\\n ws.RemoveFirst();\\n }\\n }\\n return true;\\n };\\n \\n int left = 1, right = Math.Min(m, n), ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc maxTaskAssign(tasks []int, workers []int, pills int, strength int) int {\\nn, m := len(tasks), len(workers)\\nsort.Ints(tasks)\\nsort.Ints(workers)\\n\\ncheck := func(mid int) bool {\\np := pills\\nws := list.New() // 使用双端队列\\nptr := m - 1\\n// 从大到小枚举每一个任务\\nfor i := mid - 1; i >= 0; i-- {\\nfor ptr >= m-mid && workers[ptr]+strength >= tasks[i] {\\nws.PushFront(workers[ptr]) // 添加到队头\\nptr--\\n}\\nif ws.Len() == 0 {\\nreturn false\\n}\\n// 如果双端队列中最大的元素大于等于 tasks[i]\\nif ws.Back().Value.(int) >= tasks[i] {\\nws.Remove(ws.Back()) // 移除队尾\\n} else {\\nif p == 0 {\\nreturn false\\n}\\np--\\nws.Remove(ws.Front()) // 移除队头\\n}\\n}\\nreturn true\\n}\\n\\nleft, right, ans := 1, min(m, n), 0\\nfor left <= right {\\nmid := (left + right) / 2\\nif check(mid) {\\nans = mid\\nleft = mid + 1\\n} else {\\nright = mid - 1\\n}\\n}\\nreturn ans\\n}\\n
###C
\\n\\nbool check(int* tasks, int* workers, int workersSize, int pills, int strength, int mid) {\\n int m = workersSize;\\n int p = pills;\\n int ws[m];\\n int ptr = m - 1;\\n int head = m - 1, tail = m - 1;\\n // 从大到小枚举每一个任务\\n for (int i = mid - 1; i >= 0; --i) {\\n while (ptr >= workersSize - mid && workers[ptr] + strength >= tasks[i]) {\\n ws[head] = workers[ptr];\\n --head;\\n --ptr;\\n }\\n if (head == tail) {\\n return false;\\n } else if (ws[tail] >= tasks[i]) { // 如果双端队列中最大的元素大于等于 tasks[i]\\n tail--;\\n } else {\\n if (!p) {\\n return false;\\n }\\n --p;\\n head++;\\n }\\n }\\n\\n return true;\\n}\\n\\nint compare(const void *a, const void *b) {\\n return *(int *)a - *(int *)b;\\n}\\n\\nint maxTaskAssign(int* tasks, int tasksSize, int* workers, int workersSize, int pills, int strength) {\\n int n = tasksSize, m = workersSize;\\n qsort(tasks, n, sizeof(int), compare);\\n qsort(workers, m, sizeof(int), compare);\\n\\n int left = 1, right = (m < n) ? m : n, ans = 0;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(tasks, workers, workersSize, pills, strength, mid)) {\\n ans = mid;\\n left = mid + 1;\\n }\\n else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar maxTaskAssign = function(tasks, workers, pills, strength) {\\n let n = tasks.length, m = workers.length;\\n tasks.sort((a, b) => a - b);\\n workers.sort((a, b) => a - b);\\n\\n const check = (mid) => {\\n let p = pills;\\n let ws = new Deque();\\n let ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for (let i = mid - 1; i >= 0; --i) {\\n while (ptr >= m - mid && workers[ptr] + strength >= tasks[i]) {\\n ws.pushFront(workers[ptr]);\\n --ptr;\\n }\\n if (ws.isEmpty()) {\\n return false;\\n }\\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n else if (ws.back() >= tasks[i]) {\\n ws.popBack();\\n }\\n else {\\n if (!p) {\\n return false;\\n }\\n --p;\\n ws.popFront();\\n }\\n }\\n return true;\\n }\\n\\n let left = 1, right = Math.min(m, n), ans = 0;\\n while (left <= right) {\\n let mid = Math.floor((left + right) / 2);\\n if (check(mid)) {\\n ans = mid;\\n left = mid + 1;\\n }\\n else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction maxTaskAssign(tasks: number[], workers: number[], pills: number, strength: number): number {\\n let n = tasks.length, m = workers.length;\\n tasks.sort((a, b) => a - b);\\n workers.sort((a, b) => a - b);\\n const check = (mid: number): boolean => {\\n let p = pills;\\n let ws = new Deque();\\n let ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for (let i = mid - 1; i >= 0; --i) {\\n while (ptr >= m - mid && workers[ptr] + strength >= tasks[i]) {\\n ws.pushFront(workers[ptr]);\\n --ptr;\\n }\\n if (ws.isEmpty()) {\\n return false;\\n }\\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n else if (ws.back() >= tasks[i]) {\\n ws.popBack();\\n }\\n else {\\n if (!p) {\\n return false;\\n }\\n --p;\\n ws.popFront();\\n }\\n }\\n return true;\\n }\\n\\n let left = 1, right = Math.min(m, n), ans = 0;\\n while (left <= right) {\\n let mid = Math.floor((left + right) / 2);\\n if (check(mid)) {\\n ans = mid;\\n left = mid + 1;\\n }\\n else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n}\\n\\n
###Rust
\\n\\n","description":"方法一:二分查找 + 贪心选择工人 提示 $1$\\n\\n如果我们已经知道「一定」可以完成 $k$ 个任务,那么:\\n\\n我们可以在 $\\\\textit{tasks}$ 中选择 $k$ 个值最小的任务;\\n\\n我们可以在 $\\\\textit{workers}$ 中选择 $k$ 个值最大的工人。\\n\\n提示 $2$\\n\\n如果我们可以完成 $k$ 个任务,并且满足提示 $1$,那么一定可以完成 $k-1$ 个任务,并且可以选择 $k-1$ 个值最小的任务以及 $k-1$ 个值最大的工人,同样满足提示 $1$。\\n\\n思路与算法\\n\\n根据提示 $2$,我们就可以使用二分查找的方法找到 $k$…","guid":"https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign//solution/ni-ke-yi-an-pai-de-zui-duo-ren-wu-shu-mu-p7dm","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-14T12:55:54.528Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Go/Python] 转换求和","url":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/gopython-zhuan-huan-qiu-he-by-himymben-klma","content":"use std::collections::VecDeque;\\nuse std::cmp::{max, min};\\n\\nimpl Solution {\\n pub fn max_task_assign(tasks: Vec<i32>, workers: Vec<i32>, pills: i32, strength: i32) -> i32 {\\n let n = tasks.len();\\n let m = workers.len();\\n let mut tasks = tasks;\\n let mut workers = workers;\\n tasks.sort();\\n workers.sort();\\n \\n let check = |mid: usize| -> bool {\\n let mut p = pills;\\n let mut ws = VecDeque::new();\\n let mut ptr = m - 1;\\n // 从大到小枚举每一个任务\\n for i in (0..mid).rev() {\\n while ptr as i32 >= (m - mid) as i32 && workers[ptr] + strength >= tasks[i] {\\n ws.push_front(workers[ptr]);\\n ptr -= 1;\\n }\\n if ws.is_empty() {\\n return false;\\n }\\n // 如果双端队列中最大的元素大于等于 tasks[i]\\n if *ws.back().unwrap() >= tasks[i] {\\n ws.pop_back();\\n } else {\\n if p == 0 {\\n return false;\\n }\\n p -= 1;\\n ws.pop_front();\\n }\\n }\\n true\\n };\\n \\n let mut left = 1;\\n let mut right = min(m, n);\\n let mut ans = 0;\\n while left <= right {\\n let mid = (left + right) / 2;\\n if check(mid) {\\n ans = mid as i32;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n ans\\n }\\n}\\n
解题思路
\\n前面的影响以k的值为下限,
\\n
\\n后面的影响以k的值-1为下限。代码
\\n###Python3
\\n\\nclass Solution:\\n def timeRequiredToBuy(self, tickets: List[int], k: int) -> int:\\n return sum(min(t, tickets[k]) if i <= k else min(t, tickets[k]-1) for i,t in enumerate(tickets))\\n
###golang
\\n\\n","description":"解题思路 前面的影响以k的值为下限,\\n 后面的影响以k的值-1为下限。\\n\\n代码\\n\\n###Python3\\n\\nclass Solution:\\n def timeRequiredToBuy(self, tickets: List[int], k: int) -> int:\\n return sum(min(t, tickets[k]) if i <= k else min(t, tickets[k]-1) for i,t in enumerate(tickets))\\n\\n\\n###golang\\n\\nfunc timeRequiredToBuy(tickets…","guid":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/gopython-zhuan-huan-qiu-he-by-himymben-klma","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-14T04:07:47.588Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n) 一次遍历,简洁写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/on-yi-ci-bian-li-by-endlesscheng-thmm","content":"func timeRequiredToBuy(tickets []int, k int) int {\\n ans := 0\\n for i, v := range tickets {\\n if i <= k {\\n if v < tickets[k]{\\n ans += v\\n }else {\\n ans += tickets[k]\\n }\\n } else {\\n if v < tickets[k] - 1{\\n ans += v\\n } else {\\n ans += tickets[k] - 1\\n }\\n }\\n }\\n return ans\\n}\\n
想一想,当第 $k$ 个人完成买票的那一刻,在他前后的人,分别买了多少票?
\\n假设第 $k$ 个人此时买了 $3$ 张票,那么排在他前面的人,此时也至多买了 $3$ 张票;排在他后面的人,此时至多买了 $2$ 张票。
\\n把 $\\\\textit{tickets}$ 简记为 $t$。一般地,当第 $k$ 个人买了 $t_k$ 张票时:
\\n\\n
\\n- 排在他前面的人,买的票不会超过 $t_k$,即 $\\\\min(t_i, t_k)$。
\\n- 排在他后面的人,买的票不会超过 $t_k-1$,即 $\\\\min(t_i, t_k-1)$。
\\n累加所有购票数即为答案。
\\n\\nclass Solution:\\n def timeRequiredToBuy(self, tickets: List[int], k: int) -> int:\\n tk = tickets[k]\\n return sum(min(t, tk - (i > k)) for i, t in enumerate(tickets))\\n
\\nclass Solution {\\n public int timeRequiredToBuy(int[] tickets, int k) {\\n int ans = 0;\\n int tk = tickets[k];\\n for (int i = 0; i < tickets.length; i++) {\\n ans += Math.min(tickets[i], (i <= k ? tk : tk - 1));\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int timeRequiredToBuy(vector<int>& tickets, int k) {\\n int ans = 0, tk = tickets[k];\\n for (int i = 0; i < tickets.size(); i++) {\\n ans += min(tickets[i], tk - (i > k));\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((a) < (b) ? (a) : (b))\\n\\nint timeRequiredToBuy(int* tickets, int ticketsSize, int k){\\n int ans = 0;\\n int tk = tickets[k];\\n for (int i = 0; i < ticketsSize; i++) {\\n ans += MIN(tickets[i], tk - (i > k));\\n }\\n return ans;\\n}\\n
\\nfunc timeRequiredToBuy(tickets []int, k int) (ans int) {\\n tk := tickets[k]\\n for i, t := range tickets {\\n if i <= k {\\n ans += min(t, tk)\\n } else {\\n ans += min(t, tk-1)\\n }\\n }\\n return\\n}\\n
\\nvar timeRequiredToBuy = function(tickets, k) {\\n const tk = tickets[k];\\n let ans = 0;\\n for (let i = 0; i < tickets.length; i++) {\\n ans += Math.min(tickets[i], (i <= k ? tk : tk - 1));\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn time_required_to_buy(tickets: Vec<i32>, k: i32) -> i32 {\\n let k = k as usize;\\n let tk = tickets[k];\\n tickets.iter()\\n .enumerate()\\n .map(|(i, &t)| t.min(if i <= k { tk } else { tk - 1 }))\\n .sum()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{tickets}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n思考题
\\n\\n
\\n- 输入一个正整数 $q$,返回第 $q$ 秒谁在买票。你需要返回这个人的编号,一个 $0$ 到 $n-1$ 中的数。保证此时队列中还有人。
\\n- 输入一个询问数组 $\\\\textit{queries}$,对于第 $i$ 个询问,计算第 $\\\\textit{queries}[i]$ 秒谁在买票。保证此时队列中还有人。
\\n你需要做到时间复杂度与 $\\\\textit{tickets}[i]$ 的值域无关。$\\\\textit{tickets}$ 和 $\\\\textit{queries}$ 的长度不超过 $10^5$。
\\n欢迎在评论区分享你的思路/代码。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"想一想,当第 $k$ 个人完成买票的那一刻,在他前后的人,分别买了多少票? 假设第 $k$ 个人此时买了 $3$ 张票,那么排在他前面的人,此时也至多买了 $3$ 张票;排在他后面的人,此时至多买了 $2$ 张票。\\n\\n把 $\\\\textit{tickets}$ 简记为 $t$。一般地,当第 $k$ 个人买了 $t_k$ 张票时:\\n\\n排在他前面的人,买的票不会超过 $t_k$,即 $\\\\min(t_i, t_k)$。\\n排在他后面的人,买的票不会超过 $t_k-1$,即 $\\\\min(t_i, t_k-1)$。\\n\\n累加所有购票数即为答案。\\n\\nclass…","guid":"https://leetcode.cn/problems/time-needed-to-buy-tickets//solution/on-yi-ci-bian-li-by-endlesscheng-thmm","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-14T04:05:46.900Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:离线双指针/在线二分(Python/Java/C++/Go/JS/Rust)","url":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/jiang-xun-wen-chi-xian-pai-xu-by-endless-o5j0","content":"方法一:离线算法 + 双指针
\\n\\n\\n离线算法:把 $\\\\textit{queries}$ 排序,通过改变回答询问的顺序,使问题更容易处理。
\\n在线算法:按照 $\\\\textit{queries}$ 的顺序一个一个地回答询问。
\\n暴力的做法是,对于每个询问 $q=\\\\textit{queries}[i]$,遍历 $\\\\textit{items}$,计算其中 $\\\\textit{price}\\\\le q$ 的最大 $\\\\textit{beauty}$。
\\n如何优化?
\\n假如 $\\\\textit{queries}$ 已经按照从小到大的顺序排好了,例如示例 1 $\\\\textit{queries}=[1,2,3,4,5,6]$。
\\n\\n
\\n- 首先找所有 $\\\\textit{price}\\\\le \\\\textit{queries}[0]=1$ 的物品,求得其中最大 $\\\\textit{beauty}$ 为 $2$。
\\n- 然后找所有 $\\\\textit{price}\\\\le \\\\textit{queries}[1]=2$ 的物品,由于我们已经知道 $\\\\textit{price}\\\\le 1$ 的物品的最大 $\\\\textit{beauty}$ 为 $2$。所以只需要求出 $\\\\textit{price}$ 大于 $1$ 且小于等于 $2$ 的物品中的最大 $\\\\textit{beauty}$,即 $4$,然后计算 $\\\\max(4,2)=4$,即为所有 $\\\\textit{price}\\\\le 2$ 的物品中的最大 $\\\\textit{beauty}$。
\\n- 继续,找所有 $\\\\textit{price}\\\\le \\\\textit{queries}[2]=3$ 的物品,由于我们已经知道 $\\\\textit{price}\\\\le 2$ 的物品的最大 $\\\\textit{beauty}$ 为 $4$。所以只需要求出 $\\\\textit{price}$ 大于 $2$ 且小于等于 $3$ 的物品中的最大 $\\\\textit{beauty}$,即 $5$,然后计算 $\\\\max(5,4)=5$,即为所有 $\\\\textit{price}\\\\le 3$ 的物品中的最大 $\\\\textit{beauty}$。
\\n- 依此类推,我们只需要「增量」地计算所有满足 $\\\\textit{queries}[i-1] < \\\\textit{price}\\\\le \\\\textit{queries}[i]$ 的物品中的最大 $\\\\textit{beauty}$,然后和上一次计算出的最大 $\\\\textit{beauty}$ 取最大值,即为所有 $\\\\textit{price}\\\\le \\\\textit{queries}[i]$ 的物品中的最大 $\\\\textit{beauty}$。
\\n为此,需要做两件事情:
\\n\\n
\\n- 把询问从小到大排序。但由于 $\\\\textit{answer}$ 需要按照输入的顺序回答,可以额外创建一个下标数组,对下标数组排序。
\\n- 把物品按价格从小到大排序,这样就可以用双指针「增量」地遍历满足 $\\\\textit{queries}[i-1] < \\\\textit{price}\\\\le \\\\textit{queries}[i]$ 的物品。
\\n\\nclass Solution:\\n def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:\\n items.sort(key=lambda item: item[0])\\n idx = sorted(range(len(queries)), key=lambda i: queries[i])\\n\\n ans = [0] * len(queries)\\n max_beauty = j = 0\\n for i in idx:\\n q = queries[i]\\n # 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n while j < len(items) and items[j][0] <= q:\\n max_beauty = max(max_beauty, items[j][1])\\n j += 1\\n ans[i] = max_beauty\\n return ans\\n
\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n Arrays.sort(items, (a, b) -> a[0] - b[0]);\\n Integer[] idx = new Integer[queries.length];\\n Arrays.setAll(idx, i -> i);\\n Arrays.sort(idx, (i, j) -> queries[i] - queries[j]);\\n\\n int[] ans = new int[queries.length];\\n int maxBeauty = 0;\\n int j = 0;\\n for (int i : idx) {\\n int q = queries[i];\\n // 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n while (j < items.length && items[j][0] <= q) {\\n maxBeauty = Math.max(maxBeauty, items[j][1]);\\n j++;\\n }\\n ans[i] = maxBeauty;\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {\\n ranges::sort(items, {}, [](auto& item) { return item[0]; });\\n vector<int> idx(queries.size());\\n iota(idx.begin(), idx.end(), 0);\\n ranges::sort(idx, {}, [&](int i) { return queries[i]; });\\n\\n vector<int> ans(queries.size());\\n int max_beauty = 0, j = 0;\\n for (int i : idx) {\\n int q = queries[i];\\n // 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n while (j < items.size() && items[j][0] <= q) {\\n max_beauty = max(max_beauty, items[j][1]);\\n j++;\\n }\\n ans[i] = max_beauty;\\n }\\n return ans;\\n }\\n};\\n
\\nfunc maximumBeauty(items [][]int, queries []int) []int {\\n slices.SortFunc(items, func(a, b []int) int { return a[0] - b[0] })\\n idx := make([]int, len(queries))\\n for i := range queries {\\n idx[i] = i\\n }\\n slices.SortFunc(idx, func(i, j int) int { return queries[i] - queries[j] })\\n\\n ans := make([]int, len(queries))\\n maxBeauty, j := 0, 0\\n for _, i := range idx {\\n q := queries[i]\\n // 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n for j < len(items) && items[j][0] <= q {\\n maxBeauty = max(maxBeauty, items[j][1])\\n j++\\n }\\n ans[i] = maxBeauty\\n }\\n return ans\\n}\\n
\\nvar maximumBeauty = function(items, queries) {\\n items.sort((a, b) => a[0] - b[0]);\\n const idx = queries.map((_, i) => i).sort((i, j) => queries[i] - queries[j]);\\n\\n const ans = Array(queries.length);\\n let maxBeauty = 0, j = 0;\\n for (const i of idx) {\\n const q = queries[i];\\n // 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n while (j < items.length && items[j][0] <= q) {\\n maxBeauty = Math.max(maxBeauty, items[j][1]);\\n j++;\\n }\\n ans[i] = maxBeauty;\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn maximum_beauty(mut items: Vec<Vec<i32>>, queries: Vec<i32>) -> Vec<i32> {\\n items.sort_unstable_by_key(|item| item[0]);\\n let mut idx = (0..queries.len()).collect::<Vec<_>>();\\n idx.sort_unstable_by_key(|&i| queries[i]);\\n\\n let mut ans = vec![0; queries.len()];\\n let mut max_beauty = 0;\\n let mut j = 0;\\n for i in idx {\\n let q = queries[i];\\n // 增量地遍历满足 queries[i-1] < price <= queries[i] 的物品\\n while j < items.len() && items[j][0] <= q {\\n max_beauty = max_beauty.max(items[j][1]);\\n j += 1;\\n }\\n ans[i] = max_beauty;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log n +m\\\\log m)$,其中 $n$ 是 $\\\\textit{items}$ 的长度,$m$ 是 $\\\\textit{queries}$ 的长度。瓶颈在排序上。
\\n- 空间复杂度:$\\\\mathcal{O}(m)$。
\\n方法二:在线算法 + 二分查找
\\n写法一:前缀最大值
\\n示例 1 的 $\\\\textit{items} = [[1,2],[3,2],[2,4],[5,6],[3,5]]$,将其按照 $\\\\textit{price}$ 从小到大排序,得
\\n$$
\\n
\\n[[1,2],[2,4],[3,2],[3,5],[5,6]]
\\n$$然后原地计算其 $\\\\textit{beauty}$ 的前缀最大值,得
\\n$$
\\n
\\n[[1,2],[2,4],[3,\\\\underline{4}],[3,5],[5,6]]
\\n$$注意其中 $[3,2]$ 变成了 $[3,4]$,这里的 $4$ 就是前三个物品的最大 $\\\\textit{beauty}$,即 $\\\\max(2,4,2)=4$。
\\n算好前缀最大值后,所有 $\\\\textit{price}\\\\le q$ 的物品的最大 $\\\\textit{beauty}$,就保存在满足 $\\\\textit{price}\\\\le q$ 的最右边的那个物品中!
\\n根据 二分查找 红蓝染色法【基础算法精讲 04】中的技巧,我们可以二分 $\\\\textit{price}>q$ 的第一个物品,它左边相邻物品就是 $\\\\textit{price}\\\\le q$ 的最后一个物品。(如果左边没有物品那么答案为 $0$)
\\n\\nclass Solution:\\n def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:\\n items.sort(key=lambda item: item[0])\\n for i in range(1, len(items)):\\n # 原地计算 beauty 的前缀最大值\\n items[i][1] = max(items[i][1], items[i - 1][1])\\n\\n for i, q in enumerate(queries):\\n j = bisect_right(items, q, key=lambda item: item[0])\\n queries[i] = items[j - 1][1] if j else 0\\n return queries\\n
\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n Arrays.sort(items, (a, b) -> a[0] - b[0]);\\n for (int i = 1; i < items.length; i++) {\\n // 原地计算 beauty 的前缀最大值\\n items[i][1] = Math.max(items[i][1], items[i - 1][1]);\\n }\\n\\n for (int i = 0; i < queries.length; i++) {\\n int j = upperBound(items, queries[i]);\\n queries[i] = j > 0 ? items[j - 1][1] : 0;\\n }\\n return queries;\\n }\\n\\n // https://www.bilibili.com/video/BV1AP41137w7/\\n // 返回 items 中第一个 price 大于 target 的物品的下标(注意是大于,不是大于等于)\\n // 如果这样的数不存在,则返回 items.length\\n // 时间复杂度 O(log items.length)\\n // 采用开区间写法实现\\n private int upperBound(int[][] items, int target) {\\n int left = -1;\\n int right = items.length; // 开区间 (left, right)\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // items[left][0] <= target\\n // items[right][0] > target\\n int mid = (left + right) >>> 1;\\n if (items[mid][0] > target) {\\n right = mid; // 范围缩小到 (left, mid)\\n } else {\\n left = mid; // 范围缩小到 (mid, right)\\n }\\n }\\n return right;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {\\n ranges::sort(items, {}, [](auto& item) { return item[0]; });\\n for (int i = 1; i < items.size(); i++) {\\n // 原地计算 beauty 的前缀最大值\\n items[i][1] = max(items[i][1], items[i - 1][1]);\\n }\\n\\n for (int& q : queries) {\\n int j = ranges::upper_bound(items, q, {}, [](auto& item) { return item[0]; }) - items.begin();\\n q = j ? items[j - 1][1] : 0;\\n }\\n return queries;\\n }\\n};\\n
\\nfunc maximumBeauty(items [][]int, queries []int) []int {\\n slices.SortFunc(items, func(a, b []int) int { return a[0] - b[0] })\\n for i := 1; i < len(items); i++ {\\n // 原地计算 beauty 的前缀最大值\\n items[i][1] = max(items[i][1], items[i-1][1])\\n }\\n\\n for i, q := range queries {\\n j := sort.Search(len(items), func(i int) bool { return items[i][0] > q })\\n if j > 0 {\\n queries[i] = items[j-1][1]\\n } else {\\n queries[i] = 0\\n }\\n }\\n return queries\\n}\\n
\\nvar maximumBeauty = function(items, queries) {\\n items.sort((a, b) => a[0] - b[0]);\\n for (let i = 1; i < items.length; i++) {\\n // 原地计算 beauty 的前缀最大值\\n items[i][1] = Math.max(items[i][1], items[i - 1][1]);\\n }\\n\\n for (let i = 0; i < queries.length; i++) {\\n const j = upperBound(items, queries[i]);\\n queries[i] = j > 0 ? items[j - 1][1] : 0;\\n }\\n return queries;\\n};\\n\\nfunction upperBound(items, target) {\\n let left = -1, right = items.length;\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (items[mid][0] > target) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n}\\n
\\nimpl Solution {\\n pub fn maximum_beauty(mut items: Vec<Vec<i32>>, queries: Vec<i32>) -> Vec<i32> {\\n items.sort_unstable_by_key(|item| item[0]);\\n for i in 1..items.len() {\\n // 原地计算 beauty 的前缀最大值\\n items[i][1] = items[i][1].max(items[i - 1][1]);\\n }\\n\\n queries.into_iter().map(|q| {\\n let j = items.partition_point(|item| item[0] <= q);\\n if j > 0 { items[j - 1][1] } else { 0 }\\n }).collect()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}((n+m)\\\\log n)$,其中 $n$ 是 $\\\\textit{items}$ 的长度,$m$ 是 $\\\\textit{queries}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n写法二:去除无用信息
\\n再来看这个排序后的 $\\\\textit{items}$:
\\n$$
\\n
\\n[[1,2],[2,4],[3,2],[3,5],[5,6]]
\\n$$其中 $[3,2]$ 价格比 $[2,4]$ 高,美丽值又比 $[2,4]$ 低,那么 $[3,2]$ 就完全不如 $[2,4]$,可以直接去掉。(这类似单调栈/单调队列的思想)
\\n去掉这种无用数据后,数组变成
\\n$$
\\n
\\n[[1,2],[2,4],[3,5],[5,6]]
\\n$$此时 $\\\\textit{beauty}$ 就是严格递增的。
\\n这样做的好处是 $\\\\textit{items}$ 更短,计算二分的时间也更短。
\\n如何原地计算?做法可参考 26. 删除有序数组中的重复项。
\\n\\nclass Solution:\\n def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:\\n items.sort(key=lambda item: item[0])\\n k = 0\\n for i in range(1, len(items)):\\n if items[i][1] > items[k][1]: # 有用\\n k += 1\\n items[k] = items[i]\\n\\n for i, q in enumerate(queries):\\n j = bisect_right(items, q, 0, k + 1, key=lambda item: item[0])\\n queries[i] = items[j - 1][1] if j else 0\\n return queries\\n
\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n Arrays.sort(items, (a, b) -> a[0] - b[0]);\\n int k = 0;\\n for (int i = 1; i < items.length; i++) {\\n if (items[i][1] > items[k][1]) { // 有用\\n items[++k] = items[i];\\n }\\n }\\n\\n for (int i = 0; i < queries.length; i++) {\\n int j = upperBound(items, k + 1, queries[i]);\\n queries[i] = j > 0 ? items[j - 1][1] : 0;\\n }\\n return queries;\\n }\\n\\n private int upperBound(int[][] items, int right, int target) {\\n int left = -1; // 开区间 (left, right)\\n while (left + 1 < right) { // 区间不为空\\n // 循环不变量:\\n // items[left][0] <= target\\n // items[right][0] > target\\n int mid = (left + right) >>> 1;\\n if (items[mid][0] > target) {\\n right = mid; // 范围缩小到 (left, mid)\\n } else {\\n left = mid; // 范围缩小到 (mid, right)\\n }\\n }\\n return right;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> maximumBeauty(vector<vector<int>>& items, vector<int>& queries) {\\n ranges::sort(items, {}, [](auto& item) { return item[0]; });\\n int k = 0;\\n for (int i = 1; i < items.size(); i++) {\\n if (items[i][1] > items[k][1]) { // 有用\\n items[++k] = items[i];\\n }\\n }\\n\\n for (int& q : queries) {\\n int j = upper_bound(items.begin(), items.begin() + (k + 1), q, [](int value, auto& item) {\\n return value < item[0];\\n }) - items.begin();\\n q = j ? items[j - 1][1] : 0;\\n }\\n return queries;\\n }\\n};\\n
\\nfunc maximumBeauty(items [][]int, queries []int) []int {\\nslices.SortFunc(items, func(a, b []int) int { return a[0] - b[0] })\\nk := 0\\nfor _, item := range items[1:] {\\nif item[1] > items[k][1] { // 有用\\nk++\\nitems[k] = item\\n}\\n}\\n\\nfor i, q := range queries {\\nj := sort.Search(k+1, func(i int) bool { return items[i][0] > q })\\nif j > 0 {\\nqueries[i] = items[j-1][1]\\n} else {\\nqueries[i] = 0\\n}\\n}\\nreturn queries\\n}\\n
\\nvar maximumBeauty = function(items, queries) {\\n items.sort((a, b) => a[0] - b[0]);\\n let k = 0;\\n for (let i = 1; i < items.length; i++) {\\n if (items[i][1] > items[k][1]) { // 有用\\n items[++k] = items[i];\\n }\\n }\\n\\n for (let i = 0; i < queries.length; i++) {\\n const j = upperBound(items, k + 1, queries[i]);\\n queries[i] = j > 0 ? items[j - 1][1] : 0;\\n }\\n return queries;\\n};\\n\\nfunction upperBound(items, right, target) {\\n let left = -1;\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (items[mid][0] > target) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n}\\n
\\nimpl Solution {\\n pub fn maximum_beauty(mut items: Vec<Vec<i32>>, queries: Vec<i32>) -> Vec<i32> {\\n items.sort_unstable_by_key(|item| item[0]);\\n items.dedup_by(|b, a| b[1] <= a[1]); // 去掉无用数据\\n\\n queries.into_iter().map(|q| {\\n let j = items.partition_point(|item| item[0] <= q);\\n if j > 0 { items[j - 1][1] } else { 0 }\\n }).collect()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}((n+m)\\\\log n)$,其中 $n$ 是 $\\\\textit{items}$ 的长度,$m$ 是 $\\\\textit{queries}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。忽略排序的栈开销。
\\n更多相似题目,见下面数据结构题单中的「专题:离线算法」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:离线算法 + 双指针 离线算法:把 $\\\\textit{queries}$ 排序,通过改变回答询问的顺序,使问题更容易处理。\\n\\n在线算法:按照 $\\\\textit{queries}$ 的顺序一个一个地回答询问。\\n\\n暴力的做法是,对于每个询问 $q=\\\\textit{queries}[i]$,遍历 $\\\\textit{items}$,计算其中 $\\\\textit{price}\\\\le q$ 的最大 $\\\\textit{beauty}$。\\n\\n如何优化?\\n\\n假如 $\\\\textit{queries}$ 已经按照从小到大的顺序排好了,例如示例 1 $\\\\textit…","guid":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/jiang-xun-wen-chi-xian-pai-xu-by-endless-o5j0","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-14T01:09:47.805Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"基于二分 + 单调队列的O(nlogn)算法","url":"https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign//solution/ji-yu-er-fen-dan-diao-dui-lie-de-onlogns-zy8q","content":"解决问题的关键在于想到如果完成了m个任务,最佳方案一定是力量最大的m个worker完成了要求力量最小的m个任务,每个worker都必须完成一个任务。如果能在$O(n)$的时间内解决m个worker能否完成m个任务的判定问题,就可以在$O(n\\\\log n)$时间内通过二分法找到最佳方案。
\\n解决判定问题的关键则是找到一个可行的贪心策略。按力量从小到大依次考虑每个worker $W_j$,他可以选择的任务是要求力量值不超过$W_j + strength$且尚未完成的任务之一,而且必须从当中选一个。这里分为两种情况:
\\n\\n
\\n- 如果当前可选任务中要求力量值最小的一个小于等于$W_j$,那么这个worker不用吃药就可以完成这个任务,因为后续的worker力量一定都比当前worker强,反正这个力量之最小的任务一定要被完成,当然是现在最弱的人完成最划算,而且也没必要吃药,所以当前worker应该不吃药直接完成这个要求力量值最小的任务
\\n- 如果没有可以直接完成的任务,那么这个worker必须要吃药,如果没有药了就立即失败。有药的情况下,反正要从当前可选任务中选一个,那么选要求力量值最大的任务比较划算
\\n- 当然,如果吃了药也没有能完成的任务,也直接失败
\\n这里可以利用的一点是药增加的力量值是个固定值,因而随着worker力量增加,可选任务范围是固定增加的,任务永远按照从小到大的顺序添加到可选任务范围中;而从可选任务范围中移除任务时,要么移除当前最小,要么移除当前最大,因而可以用一个双端可以移除的单调队列来实现,这样就可以做到$O(n)$复杂度了,整体复杂度也就被控制到了$O(n \\\\log n)$
\\n###python3
\\n\\n","description":"解决问题的关键在于想到如果完成了m个任务,最佳方案一定是力量最大的m个worker完成了要求力量最小的m个任务,每个worker都必须完成一个任务。如果能在$O(n)$的时间内解决m个worker能否完成m个任务的判定问题,就可以在$O(n\\\\log n)$时间内通过二分法找到最佳方案。 解决判定问题的关键则是找到一个可行的贪心策略。按力量从小到大依次考虑每个worker $W_j$,他可以选择的任务是要求力量值不超过$W_j + strength$且尚未完成的任务之一,而且必须从当中选一个。这里分为两种情况:\\n\\n如果当前可选任务中要求力量值最小的一个小于…","guid":"https://leetcode.cn/problems/maximum-number-of-tasks-you-can-assign//solution/ji-yu-er-fen-dan-diao-dui-lie-de-onlogns-zy8q","author":"ling-jian-2012","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-13T17:26:11.122Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python3 笨人笨办法 字典+二分","url":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/python3-ben-ren-ben-ban-fa-zi-dian-er-fe-ucu0","content":"class Solution:\\n def maxTaskAssign(self, tasks: List[int], workers: List[int], pills: int, strength: int) -> int:\\n tasks.sort()\\n workers.sort()\\n s = 0\\n e = min(len(tasks), len(workers)) + 1\\n while s + 1 < e:\\n m = (s + e) // 2\\n #print(\\"try\\", m)\\n # Match workers[-m:] to tasks[:m]\\n i2 = 0\\n p = pills\\n fail = False\\n valid_tasks = deque()\\n for j in range(len(workers) - m, len(workers)):\\n w = workers[j]\\n while i2 < m and tasks[i2] <= w + strength:\\n valid_tasks.append(tasks[i2])\\n i2 += 1\\n if not valid_tasks:\\n fail = True\\n break\\n if valid_tasks[0] <= w:\\n # No need for pill\\n valid_tasks.popleft()\\n else:\\n if not p:\\n fail = True\\n break\\n p -= 1\\n valid_tasks.pop()\\n if fail:\\n e = m\\n else:\\n s = m\\n return s\\n
思路
\\n以价格为键值,对应出该键值下最大的美丽值生成字典
\\n
\\n然后二分查找queries里价格所能买到的最大美丽值
\\n(这题目也太容易看成背包问题了吧!!!)代码
\\n\\n","description":"以价格为键值,对应出该键值下最大的美丽值生成字典 然后二分查找queries里价格所能买到的最大美丽值\\n (这题目也太容易看成背包问题了吧!!!)\\n\\ndef binarySearch(money, beauty, i):\\n left = 0\\n right = len(money) - 1\\n ans = 0\\n while left <= right:\\n mid = (left + right) // 2\\n if money[mid] <= i:\\n ans = max(ans…","guid":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/python3-ben-ren-ben-ban-fa-zi-dian-er-fe-ucu0","author":"jqy_htth","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-13T17:07:10.023Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划➕二分查找(不会边界也没关系我有floorKey) 好气每次都是超时才想起dp","url":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/dong-tai-gui-hua-er-fen-cha-zhao-bu-hui-k2tsp","content":"def binarySearch(money, beauty, i):\\n left = 0\\n right = len(money) - 1\\n ans = 0\\n while left <= right:\\n mid = (left + right) // 2\\n if money[mid] <= i:\\n ans = max(ans, beauty[mid])\\n left = mid + 1\\n else:\\n right = mid - 1\\n return ans\\n\\n\\nclass Solution:\\n def maximumBeauty(self, items: List[List[int]], queries: List[int]) -> List[int]:\\n # 以价格进行排序\\n items.sort() \\n dict_ = {items[0][0]: items[0][1]}\\n max_beauty = items[0][1]\\n # 生成字典,value值为对应key下最大的美丽值\\n for i in range(1, len(items)): \\n if items[i][0] == items[i - 1][0]:\\n dict_[items[i][0]] = max(dict_[items[i][0]], items[i][1], max_beauty)\\n else:\\n dict_[items[i][0]] = max(items[i][1], max_beauty)\\n max_beauty = dict_[items[i][0]]\\n money_ = list(dict_.keys())\\n beauty_ = list(dict_.values())\\n values = [0 for i in range(len(queries))]\\n for k in range(len(queries)):\\n # 二分查找\\n values[k] = binarySearch(money_, beauty_, queries[k]) \\n return values\\n
解题思路
\\n首先先用一个map把价格和美丽值做映射,用treeMap和treeSet,对应c++的map和map,注意排序,map是按照价格降序,set是升序
\\n
\\n得到的map的key,做dp咯,下面给出注释
\\n然后这里就是动态规划咯
\\n首先是dp[i]的初值是每个价格下的最大美丽值(就是我用set先排好序),然后从前往后递推
\\ndp[i] = max(dp[i],dp[i-1])代码
\\n###java
\\n\\n","description":"解题思路 首先先用一个map把价格和美丽值做映射,用treeMap和treeSet,对应c++的map和map,注意排序,map是按照价格降序,set是升序\\n 得到的map的key,做dp咯,下面给出注释\\n 然后这里就是动态规划咯\\n 首先是dp[i]的初值是每个价格下的最大美丽值(就是我用set先排好序),然后从前往后递推\\n dp[i] = max(dp[i],dp[i-1])\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {…","guid":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/dong-tai-gui-hua-er-fen-cha-zhao-bu-hui-k2tsp","author":"Cat_keeper","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-13T16:27:28.969Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【Java】排序 + 二分查找","url":"https://leetcode.cn/problems/most-beautiful-item-for-each-query//solution/java-pai-xu-er-fen-cha-zhao-by-congregal-2782","content":"class Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n //首先先用一个map把价格和美丽值做映射\\n TreeMap<Integer, TreeSet<Integer>> q2b = new TreeMap<>((o1, o2) -> o1 - o2);\\n for (int[]each : items){\\n TreeSet<Integer> cur = q2b.getOrDefault(each[0], new TreeSet<>((o1, o2) -> o2 - o1));\\n cur.add(each[1]);\\n q2b.put(each[0],cur);\\n }\\n \\n //其实这一步很蠢,就是把map的key都拿出来放list里面好遍历\\n int[]idx = new int[q2b.size()];\\n int index = 0;\\n for (int eachIdx : q2b.keySet()){\\n idx[index] = eachIdx;\\n index++;\\n }\\n\\n //然后这里就是动态规划咯\\n //首先是dp[i]的初值是每个价格下的最大美丽值(就是我用set先排好序),然后从前往后递推\\n //dp[i] = max(dp[i],dp[i-1])\\n int[]maxBeauty = new int[q2b.size()];\\n maxBeauty[0] = q2b.get(idx[0]).first();\\n Map<Integer,Integer>key2Max = new HashMap<>();\\n key2Max.put(idx[0],maxBeauty[0]);\\n for (int i = 1; i < maxBeauty.length; i++){\\n maxBeauty[i] = q2b.get(idx[i]).first();\\n maxBeauty[i] = Math.max(maxBeauty[i-1], maxBeauty[i]);\\n key2Max.put(idx[i],maxBeauty[i]);\\n }\\n\\n //然后就是res了,直接用floorKey找出小于等于的Key\\n int[]res = new int[queries.length];\\n for (int i = 0; i < queries.length; i++){\\n Integer key = q2b.floorKey(queries[i]);\\n if (key == null){\\n res[i] = 0;\\n continue;\\n }\\n res[i] = key2Max.get(key);\\n }\\n return res;\\n }\\n}\\n
看注释,主要分三步
\\n###java
\\n\\n","description":"看注释,主要分三步 ###java\\n\\nclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n int n = queries.length, m = items.length;\\n int[] ans = new int[n];\\n \\n // 按照 price 排序\\n Arrays.sort(items, new Comparatorclass Solution {\\n public int[] maximumBeauty(int[][] items, int[] queries) {\\n int n = queries.length, m = items.length;\\n int[] ans = new int[n];\\n \\n // 按照 price 排序\\n Arrays.sort(items, new Comparator<int[]>() {\\n public int compare(int[] a, int[] b) {\\n if (a[0] == b[0])\\n return a[1]-b[1];\\n else\\n return a[0]-b[0];\\n }\\n });\\n \\n // 替换 beauty 为目前为止最大的(这么做是对的,因为已经对 price 排序过了)\\n int max = 0;\\n for (int i = 0; i < m; i++) {\\n if (items[i][1] <= max)\\n items[i][1] = max;\\n else\\n max = items[i][1];\\n }\\n \\n // 针对每个 query 二分搜索符合条件的 price\\n for (int i = 0; i < n; i++) {\\n int l = 0, r = m - 1;\\n \\n while (l < r) {\\n int mid = l + (r - l) / 2 + 1;\\n \\n if (items[mid][0] > queries[i])\\n r = mid - 1;\\n else\\n l = mid;\\n }\\n \\n // 若无符合条件的 price,则结果为0\\n ans[i] = items[l][0] <= queries[i] ? items[l][1] : 0;\\n }\\n\\n \\n return ans;\\n }\\n}\\n
今天介绍的方法叫插旗法,其实我最早是在253. 会议室Ⅱ中介绍过,但是由于很多同学不是会员,刚好今天又看到这种类型的题,所以想着再次介绍一下。\\n \\n
\\n- 先来介绍一下插旗法:进入一个区间的时候将该点坐标对应的值+1,代表插上一面进入的🚩,离开时将该点坐标值-1,代表插上一面离开的🚩,在同一个点可以同时插进入的旗或离开的旗,因为这样并不形成区间重叠。
\\n\\n\\n这种方法非常适合解最大的区间重叠数量 (或最大的并行数量) 的题目,能够将时间复杂度控制在 $O(nlog{n})$,而且代码可以说是八九不离十。
\\n\\n
\\n- 下面贴出代码的模板
\\n\\nint maxConcurrent (vecotr<vecotr<int>>& time){\\n map<int, int> record;\\n for(auto& t : time){\\n record[t[0]] += 1;\\n record[t[1]] -= 1;\\n }\\n int ans = 0, concurrent = 0;\\n for(auto& p : record){\\n concurrent += p.second;\\n ans = max(ans, concurrent);\\n }\\n return ans;\\n}\\n
\\n\\n注意:第二个循环进行遍历时应该保证 map 的键按照升序排列
\\n那对于我的日程安排表Ⅰ 和 我的日程安排表Ⅱ 的代码,无非就是并行数量的修改:
\\n\\nclass MyCalendar {\\npublic:\\n map<int, int> rec;\\n\\n MyCalendar() {}\\n \\n bool book(int start, int end) {\\n rec[start] += 1;\\n rec[end] -= 1;\\n int cur = 0;\\n\\n for(auto& p : rec){\\n cur += p.second;\\n if(cur > 2){ // 若修改成 cur > 1, 则对应着「我的日程安排表Ⅰ」 的代码\\n rec[start]--, rec[end]++;\\n return false;\\n }\\n }\\n return true;\\n }\\n};\\n\\n
而对于题目我的日程安排表Ⅲ ,那真就是和模板代码一模一样了。Easy Money!
\\n
\\n以上就是题解的全部内容啦~~ 如果你觉得题解有用,别忘了点个赞噢!如果内容有误,也欢迎大家批评指正!
\\n","description":"今天介绍的方法叫插旗法,其实我最早是在253. 会议室Ⅱ中介绍过,但是由于很多同学不是会员,刚好今天又看到这种类型的题,所以想着再次介绍一下。 先来介绍一下插旗法:进入一个区间的时候将该点坐标对应的值+1,代表插上一面进入的🚩,离开时将该点坐标值-1,代表插上一面离开的🚩,在同一个点可以同时插进入的旗或离开的旗,因为这样并不形成区间重叠。\\n\\n这种方法非常适合解最大的区间重叠数量 (或最大的并行数量) 的题目,能够将时间复杂度控制在 $O(nlog{n})$,而且代码可以说是八九不离十。\\n\\n下面贴出代码的模板\\nint maxConcurrent…","guid":"https://leetcode.cn/problems/my-calendar-ii//solution/yi-fa-jie-nti-by-laughhhh-pll7","author":"laughhhh","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-09T09:16:41.812Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单模拟题","url":"https://leetcode.cn/problems/range-addition-ii//solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-006h","content":"模拟
\\n由于每次都是对 $0 \\\\leq i < a$ 和 $0 \\\\leq j < b$ 进行操作,因此每次操作都会包含点 $(0, 0)$,最后的最大值一定出现在位置 $(0, 0)$ 上。
\\n问题转换为:什么范围内的数与位置 $(0, 0)$ 上的值相等,即什么范围会被每一次操作所覆盖。
\\n不难发现,在所有的 $ops[i]$ 中的横纵坐标 $(x, y)$ 与左上角 $(0, 0)$ 形成的区域面积可确保被每次操作覆盖,$x * y$ 即是答案。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int maxCount(int m, int n, int[][] ops) {\\n for (int[] op : ops) {\\n m = Math.min(m, op[0]);\\n n = Math.min(n, op[1]);\\n }\\n return m * n;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令 $k$ 为 $ops$ 数组的长度。复杂度为 $O(k)$
\\n- 空间复杂度:$O(1)$
\\n
\\n其他「模拟」相关内容
\\n题太简单?不如来学习热乎的 $Trie$ 四部曲的最终章 「可删除/可计数/持久化」Trie ,相关阅读:
\\n\\n
\\n- $Trie$ 第一部
\\n- $Trie$ 第二部
\\n- $Trie$ 第三部
\\n- $Trie$ 第四部
\\n注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 由于每次都是对 $0 \\\\leq i < a$ 和 $0 \\\\leq j < b$ 进行操作,因此每次操作都会包含点 $(0, 0)$,最后的最大值一定出现在位置 $(0, 0)$ 上。\\n\\n问题转换为:什么范围内的数与位置 $(0, 0)$ 上的值相等,即什么范围会被每一次操作所覆盖。\\n\\n不难发现,在所有的 $ops[i]$ 中的横纵坐标 $(x, y)$ 与左上角 $(0, 0)$ 形成的区域面积可确保被每次操作覆盖,$x * y$ 即是答案。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public int maxCount…","guid":"https://leetcode.cn/problems/range-addition-ii//solution/gong-shui-san-xie-jian-dan-mo-ni-ti-by-a-006h","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-06T23:27:11.054Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"范围求和 II","url":"https://leetcode.cn/problems/range-addition-ii//solution/fan-wei-qiu-he-ii-by-leetcode-solution-kcxq","content":"方法一:维护所有操作的交集
\\n思路与算法
\\n对于每一次操作,给定 $(a, b)$,我们会将矩阵中所有满足 $0 \\\\leq i < a$ 以及 $0 \\\\leq j < b$ 的位置 $(i, j)$ 全部加上 $1$。由于 $a, b$ 均为正整数,那么 $(0, 0)$ 总是满足上述条件,并且最终位置 $(0, 0)$ 的值就等于操作的次数。
\\n因此,我们的任务即为找出矩阵中所有满足要求的次数恰好等于操作次数的位置。假设操作次数为 $k$,那么 $(i, j)$ 需要满足:
\\n$$
\\n
\\n\\\\begin{cases}
\\n0 \\\\leq i < a_0, 0 \\\\leq i < a_1, \\\\cdots, 0 \\\\leq i < a_{k-1} \\\\
\\n0 \\\\leq j < b_0, 0 \\\\leq j < b_1, \\\\cdots, 0 \\\\leq j < b_{k-1} \\\\
\\n\\\\end{cases}
\\n$$等价于:
\\n$$
\\n
\\n\\\\begin{cases}\\\\tag{1}
\\n0 \\\\leq i < \\\\min\\\\limits_k a \\\\
\\n0 \\\\leq j < \\\\min\\\\limits_k b
\\n\\\\end{cases}
\\n$$这样一来,我们只需要求出 $a$ 和 $b$ 中的最小值,分别记为 $\\\\min\\\\limits_k a$ 以及 $\\\\min\\\\limits_k b$,那么满足 $(1)$ 式的 $(i, j)$ 一共有 $\\\\min\\\\limits_k a \\\\times \\\\min\\\\limits_k b$ 对。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int maxCount(int m, int n, vector<vector<int>>& ops) {\\n int mina = m, minb = n;\\n for (const auto& op: ops) {\\n mina = min(mina, op[0]);\\n minb = min(minb, op[1]);\\n }\\n return mina * minb;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int maxCount(int m, int n, int[][] ops) {\\n int mina = m, minb = n;\\n for (int[] op : ops) {\\n mina = Math.min(mina, op[0]);\\n minb = Math.min(minb, op[1]);\\n }\\n return mina * minb;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxCount(int m, int n, int[][] ops) {\\n int mina = m, minb = n;\\n foreach (int[] op in ops) {\\n mina = Math.Min(mina, op[0]);\\n minb = Math.Min(minb, op[1]);\\n }\\n return mina * minb;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def maxCount(self, m: int, n: int, ops: List[List[int]]) -> int:\\n mina, minb = m, n\\n for a, b in ops:\\n mina = min(mina, a)\\n minb = min(minb, b)\\n return mina * minb\\n
###JavaScript
\\n\\nvar maxCount = function(m, n, ops) {\\n let mina = m, minb = n;\\n for (const op of ops) {\\n mina = Math.min(mina, op[0]);\\n minb = Math.min(minb, op[1]);\\n }\\n return mina * minb;\\n};\\n
###go
\\n\\nfunc maxCount(m, n int, ops [][]int) int {\\n mina, minb := m, n\\n for _, op := range ops {\\n mina = min(mina, op[0])\\n minb = min(minb, op[1])\\n }\\n return mina * minb\\n}\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n
###TypeScript
\\n\\nfunction maxCount(m: number, n: number, ops: number[][]): number {\\n let mina = m, minb = n;\\n for (const op of ops) {\\n mina = Math.min(mina, op[0]);\\n minb = Math.min(minb, op[1]);\\n }\\n return mina * minb;\\n}\\n
###C
\\n\\nint maxCount(int m, int n, int** ops, int opsSize, int* opsColSize) {\\n int mina = m, minb = n;\\n for (int i = 0; i < opsSize; ++i) {\\n mina = (mina < ops[i][0]) ? mina : ops[i][0];\\n minb = (minb < ops[i][1]) ? minb : ops[i][1];\\n }\\n return mina * minb;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn max_count(m: i32, n: i32, ops: Vec<Vec<i32>>) -> i32 {\\n let mut mina = m;\\n let mut minb = n;\\n for op in ops {\\n mina = mina.min(op[0]);\\n minb = minb.min(op[1]);\\n }\\n mina * minb\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:维护所有操作的交集 思路与算法\\n\\n对于每一次操作,给定 $(a, b)$,我们会将矩阵中所有满足 $0 \\\\leq i < a$ 以及 $0 \\\\leq j < b$ 的位置 $(i, j)$ 全部加上 $1$。由于 $a, b$ 均为正整数,那么 $(0, 0)$ 总是满足上述条件,并且最终位置 $(0, 0)$ 的值就等于操作的次数。\\n\\n因此,我们的任务即为找出矩阵中所有满足要求的次数恰好等于操作次数的位置。假设操作次数为 $k$,那么 $(i, j)$ 需要满足:\\n\\n$$\\n \\\\begin{cases}\\n 0 \\\\leq i < a_0, 0 \\\\leq i…","guid":"https://leetcode.cn/problems/range-addition-ii//solution/fan-wei-qiu-he-ii-by-leetcode-solution-kcxq","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-11-06T02:38:16.381Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"回溯(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/go-mo-ni-by-endlesscheng-kjpt","content":"- \\n
\\n时间复杂度:$O(k)$,其中 $k$ 是数组 $\\\\textit{ops}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n核心思路
\\n\\n
\\n- 预处理每个棋子的所有合法移动。
\\n- 写一个回溯,暴力枚举每个棋子的每个合法移动,如果这些棋子没有重叠在一起,则答案加一。
\\n细节
\\n具体来说,合法移动包含:
\\n\\n
\\n- 棋子的初始位置 $(x_0,y_0)$。
\\n- 棋子的移动方向 $(d_x,d_y)$。
\\n- 棋子的移动次数 $\\\\textit{step}$。
\\n在回溯时,可以剪枝:如果当前棋子的当前这个合法移动,与前面的棋子冲突,即同一时刻两个棋子重叠,那么不往下递归,枚举当前棋子的下一个合法移动。
\\n\\nclass Solution:\\n # 计算位于 (x0,y0) 的棋子在 dirs 这些方向上的所有合法移动\\n def generate_moves(self, x0: int, y0: int, dirs: List[Tuple[int, int]]) -> List[Tuple[int, int, int, int, int]]:\\n SIZE = 8\\n moves = [(x0, y0, 0, 0, 0)] # 原地不动\\n for dx, dy in dirs:\\n # 往 (dx,dy) 方向走 1,2,3,... 步\\n x, y = x0 + dx, y0 + dy\\n step = 1\\n while 0 < x <= SIZE and 0 < y <= SIZE:\\n moves.append((x0, y0, dx, dy, step))\\n x += dx\\n y += dy\\n step += 1\\n return moves\\n\\n # 判断两个移动是否合法,即不存在同一时刻两个棋子重叠的情况\\n def is_valid(self, move1: Tuple[int, int, int, int, int], move2: Tuple[int, int, int, int, int]) -> bool:\\n x1, y1, dx1, dy1, step1 = move1\\n x2, y2, dx2, dy2, step2 = move2\\n for i in range(max(step1, step2)):\\n # 每一秒走一步\\n if i < step1:\\n x1 += dx1\\n y1 += dy1\\n if i < step2:\\n x2 += dx2\\n y2 += dy2\\n if x1 == x2 and y1 == y2: # 重叠\\n return False\\n return True\\n\\n def countCombinations(self, pieces: List[str], positions: List[List[int]]) -> int:\\n rook_dirs = (-1, 0), (1, 0), (0, -1), (0, 1) # 上下左右\\n bishop_dirs = (1, 1), (-1, 1), (-1, -1), (1, -1) # 斜向\\n piece_dirs = {\'r\': rook_dirs, \'b\': bishop_dirs, \'q\': rook_dirs + bishop_dirs}\\n # 预处理所有合法移动\\n all_moves = [self.generate_moves(x, y, piece_dirs[piece[0]])\\n for piece, (x, y) in zip(pieces, positions)]\\n\\n n = len(pieces)\\n path = [None] * n # 注意 path 的长度是固定的\\n ans = 0\\n def dfs(i: int) -> None:\\n if i == n:\\n nonlocal ans\\n ans += 1\\n return\\n # 枚举当前棋子的所有合法移动\\n for move1 in all_moves[i]:\\n # 判断合法移动 move1 是否有效\\n if all(self.is_valid(move1, move2) for move2 in path[:i]):\\n path[i] = move1 # 直接覆盖,无需恢复现场\\n dfs(i + 1) # 枚举后续棋子的所有合法移动组合\\n dfs(0)\\n return ans\\n
\\nclass Solution {\\n // 每种棋子的移动方向\\n private static final Map<Character, int[][]> PIECE_DIRS = Map.of(\\n \'r\', new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}}, // 车\\n \'b\', new int[][]{{1, 1}, {-1, 1}, {-1, -1}, {1, -1}}, // 象\\n \'q\', new int[][]{{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}} // 皇后\\n );\\n\\n public int countCombinations(String[] pieces, int[][] positions) {\\n int n = pieces.length;\\n // 预处理所有合法移动\\n Move[][] allMoves = new Move[n][];\\n for (int i = 0; i < n; i++) {\\n allMoves[i] = generateMoves(positions[i][0], positions[i][1], PIECE_DIRS.get(pieces[i].charAt(0)));\\n }\\n\\n Move[] path = new Move[n]; // 注意 path 的长度是固定的\\n return dfs(0, n, allMoves, path);\\n }\\n\\n // 起点 (x0,y0),移动方向 (dx,dy),移动次数 step\\n private record Move(int x0, int y0, int dx, int dy, int step) {\\n }\\n\\n // 计算位于 (x0,y0) 的棋子在 dirs 这些方向上的所有合法移动\\n private Move[] generateMoves(int x0, int y0, int[][] dirs) {\\n final int SIZE = 8;\\n List<Move> moves = new ArrayList<>();\\n moves.add(new Move(x0, y0, 0, 0, 0)); // 原地不动\\n for (int[] d : dirs) {\\n // 往 d 方向走 1,2,3,... 步\\n int x = x0 + d[0], y = y0 + d[1];\\n for (int step = 1; 0 < x && x <= SIZE && 0 < y && y <= SIZE; step++) {\\n moves.add(new Move(x0, y0, d[0], d[1], step));\\n x += d[0];\\n y += d[1];\\n }\\n }\\n return moves.toArray(Move[]::new);\\n }\\n\\n // 判断两个移动是否合法,即不存在同一时刻两个棋子重叠的情况\\n private boolean isValid(Move m1, Move m2) {\\n int x1 = m1.x0, y1 = m1.y0; // 初始位置\\n int x2 = m2.x0, y2 = m2.y0;\\n for (int i = 0; i < Math.max(m1.step, m2.step); i++) {\\n // 每一秒走一步\\n if (i < m1.step) {\\n x1 += m1.dx;\\n y1 += m1.dy;\\n }\\n if (i < m2.step) {\\n x2 += m2.dx;\\n y2 += m2.dy;\\n }\\n if (x1 == x2 && y1 == y2) { // 重叠\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n private int dfs(int i, int n, Move[][] allMoves, Move[] path) {\\n if (i == n) {\\n return 1;\\n }\\n int res = 0;\\n outer:\\n // 枚举当前棋子的所有合法移动\\n for (Move move1 : allMoves[i]) {\\n // 判断合法移动 move1 是否有效\\n for (int j = 0; j < i; j++) {\\n if (!isValid(move1, path[j])) {\\n continue outer; // 无效,枚举下一个 move1\\n }\\n }\\n path[i] = move1; // 直接覆盖,无需恢复现场\\n res += dfs(i + 1, n, allMoves, path); // 枚举后续棋子的所有合法移动组合\\n }\\n return res;\\n }\\n}\\n
\\nstruct Move {\\n int x0, y0; // 起点\\n int dx, dy; // 移动方向\\n int step; // 移动次数\\n};\\n\\nclass Solution {\\n vector<pair<int, int>> DIRS = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}}; // 上下左右 + 斜向\\n unordered_map<char, vector<pair<int, int>>> PIECE_DIRS = {\\n {\'r\', {DIRS.begin(), DIRS.begin() + 4}},\\n {\'b\', {DIRS.begin() + 4, DIRS.end()}},\\n {\'q\', DIRS},\\n };\\n\\n // 计算位于 (x0,y0) 的棋子在 dirs 这些方向上的所有合法移动\\n vector<Move> generate_moves(int x0, int y0, vector<pair<int, int>>& dirs) {\\n const int SIZE = 8;\\n vector<Move> moves = {{x0, y0, 0, 0, 0}}; // 原地不动\\n for (auto [dx, dy] : dirs) {\\n // 往 d 方向走 1,2,3,... 步\\n int x = x0 + dx, y = y0 + dy;\\n for (int step = 1; 0 < x && x <= SIZE && 0 < y && y <= SIZE; step++) {\\n moves.emplace_back(x0, y0, dx, dy, step);\\n x += dx;\\n y += dy;\\n }\\n }\\n return moves;\\n }\\n\\n // 判断两个移动是否合法,即不存在同一时刻两个棋子重叠的情况\\n bool is_valid(Move& m1, Move& m2) {\\n int x1 = m1.x0, y1 = m1.y0;\\n int x2 = m2.x0, y2 = m2.y0;\\n for (int i = 0; i < max(m1.step, m2.step); i++) {\\n // 每一秒走一步\\n if (i < m1.step) {\\n x1 += m1.dx;\\n y1 += m1.dy;\\n }\\n if (i < m2.step) {\\n x2 += m2.dx;\\n y2 += m2.dy;\\n }\\n if (x1 == x2 && y1 == y2) { // 重叠\\n return false;\\n }\\n }\\n return true;\\n }\\n\\npublic:\\n int countCombinations(vector<string>& pieces, vector<vector<int>>& positions) {\\n int n = pieces.size();\\n // 预处理所有合法移动\\n vector<vector<Move>> all_moves(n);\\n for (int i = 0; i < n; i++) {\\n all_moves[i] = generate_moves(positions[i][0], positions[i][1], PIECE_DIRS[pieces[i][0]]);\\n }\\n\\n vector<Move> path(n); // 注意 path 的长度是固定的\\n int ans = 0;\\n auto dfs = [&](auto& dfs, int i) -> void {\\n if (i == n) {\\n ans++;\\n return;\\n }\\n // 枚举当前棋子的所有合法移动\\n for (Move& move1 : all_moves[i]) {\\n // 判断合法移动 move1 是否有效\\n bool ok = true;\\n for (int j = 0; j < i; j++) {\\n if (!is_valid(move1, path[j])) {\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n path[i] = move1; // 直接覆盖,无需恢复现场\\n dfs(dfs, i + 1); // 枚举后续棋子的所有合法移动组合\\n }\\n }\\n };\\n dfs(dfs, 0);\\n return ans;\\n }\\n};\\n
\\ntype move struct {\\n x0, y0 int // 起点 \\n dx, dy int // 移动方向\\n step int // 移动次数\\n}\\n\\n// 计算位于 (x0,y0) 的棋子在 dirs 这些方向上的所有合法移动\\nfunc generateMoves(x0, y0 int, dirs []struct{ x, y int }) []move {\\n const size = 8\\n moves := []move{{x0, y0, 0, 0, 0}} // 原地不动\\n for _, d := range dirs {\\n // 往 d 方向走 1,2,3,... 步\\n x, y := x0+d.x, y0+d.y\\n for step := 1; 0 < x && x <= size && 0 < y && y <= size; step++ {\\n moves = append(moves, move{x0, y0, d.x, d.y, step})\\n x += d.x\\n y += d.y\\n }\\n }\\n return moves\\n}\\n\\n// 判断两个移动是否合法,即不存在同一时刻两个棋子重叠的情况\\nfunc isValid(m1, m2 move) bool {\\n x1, y1 := m1.x0, m1.y0\\n x2, y2 := m2.x0, m2.y0\\n for i := range max(m1.step, m2.step) {\\n // 每一秒走一步\\n if i < m1.step {\\n x1 += m1.dx\\n y1 += m1.dy\\n }\\n if i < m2.step {\\n x2 += m2.dx\\n y2 += m2.dy\\n }\\n if x1 == x2 && y1 == y2 { // 重叠\\n return false\\n }\\n }\\n return true\\n}\\n\\nvar dirs = []struct{ x, y int }{{-1, 0}, {1, 0}, {0, -1}, {0, 1}, {1, 1}, {-1, 1}, {-1, -1}, {1, -1}} // 上下左右 + 斜向\\nvar pieceDirs = map[byte][]struct{ x, y int }{\'r\': dirs[:4], \'b\': dirs[4:], \'q\': dirs}\\n\\nfunc countCombinations(pieces []string, positions [][]int) (ans int) {\\n n := len(pieces)\\n // 预处理所有合法移动\\n allMoves := make([][]move, n)\\n for i, pos := range positions {\\n allMoves[i] = generateMoves(pos[0], pos[1], pieceDirs[pieces[i][0]])\\n }\\n\\n path := make([]move, n) // 注意 path 的长度是固定的\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == n {\\n ans++\\n return\\n }\\n outer:\\n // 枚举当前棋子的所有合法移动\\n for _, move1 := range allMoves[i] {\\n // 判断合法移动 move1 是否有效\\n for _, move2 := range path[:i] {\\n if !isValid(move1, move2) {\\n continue outer // 无效,枚举下一个 move1\\n }\\n }\\n path[i] = move1 // 直接覆盖,无需恢复现场\\n dfs(i + 1) // 枚举后续棋子的所有合法移动组合\\n }\\n }\\n dfs(0)\\n return\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(nLM^n)$,其中 $n$ 是 $\\\\textit{pieces}$ 的长度,$L=8$,$M=\\\\mathcal{O}(L)$ 是单个棋子的合法移动个数。搜索树是一棵 $M$ 叉树,深度为 $\\\\mathcal{O}(n)$,一共有 $\\\\mathcal{O}(M^n)$ 个节点。每个节点需要用 $\\\\mathcal{O}(nL)$ 的时间,判断第 $i$ 个棋子的其中一个合法移动是否有效。
\\n- 空间复杂度:$\\\\mathcal{O}(nM)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"核心思路 预处理每个棋子的所有合法移动。\\n写一个回溯,暴力枚举每个棋子的每个合法移动,如果这些棋子没有重叠在一起,则答案加一。\\n细节\\n\\n具体来说,合法移动包含:\\n\\n棋子的初始位置 $(x_0,y_0)$。\\n棋子的移动方向 $(d_x,d_y)$。\\n棋子的移动次数 $\\\\textit{step}$。\\n\\n在回溯时,可以剪枝:如果当前棋子的当前这个合法移动,与前面的棋子冲突,即同一时刻两个棋子重叠,那么不往下递归,枚举当前棋子的下一个合法移动。\\n\\nclass Solution:\\n # 计算位于 (x0,y0) 的棋子在 dirs 这些方向上的所有合法移动…","guid":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/go-mo-ni-by-endlesscheng-kjpt","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-10-31T01:49:07.252Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"理顺思路,按题意模拟即可","url":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/li-shun-si-lu-an-ti-yi-mo-ni-ji-ke-by-ne-kx5e","content":"看到这种题意比较长的题目首先需要理顺题意,然后理顺思路,写起代码就简单了。
\\n
\\n题意:\\n
\\n- 棋盘上有 3 种棋子,车,后,象。车只走 直线;后 直线、斜线 都走;象 只走 斜线。
\\n- 我们需要选择 移动方案,在这个方案中:\\n
\\n\\n
\\n- 首先,对每个棋子,指定 移动方向 和 步数。(棋子也可以不移动,此时移动方向已无意义,算做一种方案)
\\n- 然后,每秒钟,每个棋子都会同时沿着 指定的方向 前进一步,直到步数耗尽。 如果某一 整数 时刻,有棋子 重叠,或者棋子 移出了界外,则为 无效方案;否则为有效方案。
\\n- 返回有效方案的个数。
\\n思路:
\\n
\\n直接按题意模拟即可。首先枚举移动方案,然后再模拟移动的过程,检查是否为有效方案。###c++
\\n\\n","description":"看到这种题意比较长的题目首先需要理顺题意,然后理顺思路,写起代码就简单了。 题意:\\n\\n棋盘上有 3 种棋子,车,后,象。车只走 直线;后 直线、斜线 都走;象 只走 斜线。\\n我们需要选择 移动方案,在这个方案中:\\n首先,对每个棋子,指定 移动方向 和 步数。(棋子也可以不移动,此时移动方向已无意义,算做一种方案)\\n然后,每秒钟,每个棋子都会同时沿着 指定的方向 前进一步,直到步数耗尽。 如果某一 整数 时刻,有棋子 重叠,或者棋子 移出了界外,则为 无效方案;否则为有效方案。\\n返回有效方案的个数。\\n\\n思路:\\n 直接按题意模拟即可。首先枚举移动方案…","guid":"https://leetcode.cn/problems/number-of-valid-move-combinations-on-chessboard//solution/li-shun-si-lu-an-ti-yi-mo-ni-ji-ke-by-ne-kx5e","author":"newhar","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-10-30T16:37:13.886Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【微扰理论】看起来像背包问题的一定可以写成更容易理解的记忆化搜索","url":"https://leetcode.cn/problems/shopping-offers//solution/wei-rao-li-lun-kan-qi-lai-xiang-bei-bao-l4trt","content":"class Solution {\\npublic:\\n int countCombinations(vector<string>& pieces, vector<vector<int>>& pos) {\\n int dx[] = {1,-1,0,0,1,-1,-1,1};\\n int dy[] = {0,0,1,-1,1,-1,1,-1};\\n\\n for(int i = 0; i < pos.size(); ++i) --pos[i][0], --pos[i][1];\\n \\n pair<int,int> m[4];\\n \\n auto sim = [&]() -> int {\\n int board[8][8];\\n memset(board, 0, sizeof(board));\\n pair<int,int> move[4]; // 这里如果是 vector 就会很慢,被卡常了\\n int curpos[4][2];\\n for(int i = 0; i < pos.size(); ++i) \\n move[i] = m[i], curpos[i][0] = pos[i][0], curpos[i][1] = pos[i][1];\\n for(;;) {\\n bool moved = false;\\n for(int i = 0; i < pos.size(); ++i) {\\n if(move[i].second > 0) {\\n moved = true;\\n --move[i].second;\\n curpos[i][0] += dx[move[i].first];\\n curpos[i][1] += dy[move[i].first];\\n }\\n }\\n if(!moved) return 1;\\n for(int i = 0; i < pos.size(); ++i) {\\n if(++board[curpos[i][0]][curpos[i][1]] > 1) return 0;\\n }\\n \\n for(int i = 0; i < pos.size(); ++i) {\\n board[curpos[i][0]][curpos[i][1]] = 0;\\n }\\n }\\n };\\n \\n int res = 0;\\n\\n function<void(int)> dfs = [&] (int i) {\\n if(i == pieces.size()){ res += sim() ; return;}\\n int mind, maxd;\\n if(pieces[i][0] == \'r\') mind = 0, maxd = 3;\\n if(pieces[i][0] == \'q\') mind = 0, maxd = 7;\\n if(pieces[i][0] == \'b\') mind = 4, maxd = 7;\\n for(int d = mind; d <= maxd; ++d) {\\n for(int l = 1; l <= 8; ++l) {\\n if(pos[i][0] + l * dx[d] >= 0 && pos[i][0] + l*dx[d] < 8 \\n && pos[i][1] + l*dy[d] >= 0 && pos[i][1] + l*dy[d] < 8) { // 剪枝限制移动步数\\n m[i].first = d, m[i].second = l;\\n dfs(i + 1);\\n }\\n }\\n }\\n m[i].first = 0;\\n m[i].second = 0;\\n dfs(i + 1);\\n };\\n \\n dfs(0);\\n \\n return res;\\n }\\n};\\n
解题思路
\\n今天周末;题解还是写的简单一点 大家多看注释哦
\\n不知道大家会不会和我一样,看到这个题目就觉得像是动态规划中的背包问题;而动态规划和记忆化搜索其实是等价的,这道题用搜索的思路去解决个人觉得是更简单的。
\\n
\\n我们需要做的事情其实很简单,就是如果发现买一些大礼包可以让我们用更便宜的价格买到更多的东西,我们就试图去买一些大礼包来降低我们的成本。但到底怎么买是最优的呢?基于和背包问题类似的原因,我们不能简单的通过贪心策略去得到全局最优解,那么我们只能一个个试。我们在dfs的时候可以用一个needs数组来标记还需要购买的商品;然后去遍历每个大礼包,购买他,看看购买的情况下能获得的价格+当前礼包的价格是不是更便宜。整个问题都是具有明显父子问题结构的,我们用一个cache来记录每一个needs数组对应的最小的价格即可达到比较好的时间复杂度。
\\n代码
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n // 不同的needs所需的价格\\n map<vector<int>, int> _cache;\\n\\n int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {\\n return dfs(needs, price, special);\\n }\\n\\n int dfs(vector<int> needs, vector<int>& price, vector<vector<int>>& special) {\\n // 如果子问题已经计算过 直接返回\\n if (_cache[needs]) { return _cache[needs]; }\\n int ans = 0;\\n // 最弱的方式;不购买大礼包\\n for (int i = 0; i < needs.size(); i++) {\\n ans += needs[i] * price[i];\\n }\\n\\n // 遍历每个礼包,购买它,看看是不是能获得更便宜的价格\\n for (int i = 0; i < special.size(); i++) {\\n vector<int> next = needs;\\n bool valid = true;\\n // 因为购买的数量需要正好是needs 所以大礼包的某个商品不能超过needs的商品数量\\n for (int item = 0; item < price.size(); item++) {\\n if (special[i][item] > needs[item]) {\\n valid = false;\\n break;\\n }\\n }\\n\\n // 当前大礼包不符合要求 跳过\\n if (!valid) continue;\\n // 当前大礼包符合要求,用next数组记录买过大礼包之后还需要买多少商品\\n for (int item = 0; item < price.size(); item++) {\\n next[item] -= special[i][item];\\n }\\n\\n // 在整个遍历过程中,我们要找价格最小的一种方式\\n ans = min(ans, dfs(next, price, special) + special[i].back());\\n }\\n\\n // 更新cache\\n _cache[needs] = ans;\\n return ans;\\n }\\n\\n};\\n
关于我
\\n大家好,我是微扰酱。现在是五道口【悖论13】剧本杀的股东之一,点评搜索即可,欢迎大家来探店。
\\n
\\n微扰酱18年毕业于上海交通大学,是一个在阿里、字节、腾讯都工作过的工程师,有丰富的面试经验。从 2021.4 开始在emqx从事存储研发,希望在今年多多输出。最后,如果对你有帮助,可以点个赞支持一下我哦 也欢迎在leetcode上关注我。
\\n","description":"解题思路 今天周末;题解还是写的简单一点 大家多看注释哦\\n\\n不知道大家会不会和我一样,看到这个题目就觉得像是动态规划中的背包问题;而动态规划和记忆化搜索其实是等价的,这道题用搜索的思路去解决个人觉得是更简单的。\\n 我们需要做的事情其实很简单,就是如果发现买一些大礼包可以让我们用更便宜的价格买到更多的东西,我们就试图去买一些大礼包来降低我们的成本。但到底怎么买是最优的呢?基于和背包问题类似的原因,我们不能简单的通过贪心策略去得到全局最优解,那么我们只能一个个试。\\n\\n我们在dfs的时候可以用一个needs数组来标记还需要购买的商品;然后去遍历每个大礼包,购买他…","guid":"https://leetcode.cn/problems/shopping-offers//solution/wei-rao-li-lun-kan-qi-lai-xiang-bei-bao-l4trt","author":"wfnuser","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-10-24T06:37:43.737Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题双解 :「转换 DFS」&「完全背包」","url":"https://leetcode.cn/problems/shopping-offers//solution/gong-shui-san-xie-yi-ti-shuang-jie-zhuan-qgk1","content":"写在前面
\\nemmmm 祝大家 $1024$ 节日快乐 🤣
\\n
\\n转换 DFS(转换为礼包处理)
\\n对于某个 $need[i]$ 而言,既可以「单买」也可以使用「礼包形式购买」,同时两种购买方式都存在对「份数」的决策(单买多少份/买多少个相应的礼包)。
\\n利用物品数量和礼包数量数据范围都较少,我们可以先对「单买」情况进行预处理,将其转换为「礼包」形式。若 $price[0] = 100$,则使用礼包 $[1, 0, 0, ...,0, 100]$ 来代指。
\\n然后再预处理每个礼包最多选多少个,并使用哈希表进行存储。
\\n最后使用
\\nDFS
对每个「礼包」如何选择进行爆搜即可。代码:
\\n###Java
\\n\\nclass Solution {\\n int ans = 0x3f3f3f3f;\\n List<Integer> price, needs;\\n List<List<Integer>> special;\\n Map<Integer, Integer> map = new HashMap<>();\\n int n, m;\\n public int shoppingOffers(List<Integer> _price, List<List<Integer>> _special, List<Integer> _needs) {\\n price = _price; special = _special; needs = _needs;\\n n = price.size();\\n List<Integer> temp = new ArrayList<>();\\n for (int i = 0; i < n; i++) temp.add(0);\\n for (int i = 0; i < n; i++) {\\n List<Integer> clone = new ArrayList<>(temp);\\n clone.set(i, 1);\\n clone.add(price.get(i));\\n special.add(clone);\\n }\\n m = special.size();\\n for (int i = 0; i < m; i++) {\\n List<Integer> x = special.get(i);\\n int k = 0;\\n for (int j = 0; j < n; j++) {\\n int a = x.get(j), b = needs.get(j);\\n if (a == 0 || b == 0) continue;\\n if (a > b) {\\n k = 0;\\n break;\\n }\\n k = k == 0 ? b / a : Math.min(k, b / a);\\n }\\n map.put(i, k);\\n }\\n dfs(0, needs, 0);\\n return ans;\\n }\\n void dfs(int u, List<Integer> list, int cur) {\\n if (cur >= ans) return ;\\n int cnt = 0;\\n for (int i = 0; i < n; i++) cnt += list.get(i);\\n if (cnt == 0) {\\n ans = cur;\\n return ;\\n }\\n if (u == m) return;\\n List<Integer> x = special.get(u);\\n out:for (int k = 0; k <= map.get(u); k++) {\\n List<Integer> clist = new ArrayList<>(list);\\n for (int i = 0; i < n; i++) {\\n int a = x.get(i), b = clist.get(i);\\n if (a * k > b) break out;\\n clist.set(i, b - a * k);\\n }\\n dfs(u + 1, clist, cur + k * x.get(n));\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:令物品数量为 $n$,原礼包数量为 $m$。将「单买」预处理成「礼包」,共有 $n$ 种「单买」情况需要转换,同时每个转换需要对数组进行复制,复杂度为 $O(n^2)$,预处理完后,共有 $n + m$ 个礼包;预处理每个礼包所能选的最大数量,复杂度为 $O((n + m) * n)$;对礼包的选择方案进行决策,一个礼包最多选择 $k = \\\\max(needs[i]) = 10$ 个,复杂度为 $O(k ^ {n + m})$。整体复杂度为 $O(k ^ {n + m} + (n + m) * n)$
\\n- 空间复杂度:$O(k ^ {n + m})$
\\n
\\n完全背包
\\n这还是一道很有意思的「完全背包」问题。
\\n不了解「完全背包」的同学,可以先看前置🧀:完全背包。目前「背包问题」专题 已经讲了 $21$ 篇,大概还有 $2$ - $4$ 篇彻底讲完,完全覆盖了所有的「背包问题」。
\\n背包问题难点在于对「成本」和「价值」的抽象。
\\n对于本题,我们可以定义 $f[i, j_0, j_1, j_2, ... , j_{n - 1}]$ 为考虑前 $i$ 个大礼包,购买 $j_0$ 件物品 $0$,购买 $j_1$ 件物品 $1$,....,购买 $j_{n - 1}$ 件物品 $n - 1$ 时的最小花费。
\\n同时,我们有初始化条件 $f[0, 0, 0, ... , 0] = 0$(其余 $f[0, x_0, x_1, x_2, ..., x_n]$ 为正无穷)的初始化条件,最终答案为 $f[m - 1, need[0], need[1], ..., need[n - 1]]$。
\\n这样的朴素完全背包做法复杂度过高,根据我们的前置🧀 完全背包 中的数学推导分析,我们发现完全背包的一维空间优化,是具有优化复杂度的意义。
\\n因此,我们可以对礼包维度进行优化,使用 $f[need[0], need[1], ... , need[n - 1]]$ 来作为状态表示。
\\n不失一般性的考虑 $f[j_0, j_1, ... , j_{n - 1}]$ 该如何转移(以物品 $0$ 为例进行分析,其他同理):
\\n\\n
\\n- 不选择任何大礼包,只进行单买:$f[j_0, j_1, ... , j_{n - 1}] = min(f[j_0, j_1, ... , j_{n - 1}], f[j_0 - 1, j_1, ..., j_{n - 1}] + price[0]$;
\\n- 购买大礼包:$f[j_0, j_1, ... , j_{n - 1}] = min(f[j_0, j_1, ... , j_{n - 1}], f[j_0 - special[i][0], j_1 - special[i][1],, ..., j_{n - 1} - special[i][n - 1]] + special[i][n]$
\\n最终的 $f[j_0, j_1, ... , j_{n - 1}]$ 为上述所有方案中的最小值。
\\n\\n\\n一些细节:实现时,为了防止过多的维度,以及可能存在的 MLE 风险,我们可以对维度进行压缩处理,而最简单的方式可以通过与排列数建立映射关系。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {\\n int n = price.size();\\n int[] g = new int[n + 1];\\n g[0] = 1;\\n for (int i = 1; i <= n; i++) {\\n g[i] = g[i - 1] * (needs.get(i - 1) + 1);\\n }\\n int mask = g[n];\\n int[] f = new int[mask];\\n int[] cnt = new int[n];\\n for (int state = 1; state < mask; state++) {\\n f[state] = 0x3f3f3f3f;\\n Arrays.fill(cnt, 0);\\n for (int i = 0; i < n; i++) {\\n cnt[i] = state % g[i + 1] / g[i];\\n }\\n for (int i = 0; i < n; i++) {\\n if (cnt[i] > 0) f[state] = Math.min(f[state], f[state - g[i]] + price.get(i));\\n }\\n out:for (List<Integer> x : special) {\\n int cur = state;\\n for (int i = 0; i < n; i++) {\\n if (cnt[i] < x.get(i)) continue out;\\n cur -= x.get(i) * g[i];\\n }\\n f[state] = Math.min(f[state], f[cur] + x.get(n));\\n }\\n }\\n return f[mask - 1];\\n }\\n}\\n
\\n
\\n- 时间复杂度:令物品数量为 $n$,原礼包数量为 $m$。每个物品最多需要 $k = \\\\max(needs[i]) = 10$ 个,共有 $k^n$ 个状态需要转移,转移时需要考虑「单买」和「礼包」决策,复杂度分别为 $O(n)$ 和 $O(m * n)$。整体复杂度为 $O(k^n * (m * n))$
\\n- 空间复杂度:$O(k^n * (m * n))$
\\n
\\n背包问题(目录)
\\n\\n
\\n- \\n
\\n01背包 : 背包问题 第一讲
\\n\\n
\\n- \\n
\\n【练习】01背包 : 背包问题 第二讲
\\n- \\n
\\n【学习&练习】01背包 : 背包问题 第三讲
\\n- \\n
\\n【加餐/补充】01 背包:背包问题 第二十一讲
\\n- \\n
\\n完全背包 : 背包问题 第四讲
\\n\\n- \\n
\\n多重背包 : 背包问题 第八讲
\\n- \\n
\\n多重背包(优化篇)
\\n\\n- \\n
\\n混合背包 : 背包问题 第十一讲
\\n- \\n
\\n分组背包 : 背包问题 第十二讲
\\n\\n
\\n- 【练习】分组背包 : 背包问题 第十三讲
\\n- \\n
\\n多维背包
\\n\\n- \\n
\\n树形背包 : 背包问题 第十六讲
\\n\\n- \\n
\\n背包求方案数
\\n\\n[注:因为之前实在找不到题,这道「求方案数」题作为“特殊”的「多维费用背包问题求方案数」讲过]
\\n- \\n
\\n背包求具体方案
\\n\\n
\\n- 【练习】背包求具体方案 : 背包问题 第二十讲
\\n- \\n
\\n泛化背包
\\n\\n
\\n- 【练习】泛化背包
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"写在前面 emmmm 祝大家 $1024$ 节日快乐 🤣\\n\\n转换 DFS(转换为礼包处理)\\n\\n对于某个 $need[i]$ 而言,既可以「单买」也可以使用「礼包形式购买」,同时两种购买方式都存在对「份数」的决策(单买多少份/买多少个相应的礼包)。\\n\\n利用物品数量和礼包数量数据范围都较少,我们可以先对「单买」情况进行预处理,将其转换为「礼包」形式。若 $price[0] = 100$,则使用礼包 $[1, 0, 0, ...,0, 100]$ 来代指。\\n\\n然后再预处理每个礼包最多选多少个,并使用哈希表进行存储。\\n\\n最后使用 DFS 对每个「礼包…","guid":"https://leetcode.cn/problems/shopping-offers//solution/gong-shui-san-xie-yi-ti-shuang-jie-zhuan-qgk1","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-10-24T00:47:39.905Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"大礼包","url":"https://leetcode.cn/problems/shopping-offers//solution/da-li-bao-by-leetcode-solution-p1ww","content":"方法一:记忆化搜索
\\n思路
\\n首先,我们过滤掉不需要计算的大礼包。
\\n如果大礼包完全没有优惠(大礼包的价格大于等于原价购买大礼包内所有物品的价格),或者大礼包内不包含任何的物品,那么购买这些大礼包不可能使整体价格降低。因此,我们可以不考虑这些大礼包,并将它们过滤掉,以提高效率和方便编码。
\\n因为题目规定了「不能购买超出购物清单指定数量的物品」,所以只要我们购买过滤后的大礼包,都一定可以令整体价格降低。
\\n然后,我们计算满足购物清单所需花费的最低价格。
\\n因为 $1 \\\\le \\\\textit{needs} \\\\le 6$ 和 $0 \\\\le needs[i] \\\\le 10$,所以最多只有 $11^6 = 1771561$ 种不同的购物清单 $\\\\textit{needs}$。我们可以将所有可能的购物清单作为状态,并考虑这些状态之间相互转移的方法。
\\n用 $\\\\textit{dp}[\\\\textit{needs}]$ 表示满足购物清单 $\\\\textit{needs}$ 所需花费的最低价格。在进行状态转移时,我们考虑在满足购物清单 $\\\\textit{needs}$ 时的最后一次购买;其中,将原价购买购物清单中的所有物品也视作一次购买。具体地,有以下两种情况:
\\n\\n
\\n- \\n
\\n第一种情况是购买大礼包,此时状态转移方程为:
\\n$$
\\n
\\n\\\\textit{dp}[\\\\textit{needs}] = \\\\min_{i \\\\in K} {\\\\textit{price}_i + \\\\textit{dp}[\\\\textit{needs} - \\\\textit{needs}_i]}
\\n$$其中,$K$ 表示所有可以购买的大礼包的下标集合,$i$ 表示其中一个大礼包的下标,$\\\\textit{price}_i$ 表示第 $i$ 个大礼包的价格,$\\\\textit{needs}_i$ 表示大礼包中包含的物品清单,$\\\\textit{needs} - \\\\textit{needs}_i$ 表示购物清单 $\\\\textit{needs}$ 减去第 $i$ 个大礼包中包含的物品清单后剩余的物品清单。
\\n- \\n
\\n第二种情况是不购买任何大礼包,原价购买购物清单中的所有物品,此时 $\\\\textit{dp}[\\\\textit{needs}]$ 可以直接求出。
\\n$\\\\textit{dp}[\\\\textit{needs}]$ 为这两种情况的最小值。
\\n算法
\\n因为大礼包中可能包含多个物品,所以并不是所有状态都可以得到。因此,我们使用记忆化搜索而不是完全遍历的方法,来计算出满足每个购物清单 $\\\\textit{needs}$ 所需花费的最低价格。
\\n具体地,在计算满足当前购物清单 $\\\\textit{cur_needs}$ 所需花费的最低价格 $\\\\textit{min_price}$ 时,我们可以采用如下方法:
\\n\\n
\\n- \\n
\\n将 $\\\\textit{min_price}$ 初始化为原价购买购物清单 $\\\\textit{cur_needs}$ 中的所有物品的花费;
\\n- \\n
\\n逐个遍历所有可以购买的大礼包,不妨设当前遍历的大礼包为 $\\\\textit{cur_special}$,其价格为 $\\\\textit{special_price}$:
\\n\\n
\\n- \\n
\\n计算购买大礼包 $\\\\textit{cur_special}$ 后新的购物清单 $\\\\textit{nxt_needs}$,并递归地计算满足购物清单 $\\\\textit{nxt_needs}$ 所需花费的最低价格 $\\\\textit{nxt_price}$;
\\n- \\n
\\n计算满足当前购物清单 $\\\\textit{cur_needs}$ 所需花费的最低价格 $\\\\textit{cur_price} = \\\\textit{special_price} + \\\\textit{nxt_price}$;
\\n- \\n
\\n如果 $\\\\textit{cur_price} < \\\\textit{min_price}$,则将 $\\\\textit{min_price}$ 更新为 $\\\\textit{cur_price}$。
\\n- \\n
\\n返回计算满足购物清单 $\\\\textit{cur_needs}$ 所需花费的最低价格 $\\\\textit{min_price}$。
\\n细节
\\n我们也可以考虑使用状态压缩的方法来存储购物清单 $\\\\textit{needs}$。但是因为购物清单中每种物品都可能有 $[0,10]$ 个,使用状态压缩需要设计一个相对复杂的方法来解决计算状态变化以及比较状态大小的问题,性价比较低,所以在这里不使用状态压缩的方法,感兴趣的读者可以自行实现。
\\n代码
\\n###Python
\\n\\nfrom functools import lru_cache\\n\\nclass Solution:\\n def shoppingOffers(self, price: List[int], special: List[List[int]], needs: List[int]) -> int:\\n n = len(price)\\n\\n # 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n filter_special = []\\n for sp in special:\\n if sum(sp[i] for i in range(n)) > 0 and sum(sp[i] * price[i] for i in range(n)) > sp[-1]:\\n filter_special.append(sp)\\n\\n # 记忆化搜索计算满足购物清单所需花费的最低价格\\n @lru_cache(None)\\n def dfs(cur_needs):\\n # 不购买任何大礼包,原价购买购物清单中的所有物品\\n min_price = sum(need * price[i] for i, need in enumerate(cur_needs))\\n for cur_special in filter_special:\\n special_price = cur_special[-1]\\n nxt_needs = []\\n for i in range(n):\\n if cur_special[i] > cur_needs[i]: # 不能购买超出购物清单指定数量的物品\\n break\\n nxt_needs.append(cur_needs[i] - cur_special[i])\\n if len(nxt_needs) == n: # 大礼包可以购买\\n min_price = min(min_price, dfs(tuple(nxt_needs)) + special_price)\\n return min_price\\n\\n return dfs(tuple(needs))\\n
###Java
\\n\\nclass Solution {\\n Map<List<Integer>, Integer> memo = new HashMap<List<Integer>, Integer>();\\n\\n public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {\\n int n = price.size();\\n\\n // 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n List<List<Integer>> filterSpecial = new ArrayList<List<Integer>>();\\n for (List<Integer> sp : special) {\\n int totalCount = 0, totalPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n totalCount += sp.get(i);\\n totalPrice += sp.get(i) * price.get(i);\\n }\\n if (totalCount > 0 && totalPrice > sp.get(n)) {\\n filterSpecial.add(sp);\\n }\\n }\\n\\n return dfs(price, special, needs, filterSpecial, n);\\n }\\n\\n // 记忆化搜索计算满足购物清单所需花费的最低价格\\n public int dfs(List<Integer> price, List<List<Integer>> special, List<Integer> curNeeds, List<List<Integer>> filterSpecial, int n) {\\n if (!memo.containsKey(curNeeds)) {\\n int minPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n minPrice += curNeeds.get(i) * price.get(i); // 不购买任何大礼包,原价购买购物清单中的所有物品\\n }\\n for (List<Integer> curSpecial : filterSpecial) {\\n int specialPrice = curSpecial.get(n);\\n List<Integer> nxtNeeds = new ArrayList<Integer>();\\n for (int i = 0; i < n; ++i) {\\n if (curSpecial.get(i) > curNeeds.get(i)) { // 不能购买超出购物清单指定数量的物品\\n break;\\n }\\n nxtNeeds.add(curNeeds.get(i) - curSpecial.get(i));\\n }\\n if (nxtNeeds.size() == n) { // 大礼包可以购买\\n minPrice = Math.min(minPrice, dfs(price, special, nxtNeeds, filterSpecial, n) + specialPrice);\\n }\\n }\\n memo.put(curNeeds, minPrice);\\n }\\n return memo.get(curNeeds);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n Dictionary<IList<int>, int> memo = new Dictionary<IList<int>, int>();\\n\\n public int ShoppingOffers(IList<int> price, IList<IList<int>> special, IList<int> needs) {\\n int n = price.Count;\\n\\n // 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n IList<IList<int>> filterSpecial = new List<IList<int>>();\\n foreach (IList<int> sp in special) {\\n int totalCount = 0, totalPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n totalCount += sp[i];\\n totalPrice += sp[i] * price[i];\\n }\\n if (totalCount > 0 && totalPrice > sp[n]) {\\n filterSpecial.Add(sp);\\n }\\n }\\n\\n return DFS(price, special, needs, filterSpecial, n);\\n }\\n\\n // 记忆化搜索计算满足购物清单所需花费的最低价格\\n public int DFS(IList<int> price, IList<IList<int>> special, IList<int> curNeeds, IList<IList<int>> filterSpecial, int n) {\\n if (!memo.ContainsKey(curNeeds)) {\\n int minPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n minPrice += curNeeds[i] * price[i]; // 不购买任何大礼包,原价购买购物清单中的所有物品\\n }\\n foreach (IList<int> curSpecial in filterSpecial) {\\n int specialPrice = curSpecial[n];\\n IList<int> nxtNeeds = new List<int>();\\n for (int i = 0; i < n; ++i) {\\n if (curSpecial[i] > curNeeds[i]) { // 不能购买超出购物清单指定数量的物品\\n break;\\n }\\n nxtNeeds.Add(curNeeds[i] - curSpecial[i]);\\n }\\n if (nxtNeeds.Count == n) { // 大礼包可以购买\\n minPrice = Math.Min(minPrice, DFS(price, special, nxtNeeds, filterSpecial, n) + specialPrice);\\n }\\n }\\n memo.Add(curNeeds, minPrice);\\n }\\n return memo[curNeeds];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n map<vector<int>, int> memo;\\n\\n int shoppingOffers(vector<int>& price, vector<vector<int>>& special, vector<int>& needs) {\\n int n = price.size();\\n\\n // 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n vector<vector<int>> filterSpecial;\\n for (auto & sp : special) {\\n int totalCount = 0, totalPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n totalCount += sp[i];\\n totalPrice += sp[i] * price[i];\\n }\\n if (totalCount > 0 && totalPrice > sp[n]) {\\n filterSpecial.emplace_back(sp);\\n }\\n }\\n\\n return dfs(price, special, needs, filterSpecial, n);\\n }\\n\\n // 记忆化搜索计算满足购物清单所需花费的最低价格\\n int dfs(vector<int> price,const vector<vector<int>> & special, vector<int> curNeeds, vector<vector<int>> & filterSpecial, int n) {\\n if (!memo.count(curNeeds)) {\\n int minPrice = 0;\\n for (int i = 0; i < n; ++i) {\\n minPrice += curNeeds[i] * price[i]; // 不购买任何大礼包,原价购买购物清单中的所有物品\\n }\\n for (auto & curSpecial : filterSpecial) {\\n int specialPrice = curSpecial[n];\\n vector<int> nxtNeeds;\\n for (int i = 0; i < n; ++i) {\\n if (curSpecial[i] > curNeeds[i]) { // 不能购买超出购物清单指定数量的物品\\n break;\\n }\\n nxtNeeds.emplace_back(curNeeds[i] - curSpecial[i]);\\n }\\n if (nxtNeeds.size() == n) { // 大礼包可以购买\\n minPrice = min(minPrice, dfs(price, special, nxtNeeds, filterSpecial, n) + specialPrice);\\n }\\n }\\n memo[curNeeds] = minPrice;\\n }\\n return memo[curNeeds];\\n }\\n};\\n
###JavaScript
\\n\\nvar shoppingOffers = function(price, special, needs) {\\n const memo = new Map();\\n\\n // 记忆化搜索计算满足购物清单所需花费的最低价格\\n const dfs = (price, special, curNeeds, filterSpecial, n) => {\\n if (!memo.has(curNeeds)) {\\n let minPrice = 0;\\n for (let i = 0; i < n; ++i) {\\n minPrice += curNeeds[i] * price[i]; // 不购买任何大礼包,原价购买购物清单中的所有物品\\n }\\n for (const curSpecial of filterSpecial) {\\n const specialPrice = curSpecial[n];\\n const nxtNeeds = [];\\n for (let i = 0; i < n; ++i) {\\n if (curSpecial[i] > curNeeds[i]) { // 不能购买超出购物清单指定数量的物品\\n break;\\n }\\n nxtNeeds.push(curNeeds[i] - curSpecial[i]);\\n }\\n if (nxtNeeds.length === n) { // 大礼包可以购买\\n minPrice = Math.min(minPrice, dfs(price, special, nxtNeeds, filterSpecial, n) + specialPrice);\\n }\\n }\\n memo.set(curNeeds, minPrice);\\n }\\n return memo.get(curNeeds);\\n }\\n\\n const n = price.length;\\n\\n // 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n const filterSpecial = [];\\n for (const sp of special) {\\n let totalCount = 0, totalPrice = 0;\\n for (let i = 0; i < n; ++i) {\\n totalCount += sp[i];\\n totalPrice += sp[i] * price[i];\\n }\\n if (totalCount > 0 && totalPrice > sp[n]) {\\n filterSpecial.push(sp);\\n }\\n }\\n\\n return dfs(price, special, needs, filterSpecial, n);\\n};\\n
###go
\\n\\nfunc shoppingOffers(price []int, special [][]int, needs []int) int {\\n n := len(price)\\n\\n // 过滤不需要计算的大礼包,只保留需要计算的大礼包\\n filterSpecial := [][]int{}\\n for _, s := range special {\\n totalCount, totalPrice := 0, 0\\n for i, c := range s[:n] {\\n totalCount += c\\n totalPrice += c * price[i]\\n }\\n if totalCount > 0 && totalPrice > s[n] {\\n filterSpecial = append(filterSpecial, s)\\n }\\n }\\n\\n // 记忆化搜索计算满足购物清单所需花费的最低价格\\n dp := map[string]int{}\\n var dfs func([]byte) int\\n dfs = func(curNeeds []byte) (minPrice int) {\\n if res, has := dp[string(curNeeds)]; has {\\n return res\\n }\\n for i, p := range price {\\n minPrice += int(curNeeds[i]) * p // 不购买任何大礼包,原价购买购物清单中的所有物品\\n }\\n nextNeeds := make([]byte, n)\\n outer:\\n for _, s := range filterSpecial {\\n for i, need := range curNeeds {\\n if need < byte(s[i]) { // 不能购买超出购物清单指定数量的物品\\n continue outer\\n }\\n nextNeeds[i] = need - byte(s[i])\\n }\\n minPrice = min(minPrice, dfs(nextNeeds)+s[n])\\n }\\n dp[string(curNeeds)] = minPrice\\n return\\n }\\n\\n curNeeds := make([]byte, n)\\n for i, need := range needs {\\n curNeeds[i] = byte(need)\\n }\\n return dfs(curNeeds)\\n}\\n\\nfunc min(a, b int) int {\\n if a > b {\\n return b\\n }\\n return a\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:记忆化搜索 思路\\n\\n首先,我们过滤掉不需要计算的大礼包。\\n\\n如果大礼包完全没有优惠(大礼包的价格大于等于原价购买大礼包内所有物品的价格),或者大礼包内不包含任何的物品,那么购买这些大礼包不可能使整体价格降低。因此,我们可以不考虑这些大礼包,并将它们过滤掉,以提高效率和方便编码。\\n\\n因为题目规定了「不能购买超出购物清单指定数量的物品」,所以只要我们购买过滤后的大礼包,都一定可以令整体价格降低。\\n\\n然后,我们计算满足购物清单所需花费的最低价格。\\n\\n因为 $1 \\\\le \\\\textit{needs} \\\\le 6$ 和 $0 \\\\le needs[i] \\\\le 10$…","guid":"https://leetcode.cn/problems/shopping-offers//solution/da-li-bao-by-leetcode-solution-p1ww","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-10-23T04:37:28.784Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单放假模拟题目","url":"https://leetcode.cn/problems/destination-city//solution/gong-shui-san-xie-jian-dan-fang-jia-mo-n-y47c","content":"- \\n
\\n时间复杂度:$O(n \\\\times k \\\\times m^n)$,其中 $k$ 表示大礼包的数量,$m$ 表示每种物品的需求量的可能情况数(等于最大需求量加 $1$),$n$ 表示物品数量。我们最多需要处理 $m^n$ 个状态,每个状态需要遍历 $k$ 种大礼包的情况,每个大礼包需要遍历 $n$ 种商品以检查大礼包是否可以购买。
\\n- \\n
\\n空间复杂度:$O(n \\\\times m^n)$,用于存储记忆化搜索中 $m^n$ 个状态的计算结果,每个状态需要存储 $n$ 个商品的需求量。
\\n写在前面
\\n祝大家国庆快乐 😊 吃好玩好 🍭🍰🚚🌊🎆
\\n
\\n模拟 + 哈希表
\\n根据题意,我们可以取一个任意城市作为起点,然后使用 $paths$ 中的路线信息开始搜索,直到当前城市无法到达下一个城市,即是答案。
\\n实现上,为了可以快速找到某个城市所能到达的城市,可以先使用哈希表对 $paths$ 中的路线信息进行预处理。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public String destCity(List<List<String>> ps) {\\n Map<String, String> map = new HashMap<>();\\n for (List<String> p : ps) map.put(p.get(0), p.get(1));\\n String ans = ps.get(0).get(0);\\n while (map.containsKey(ans)) ans = map.get(ans);\\n return ans;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n最后
\\n题太简单?不如
\\n加练如下题目去跳舞 💃🏻💃🏻注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"写在前面 祝大家国庆快乐 😊 吃好玩好 🍭🍰🚚🌊🎆\\n\\n模拟 + 哈希表\\n\\n根据题意,我们可以取一个任意城市作为起点,然后使用 $paths$ 中的路线信息开始搜索,直到当前城市无法到达下一个城市,即是答案。\\n\\n实现上,为了可以快速找到某个城市所能到达的城市,可以先使用哈希表对 $paths$ 中的路线信息进行预处理。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public String destCity(List> ps) {\\n Map
map…","guid":"https://leetcode.cn/problems/destination-city//solution/gong-shui-san-xie-jian-dan-fang-jia-mo-n-y47c","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-30T23:03:47.619Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【算法小爱】简单模拟,国庆快乐!(附带技术类文章目录)","url":"https://leetcode.cn/problems/destination-city//solution/suan-fa-xiao-ai-guo-qing-kuai-le-by-hele-lgyh","content":" \\n\\n大家好,我是小爱,一个热爱算法并不断努力的女孩子!关注我,和我一起交流算法心得呀!
\\n
\\n解法:模拟
\\n题目要求我们找出终点站,终点站有如下特征:
\\n\\n
\\n- 至少有一条线路去向该终点站
\\n- 没有线路从终点站出发
\\n由此,对于每一个
\\nstring
类型代表的城市,我们可以分别用两个数组记录从该城市出发的线路数量(出度),以及去向该城市的线路数量(入度)。然后我们遍历所有城市,如果一个城市的出度为 $0$ 且入度不为 $0$,说明该城市就是我们要找的终点站。算法细节:
\\n由于我们需要对应城市记录出度以及入度,为了方便起见,我们利用哈希表记录。哈希表键为
\\nstring
类型的城市,值为int
型记录的出度或入度。根据上面分析,终点站出度为 $0$,且入度不为 $0$,所以我们只需要遍历记录入度的哈希表中的所有城市(此时保证了这些城市入度不为 $0$,否则不会被记录到哈希表中),判断出度是否为 $0$ 即可。
\\n
\\n代码:
\\n\\nclass Solution {\\npublic:\\n string destCity(vector<vector<string>>& paths) {\\n // in 记录入度,out 记录出度\\n unordered_map<string, int> in, out;\\n for(auto p : paths) {\\n // 遍历所有线路,出发城市出度加一,结束城市入度加一\\n out[p[0]]++;\\n in[p[1]]++;\\n }\\n for(auto [city, time] : in) {\\n // 遍历入度哈希表,如果当前城市 city 出度为 0,则为终点站\\n if(out[city] == 0) return city;\\n }\\n return \\"\\";\\n }\\n};\\n
\\n复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(n)$,其中 $n$ 为总城市数量
\\n- 空间复杂度:$O(n)$,分别用两个哈希表记录所有城市信息
\\n
\\n推荐阅读
\\n小爱每周持续推出原创 C++、算法类文章,欢迎大家提出宝贵意见呀!
\\n\\n","description":"大家好,我是小爱,一个热爱算法并不断努力的女孩子!关注我,和我一起交流算法心得呀! 解法:模拟\\n\\n题目要求我们找出终点站,终点站有如下特征:\\n\\n至少有一条线路去向该终点站\\n没有线路从终点站出发\\n\\n由此,对于每一个 string 类型代表的城市,我们可以分别用两个数组记录从该城市出发的线路数量(出度),以及去向该城市的线路数量(入度)。然后我们遍历所有城市,如果一个城市的出度为 $0$ 且入度不为 $0$,说明该城市就是我们要找的终点站。\\n\\n算法细节:\\n\\n由于我们需要对应城市记录出度以及入度,为了方便起见,我们利用哈希表记录。哈希表键为 string 类型…","guid":"https://leetcode.cn/problems/destination-city//solution/suan-fa-xiao-ai-guo-qing-kuai-le-by-hele-lgyh","author":"BNDSllx","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-30T16:10:03.361Z","media":[{"url":"https://pic.leetcode-cn.com/1633062833-RhlOvw-image.png","type":"photo","width":900,"height":383,"blurhash":"LJRfa*.AIT?bNaaJNGxu.8t7aztQ"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"旅行终点站","url":"https://leetcode.cn/problems/destination-city//solution/lu-xing-zhong-dian-zhan-by-leetcode-solu-pscd","content":"
\\nC++ 类 第一部分
\\nC++ 基础语法
\\n树状数组方法一:哈希表
\\n根据终点站的定义,终点站不会出现在 $\\\\textit{cityA}_i$ 中,因为存在从 $\\\\textit{cityA}_i$ 出发的线路,所以终点站只会出现在 $\\\\textit{cityB}_i$ 中。据此,我们可以遍历 $\\\\textit{cityB}_i$,返回不在 $\\\\textit{cityA}_i$ 中的城市,即为答案。
\\n代码实现时,可以先将所有 $\\\\textit{cityA}_i$ 存于一哈希表中,然后遍历 $\\\\textit{cityB}_i$ 并查询 $\\\\textit{cityB}_i$ 是否在哈希表中。
\\n###Python
\\n\\nclass Solution:\\n def destCity(self, paths: List[List[str]]) -> str:\\n citiesA = {path[0] for path in paths}\\n return next(path[1] for path in paths if path[1] not in citiesA)\\n
###C++
\\n\\nclass Solution {\\npublic:\\n string destCity(vector<vector<string>> &paths) {\\n unordered_set<string> citiesA;\\n for (auto &path : paths) {\\n citiesA.insert(path[0]);\\n }\\n for (auto &path : paths) {\\n if (!citiesA.count(path[1])) {\\n return path[1];\\n }\\n }\\n return \\"\\";\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public String destCity(List<List<String>> paths) {\\n Set<String> citiesA = new HashSet<String>();\\n for (List<String> path : paths) {\\n citiesA.add(path.get(0));\\n }\\n for (List<String> path : paths) {\\n if (!citiesA.contains(path.get(1))) {\\n return path.get(1);\\n }\\n }\\n return \\"\\";\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public string DestCity(IList<IList<string>> paths) {\\n ISet<string> citiesA = new HashSet<string>();\\n foreach (IList<string> path in paths) {\\n citiesA.Add(path[0]);\\n }\\n foreach (IList<string> path in paths) {\\n if (!citiesA.Contains(path[1])) {\\n return path[1];\\n }\\n }\\n return \\"\\";\\n }\\n}\\n
###Go
\\n\\nfunc destCity(paths [][]string) string {\\n citiesA := map[string]bool{}\\n for _, path := range paths {\\n citiesA[path[0]] = true\\n }\\n for _, path := range paths {\\n if !citiesA[path[1]] {\\n return path[1]\\n }\\n }\\n return \\"\\"\\n}\\n
###JavaScript
\\n\\nvar destCity = function(paths) {\\n const citiesA = new Set();\\n for (const path of paths) {\\n citiesA.add(path[0]);\\n }\\n for (const path of paths) {\\n if (!citiesA.has(path[1])) {\\n return path[1];\\n }\\n }\\n return \\"\\";\\n};\\n
###TypeScript
\\n\\nfunction destCity(paths: string[][]): string {\\n const citiesA = new Set();\\n for (const path of paths) {\\n citiesA.add(path[0]);\\n }\\n for (const path of paths) {\\n if (!citiesA.has(path[1])) {\\n return path[1];\\n }\\n }\\n return \\"\\";\\n};\\n
###Rust
\\n\\nuse std::collections::HashSet;\\n\\nimpl Solution {\\n pub fn dest_city(paths: Vec<Vec<String>>) -> String {\\n let mut citiesA: HashSet<String> = HashSet::new();\\n for path in &paths {\\n citiesA.insert(path[0].clone());\\n }\\n for path in &paths {\\n if !citiesA.contains(&path[1]) {\\n return path[1].clone();\\n }\\n }\\n String::from(\\"\\")\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func destCity(paths: ArrayList<ArrayList<String>>): String {\\n let citiesA = HashSet<String>()\\n for (path in paths) {\\n citiesA.put(path[0])\\n }\\n for (path in paths) {\\n if (!citiesA.contains(path[1])) {\\n return path[1]\\n }\\n }\\n return \\"\\"\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:哈希表 根据终点站的定义,终点站不会出现在 $\\\\textit{cityA}_i$ 中,因为存在从 $\\\\textit{cityA}_i$ 出发的线路,所以终点站只会出现在 $\\\\textit{cityB}_i$ 中。据此,我们可以遍历 $\\\\textit{cityB}_i$,返回不在 $\\\\textit{cityA}_i$ 中的城市,即为答案。\\n\\n代码实现时,可以先将所有 $\\\\textit{cityA}_i$ 存于一哈希表中,然后遍历 $\\\\textit{cityB}_i$ 并查询 $\\\\textit{cityB}_i$ 是否在哈希表中。\\n\\n###Python…","guid":"https://leetcode.cn/problems/destination-city//solution/lu-xing-zhong-dian-zhan-by-leetcode-solu-pscd","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-30T15:23:26.449Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"map一行代码","url":"https://leetcode.cn/problems/build-array-from-permutation//solution/mapyi-xing-dai-ma-by-zhanhongzhu-9dl6","content":"- \\n
\\n时间复杂度:$O(nm)$,其中 $n$ 是数组 $\\\\textit{paths}$ 的长度,$m$ 是城市名称的最大长度。
\\n- \\n
\\n空间复杂度:$O(nm)$。
\\n###js
\\n\\n","description":"###js var buildArray = function(nums) {\\n return nums.map(v=>nums[v])\\n};","guid":"https://leetcode.cn/problems/build-array-from-permutation//solution/mapyi-xing-dai-ma-by-zhanhongzhu-9dl6","author":"zhanhongzhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-29T07:26:09.855Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"2012. 数组美丽值求和 前后缀思路标记最值","url":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/bian-li-shu-zu-san-ci-by-agvensome-9yf6","content":"var buildArray = function(nums) {\\n return nums.map(v=>nums[v])\\n};\\n
思想
\\n
\\n前缀和思想。题目2012. 数组美丽值求和好像接雨水的挡板问题,美丽值就是左边最大值比自己小,右边最小值比自己大###cpp
\\n\\n","description":"思想 前缀和思想。题目2012. 数组美丽值求和好像接雨水的挡板问题,美丽值就是左边最大值比自己小,右边最小值比自己大\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int sumOfBeauties(vectorclass Solution {\\npublic:\\n int sumOfBeauties(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> l_max(n, INT_MIN); l_max[0] = nums[0]; // 某元素左边最大元素\\n vector<int> r_min(n, INT_MAX); r_min[n-2] = nums[n-1]; // 某元素右边最小元素\\n for (int i = 1; i < n; ++i) {\\n l_max[i] = max( l_max[i-1], nums[i-1] );\\n }\\n for (int i = n-2; i >= 0; --i) {\\n r_min[i] = min( r_min[i+1], nums[i+1] );\\n }\\n int ans = 0;\\n for (int i = 1; i < n-1; ++i) {\\n if (nums[i] > l_max[i] && nums[i] < r_min[i]) ans += 2;\\n else if (nums[i] > nums[i-1] && nums[i] < nums[i+1]) ++ans;\\n }\\n return ans;\\n }\\n};\\n
& nums) {\\n int n = nums.size();\\n vector l_max(n, INT_MIN); l_max[0] = nums[0]; // 某元素左边最大元素\\n vector r_min(n, INT…","guid":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/bian-li-shu-zu-san-ci-by-agvensome-9yf6","author":"agvensome","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-19T04:24:55.338Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"后向最大值遍历+前向最小值遍历","url":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/hou-xiang-zui-da-zhi-bian-li-qian-xiang-h0reo","content":" \\n
\\n第n个数如果为满足条件1,那么必是前面所有数的最大值,也是后面所有数的最小值两个情况不好同时实现,分两个遍历进行标记
\\n\\nclass Solution {\\npublic:\\n int sumOfBeauties(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> flag(n);\\n int min = nums[0], max = nums[n-1];\\n //向后遍历,编辑并更新最大值\\n for (int i = 1; i < n; ++i)\\n if(nums[i] > min){\\n min = nums[i];\\n flag[i]++;\\n }\\n //向前遍历,标记并更新最小值\\n for (int i = n - 2; i >= 1; --i)\\n if(nums[i] < max){\\n max = nums[i];\\n flag[i]++;\\n }\\n int ans = 0;\\n for (int i = 1; i < n - 1; ++i) {\\n //满足条件2,条件1也满足\\n if(nums[i - 1] < nums[i] && nums[i] < nums[i + 1])\\n ans ++;\\n //满足条件1,但因为前面已经算过一次,无需×2\\n if(flag[i]==2)\\n ans++;\\n }\\n return ans;\\n }\\n};\\n
值得注意的是,满足条件1一定满足条件2,所以先计算条件2,条件1就无需乘以2了
\\n","description":"第n个数如果为满足条件1,那么必是前面所有数的最大值,也是后面所有数的最小值\\n\\n两个情况不好同时实现,分两个遍历进行标记\\nclass Solution {\\npublic:\\n int sumOfBeauties(vector& nums) {\\n int n = nums.size();\\n vector flag(n);\\n int min = nums[0], max = nums[n-1];\\n //向后遍历,编辑并更新最大值\\n for (int i = 1; i…","guid":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/hou-xiang-zui-da-zhi-bian-li-qian-xiang-h0reo","author":"sherspidey","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-19T04:16:15.478Z","media":[{"url":"https://pic.leetcode-cn.com/1632024820-vBdjye-image.png","type":"photo","width":455,"height":125,"blurhash":"L05hfD?a4UMzJ{tOVz4U9X%f?Jx1"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"前缀最大值+后缀最小值(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/qian-zhui-zui-da-zhi-hou-zhui-zui-xiao-z-h9qz","content":" \\n\\n对于所有 $0 \\\\le j < i$ 且 $i < k \\\\le n - 1$,满足 $\\\\textit{nums}[j] < \\\\textit{nums}[i] < \\\\textit{nums}[k]$。
\\n题目的这个要求,相当于:
\\n\\n
\\n- $\\\\textit{nums}[i]$ 要大于 $i$ 左边的所有数,也就是大于前缀 $[0,i-1]$ 中的最大值。
\\n- $\\\\textit{nums}[i]$ 要小于 $i$ 右边的所有数,也就是小于后缀 $[i+1,n-1]$ 中的最小值。
\\n这可以通过遍历算出来。
\\n定义 $\\\\textit{sufMin}[i]$ 表示后缀 $[i,n-1]$ 中的最小值。
\\n那么 $\\\\textit{sufMin}[i]$ 等于 $\\\\textit{nums}[i]$ 与后缀 $[i+1,n-1]$ 中的最小值,二者取最小值,即
\\n$$
\\n
\\n\\\\textit{sufMin}[i] = \\\\min(\\\\textit{nums}[i], \\\\textit{sufMin}[i+1])
\\n$$注意上式需要从右到左遍历 $\\\\textit{nums}$ 计算。
\\n对于前缀最大值,也同理。
\\n我们可以在从左到右遍历 $\\\\textit{nums}$ 的过程中,维护前缀最大值 $\\\\textit{preMax}$。注意这只需要一个变量,因为我们可以一边计算 $\\\\textit{preMax}$,一边计算答案。
\\n\\nclass Solution:\\n def sumOfBeauties(self, nums: List[int]) -> int:\\n n = len(nums)\\n suf_min = [0] * n # 后缀最小值\\n suf_min[n - 1] = nums[n - 1]\\n for i in range(n - 2, 1, -1):\\n suf_min[i] = min(suf_min[i + 1], nums[i])\\n\\n ans = 0\\n pre_max = nums[0] # 前缀最大值\\n for i in range(1, n - 1):\\n x = nums[i]\\n # 此时 pre_max 表示 [0, i-1] 中的最大值\\n if pre_max < x < suf_min[i + 1]:\\n ans += 2\\n elif nums[i - 1] < x < nums[i + 1]:\\n ans += 1\\n # 更新后 pre_max 表示 [0, i] 中的最大值\\n pre_max = max(pre_max, x)\\n return ans\\n
\\nclass Solution {\\n public int sumOfBeauties(int[] nums) {\\n int n = nums.length;\\n int[] sufMin = new int[n]; // 后缀最小值\\n sufMin[n - 1] = nums[n - 1];\\n for (int i = n - 2; i > 1; i--) {\\n sufMin[i] = Math.min(sufMin[i + 1], nums[i]);\\n }\\n\\n int ans = 0;\\n int preMax = nums[0]; // 前缀最大值\\n for (int i = 1; i < n - 1; i++) {\\n int x = nums[i];\\n // 此时 preMax 表示 [0, i-1] 中的最大值\\n if (preMax < x && x < sufMin[i + 1]) {\\n ans += 2;\\n } else if (nums[i - 1] < x && x < nums[i + 1]) {\\n ans++;\\n }\\n // 更新后 preMax 表示 [0, i] 中的最大值\\n preMax = Math.max(preMax, x);\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int sumOfBeauties(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> suf_min(n); // 后缀最小值\\n suf_min[n - 1] = nums[n - 1];\\n for (int i = n - 2; i > 1; i--) {\\n suf_min[i] = min(suf_min[i + 1], nums[i]);\\n }\\n\\n int ans = 0;\\n int pre_max = nums[0]; // 前缀最大值\\n for (int i = 1; i < n - 1; i++) {\\n int x = nums[i];\\n // 此时 pre_max 表示 [0, i-1] 中的最大值\\n if (pre_max < x && x < suf_min[i + 1]) {\\n ans += 2;\\n } else if (nums[i - 1] < x && x < nums[i + 1]) {\\n ans++;\\n }\\n // 更新后 pre_max 表示 [0, i] 中的最大值\\n pre_max = max(pre_max, x);\\n }\\n return ans;\\n }\\n};\\n
\\n#define MIN(a, b) ((b) < (a) ? (b) : (a))\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint sumOfBeauties(int* nums, int numsSize) {\\n int* suf_min = malloc(numsSize * sizeof(int)); // 后缀最小值\\n suf_min[numsSize - 1] = nums[numsSize - 1];\\n for (int i = numsSize - 2; i > 1; i--) {\\n suf_min[i] = MIN(suf_min[i + 1], nums[i]);\\n }\\n\\n int ans = 0;\\n int pre_max = nums[0]; // 前缀最大值\\n for (int i = 1; i < numsSize - 1; i++) {\\n int x = nums[i];\\n // 此时 pre_max 表示 [0, i-1] 中的最大值\\n if (pre_max < x && x < suf_min[i + 1]) {\\n ans += 2;\\n } else if (nums[i - 1] < x && x < nums[i + 1]) {\\n ans++;\\n }\\n // 更新后 pre_max 表示 [0, i] 中的最大值\\n pre_max = MAX(pre_max, x);\\n }\\n\\n free(suf_min);\\n return ans;\\n}\\n
\\nfunc sumOfBeauties(nums []int) (ans int) {\\nn := len(nums)\\nsufMin := make([]int, n) // 后缀最小值\\nsufMin[n-1] = nums[n-1]\\nfor i := n - 2; i > 1; i-- {\\nsufMin[i] = min(sufMin[i+1], nums[i])\\n}\\n\\npreMax := nums[0] // 前缀最大值\\nfor i := 1; i < n-1; i++ {\\nx := nums[i]\\n// 此时 preMax 表示 [0, i-1] 中的最大值\\nif preMax < x && x < sufMin[i+1] {\\nans += 2\\n} else if nums[i-1] < x && x < nums[i+1] {\\nans++\\n}\\n// 更新后 preMax 表示 [0, i] 中的最大值\\npreMax = max(preMax, x)\\n}\\nreturn\\n}\\n
\\nvar sumOfBeauties = function(nums) {\\n const n = nums.length;\\n const sufMin = Array(n); // 后缀最小值\\n sufMin[n - 1] = nums[n - 1];\\n for (let i = n - 2; i > 1; i--) {\\n sufMin[i] = Math.min(sufMin[i + 1], nums[i]);\\n }\\n\\n let ans = 0;\\n let preMax = nums[0]; // 前缀最大值\\n for (let i = 1; i < n - 1; i++) {\\n const x = nums[i];\\n // 此时 preMax 表示 [0, i-1] 中的最大值\\n if (preMax < x && x < sufMin[i + 1]) {\\n ans += 2;\\n } else if (nums[i - 1] < x && x < nums[i + 1]) {\\n ans++;\\n }\\n // 更新后 preMax 表示 [0, i] 中的最大值\\n preMax = Math.max(preMax, x);\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn sum_of_beauties(nums: Vec<i32>) -> i32 {\\n let n = nums.len();\\n let mut suf_min = vec![0; n]; // 后缀最小值\\n suf_min[n - 1] = nums[n - 1];\\n for i in (2..n - 1).rev() {\\n suf_min[i] = suf_min[i + 1].min(nums[i]);\\n }\\n\\n let mut ans = 0;\\n let mut pre_max = nums[0]; // 前缀最大值\\n for i in 1..n - 1 {\\n let x = nums[i];\\n // 此时 pre_max 表示 [0, i-1] 中的最大值\\n if pre_max < x && x < suf_min[i + 1] {\\n ans += 2;\\n } else if nums[i - 1] < x && x < nums[i + 1] {\\n ans += 1;\\n }\\n // 更新后 pre_max 表示 [0, i] 中的最大值\\n pre_max = pre_max.max(x);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(n)$。
\\n更多相似题目,见下面动态规划题单中的「专题:前后缀分解」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 【本题相关】动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"对于所有 $0 \\\\le j < i$ 且 $i < k \\\\le n - 1$,满足 $\\\\textit{nums}[j] < \\\\textit{nums}[i] < \\\\textit{nums}[k]$。 题目的这个要求,相当于:\\n\\n$\\\\textit{nums}[i]$ 要大于 $i$ 左边的所有数,也就是大于前缀 $[0,i-1]$ 中的最大值。\\n$\\\\textit{nums}[i]$ 要小于 $i$ 右边的所有数,也就是小于后缀 $[i+1,n-1]$ 中的最小值。\\n\\n这可以通过遍历算出来。\\n\\n定义 $\\\\textit{sufMin}[i]$ 表示后缀…","guid":"https://leetcode.cn/problems/sum-of-beauty-in-the-array//solution/qian-zhui-zui-da-zhi-hou-zhui-zui-xiao-z-h9qz","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-09-19T04:07:21.230Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】简单字符串模拟","url":"https://leetcode.cn/problems/reverse-string-ii//solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-m-p88f","content":"模拟
\\n使用
\\nl
和r
两个指针分别圈出每次需要翻转的“理论”范围,每次翻转完更新l
和r
,同时注意范围 $[l, r]$ 内不足 $k$ 个的情况(将r
与真实边界n - 1
取个 $min$)。代码:
\\n###Java
\\n\\nclass Solution {\\n public String reverseStr(String s, int k) {\\n char[] cs = s.toCharArray();\\n int n = s.length();\\n for (int l = 0; l < n; l = l + 2 * k) {\\n int r = l + k - 1;\\n reverse(cs, l, Math.min(r, n - 1));\\n }\\n return String.valueOf(cs);\\n }\\n void reverse(char[] cs, int l, int r) {\\n while (l < r) {\\n char c = cs[l];\\n cs[l] = cs[r];\\n cs[r] = c;\\n l++; r--;\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:Java 中
\\nString
属于不可变,复杂度为 $O(n)$
\\n特别感谢
\\n再次感谢昨天有给三叶点 ❤️ 的小伙伴,截止至 2021/08/20 10:00:00 有 $115$ 人,太有排面了,截个全家福纪念一下 🤣 呜呜呜,感谢(其他没有入镜的同学,是在帮忙拿相机拍照啦,也感谢他们 ~
\\n\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"模拟 使用 l 和 r 两个指针分别圈出每次需要翻转的“理论”范围,每次翻转完更新 l 和 r,同时注意范围 $[l, r]$ 内不足 $k$ 个的情况(将 r 与真实边界 n - 1取个 $min$)。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public String reverseStr(String s, int k) {\\n char[] cs = s.toCharArray();\\n int n = s.length();\\n for (int l = 0; l < n; l…","guid":"https://leetcode.cn/problems/reverse-string-ii//solution/gong-shui-san-xie-jian-dan-zi-fu-chuan-m-p88f","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-20T01:17:47.305Z","media":[{"url":"https://pic.leetcode-cn.com/1629424929-fmfBdW-image.png","type":"photo","width":2956,"height":1586,"blurhash":"LvMH7Dt7ofog00ayWBayITWBa#WB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"反转字符串 II","url":"https://leetcode.cn/problems/reverse-string-ii//solution/fan-zhuan-zi-fu-chuan-ii-by-leetcode-sol-ua7s","content":"方法一:模拟
\\n我们直接按题意进行模拟:反转每个下标从 $2k$ 的倍数开始的,长度为 $k$ 的子串。若该子串长度不足 $k$,则反转整个子串。
\\n###Python
\\n\\nclass Solution:\\n def reverseStr(self, s: str, k: int) -> str:\\n t = list(s)\\n for i in range(0, len(t), 2 * k):\\n t[i: i + k] = reversed(t[i: i + k])\\n return \\"\\".join(t)\\n
###C++
\\n\\nclass Solution {\\npublic:\\n string reverseStr(string s, int k) {\\n int n = s.length();\\n for (int i = 0; i < n; i += 2 * k) {\\n reverse(s.begin() + i, s.begin() + min(i + k, n));\\n }\\n return s;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public String reverseStr(String s, int k) {\\n int n = s.length();\\n char[] arr = s.toCharArray();\\n for (int i = 0; i < n; i += 2 * k) {\\n reverse(arr, i, Math.min(i + k, n) - 1);\\n }\\n return new String(arr);\\n }\\n\\n public void reverse(char[] arr, int left, int right) {\\n while (left < right) {\\n char temp = arr[left];\\n arr[left] = arr[right];\\n arr[right] = temp;\\n left++;\\n right--;\\n }\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public string ReverseStr(string s, int k) {\\n int n = s.Length;\\n char[] arr = s.ToCharArray();\\n for (int i = 0; i < n; i += 2 * k) {\\n Reverse(arr, i, Math.Min(i + k, n) - 1);\\n }\\n return new string(arr);\\n }\\n\\n public void Reverse(char[] arr, int left, int right) {\\n while (left < right) {\\n char temp = arr[left];\\n arr[left] = arr[right];\\n arr[right] = temp;\\n left++;\\n right--;\\n }\\n }\\n}\\n
###go
\\n\\nfunc reverseStr(s string, k int) string {\\n t := []byte(s)\\n for i := 0; i < len(s); i += 2 * k {\\n sub := t[i:min(i+k, len(s))]\\n for j, n := 0, len(sub); j < n/2; j++ {\\n sub[j], sub[n-1-j] = sub[n-1-j], sub[j]\\n }\\n }\\n return string(t)\\n}\\n\\nfunc min(a, b int) int {\\n if a < b {\\n return a\\n }\\n return b\\n}\\n
###JavaScript
\\n\\nvar reverseStr = function(s, k) {\\n const n = s.length;\\n const arr = Array.from(s);\\n for (let i = 0; i < n; i += 2 * k) {\\n reverse(arr, i, Math.min(i + k, n) - 1);\\n }\\n return arr.join(\'\');\\n};\\n\\nconst reverse = (arr, left, right) => {\\n while (left < right) {\\n const temp = arr[left];\\n arr[left] = arr[right];\\n arr[right] = temp;\\n left++;\\n right--;\\n }\\n}\\n
###C
\\n\\nvoid swap(char* a, char* b) {\\n char tmp = *a;\\n *a = *b, *b = tmp;\\n}\\n\\nvoid reverse(char* l, char* r) {\\n while (l < r) {\\n swap(l++, --r);\\n }\\n}\\n\\nint min(int a, int b) {\\n return a < b ? a : b;\\n}\\n\\nchar* reverseStr(char* s, int k) {\\n int n = strlen(s);\\n for (int i = 0; i < n; i += 2 * k) {\\n reverse(&s[i], &s[min(i + k, n)]);\\n }\\n return s;\\n}\\n
###TypeScript
\\n\\nfunction reverseStr(s: string, k: number): string {\\n const n = s.length;\\n const arr = s.split(\'\');\\n for (let i = 0; i < n; i += 2 * k) {\\n let left = i;\\n let right = Math.min(i + k - 1, n - 1);\\n while (left < right) {\\n [arr[left], arr[right]] = [arr[right], arr[left]];\\n left++;\\n right--;\\n }\\n }\\n return arr.join(\'\');\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn reverse_str(s: String, k: i32) -> String {\\n let mut chars: Vec<char> = s.chars().collect();\\n let n = chars.len();\\n for i in (0..n).step_by((2 * k) as usize) {\\n let end = std::cmp::min(i + k as usize, n);\\n chars[i..end].reverse();\\n }\\n chars.into_iter().collect()\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:模拟 我们直接按题意进行模拟:反转每个下标从 $2k$ 的倍数开始的,长度为 $k$ 的子串。若该子串长度不足 $k$,则反转整个子串。\\n\\n###Python\\n\\nclass Solution:\\n def reverseStr(self, s: str, k: int) -> str:\\n t = list(s)\\n for i in range(0, len(t), 2 * k):\\n t[i: i + k] = reversed(t[i: i + k])\\n return \\"\\".join…","guid":"https://leetcode.cn/problems/reverse-string-ii//solution/fan-zhuan-zi-fu-chuan-ii-by-leetcode-sol-ua7s","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-19T15:27:33.341Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"使字符串平衡的最小交换次数 - 简单贪心","url":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/shi-zi-fu-chuan-ping-heng-de-zui-xiao-ji-lr8e","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$ 或 $O(n)$,取决于使用的语言中字符串类型的性质。如果字符串是可修改的,那么我们可以直接在原字符串上修改,空间复杂度为 $O(1)$,否则需要使用 $O(n)$ 的空间将字符串临时转换为可以修改的数据结构(例如数组),空间复杂度为 $O(n)$。
\\n使字符串平衡的最小交换次数
\\n第三道题,要我们配对\'[\' 和\']\',大概意思就是每次操作可以交换两个位置的\']\'和\'[\',求最小的操作次数能让该字符串中的\']\'和\'[\'两两匹配。所谓两两匹配的意思是——\'[\' 要在 \']\' 的左边。
\\n首先,交换位置的两个下标对应的一定是不同的字符。显然,如果交换相同的字符,那么和不交换是一样的结果。
\\n再推,\'[\'一定是在字符串的越前面越好,同理,\']\'在字符串的越后面越好。显然,\'[\'在越前面越容易和后面的\']\'配对。
\\n结合以上两点,交换肯定发生在左侧的\']\'和右侧的\'[\'上,即没有配对成功的\']\'和\'[\'。
\\n再推,每次交换可以让两对\'[]\'配对成功。交换\']\'和\'[\'后,它们分别和不同的配对,这样就会出现两对配对成功。如果除去它们没有不同的未配对的\']\'或\'[\',则它们两个配对,只有一对\'[]\'配对成功。
\\n以上,我们找出所有未配对的\'[]\',计算交换次数就好了。
\\n`
\\n
\\n###c++\\n","description":"使字符串平衡的最小交换次数 第三道题,要我们配对\'[\' 和\']\',大概意思就是每次操作可以交换两个位置的\']\'和\'[\',求最小的操作次数能让该字符串中的\']\'和\'[\'两两匹配。所谓两两匹配的意思是——\'[\' 要在 \']\' 的左边。\\n\\n首先,交换位置的两个下标对应的一定是不同的字符。显然,如果交换相同的字符,那么和不交换是一样的结果。\\n\\n再推,\'[\'一定是在字符串的越前面越好,同理,\']\'在字符串的越后面越好。显然,\'[\'在越前面越容易和后面的\']\'配对。\\n\\n结合以上两点,交换肯定发生在左侧的\']\'和右侧的\'[\'上,即没有配对成功的\']\'和\'[\'。\\n\\n再推,每次交…","guid":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/shi-zi-fu-chuan-ping-heng-de-zui-xiao-ji-lr8e","author":"MGAronya","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-08T12:22:04.665Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"使字符串平衡的最小交换次数","url":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/shi-zi-fu-chuan-ping-heng-de-zui-xiao-ji-f7ye","content":"class Solution {\\npublic:\\n int minSwaps(string s) {\\n int tmp1 = 0, tmp2 = 0;\\n for(auto c: s){\\n if(c == \'[\') ++tmp1; //记录[正在等待配对\\n else if(tmp1) --tmp1; //成功配对\\n else ++tmp2; //未成功配对\\n }\\n if(tmp2 & 1) ++tmp2; //向上取整\\n return tmp2 >> 1;\\n }\\n};\\n
方法一:猜测结论 + 构造答案
\\n提示 $1$
\\n记 $\\\\textit{cnt}[i]$ 表示字符串 $s[0..i]$ 的前缀和,其中左括号 $[$ 记为 $1$,右括号 $]$ 记为 $-1$。
\\n如果所有的前缀和均大于等于 $0$,那么字符串 $s$ 就是平衡的。
\\n提示 $1$ 解释
\\n我们可以将求前缀和的过程看成使用栈处理字符串 $s$ 的过程。
\\n我们对 $s$ 进行一次遍历,如果遍历到左括号,那么将其入栈。如果遍历到右括号,那么将栈顶的一个左括号与其匹配并弹出。
\\n如果栈始终是合法的(即不会有遍历到右括号但栈顶没有左括号的情况),那么 $s$ 就是平衡的。如果遇到了不合法的情况,那么可以可能栈「欠了」一个左括号。所以 $\\\\textit{cnt}$ 中每一个正数(以及 $0$)记录了遍历到当前位置的栈中包含的左括号数量,每一个负数记录了遍历到当前位置的栈欠着的左括号的个数的相反数。
\\n因此,如果 $\\\\textit{cnt}$ 中的所有元素均大于 $0$,说明栈没有欠过左括号,即字符串 $s$ 是平衡的。
\\n提示 $2$
\\n我们可以猜测一个结论:
\\n\\n\\n设 $\\\\textit{cnt}$ 中的最小值为 $\\\\textit{cnt}[i]$,那么最少需要交换的次数为:
\\n$$
\\n
\\n\\\\lceil \\\\frac{-\\\\textit{cnt}[i]}{2} \\\\rceil
\\n$$其中 $\\\\lceil x \\\\rceil$ 表示将 $x$ 向上取整。
\\n猜测这个结论的缘由是比较直观的:$\\\\textit{cnt}[i]$ 表示遍历的过程中,栈最多欠了 $-\\\\textit{cnt}[i]$ 个左括号。为了使得 $s$ 平衡,我们需要在此之前补上一些左括号,而补括号的唯一方法是将前面的右括号与后面的左括号进行交换,一次交换会使得 $\\\\textit{cnt}[i]$ 的值增加 $2$(原先是右括号权值为 $-1$,现在是左括号权值为 $1$,变化量为 $2$),因此至少需要交换 $\\\\lceil \\\\dfrac{-\\\\textit{cnt}[i]}{2} \\\\rceil$ 次。
\\n那么我们能否构造出一种交换 $\\\\lceil \\\\dfrac{-\\\\textit{cnt}[i]}{2} \\\\rceil$ 次的方法呢?由于我们希望每交换一次,$-\\\\textit{cnt}[i]$ 的值减少 $2$,因此可以考虑使用数学归纳法:
\\n\\n
\\n- \\n
\\n当 $-\\\\textit{cnt}[i] = 0$ 时,字符串 $s$ 是平衡的,需要交换的次数为 $0$;
\\n- \\n
\\n假设当 $-\\\\textit{cnt}[i] = k$ 时需要交换的次数与我们的猜测相符,那么当 $-\\\\textit{cnt}[i] = k+2$ 时:
\\n\\n
\\n- \\n
\\n我们记 $\\\\textit{cnt}$ 中第一个负数的出现位置为 $\\\\textit{first}$,那么 $s[\\\\textit{first}]$ 一定是一个右括号;
\\n- \\n
\\n我们记 $\\\\textit{cnt}$ 中最后一个负数的出现位置为 $\\\\textit{last}$,那么 $\\\\textit{last}$ 一定不为 $n-1$(因为 $s$ 中左右括号数量相同),并且 $s[\\\\textit{last} + 1]$ 一定是一个左括号;
\\n- \\n
\\n我们交换 $s[\\\\textit{first}]$ 与 $s[\\\\textit{last} + 1]$。对于所有在 $[0, \\\\textit{first} - 1] \\\\cup [\\\\textit{last} + 1, n-1)$ 范围内的下标,它们对应的 $\\\\textit{cnt}$ 值均为不变(且均为非负数),对于所有在 $[\\\\textit{first}, \\\\textit{last}]$ 范围内的下标,它们对应的 $\\\\textit{cnt}$ 值均增加了 $2$。由于所有的负数都在 $[\\\\textit{first}, \\\\textit{last}]$ 范围内,因此 $\\\\textit{cnt}[i]$ 也会增加 $2$,那么我们通过一次交换就归纳到了 $-\\\\textit{cnt}[i] = k$ 的情况。
\\n- \\n
\\n需要注意的是,$-\\\\textit{cnt}[i] = 1$ 的情况也会归纳到 $-\\\\textit{cnt}[i] = 0$(而不是 $-\\\\textit{cnt}[i] = -1$),这也是答案中包含上取整的来由。
\\n我们通过数学归纳法给出了一种交换的构造,因此最少的交换次数即为 $\\\\lceil \\\\dfrac{-\\\\textit{cnt}[i]}{2} \\\\rceil$ 中的最小值。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minSwaps(string s) {\\n int cnt = 0, mincnt = 0;\\n for (char ch: s) {\\n if (ch == \'[\') {\\n cnt += 1;\\n }\\n else {\\n cnt -= 1;\\n mincnt = min(mincnt, cnt);\\n }\\n }\\n return (-mincnt + 1) / 2;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def minSwaps(self, s: str) -> int:\\n cnt = mincnt = 0\\n for ch in s:\\n if ch == \'[\':\\n cnt += 1\\n else:\\n cnt -= 1\\n mincnt = min(mincnt, cnt)\\n return (-mincnt + 1) // 2\\n
###Java
\\n\\nclass Solution {\\n public int minSwaps(String s) {\\n int cnt = 0, mincnt = 0;\\n for (char ch : s.toCharArray()) {\\n if (ch == \'[\') {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = Math.min(mincnt, cnt);\\n }\\n }\\n return (-mincnt + 1) / 2;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinSwaps(string s) {\\n int cnt = 0, mincnt = 0;\\n foreach (char ch in s) {\\n if (ch == \'[\') {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = Math.Min(mincnt, cnt);\\n }\\n }\\n return (-mincnt + 1) / 2;\\n }\\n}\\n
###Go
\\n\\nfunc minSwaps(s string) int {\\n cnt, mincnt := 0, 0\\n for _, ch := range s {\\n if ch == \'[\' {\\n cnt++\\n } else {\\n cnt--\\n mincnt = min(mincnt, cnt)\\n }\\n }\\n return (-mincnt + 1) / 2\\n}\\n
###C
\\n\\nint minSwaps(char* s) {\\n int cnt = 0, mincnt = 0;\\n for (int i = 0; s[i] != \'\\\\0\'; ++i) {\\n if (s[i] == \'[\') {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = fmin(mincnt, cnt);\\n }\\n }\\n return (-mincnt + 1) / 2;\\n}\\n
###JavaScript
\\n\\nvar minSwaps = function(s) {\\n let cnt = 0, mincnt = 0;\\n for (let ch of s) {\\n if (ch === \'[\') {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = Math.min(mincnt, cnt);\\n }\\n }\\n return Math.floor((-mincnt + 1) / 2);\\n};\\n
###TypeScript
\\n\\nfunction minSwaps(s: string): number {\\n let cnt = 0, mincnt = 0;\\n for (let ch of s) {\\n if (ch === \'[\') {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = Math.min(mincnt, cnt);\\n }\\n }\\n return Math.floor((-mincnt + 1) / 2);\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn min_swaps(s: String) -> i32 {\\n let mut cnt = 0;\\n let mut mincnt = 0;\\n for ch in s.chars() {\\n if ch == \'[\' {\\n cnt += 1;\\n } else {\\n cnt -= 1;\\n mincnt = mincnt.min(cnt);\\n }\\n }\\n (-mincnt + 1) / 2\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:猜测结论 + 构造答案 提示 $1$\\n\\n记 $\\\\textit{cnt}[i]$ 表示字符串 $s[0..i]$ 的前缀和,其中左括号 $[$ 记为 $1$,右括号 $]$ 记为 $-1$。\\n\\n如果所有的前缀和均大于等于 $0$,那么字符串 $s$ 就是平衡的。\\n\\n提示 $1$ 解释\\n\\n我们可以将求前缀和的过程看成使用栈处理字符串 $s$ 的过程。\\n\\n我们对 $s$ 进行一次遍历,如果遍历到左括号,那么将其入栈。如果遍历到右括号,那么将栈顶的一个左括号与其匹配并弹出。\\n\\n如果栈始终是合法的(即不会有遍历到右括号但栈顶没有左括号的情况),那么 $s$ 就是平衡的…","guid":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/shi-zi-fu-chuan-ping-heng-de-zui-xiao-ji-f7ye","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-08T04:12:34.747Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心,三种写法,一步步优化(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/go-tan-xin-by-endlesscheng-7h9n","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串 $s$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n考察平衡字符串的性质。
\\n在平衡字符串的任意一个前缀中,左括号的数量都大于等于右括号的数量。例如平衡字符串 $\\\\texttt{\\"[[]][]\\"}$,它的前缀有 $\\\\texttt{\\"[[]\\"}$,$\\\\texttt{\\"[[]][\\"}$ 等,都满足这一性质。为什么?因为对于前缀来说,每个右括号的左边,必然有与之匹配的左括号,但左括号不一定有与之匹配的右括号。
\\n根据这一性质,从左到右遍历字符串 $s$,统计未匹配的左括号的个数 $c$:遇到左括号就把 $c$ 加一,遇到右括号就把 $c$ 减一。如果任何时刻 $c$ 都不为负数,那么 $s$ 就是平衡字符串。(注意题目保证左右括号个数相等,所以最终 $c$ 一定为 $0$。)
\\n反之,如果遍历到右括号,且此时 $c=0$,那么减一后 $c$ 是负数,说明右括号比左括号多,必然存在一个右括号,没有相匹配的左括号,无论后面的字符是什么样的,$s$ 都不可能是平衡字符串。例如 $s=\\\\texttt{\\"[]][\\"}$。
\\n这时就需要把这个右括号换走了。和另一个右括号交换是没有意义的($s$ 不变),所以一定要和左括号交换。如何交换最优呢?
\\n根据 $c$ 的计算规则,把左括号视作 $+1$,右括号视作 $-1$。为了让 $c$ 不是负数,执行 $-1$ 的时刻越晚(越靠后),$c$ 就越不可能变成负数。
\\n所以这个右括号要换到最后面去,也就是找最右边的左括号交换。
\\n例如 $s=\\\\texttt{\\"][][\\"}$:
\\n\\n
\\n- 如果把第一个右括号和第一个左括号交换,得 $\\\\texttt{\\"[]][\\"}$,不是平衡字符串,仍需继续交换。
\\n- 如果把第一个右括号和第二个左括号交换,得 $\\\\texttt{\\"[[]]\\"}$,是平衡字符串。
\\n优化前:模拟交换流程
\\n\\nclass Solution:\\n def minSwaps(self, s: str) -> int:\\n s = list(s)\\n ans = c = 0\\n j = len(s) - 1\\n for b in s:\\n if b == \'[\':\\n c += 1\\n elif c > 0:\\n c -= 1\\n else: # c == 0\\n # 找最右边的左括号交换\\n while s[j] == \']\':\\n j -= 1\\n s[j] = \']\' # s[i] = \'[\' 可以省略\\n ans += 1\\n c += 1 # s[i] 变成左括号,c 加一\\n return ans\\n
\\nclass Solution {\\n public int minSwaps(String S) {\\n char[] s = S.toCharArray();\\n int ans = 0;\\n int c = 0;\\n int j = s.length - 1;\\n for (char b : s) {\\n if (b == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n // 找最右边的左括号交换\\n while (s[j] == \']\') {\\n j--;\\n }\\n s[j] = \']\'; // s[i] = \'[\' 可以省略\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minSwaps(string s) {\\n int ans = 0, c = 0;\\n int j = s.size() - 1;\\n for (char b : s) {\\n if (b == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n // 找最右边的左括号交换\\n while (s[j] == \']\') {\\n j--;\\n }\\n s[j] = \']\'; // s[i] = \'[\' 可以省略\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint minSwaps(char* s) {\\n int n = strlen(s);\\n int ans = 0, c = 0;\\n int j = n - 1;\\n for (int i = 0; i < n; i++) {\\n if (s[i] == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n // 找最右边的左括号交换\\n while (s[j] == \']\') {\\n j--;\\n }\\n s[j] = \']\'; // s[i] = \'[\' 可以省略\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n}\\n
\\nfunc minSwaps(S string) (ans int) {\\ns := []byte(S)\\nc := 0\\nj := len(s) - 1\\nfor _, b := range s {\\nif b == \'[\' {\\nc++\\n} else if c > 0 {\\nc--\\n} else { // c == 0\\n// 找最右边的左括号交换\\nfor s[j] == \']\' {\\nj--\\n}\\ns[j] = \']\' // s[i] = \'[\' 可以省略\\nans++\\nc++ // s[i] 变成左括号,c 加一\\n}\\n}\\nreturn\\n}\\n
\\nvar minSwaps = function(s) {\\n s = s.split(\'\');\\n let ans = 0, c = 0;\\n let j = s.length - 1;\\n for (const b of s) {\\n if (b === \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c === 0\\n // 找最右边的左括号交换\\n while (s[j] === \']\') {\\n j--;\\n }\\n s[j] = \']\'; // s[i] = \'[\' 可以省略\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn min_swaps(mut s: String) -> i32 {\\n let mut s = unsafe { s.as_bytes_mut() };\\n let mut ans = 0;\\n let mut c = 0;\\n let mut j = s.len() - 1;\\n for i in 0..s.len() {\\n if s[i] == b\'[\' {\\n c += 1;\\n } else if c > 0 {\\n c -= 1;\\n } else { // c == 0\\n // 找最右边的左括号交换\\n while s[j] == b\']\' {\\n j -= 1;\\n }\\n s[j] = b\']\'; // s[i] = \'[\' 可以省略\\n ans += 1;\\n c += 1; // s[i] 变成左括号,c 加一\\n }\\n }\\n ans\\n }\\n}\\n
优化一:不需要真的交换
\\n不需要写交换括号的逻辑。
\\n如果不交换,继续向后遍历,若在下标 $i$ 处遇到了(本该被交换的)左括号,那么在执行了交换的字符串中,$[i,n-1]$ 中的左括号全部被换成了右括号,即此时该字符串已经是平衡的了,继续遍历不会导致 $c<0$,不会继续增大答案,所以不交换并不会导致计算错误。
\\n因此,当遍历到右括号且 $c=0$ 时,只需将 $c$ 和答案(交换次数)加一,即视作将后面的一个左括号与该右括号交换。
\\n示例 2 的 $s=\\\\texttt{\\"]]][[[\\"}$,第一、第三个右括号与后两个左括号交换,得 $\\\\texttt{\\"[][[]]\\"}$。但实际上我们只是把第一、第三个右括号视作左括号,没有真的交换,所以看上去遍历的是 $\\\\texttt{\\"[][[[[\\"}$,最终 $c=4$。但这并不会导致计算错误,正如上文所说,如果遍历到了这些(本该被交换的)左括号,那么后面 $c$ 不会再变成负数,我们不会继续增大答案。
\\n\\nclass Solution:\\n def minSwaps(self, s: str) -> int:\\n ans = c = 0\\n for b in s:\\n if b == \'[\':\\n c += 1\\n elif c > 0:\\n c -= 1\\n else: # c == 0\\n ans += 1\\n c += 1 # s[i] 变成左括号,c 加一\\n return ans\\n
\\nclass Solution {\\n public int minSwaps(String s) {\\n int ans = 0;\\n int c = 0;\\n for (char b : s.toCharArray()) {\\n if (b == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minSwaps(string s) {\\n int ans = 0, c = 0;\\n for (char b : s) {\\n if (b == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n }\\n};\\n
\\nint minSwaps(char* s) {\\n int ans = 0, c = 0;\\n for (int i = 0; s[i]; i++) {\\n if (s[i] == \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c == 0\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n}\\n
\\nfunc minSwaps(s string) (ans int) {\\nc := 0\\nfor _, b := range s {\\nif b == \'[\' {\\nc++\\n} else if c > 0 {\\nc--\\n} else { // c == 0\\nans++\\nc++ // s[i] 变成左括号,c 加一\\n}\\n}\\nreturn\\n}\\n
\\nvar minSwaps = function(s) {\\n let ans = 0, c = 0;\\n for (const b of s) {\\n if (b === \'[\') {\\n c++;\\n } else if (c > 0) {\\n c--;\\n } else { // c === 0\\n ans++;\\n c++; // s[i] 变成左括号,c 加一\\n }\\n }\\n return ans;\\n};\\n
\\nimpl Solution {\\n pub fn min_swaps(s: String) -> i32 {\\n let mut ans = 0;\\n let mut c = 0;\\n for b in s.bytes() {\\n if b == b\'[\' {\\n c += 1;\\n } else if c > 0 {\\n c -= 1;\\n } else { // c == 0\\n ans += 1;\\n c += 1; // s[i] 变成左括号,c 加一\\n }\\n }\\n ans\\n }\\n}\\n
优化二:去掉 ans 变量
\\n在前文的例子中,$\\\\texttt{\\"[][[[[\\"}$ 的后两个括号本应是右括号,本应把 $c$ 执行两次 $-1$,最终得到 $c=0$,但我们反而执行了两次 $+1$,最终得到 $c=4$。换句话说,每次交换都会让最终的 $c$ 增大 $2$(把 $-1$ 改成了 $+1$),所以最终的 $c$ 除以 $2$,便是交换次数。
\\n\\nclass Solution:\\n def minSwaps(self, s: str) -> int:\\n c = 0\\n for b in s:\\n if b == \'[\' or c == 0:\\n c += 1\\n else:\\n c -= 1\\n return c // 2\\n
\\nclass Solution {\\n public int minSwaps(String s) {\\n int c = 0;\\n for (char b : s.toCharArray()) {\\n if (b == \'[\' || c == 0) {\\n c++;\\n } else {\\n c--;\\n }\\n }\\n return c / 2;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minSwaps(string s) {\\n int c = 0;\\n for (char b : s) {\\n if (b == \'[\' || c == 0) {\\n c++;\\n } else {\\n c--;\\n }\\n }\\n return c / 2;\\n }\\n};\\n
\\nint minSwaps(char* s) {\\n int c = 0;\\n for (int i = 0; s[i]; i++) {\\n if (s[i] == \'[\' || c == 0) {\\n c++;\\n } else {\\n c--;\\n }\\n }\\n return c / 2;\\n}\\n
\\nfunc minSwaps(s string) int {\\nc := 0\\nfor _, b := range s {\\nif b == \'[\' || c == 0 {\\nc++\\n} else {\\nc--\\n}\\n}\\nreturn c / 2\\n}\\n
\\nvar minSwaps = function(s) {\\n let c = 0;\\n for (const b of s) {\\n if (b === \'[\' || c === 0) {\\n c++;\\n } else {\\n c--;\\n }\\n }\\n return c / 2;\\n};\\n
\\nimpl Solution {\\n pub fn min_swaps(s: String) -> i32 {\\n let mut c = 0;\\n for b in s.bytes() {\\n if b == b\'[\' || c == 0 {\\n c += 1;\\n } else {\\n c -= 1;\\n }\\n }\\n c / 2\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $s$ 的长度。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面数据结构题单中的「§3.4 合法括号字符串」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 【本题相关】常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"考察平衡字符串的性质。 在平衡字符串的任意一个前缀中,左括号的数量都大于等于右括号的数量。例如平衡字符串 $\\\\texttt{\\"[[]][]\\"}$,它的前缀有 $\\\\texttt{\\"[[]\\"}$,$\\\\texttt{\\"[[]][\\"}$ 等,都满足这一性质。为什么?因为对于前缀来说,每个右括号的左边,必然有与之匹配的左括号,但左括号不一定有与之匹配的右括号。\\n\\n根据这一性质,从左到右遍历字符串 $s$,统计未匹配的左括号的个数 $c$:遇到左括号就把 $c$ 加一,遇到右括号就把 $c$ 减一。如果任何时刻 $c$ 都不为负数,那么 $s$ 就是平衡字符串…","guid":"https://leetcode.cn/problems/minimum-number-of-swaps-to-make-the-string-balanced//solution/go-tan-xin-by-endlesscheng-7h9n","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-08T04:09:27.065Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】涵盖所有的「存图方式」与「最短路算法(详尽注释)」","url":"https://leetcode.cn/problems/network-delay-time//solution/gong-shui-san-xie-yi-ti-wu-jie-wu-chong-oghpz","content":"\\n\\n欢迎关注我 ❤️ 提供写「证明」&「思路」的高质量专项题解
\\n
\\n后台回复「刷题路线」有惊喜,更有「长期送实体书」活动等你来 🎉 🎉基本分析
\\n为了方便,我们约定 $n$ 为点数,$m$ 为边数。
\\n根据题意,首先 $n$ 的数据范围只有 $100$,$m$ 的数据范围为 $6000$,使用「邻接表」或「邻接矩阵」来存图都可以。
\\n同时求的是「从 $k$ 点出发,所有点都被访问到的最短时间」,将问题转换一下其实就是求「从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值」。
\\n
\\n存图方式
\\n在开始讲解最短路之前,我们先来学习三种「存图」方式。
\\n邻接矩阵
\\n这是一种使用二维矩阵来进行存图的方式。
\\n适用于边数较多的稠密图使用,当边数量接近点的数量的平方,即 $m \\\\approx n^2$ 时,可定义为稠密图。
\\n###Java
\\n\\n// 邻接矩阵数组:w[a][b] = c 代表从 a 到 b 有权重为 c 的边\\nint[][] w = new int[N][N];\\n\\n// 加边操作\\nvoid add(int a, int b, int c) {\\n w[a][b] = c;\\n}\\n
邻接表
\\n这也是一种在图论中十分常见的存图方式,与数组存储单链表的实现一致(头插法)。
\\n这种存图方式又叫链式前向星存图。
\\n适用于边数较少的稀疏图使用,当边数量接近点的数量,即 $m \\\\approx n$ 时,可定义为稀疏图。
\\n###Java
\\n\\nint[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];\\nint idx;\\n\\nvoid add(int a, int b, int c) {\\n e[idx] = b;\\n ne[idx] = he[a];\\n he[a] = idx;\\n w[idx] = c;\\n idx++;\\n}\\n
首先
\\nidx
是用来对边进行编号的,然后对存图用到的几个数组作简单解释:\\n
\\n- \\n
he
数组:存储是某个节点所对应的边的集合(链表)的头结点;- \\n
e
数组:由于访问某一条边指向的节点;- \\n
ne
数组:由于是以链表的形式进行存边,该数组就是用于找到下一条边;- \\n
w
数组:用于记录某条边的权重为多少。因此当我们想要遍历所有由
\\na
点发出的边时,可以使用如下方式:###Java
\\n\\nfor (int i = he[a]; i != -1; i = ne[i]) {\\n int b = e[i], c = w[i]; // 存在由 a 指向 b 的边,权重为 c\\n}\\n
类
\\n这是一种最简单,但是相比上述两种存图方式,使用得较少的存图方式。
\\n只有当我们需要确保某个操作复杂度严格为 $O(m)$ 时,才会考虑使用。
\\n具体的,我们建立一个类来记录有向边信息:
\\n###Java
\\n\\nclass Edge {\\n // 代表从 a 到 b 有一条权重为 c 的边\\n int a, b, c;\\n Edge(int _a, int _b, int _c) {\\n a = _a; b = _b; c = _c;\\n }\\n}\\n
通常我们会使用 List 存起所有的边对象,并在需要遍历所有边的时候,进行遍历:
\\n###Java
\\n\\nList<Edge> es = new ArrayList<>();\\n\\n...\\n\\nfor (Edge e : es) {\\n ...\\n}\\n
\\nFloyd(邻接矩阵)
\\n根据「基本分析」,我们可以使用复杂度为 $O(n^3)$ 的「多源汇最短路」算法 Floyd 算法进行求解,同时使用「邻接矩阵」来进行存图。
\\n此时计算量约为 $10^6$,可以过。
\\n跑一遍 Floyd,可以得到「从任意起点出发,到达任意起点的最短距离」。然后从所有 $w[k][x]$ 中取 $max$ 即是「从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值」。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n int N = 110, M = 6010;\\n // 邻接矩阵数组:w[a][b] = c 代表从 a 到 b 有权重为 c 的边\\n int[][] w = new int[N][N];\\n int INF = 0x3f3f3f3f;\\n int n, k;\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n // 初始化邻接矩阵\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= n; j++) {\\n w[i][j] = w[j][i] = i == j ? 0 : INF;\\n }\\n }\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n w[u][v] = c;\\n }\\n // 最短路\\n floyd();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, w[k][i]);\\n }\\n return ans >= INF / 2 ? -1 : ans;\\n }\\n void floyd() {\\n // floyd 基本流程为三层循环:\\n // 枚举中转点 - 枚举起点 - 枚举终点 - 松弛操作 \\n for (int p = 1; p <= n; p++) {\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= n; j++) {\\n w[i][j] = Math.min(w[i][j], w[i][p] + w[p][j]);\\n }\\n }\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n^3)$
\\n- 空间复杂度:$O(n^2)$
\\n
\\n朴素 Dijkstra(邻接矩阵)
\\n同理,我们可以使用复杂度为 $O(n^2)$ 的「单源最短路」算法朴素 Dijkstra 算法进行求解,同时使用「邻接矩阵」来进行存图。
\\n根据题意,$k$ 点作为源点,跑一遍 Dijkstra 我们可以得到从源点 $k$ 到其他点 $x$ 的最短距离,再从所有最短路中取 $max$ 即是「从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值」。
\\n朴素 Dijkstra 复杂度为 $O(n^2)$,可以过。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n int N = 110, M = 6010;\\n // 邻接矩阵数组:w[a][b] = c 代表从 a 到 b 有权重为 c 的边\\n int[][] w = new int[N][N];\\n // dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y\\n int[] dist = new int[N];\\n // 记录哪些点已经被更新过\\n boolean[] vis = new boolean[N];\\n int INF = 0x3f3f3f3f;\\n int n, k;\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n // 初始化邻接矩阵\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= n; j++) {\\n w[i][j] = w[j][i] = i == j ? 0 : INF;\\n }\\n }\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n w[u][v] = c;\\n }\\n // 最短路\\n dijkstra();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, dist[i]);\\n }\\n return ans > INF / 2 ? -1 : ans;\\n }\\n void dijkstra() {\\n // 起始先将所有的点标记为「未更新」和「距离为正无穷」\\n Arrays.fill(vis, false);\\n Arrays.fill(dist, INF);\\n // 只有起点最短距离为 0\\n dist[k] = 0;\\n // 迭代 n 次\\n for (int p = 1; p <= n; p++) {\\n // 每次找到「最短距离最小」且「未被更新」的点 t\\n int t = -1;\\n for (int i = 1; i <= n; i++) {\\n if (!vis[i] && (t == -1 || dist[i] < dist[t])) t = i;\\n }\\n // 标记点 t 为已更新\\n vis[t] = true;\\n // 用点 t 的「最小距离」更新其他点\\n for (int i = 1; i <= n; i++) {\\n dist[i] = Math.min(dist[i], dist[t] + w[t][i]);\\n }\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n^2)$
\\n- 空间复杂度:$O(n^2)$
\\n
\\n堆优化 Dijkstra(邻接表)
\\n由于边数据范围不算大,我们还可以使用复杂度为 $O(m\\\\log{n})$ 的堆优化 Dijkstra 算法进行求解。
\\n堆优化 Dijkstra 算法与朴素 Dijkstra 都是「单源最短路」算法。
\\n跑一遍堆优化 Dijkstra 算法求最短路,再从所有最短路中取 $max$ 即是「从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值」。
\\n此时算法复杂度为 $O(m\\\\log{n})$,可以过。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n int N = 110, M = 6010;\\n // 邻接表\\n int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];\\n // dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y\\n int[] dist = new int[N];\\n // 记录哪些点已经被更新过\\n boolean[] vis = new boolean[N];\\n int n, k, idx;\\n int INF = 0x3f3f3f3f;\\n void add(int a, int b, int c) {\\n e[idx] = b;\\n ne[idx] = he[a];\\n he[a] = idx;\\n w[idx] = c;\\n idx++;\\n }\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n // 初始化链表头\\n Arrays.fill(he, -1);\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n add(u, v, c);\\n }\\n // 最短路\\n dijkstra();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, dist[i]);\\n }\\n return ans > INF / 2 ? -1 : ans;\\n }\\n void dijkstra() {\\n // 起始先将所有的点标记为「未更新」和「距离为正无穷」\\n Arrays.fill(vis, false);\\n Arrays.fill(dist, INF);\\n // 只有起点最短距离为 0\\n dist[k] = 0;\\n // 使用「优先队列」存储所有可用于更新的点\\n // 以 (点编号, 到起点的距离) 进行存储,优先弹出「最短距离」较小的点\\n PriorityQueue<int[]> q = new PriorityQueue<>((a,b)->a[1]-b[1]);\\n q.add(new int[]{k, 0});\\n while (!q.isEmpty()) {\\n // 每次从「优先队列」中弹出\\n int[] poll = q.poll();\\n int id = poll[0], step = poll[1];\\n // 如果弹出的点被标记「已更新」,则跳过\\n if (vis[id]) continue;\\n // 标记该点「已更新」,并使用该点更新其他点的「最短距离」\\n vis[id] = true;\\n for (int i = he[id]; i != -1; i = ne[i]) {\\n int j = e[i];\\n if (dist[j] > dist[id] + w[i]) {\\n dist[j] = dist[id] + w[i];\\n q.add(new int[]{j, dist[j]});\\n }\\n }\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(m\\\\log{n} + n)$
\\n- 空间复杂度:$O(m)$
\\n
\\nBellman Ford(类 & 邻接表)
\\n虽然题目规定了不存在「负权边」,但我们仍然可以使用可以在「负权图中求最短路」的 Bellman Ford 进行求解,该算法也是「单源最短路」算法,复杂度为 $O(n * m)$。
\\n通常为了确保 $O(n * m)$,可以单独建一个类代表边,将所有边存入集合中,在 $n$ 次松弛操作中直接对边集合进行遍历(代码见 $P1$)。
\\n由于本题边的数量级大于点的数量级,因此也能够继续使用「邻接表」的方式进行边的遍历,遍历所有边的复杂度的下界为 $O(n)$,上界可以确保不超过 $O(m)$(代码见 $P2$)。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n class Edge {\\n int a, b, c;\\n Edge(int _a, int _b, int _c) {\\n a = _a; b = _b; c = _c;\\n }\\n }\\n int N = 110, M = 6010;\\n // dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y\\n int[] dist = new int[N];\\n int INF = 0x3f3f3f3f;\\n int n, m, k;\\n // 使用类进行存边\\n List<Edge> es = new ArrayList<>();\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n m = ts.length;\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n es.add(new Edge(u, v, c));\\n }\\n // 最短路\\n bf();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, dist[i]);\\n }\\n return ans > INF / 2 ? -1 : ans;\\n }\\n void bf() {\\n // 起始先将所有的点标记为「距离为正无穷」\\n Arrays.fill(dist, INF);\\n // 只有起点最短距离为 0\\n dist[k] = 0;\\n // 迭代 n 次\\n for (int p = 1; p <= n; p++) {\\n int[] prev = dist.clone();\\n // 每次都使用上一次迭代的结果,执行松弛操作\\n for (Edge e : es) {\\n int a = e.a, b = e.b, c = e.c;\\n dist[b] = Math.min(dist[b], prev[a] + c);\\n }\\n }\\n }\\n}\\n
###Java
\\n\\nclass Solution {\\n int N = 110, M = 6010;\\n // 邻接表\\n int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];\\n // dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y\\n int[] dist = new int[N];\\n int INF = 0x3f3f3f3f;\\n int n, m, k, idx;\\n void add(int a, int b, int c) {\\n e[idx] = b;\\n ne[idx] = he[a];\\n he[a] = idx;\\n w[idx] = c;\\n idx++;\\n }\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n m = ts.length;\\n // 初始化链表头\\n Arrays.fill(he, -1);\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n add(u, v, c);\\n }\\n // 最短路\\n bf();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, dist[i]);\\n }\\n return ans > INF / 2 ? -1 : ans;\\n }\\n void bf() {\\n // 起始先将所有的点标记为「距离为正无穷」\\n Arrays.fill(dist, INF);\\n // 只有起点最短距离为 0\\n dist[k] = 0;\\n // 迭代 n 次\\n for (int p = 1; p <= n; p++) {\\n int[] prev = dist.clone();\\n // 每次都使用上一次迭代的结果,执行松弛操作\\n for (int a = 1; a <= n; a++) {\\n for (int i = he[a]; i != -1; i = ne[i]) {\\n int b = e[i];\\n dist[b] = Math.min(dist[b], prev[a] + w[i]);\\n }\\n }\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n*m)$
\\n- 空间复杂度:$O(m)$
\\n
\\nSPFA(邻接表)
\\nSPFA 是对 Bellman Ford 的优化实现,可以使用队列进行优化,也可以使用栈进行优化。
\\n通常情况下复杂度为 $O(km)$,$k$ 一般为 $4$ 到 $5$,最坏情况下仍为 $O(n * m)$,当数据为网格图时,复杂度会从 $O(km)$ 退化为 $O(n*m)$。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n int N = 110, M = 6010;\\n // 邻接表\\n int[] he = new int[N], e = new int[M], ne = new int[M], w = new int[M];\\n // dist[x] = y 代表从「源点/起点」到 x 的最短距离为 y\\n int[] dist = new int[N];\\n // 记录哪一个点「已在队列」中\\n boolean[] vis = new boolean[N];\\n int INF = 0x3f3f3f3f;\\n int n, k, idx;\\n void add(int a, int b, int c) {\\n e[idx] = b;\\n ne[idx] = he[a];\\n he[a] = idx;\\n w[idx] = c;\\n idx++;\\n }\\n public int networkDelayTime(int[][] ts, int _n, int _k) {\\n n = _n; k = _k;\\n // 初始化链表头\\n Arrays.fill(he, -1);\\n // 存图\\n for (int[] t : ts) {\\n int u = t[0], v = t[1], c = t[2];\\n add(u, v, c);\\n }\\n // 最短路\\n spfa();\\n // 遍历答案\\n int ans = 0;\\n for (int i = 1; i <= n; i++) {\\n ans = Math.max(ans, dist[i]);\\n }\\n return ans > INF / 2 ? -1 : ans;\\n }\\n void spfa() {\\n // 起始先将所有的点标记为「未入队」和「距离为正无穷」\\n Arrays.fill(vis, false);\\n Arrays.fill(dist, INF);\\n // 只有起点最短距离为 0\\n dist[k] = 0;\\n // 使用「双端队列」存储,存储的是点编号\\n Deque<Integer> d = new ArrayDeque<>();\\n // 将「源点/起点」进行入队,并标记「已入队」\\n d.addLast(k);\\n vis[k] = true;\\n while (!d.isEmpty()) {\\n // 每次从「双端队列」中取出,并标记「未入队」\\n int poll = d.pollFirst();\\n vis[poll] = false;\\n // 尝试使用该点,更新其他点的最短距离\\n // 如果更新的点,本身「未入队」则加入队列中,并标记「已入队」\\n for (int i = he[poll]; i != -1; i = ne[i]) {\\n int j = e[i];\\n if (dist[j] > dist[poll] + w[i]) {\\n dist[j] = dist[poll] + w[i];\\n if (vis[j]) continue;\\n d.addLast(j);\\n vis[j] = true;\\n }\\n }\\n }\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n*m)$
\\n- 空间复杂度:$O(m)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与看题解学算法送实体书长期活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"欢迎关注我 ❤️ 提供写「证明」&「思路」的高质量专项题解 后台回复「刷题路线」有惊喜,更有「长期送实体书」活动等你来 🎉 🎉\\n\\n基本分析\\n\\n为了方便,我们约定 $n$ 为点数,$m$ 为边数。\\n\\n根据题意,首先 $n$ 的数据范围只有 $100$,$m$ 的数据范围为 $6000$,使用「邻接表」或「邻接矩阵」来存图都可以。\\n\\n同时求的是「从 $k$ 点出发,所有点都被访问到的最短时间」,将问题转换一下其实就是求「从 $k$ 点出发,到其他点 $x$ 的最短距离的最大值」。\\n\\n存图方式\\n\\n在开始讲解最短路之前,我们先来学习三种「存图」方式。\\n\\n邻接矩阵\\n\\n这…","guid":"https://leetcode.cn/problems/network-delay-time//solution/gong-shui-san-xie-yi-ti-wu-jie-wu-chong-oghpz","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-02T02:21:12.285Z","media":[{"url":"https://pic.leetcode-cn.com/1628304636-QXNdHd-image.png","type":"photo","width":1092,"height":550,"blurhash":"LPRMuO-qxu-;~DWUWUbFoyoLafj["},{"url":"https://pic.leetcode-cn.com/1628304649-fZWlTG-image.png","type":"photo","width":1066,"height":560,"blurhash":"LQRMuP-qxu-;~DWUWUa{ozj[afj["},{"url":"https://pic.leetcode-cn.com/1628304662-JgsyVc-image.png","type":"photo","width":1066,"height":558,"blurhash":"LPRWI}-qxu-;~DWUWUbFozoLafj["},{"url":"https://pic.leetcode-cn.com/1628304674-vZRSkx-image.png","type":"photo","width":1124,"height":568,"blurhash":"LQRMuP%Mxb-;~DWUWUfktQofafj["},{"url":"https://pic.leetcode-cn.com/1628304686-hfZCXS-image.png","type":"photo","width":1064,"height":568,"blurhash":"LQRWI}-pxu-;~DWUWUa{o|oLafj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【GTAlgorithm】图解算法,吃透一个Dijkstra就够了!C++/Java/Python","url":"https://leetcode.cn/problems/network-delay-time//solution/gtalgorithm-dan-yuan-zui-duan-lu-chi-tou-w3zc","content":"解法:最短路
\\n\\n
\\n- \\n
\\n题目实际是求节点 $K$ 到其他所有点中最远的距离,那么首先需要求出节点 $K$ 到其他所有点的最短路,然后取最大值即可。
\\n- \\n
\\n单源最短路问题可以使用
\\nDijkstra
算法,其核心思路是贪心算法。流程如下:\\n
\\n- \\n
\\n首先,
\\nDijkstra
算法需要从当前全部未确定最短路的点中,找到距离源点最短的点 $x$。- \\n
\\n其次,通过点 $x$ 更新其他所有点距离源点的最短距离。例如目前点
\\nA
距离源点最短,距离为3
;有一条A->B
的有向边,权值为1
,那么从源点先去A
点再去B
点距离为3 + 1 = 4
,若原先从源点到B
的有向边权值为5
,那么我们便可以更新B
到源点的最短距离为4
。- \\n
\\n当全部其他点都遍历完成后,一次循环结束,将 $x$ 标记为已经确定最短路。进入下一轮循环,直到全部点被标记为确定了最短路。
\\n
\\n\\n\\n我们通过一个[例子]对 Dijkstra 算法的流程深入了解一下:
\\n\\n
\\n以上图片为一个有向带权图,圆圈中为节点序号,箭头上为边权,右侧为所有点距离源点0
的距离。
\\n
\\n将顶点0
进行标识,并作为点 $x$,更新其到其他所有点的距离。一轮循环结束。
\\n
\\n将顶点
\\n2
进行标识,并作为新的点 $x$,更新。我们看到,原本点1
的最短距离为5
,被更新为了3
。同理还更新了点3
和点4
的最短距离。\\n
\\n将顶点
\\n1
进行标识,并作为新的点 $x$,同样更新了点4
到源点的最短距离。\\n
再分别标识点
\\n4
和点3
,循环结束。
\\n\\n
\\n- 我们来看在实现时需要的代码支持:\\n
\\n\\n
\\n- 首先,
\\nDijkstra
算法需要存储各个边权,由于本题节点数量不超过 $100$,所以代码中使用了邻接矩阵g[i][j]
存储从点i
到点j
的距离。若两点之间没有给出有向边,则初始化为inf
。算法还需要记录所有点到源点的最短距离,代码中使用了dist[i]
数组存储源点到点i
的最短距离,初始值也全部设为inf
。由于本题源点为 $K$,所以该点距离设为0
。- 其次,
\\nDijkstra
算法需要标记某一节点是否已确定了最短路,在代码中使用了used[i]
数组存储,若已确定最短距离,则值为true
,否则值为false
。- 之所以
\\ninf
设置为INT_MAX / 2
,是因为在更新最短距离的时候,要有两个距离相加,为了防止溢出int
型,所以除以2
。代码(C++)
\\n\\nclass Solution {\\npublic:\\n int networkDelayTime(vector<vector<int>> ×, int n, int k) {\\n const int inf = INT_MAX / 2;\\n\\n // 邻接矩阵存储边信息\\n vector<vector<int>> g(n, vector<int>(n, inf));\\n for (auto &t : times) {\\n // 边序号从 0 开始\\n int x = t[0] - 1, y = t[1] - 1;\\n g[x][y] = t[2];\\n }\\n\\n // 从源点到某点的距离数组\\n vector<int> dist(n, inf);\\n // 由于从 k 开始,所以该点距离设为 0,也即源点\\n dist[k - 1] = 0;\\n\\n // 节点是否被更新数组\\n vector<bool> used(n);\\n\\n for (int i = 0; i < n; ++i) {\\n // 在还未确定最短路的点中,寻找距离最小的点\\n int x = -1;\\n for (int y = 0; y < n; ++y) {\\n if (!used[y] && (x == -1 || dist[y] < dist[x])) {\\n x = y;\\n }\\n }\\n\\n // 用该点更新所有其他点的距离\\n used[x] = true;\\n for (int y = 0; y < n; ++y) {\\n dist[y] = min(dist[y], dist[x] + g[x][y]);\\n }\\n }\\n\\n // 找到距离最远的点\\n int ans = *max_element(dist.begin(), dist.end());\\n return ans == inf ? -1 : ans;\\n }\\n};\\n\\n
\\nclass Solution {\\n public int networkDelayTime(int[][] times, int n, int k) {\\n final int INF = Integer.MAX_VALUE / 2;\\n\\n // 邻接矩阵存储边信息\\n int[][] g = new int[n][n];\\n for (int i = 0; i < n; ++i) {\\n Arrays.fill(g[i], INF);\\n }\\n for (int[] t : times) {\\n // 边序号从 0 开始\\n int x = t[0] - 1, y = t[1] - 1;\\n g[x][y] = t[2];\\n }\\n\\n // 从源点到某点的距离数组\\n int[] dist = new int[n];\\n Arrays.fill(dist, INF);\\n // 由于从 k 开始,所以该点距离设为 0,也即源点\\n dist[k - 1] = 0;\\n\\n // 节点是否被更新数组\\n boolean[] used = new boolean[n];\\n\\n for (int i = 0; i < n; ++i) {\\n // 在还未确定最短路的点中,寻找距离最小的点\\n int x = -1;\\n for (int y = 0; y < n; ++y) {\\n if (!used[y] && (x == -1 || dist[y] < dist[x])) {\\n x = y;\\n }\\n }\\n\\n // 用该点更新所有其他点的距离\\n used[x] = true;\\n for (int y = 0; y < n; ++y) {\\n dist[y] = Math.min(dist[y], dist[x] + g[x][y]);\\n }\\n }\\n\\n // 找到距离最远的点\\n int ans = Arrays.stream(dist).max().getAsInt();\\n return ans == INF ? -1 : ans;\\n }\\n}\\n\\n
\\nclass Solution:\\n def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:\\n # 邻接矩阵\\n g = [[float(\'inf\')] * n for _ in range(n)]\\n for x, y, time in times:\\n g[x - 1][y - 1] = time\\n\\n # 距离数组\\n dist = [float(\'inf\')] * n\\n dist[k - 1] = 0\\n\\n # 标记数组\\n used = [False] * n\\n for _ in range(n):\\n # 找到未标记最近的点\\n x = -1\\n for y, u in enumerate(used):\\n if not u and (x == -1 or dist[y] < dist[x]):\\n x = y\\n \\n # 更新\\n used[x] = True\\n for y, time in enumerate(g[x]):\\n dist[y] = min(dist[y], dist[x] + time)\\n\\n ans = max(dist)\\n return ans if ans < float(\'inf\') else -1\\n\\n
\\n
\\n","description":"解法:最短路 题目实际是求节点 $K$ 到其他所有点中最远的距离,那么首先需要求出节点 $K$ 到其他所有点的最短路,然后取最大值即可。\\n\\n单源最短路问题可以使用 Dijkstra 算法,其核心思路是贪心算法。流程如下:\\n\\n首先,Dijkstra 算法需要从当前全部未确定最短路的点中,找到距离源点最短的点 $x$。\\n\\n其次,通过点 $x$ 更新其他所有点距离源点的最短距离。例如目前点 A 距离源点最短,距离为 3;有一条 A->B 的有向边,权值为 1,那么从源点先去 A 点再去 B 点距离为 3 + 1 = 4,若原先从源点到 B 的有向边权…","guid":"https://leetcode.cn/problems/network-delay-time//solution/gtalgorithm-dan-yuan-zui-duan-lu-chi-tou-w3zc","author":"已注销","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-08-02T02:06:45.332Z","media":[{"url":"https://pic.leetcode-cn.com/1627869535-JqaTqX-image.png","type":"photo","width":752,"height":407,"blurhash":"LLT8%f?^ml-;-;j[f6j[tllnVEcs"},{"url":"https://pic.leetcode-cn.com/1627869608-ZNeWka-image.png","type":"photo","width":892,"height":426,"blurhash":"LQSYv}ysZ%-;-;j[ayj[={%zmkTd"},{"url":"https://pic.leetcode-cn.com/1627869725-ywvvoW-image.png","type":"photo","width":757,"height":414,"blurhash":"LPR{ojysz:t--;j[f6j[}o%#MdyC"},{"url":"https://pic.leetcode-cn.com/1627869851-VtwdFS-image.png","type":"photo","width":776,"height":417,"blurhash":"LPS6DJ*JrCpd%MbbjZbI};yWQ-%f"},{"url":"https://pic.leetcode-cn.com/1627869870-IgtqGV-image.png","type":"photo","width":760,"height":420,"blurhash":"LLRypd_NMxx^X:$LJVs9^i?tIB-."},{"url":"https://pic.leetcode-cn.com/1627869881-fDnltO-image.png","type":"photo","width":770,"height":407,"blurhash":"LMRymW?^MeyEcG$LJVw[?E?tDj?Z"},{"url":"https://pic.leetcode-cn.com/1627869914-XQvKqz-image.png","type":"photo","width":802,"height":404,"blurhash":"LLRfnQ~pD,?a%Nj[ayfk-:^%9H^%"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"用三种不同颜色为网格涂色","url":"https://leetcode.cn/problems/painting-a-grid-with-three-different-colors//solution/yong-san-chong-bu-tong-yan-se-wei-wang-g-7nb2","content":"- 时间复杂度:$O(n^2+m)$,其中 $n, m$ 分别为节点和边的数量。如果用优先队列存储,能够将复杂度降为 $O(mlgm)$。
\\n- 空间复杂度:$O(n^2)$,利用了邻接矩阵。
\\n方法一:状态压缩动态规划
\\n提示 $1$
\\n要使得任意两个相邻的格子的颜色均不相同,我们需要保证:
\\n\\n
\\n- \\n
\\n同一行内任意两个相邻格子的颜色互不相同;
\\n- \\n
\\n相邻的两行之间,同一列上的两个格子的颜色互不相同。
\\n因此,我们可以考虑:
\\n\\n
\\n- \\n
\\n首先通过枚举的方法,找出所有对一行进行涂色的方案数;
\\n- \\n
\\n然后通过动态规划的方法,计算出对整个 $m \\\\times n$ 的方格进行涂色的方案数。
\\n在本题中,$m$ 和 $n$ 的最大值分别是 $5$ 和 $1000$,我们需要将较小的 $m$ 看成行的长度,较大的 $n$ 看成列的长度,这样才可以对一行进行枚举。
\\n思路与算法
\\n我们首先枚举对一行进行涂色的方案数。
\\n对于我们可以选择红绿蓝三种颜色,我们可以将它们看成 $0, 1, 2$。这样一来,一种涂色方案就对应着一个长度为 $m$ 的三进制数,其十进制的范围为 $[0, 3^m)$。
\\n因此,我们可以枚举 $[0, 3^m)$ 范围内的所有整数,将其转换为长度为 $m$ 的三进制串,再判断其是否满足任意相邻的两个数位均不相同即可。
\\n随后我们就可以使用动态规划来计算方案数了。我们用 $f[i][\\\\textit{mask}]$ 表示我们已经对 $0, 1, \\\\cdots, i$ 行进行了涂色,并且第 $i$ 行的涂色方案对应的三进制表示为 $\\\\textit{mask}$ 的前提下的总方案数。在进行状态转移时,我们可以考虑第 $i-1$ 行的涂色方案 $\\\\textit{mask}\'$:
\\n$$
\\n
\\nf[i][\\\\textit{mask}] = \\\\sum_{\\\\textit{mask} ~与~ \\\\textit{mask}\' 同一数位上的数字均不相同} f[i-1][\\\\textit{mask}\']
\\n$$只要 $\\\\textit{mask}\'$ 与 $\\\\textit{mask}$ 同一数位上的数字均不相同,就说明这两行可以相邻,我们就可以进行状态转移。
\\n最终的答案即为所有满足 $\\\\textit{mask} \\\\in [0, 3^m)$ 的 $f[n-1][\\\\textit{mask}]$ 之和。
\\n细节
\\n上述动态规划中的边界条件在于第 $0$ 行的涂色。当 $i=0$ 时,$f[i-1][..]$ 不是合法状态,无法进行转移,我们需要对它们进行特判:即如果 $\\\\textit{mask}$ 任意相邻的两个数位均不相同,那么 $f[0][\\\\textit{mask}] = 1$,否则 $f[0][\\\\textit{mask}] = 0$。
\\n在其余情况下的状态转移时,对于给定的 $\\\\textit{mask}$,我们总是要找出所有满足要求的 $\\\\textit{mask}\'$,因此我们不妨也把它们预处理出来,具体可以参考下方给出的代码。
\\n最后需要注意的是,在状态转移方程中,$f[i][..]$ 只会从 $f[i-1][..]$ 转移而来,因此我们可以使用两个长度为 $3^m$ 的一维数组,交替地进行状态转移。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n static constexpr int mod = 1000000007;\\n\\npublic:\\n int colorTheGrid(int m, int n) {\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n // 键表示 mask,值表示 mask 的三进制串(以列表的形式存储)\\n unordered_map<int, vector<int>> valid;\\n\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n int mask_end = pow(3, m);\\n for (int mask = 0; mask < mask_end; ++mask) {\\n vector<int> color;\\n int mm = mask;\\n for (int i = 0; i < m; ++i) {\\n color.push_back(mm % 3);\\n mm /= 3;\\n }\\n bool check = true;\\n for (int i = 0; i < m - 1; ++i) {\\n if (color[i] == color[i + 1]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n valid[mask] = move(color);\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n unordered_map<int, vector<int>> adjacent;\\n for (const auto& [mask1, color1]: valid) {\\n for (const auto& [mask2, color2]: valid) {\\n bool check = true;\\n for (int i = 0; i < m; ++i) {\\n if (color1[i] == color2[i]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n adjacent[mask1].push_back(mask2);\\n }\\n }\\n }\\n\\n vector<int> f(mask_end);\\n for (const auto& [mask, _]: valid) {\\n f[mask] = 1;\\n }\\n for (int i = 1; i < n; ++i) {\\n vector<int> g(mask_end);\\n for (const auto& [mask2, _]: valid) {\\n for (int mask1: adjacent[mask2]) {\\n g[mask2] += f[mask1];\\n if (g[mask2] >= mod) {\\n g[mask2] -= mod;\\n }\\n }\\n }\\n f = move(g);\\n }\\n\\n int ans = 0;\\n for (int num: f) {\\n ans += num;\\n if (ans >= mod) {\\n ans -= mod;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def colorTheGrid(self, m: int, n: int) -> int:\\n mod = 10**9 + 7\\n # 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n # 键表示 mask,值表示 mask 的三进制串(以列表的形式存储)\\n valid = dict()\\n \\n # 在 [0, 3^m) 范围内枚举满足要求的 mask\\n for mask in range(3**m):\\n color = list()\\n mm = mask\\n for i in range(m):\\n color.append(mm % 3)\\n mm //= 3\\n if any(color[i] == color[i + 1] for i in range(m - 1)):\\n continue\\n valid[mask] = color\\n \\n # 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n adjacent = defaultdict(list)\\n for mask1, color1 in valid.items():\\n for mask2, color2 in valid.items():\\n if not any(x == y for x, y in zip(color1, color2)):\\n adjacent[mask1].append(mask2)\\n \\n f = [int(mask in valid) for mask in range(3**m)]\\n for i in range(1, n):\\n g = [0] * (3**m)\\n for mask2 in valid.keys():\\n for mask1 in adjacent[mask2]:\\n g[mask2] += f[mask1]\\n if g[mask2] >= mod:\\n g[mask2] -= mod\\n f = g\\n \\n return sum(f) % mod\\n
###Java
\\n\\nclass Solution {\\n static final int mod = 1000000007;\\n\\n public int colorTheGrid(int m, int n) {\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n Map<Integer, List<Integer>> valid = new HashMap<>();\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n int maskEnd = (int) Math.pow(3, m);\\n for (int mask = 0; mask < maskEnd; ++mask) {\\n List<Integer> color = new ArrayList<>();\\n int mm = mask;\\n for (int i = 0; i < m; ++i) {\\n color.add(mm % 3);\\n mm /= 3;\\n }\\n boolean check = true;\\n for (int i = 0; i < m - 1; ++i) {\\n if (color.get(i).equals(color.get(i + 1))) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n valid.put(mask, color);\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n Map<Integer, List<Integer>> adjacent = new HashMap<>();\\n for (int mask1 : valid.keySet()) {\\n for (int mask2 : valid.keySet()) {\\n boolean check = true;\\n for (int i = 0; i < m; ++i) {\\n if (valid.get(mask1).get(i).equals(valid.get(mask2).get(i))) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n adjacent.computeIfAbsent(mask1, k -> new ArrayList<>()).add(mask2);\\n }\\n }\\n }\\n\\n Map<Integer, Integer> f = new HashMap<>();\\n for (int mask : valid.keySet()) {\\n f.put(mask, 1);\\n }\\n for (int i = 1; i < n; ++i) {\\n Map<Integer, Integer> g = new HashMap<>();\\n for (int mask2 : valid.keySet()) {\\n for (int mask1 : adjacent.getOrDefault(mask2, new ArrayList<>())) {\\n g.put(mask2, (g.getOrDefault(mask2, 0) + f.getOrDefault(mask1, 0)) % mod);\\n }\\n }\\n f = g;\\n }\\n\\n int ans = 0;\\n for (int num : f.values()) {\\n ans = (ans + num) % mod;\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n private const int mod = 1000000007;\\n\\n public int ColorTheGrid(int m, int n) {\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n var valid = new Dictionary<int, List<int>>();\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n int maskEnd = (int)Math.Pow(3, m);\\n for (int mask = 0; mask < maskEnd; ++mask) {\\n var color = new List<int>();\\n int mm = mask;\\n for (int i = 0; i < m; ++i) {\\n color.Add(mm % 3);\\n mm /= 3;\\n }\\n bool check = true;\\n for (int i = 0; i < m - 1; ++i) {\\n if (color[i] == color[i + 1]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n valid[mask] = color;\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n var adjacent = new Dictionary<int, List<int>>();\\n foreach (var mask1 in valid.Keys) {\\n foreach (var mask2 in valid.Keys) {\\n bool check = true;\\n for (int i = 0; i < m; ++i) {\\n if (valid[mask1][i] == valid[mask2][i]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n if (!adjacent.ContainsKey(mask1)) {\\n adjacent[mask1] = new List<int>();\\n }\\n adjacent[mask1].Add(mask2);\\n }\\n }\\n }\\n\\n var f = new Dictionary<int, int>();\\n foreach (var mask in valid.Keys) {\\n f[mask] = 1;\\n }\\n for (int i = 1; i < n; ++i) {\\n var g = new Dictionary<int, int>();\\n foreach (var mask2 in valid.Keys) {\\n if (adjacent.ContainsKey(mask2)) {\\n foreach (var mask1 in adjacent[mask2]) {\\n if (!g.ContainsKey(mask2)) {\\n g[mask2] = 0;\\n }\\n g[mask2] = (g[mask2] + f[mask1]) % mod;\\n }\\n }\\n }\\n f = g;\\n }\\n\\n int ans = 0;\\n foreach (var num in f.Values) {\\n ans = (ans + num) % mod;\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nconst mod = 1000000007\\n\\nfunc colorTheGrid(m int, n int) int {\\n// 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\nvalid := make(map[int][]int)\\n\\n// 在 [0, 3^m) 范围内枚举满足要求的 mask\\nmaskEnd := int(math.Pow(3, float64(m)))\\nfor mask := 0; mask < maskEnd; mask++ {\\ncolor := make([]int, m)\\nmm := mask\\nfor i := 0; i < m; i++ {\\ncolor[i] = mm % 3\\nmm /= 3\\n}\\ncheck := true\\nfor i := 0; i < m-1; i++ {\\nif color[i] == color[i+1] {\\ncheck = false\\nbreak\\n}\\n}\\nif check {\\nvalid[mask] = color\\n}\\n}\\n\\n// 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\nadjacent := make(map[int][]int)\\nfor mask1 := range valid {\\nfor mask2 := range valid {\\ncheck := true\\nfor i := 0; i < m; i++ {\\nif valid[mask1][i] == valid[mask2][i] {\\ncheck = false\\nbreak\\n}\\n}\\nif check {\\nadjacent[mask1] = append(adjacent[mask1], mask2)\\n}\\n}\\n}\\n\\nf := make(map[int]int)\\nfor mask := range valid {\\nf[mask] = 1\\n}\\nfor i := 1; i < n; i++ {\\ng := make(map[int]int)\\nfor mask2 := range valid {\\nfor _, mask1 := range adjacent[mask2] {\\ng[mask2] = (g[mask2] + f[mask1]) % mod\\n}\\n}\\nf = g\\n}\\n\\nans := 0\\nfor _, num := range f {\\nans = (ans + num) % mod\\n}\\nreturn ans\\n}\\n
###C
\\n\\n#define MOD 1000000007\\n\\nstruct ListNode *createListNode(int val) {\\n struct ListNode *obj = (struct ListNode*)malloc(sizeof(struct ListNode));\\n obj->val = val;\\n obj->next = NULL;\\n return obj;\\n}\\n\\nvoid freeList(struct ListNode *list) {\\n while (list) {\\n struct ListNode *p = list;\\n list = list->next;\\n free(p);\\n }\\n}\\n\\ntypedef struct {\\n int key;\\n struct ListNode *val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n struct ListNode *p = createListNode(val);\\n if (!pEntry) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = p;\\n HASH_ADD_INT(*obj, key, pEntry);\\n } else {\\n p->next = pEntry->val;\\n pEntry->val = p;\\n }\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, struct ListNode *list) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = list;\\n HASH_ADD_INT(*obj, key, pEntry);\\n } else {\\n freeList(pEntry->val);\\n pEntry->val = list;\\n }\\n return true;\\n}\\n\\nstruct ListNode* hashGetItem(HashItem **obj, int key) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return NULL;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n freeList(curr->val); \\n free(curr);\\n }\\n}\\n\\n// 主函数\\nint colorTheGrid(int m, int n) {\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n // 键表示 mask,值表示 mask 的三进制串(以列表的形式存储)\\n HashItem *valid = NULL;\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n int mask_end = pow(3, m);\\n for (int mask = 0; mask < mask_end; ++mask) {\\n int mm = mask;\\n int color[m];\\n for (int i = 0; i < m; ++i) {\\n color[i] = mm % 3;\\n mm /= 3;\\n }\\n bool check = true;\\n for (int i = 0; i < m - 1; ++i) {\\n if (color[i] == color[i + 1]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n for (int i = 0; i < m; i++) {\\n hashAddItem(&valid, mask, color[i]);\\n }\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n HashItem *adjacent = NULL;\\n for (HashItem *pEntry1 = valid; pEntry1; pEntry1 = pEntry1->hh.next) {\\n int mask1 = pEntry1->key;\\n for (HashItem *pEntry2 = valid; pEntry2; pEntry2 = pEntry2->hh.next) {\\n int mask2 = pEntry2->key;\\n bool check = true;\\n for (struct ListNode *p1 = pEntry1->val, *p2 = pEntry2->val; p1 && p2; p1 = p1->next, p2 = p2->next) {\\n if (p1->val == p2->val) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n hashAddItem(&adjacent, mask1, mask2);\\n }\\n }\\n }\\n\\n int f[mask_end];\\n memset(f, 0, sizeof(f));\\n for (HashItem *pEntry = valid; pEntry; pEntry = pEntry->hh.next) {\\n int mask = pEntry->key;\\n f[mask] = 1;\\n }\\n for (int i = 1; i < n; ++i) {\\n int g[mask_end];\\n memset(g, 0, sizeof(g));\\n for (HashItem *pEntry1 = valid; pEntry1; pEntry1 = pEntry1->hh.next) {\\n int mask2 = pEntry1->key;\\n for (struct ListNode *p = hashGetItem(&adjacent, mask2); p != NULL; p = p->next) {\\n int mask1 = p->val;\\n g[mask2] += f[mask1];\\n if (g[mask2] >= MOD) {\\n g[mask2] -= MOD;\\n }\\n }\\n }\\n memcpy(f, g, sizeof(f));\\n }\\n\\n int ans = 0;\\n for (int i = 0; i < mask_end; i++) {\\n ans += f[i];\\n if (ans >= MOD) {\\n ans -= MOD;\\n }\\n }\\n hashFree(&valid);\\n hashFree(&adjacent);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar colorTheGrid = function(m, n) {\\n const mod = 1000000007;\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n const valid = new Map();\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n const maskEnd = Math.pow(3, m);\\n for (let mask = 0; mask < maskEnd; ++mask) {\\n const color = [];\\n let mm = mask;\\n for (let i = 0; i < m; ++i) {\\n color.push(mm % 3);\\n mm = Math.floor(mm / 3);\\n }\\n let check = true;\\n for (let i = 0; i < m - 1; ++i) {\\n if (color[i] === color[i + 1]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n valid.set(mask, color);\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n const adjacent = new Map();\\n for (const [mask1, color1] of valid.entries()) {\\n for (const [mask2, color2] of valid.entries()) {\\n let check = true;\\n for (let i = 0; i < m; ++i) {\\n if (color1[i] === color2[i]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n if (!adjacent.has(mask1)) {\\n adjacent.set(mask1, []);\\n }\\n adjacent.get(mask1).push(mask2);\\n }\\n }\\n }\\n\\n let f = new Map();\\n for (const [mask, _] of valid.entries()) {\\n f.set(mask, 1);\\n }\\n for (let i = 1; i < n; ++i) {\\n const g = new Map();\\n for (const [mask2, _] of valid.entries()) {\\n for (const mask1 of adjacent.get(mask2) || []) {\\n g.set(mask2, ((g.get(mask2) || 0) + f.get(mask1)) % mod);\\n }\\n }\\n f = g;\\n }\\n\\n let ans = 0;\\n for (const num of f.values()) {\\n ans = (ans + num) % mod;\\n }\\n return ans;\\n}\\n
###TypeScript
\\n\\nfunction colorTheGrid(m: number, n: number): number {\\n const mod = 1000000007;\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n const valid = new Map<number, number[]>();\\n\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n const maskEnd = Math.pow(3, m);\\n for (let mask = 0; mask < maskEnd; ++mask) {\\n const color: number[] = [];\\n let mm = mask;\\n for (let i = 0; i < m; ++i) {\\n color.push(mm % 3);\\n mm = Math.floor(mm / 3);\\n }\\n let check = true;\\n for (let i = 0; i < m - 1; ++i) {\\n if (color[i] === color[i + 1]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n valid.set(mask, color);\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n const adjacent = new Map<number, number[]>();\\n for (const [mask1, color1] of valid.entries()) {\\n for (const [mask2, color2] of valid.entries()) {\\n let check = true;\\n for (let i = 0; i < m; ++i) {\\n if (color1[i] === color2[i]) {\\n check = false;\\n break;\\n }\\n }\\n if (check) {\\n if (!adjacent.has(mask1)) {\\n adjacent.set(mask1, []);\\n }\\n adjacent.get(mask1)!.push(mask2);\\n }\\n }\\n }\\n\\n let f = new Map<number, number>();\\n for (const [mask, _] of valid.entries()) {\\n f.set(mask, 1);\\n }\\n for (let i = 1; i < n; ++i) {\\n const g = new Map<number, number>();\\n for (const [mask2, _] of valid.entries()) {\\n for (const mask1 of adjacent.get(mask2) || []) {\\n g.set(mask2, ((g.get(mask2) || 0) + f.get(mask1)!) % mod);\\n }\\n }\\n f = g;\\n }\\n\\n let ans = 0;\\n for (const num of f.values()) {\\n ans = (ans + num) % mod;\\n }\\n return ans;\\n}\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nconst MOD: i32 = 1_000_000_007;\\n\\nimpl Solution {\\n pub fn color_the_grid(m: i32, n: i32) -> i32 {\\n let m = m as usize;\\n let n = n as usize;\\n // 哈希映射 valid 存储所有满足要求的对一行进行涂色的方案\\n let mut valid = HashMap::new();\\n // 在 [0, 3^m) 范围内枚举满足要求的 mask\\n let mask_end = 3i32.pow(m as u32);\\n for mask in 0..mask_end {\\n let mut color = Vec::new();\\n let mut mm = mask;\\n for _ in 0..m {\\n color.push(mm % 3);\\n mm /= 3;\\n }\\n let mut check = true;\\n for i in 0..m - 1 {\\n if color[i] == color[i + 1] {\\n check = false;\\n break;\\n }\\n }\\n if check {\\n valid.insert(mask, color);\\n }\\n }\\n\\n // 预处理所有的 (mask1, mask2) 二元组,满足 mask1 和 mask2 作为相邻行时,同一列上两个格子的颜色不同\\n let mut adjacent = HashMap::new();\\n for (&mask1, color1) in &valid {\\n for (&mask2, color2) in &valid {\\n let mut check = true;\\n for i in 0..m {\\n if color1[i] == color2[i] {\\n check = false;\\n break;\\n }\\n }\\n if check {\\n adjacent.entry(mask1).or_insert(Vec::new()).push(mask2);\\n }\\n }\\n }\\n\\n let mut f = HashMap::new();\\n for &mask in valid.keys() {\\n f.insert(mask, 1);\\n }\\n for _ in 1..n {\\n let mut g = HashMap::new();\\n for &mask2 in valid.keys() {\\n let mut total = 0;\\n if let Some(list) = adjacent.get(&mask2) {\\n for &mask1 in list {\\n total = (total + f.get(&mask1).unwrap_or(&0)) % MOD;\\n }\\n }\\n g.insert(mask2, total);\\n }\\n f = g;\\n }\\n\\n let mut ans = 0;\\n for &num in f.values() {\\n ans = (ans + num) % MOD;\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(3^{2m} \\\\cdot n)$。
\\n\\n
\\n- \\n
\\n预处理 $\\\\textit{mask}$ 的时间复杂度为 $O(m \\\\cdot 3^m)$;
\\n- \\n
\\n预处理 $(\\\\textit{mask}, \\\\textit{mask}\')$ 二元组的时间复杂度为 $O(3^{2m})$;
\\n- \\n
\\n动态规划的时间复杂度为 $O(3^{2m} \\\\cdot n)$,其在渐近意义下大于前两者。
\\n- \\n
\\n空间复杂度:$O(3^{2m})$。
\\n\\n
\\n- \\n
\\n存储 $\\\\textit{mask}$ 的哈希映射需要的空间为 $O(m \\\\cdot 3^m)$;
\\n- \\n
\\n存储 $(\\\\textit{mask}, \\\\textit{mask}\')$ 二元组需要的空间为 $O(3^{2m})$,在渐进意义下大于其余两者;
\\n- \\n
\\n动态规划存储状态需要的空间为 $O(3^m)$。
\\n不过需要注意的是,在实际的情况下,当 $m=5$ 时,满足要求的 $\\\\textit{mask}$ 仅有 $48$ 个,远小于 $3^m=324$;满足要求的 $(\\\\textit{mask}, \\\\textit{mask}\')$ 二元组仅有 $486$ 对,远小于 $3^{2m}=59049$。因此该算法的实际运行时间会较快。
\\n","description":"方法一:状态压缩动态规划 提示 $1$\\n\\n要使得任意两个相邻的格子的颜色均不相同,我们需要保证:\\n\\n同一行内任意两个相邻格子的颜色互不相同;\\n\\n相邻的两行之间,同一列上的两个格子的颜色互不相同。\\n\\n因此,我们可以考虑:\\n\\n首先通过枚举的方法,找出所有对一行进行涂色的方案数;\\n\\n然后通过动态规划的方法,计算出对整个 $m \\\\times n$ 的方格进行涂色的方案数。\\n\\n在本题中,$m$ 和 $n$ 的最大值分别是 $5$ 和 $1000$,我们需要将较小的 $m$ 看成行的长度,较大的 $n$ 看成列的长度,这样才可以对一行进行枚举。\\n\\n思路与算法…","guid":"https://leetcode.cn/problems/painting-a-grid-with-three-different-colors//solution/yong-san-chong-bu-tong-yan-se-wei-wang-g-7nb2","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-11T07:17:39.585Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"规定时间内到达终点的最小花费","url":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/gui-ding-shi-jian-nei-dao-da-zhong-dian-n3ews","content":"方法一:动态规划
\\n思路与算法
\\n我们用 $f[t][i]$ 表示使用恰好 $t$ 分钟到达城市 $i$ 需要的最少通行费总和。
\\n在状态转移时,我们考虑最后一次通行是从城市 $j$ 到达城市 $i$ 的,那么有状态转移方程:
\\n$$
\\n
\\nf[t][i] = \\\\min_{(j, i) \\\\in E} \\\\big{ f[t-\\\\textit{cost}(j, i)][j] + \\\\textit{passingFees}[i] \\\\big}
\\n$$其中 $(j, i) \\\\in E$ 表示城市 $j$ 与 $i$ 存在一条道路,$\\\\textit{cost}(j, i)$ 表示这条道路的耗费时间。
\\n最终的答案即为 $f[1][n-1], f[2][n-1], \\\\cdots, f[\\\\textit{maxTime}][n-1]$ 中的最小值。
\\n细节
\\n初始状态为 $f[0][0] = \\\\textit{passingFees}[0]$,即我们一开始位于 $0$ 号城市,需要交 $\\\\textit{passingFees}[0]$ 的通行费。
\\n由于我们的状态转移方程中的目标的最小值,因此对于其它的状态,我们可以在一开始赋予它们一个极大值 $\\\\infty$。如果最终的答案为 $\\\\infty$,说明无法在 $\\\\textit{maxTime}$ 及以内完成旅行,返回 $-1$。
\\n此外,本题中的道路是以数组 $\\\\textit{edges}$ 的形式给定的,在动态规划的过程中,如果我们使用两重循环枚举 $t$ 和 $i$,就不能利用 $\\\\textit{edges}$,而需要使用额外的数据结构存储以 $i$ 为端点的所有道路。一种合理的解决方法是,我们使用一重循环枚举 $t$,另一重循环枚举 $\\\\textit{edges}$ 中的每一条边 $(i, j, \\\\textit{cost})$,通过这条边更新 $f[t][i]$ 以及 $f[t][j]$ 的值。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n // 极大值\\n static constexpr int INFTY = INT_MAX / 2;\\n\\npublic:\\n int minCost(int maxTime, vector<vector<int>>& edges, vector<int>& passingFees) {\\n int n = passingFees.size();\\n vector<vector<int>> f(maxTime + 1, vector<int>(n, INFTY));\\n f[0][0] = passingFees[0];\\n for (int t = 1; t <= maxTime; ++t) {\\n for (const auto& edge: edges) {\\n int i = edge[0], j = edge[1], cost = edge[2];\\n if (cost <= t) {\\n f[t][i] = min(f[t][i], f[t - cost][j] + passingFees[i]);\\n f[t][j] = min(f[t][j], f[t - cost][i] + passingFees[j]);\\n }\\n }\\n }\\n\\n int ans = INFTY;\\n for (int t = 1; t <= maxTime; ++t) {\\n ans = min(ans, f[t][n - 1]);\\n }\\n return ans == INFTY ? -1 : ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int minCost(int maxTime, int[][] edges, int[] passingFees) {\\n int n = passingFees.length;\\n int[][] f = new int[maxTime + 1][n];\\n for (int i = 0; i <= maxTime; i++) {\\n Arrays.fill(f[i], Integer.MAX_VALUE);\\n }\\n f[0][0] = passingFees[0];\\n for (int t = 1; t <= maxTime; t++) {\\n for (int[] edge : edges) {\\n int i = edge[0], j = edge[1], cost = edge[2];\\n if (cost <= t) {\\n if (f[t - cost][j] != Integer.MAX_VALUE) {\\n f[t][i] = Math.min(f[t][i], f[t - cost][j] + passingFees[i]);\\n }\\n if (f[t - cost][i] != Integer.MAX_VALUE) {\\n f[t][j] = Math.min(f[t][j], f[t - cost][i] + passingFees[j]);\\n }\\n }\\n }\\n }\\n int ans = Integer.MAX_VALUE;\\n for (int t = 1; t <= maxTime; t++) {\\n ans = Math.min(ans, f[t][n - 1]);\\n }\\n return ans == Integer.MAX_VALUE ? -1 : ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinCost(int maxTime, int[][] edges, int[] passingFees) {\\n int n = passingFees.Length;\\n int[,] f = new int[maxTime + 1, n];\\n for (int i = 0; i <= maxTime; i++) {\\n for (int j = 0; j < n; j++) {\\n f[i, j] = int.MaxValue;\\n }\\n }\\n f[0, 0] = passingFees[0];\\n for (int t = 1; t <= maxTime; t++) {\\n foreach (var edge in edges) {\\n int i = edge[0], j = edge[1], cost = edge[2];\\n if (cost <= t) {\\n if (f[t - cost, j] != int.MaxValue) {\\n f[t, i] = Math.Min(f[t, i], f[t - cost, j] + passingFees[i]);\\n }\\n if (f[t - cost, i] != int.MaxValue) {\\n f[t, j] = Math.Min(f[t, j], f[t - cost, i] + passingFees[j]);\\n }\\n }\\n }\\n }\\n \\n int ans = int.MaxValue;\\n for (int t = 1; t <= maxTime; t++) {\\n ans = Math.Min(ans, f[t, n - 1]);\\n }\\n return ans == int.MaxValue ? -1 : ans;\\n }\\n}\\n
###Go
\\n\\nfunc minCost(maxTime int, edges [][]int, passingFees []int) int {\\n n := len(passingFees)\\n f := make([][]int, maxTime+1)\\n for i := range f {\\n f[i] = make([]int, n)\\n for j := range f[i] {\\n f[i][j] = math.MaxInt32\\n }\\n }\\n f[0][0] = passingFees[0]\\n for t := 1; t <= maxTime; t++ {\\n for _, edge := range edges {\\n i, j, cost := edge[0], edge[1], edge[2]\\n if cost <= t {\\n if f[t-cost][j] != math.MaxInt32 {\\n f[t][i] = min(f[t][i], f[t - cost][j] + passingFees[i])\\n }\\n if f[t-cost][i] != math.MaxInt32 {\\n f[t][j] = min(f[t][j], f[t - cost][i] + passingFees[j])\\n }\\n }\\n }\\n }\\n\\n ans := math.MaxInt32\\n for t := 1; t <= maxTime; t++ {\\n ans = min(ans, f[t][n - 1])\\n }\\n if ans == math.MaxInt32 {\\n return -1\\n }\\n return ans\\n}\\n
###Python
\\n\\nclass Solution:\\n def minCost(self, maxTime: int, edges: List[List[int]], passingFees: List[int]) -> int:\\n n = len(passingFees)\\n f = [[float(\\"inf\\")] * n for _ in range(maxTime + 1)]\\n f[0][0] = passingFees[0]\\n for t in range(1, maxTime + 1):\\n for i, j, cost in edges:\\n if cost <= t:\\n f[t][i] = min(f[t][i], f[t - cost][j] + passingFees[i])\\n f[t][j] = min(f[t][j], f[t - cost][i] + passingFees[j])\\n\\n ans = min(f[t][n - 1] for t in range(1, maxTime + 1))\\n return -1 if ans == float(\\"inf\\") else ans\\n
###C
\\n\\nint minCost(int maxTime, int** edges, int edgesSize, int* edgesColSize, int* passingFees, int passingFeesSize) {\\n int n = passingFeesSize;\\n int f[maxTime + 1][n];\\n for (int i = 0; i <= maxTime; i++) {\\n for (int j = 0; j < n; j++) {\\n f[i][j] = INT_MAX;\\n }\\n }\\n f[0][0] = passingFees[0];\\n for (int t = 1; t <= maxTime; t++) {\\n for (int k = 0; k < edgesSize; k++) {\\n int i = edges[k][0], j = edges[k][1], cost = edges[k][2];\\n if (cost <= t) {\\n if (f[t - cost][j] != INT_MAX) {\\n f[t][i] = fmin(f[t][i], f[t - cost][j] + passingFees[i]);\\n }\\n if (f[t - cost][i] != INT_MAX) {\\n f[t][j] = fmin(f[t][j], f[t - cost][i] + passingFees[j]);\\n }\\n }\\n }\\n }\\n\\n int ans = INT_MAX;\\n for (int t = 1; t <= maxTime; t++) {\\n ans = fmin(ans, f[t][n - 1]);\\n }\\n return ans == INT_MAX ? -1 : ans;\\n}\\n
###JavaScript
\\n\\nvar minCost = function(maxTime, edges, passingFees) {\\n const n = passingFees.length;\\n const f = Array.from({ length: maxTime + 1 }, () => Array(n).fill(Infinity));\\n f[0][0] = passingFees[0];\\n for (let t = 1; t <= maxTime; t++) {\\n for (const [i, j, cost] of edges) {\\n if (cost <= t) {\\n if (f[t - cost][j] !== Infinity) {\\n f[t][i] = Math.min(f[t][i], f[t - cost][j] + passingFees[i]);\\n }\\n if (f[t - cost][i] !== Infinity) {\\n f[t][j] = Math.min(f[t][j], f[t - cost][i] + passingFees[j]);\\n }\\n }\\n }\\n }\\n\\n let ans = Infinity;\\n for (let t = 1; t <= maxTime; t++) {\\n ans = Math.min(ans, f[t][n - 1]);\\n }\\n return ans === Infinity ? -1 : ans;\\n};\\n
###TypeScript
\\n\\nfunction minCost(maxTime: number, edges: number[][], passingFees: number[]): number {\\n const n = passingFees.length;\\n const f: number[][] = Array.from({ length: maxTime + 1 }, () => Array(n).fill(Infinity));\\n f[0][0] = passingFees[0];\\n\\n for (let t = 1; t <= maxTime; t++) {\\n for (const [i, j, cost] of edges) {\\n if (cost <= t) {\\n if (f[t - cost][j] !== Infinity) {\\n f[t][i] = Math.min(f[t][i], f[t - cost][j] + passingFees[i]);\\n }\\n if (f[t - cost][i] !== Infinity) {\\n f[t][j] = Math.min(f[t][j], f[t - cost][i] + passingFees[j]);\\n }\\n }\\n }\\n }\\n\\n let ans = Infinity;\\n for (let t = 1; t <= maxTime; t++) {\\n ans = Math.min(ans, f[t][n - 1]);\\n }\\n return ans === Infinity ? -1 : ans;\\n};\\n
###Rust
\\n\\nuse std::cmp::min;\\n\\nimpl Solution {\\n pub fn min_cost(max_time: i32, edges: Vec<Vec<i32>>, passing_fees: Vec<i32>) -> i32 {\\n let n = passing_fees.len();\\n let mut f = vec![vec![i32::MAX; n]; (max_time + 1) as usize];\\n f[0][0] = passing_fees[0];\\n\\n for t in 1..=max_time {\\n for edge in &edges {\\n let (i, j, cost) = (edge[0] as usize, edge[1] as usize, edge[2]);\\n if cost <= t {\\n if f[(t - cost) as usize][j] != i32::MAX {\\n f[t as usize][i] = min(f[t as usize][i], f[(t - cost) as usize][j] + passing_fees[i]);\\n }\\n if f[(t - cost) as usize][i] != i32::MAX {\\n f[t as usize][j] = min(f[t as usize][j], f[(t - cost) as usize][i] + passing_fees[j]);\\n }\\n }\\n }\\n }\\n\\n let mut ans = i32::MAX;\\n for t in 1..=max_time {\\n ans = min(ans, f[t as usize][n - 1]);\\n }\\n if ans == i32::MAX {\\n -1\\n } else {\\n ans\\n }\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func minCost(maxTime: Int64, edges: Array<Array<Int64>>, passingFees: Array<Int64>): Int64 {\\n let n = passingFees.size\\n let infinity = Int.Max >> 1\\n var f = Array<Array<Int64>>(maxTime + 1, {_ => Array<Int64>(n, item: infinity)})\\n f[0][0] = passingFees[0]\\n for (t in 1..= maxTime) {\\n for (edge in edges) {\\n let i = edge[0]\\n let j = edge[1]\\n let cost = edge[2]\\n if (cost <= t) {\\n f[t][i] = min(f[t][i], f[t - cost][j] + passingFees[i])\\n f[t][j] = min(f[t][j], f[t - cost][i] + passingFees[j])\\n }\\n }\\n }\\n\\n var ans = infinity\\n for (t in 1..= maxTime) {\\n ans = min(ans, f[t][n - 1])\\n }\\n if (ans == infinity) {\\n return -1\\n }\\n return ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:动态规划 思路与算法\\n\\n我们用 $f[t][i]$ 表示使用恰好 $t$ 分钟到达城市 $i$ 需要的最少通行费总和。\\n\\n在状态转移时,我们考虑最后一次通行是从城市 $j$ 到达城市 $i$ 的,那么有状态转移方程:\\n\\n$$\\n f[t][i] = \\\\min_{(j, i) \\\\in E} \\\\big{ f[t-\\\\textit{cost}(j, i)][j] + \\\\textit{passingFees}[i] \\\\big}\\n $$\\n\\n其中 $(j, i) \\\\in E$ 表示城市 $j$ 与 $i$ 存在一条道路,$\\\\textit{cost}(j, i…","guid":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/gui-ding-shi-jian-nei-dao-da-zhong-dian-n3ews","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-11T06:07:11.615Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(2^m*n)大聪明解法,推了个小时,0ms用时,绝对双百","url":"https://leetcode.cn/problems/painting-a-grid-with-three-different-colors//solution/onda-cong-ming-suan-fa-tui-liao-ge-xiao-nvfk0","content":"- \\n
\\n时间复杂度:$O((n+m) \\\\cdot \\\\textit{maxTimes})$,其中 $m$ 是数组 $\\\\textit{edges}$ 的长度。
\\n\\n
\\n- \\n
\\n我们需要 $O(n \\\\cdot \\\\textit{maxTimes})$ 的时间初始化数组 $f$;
\\n- \\n
\\n动态规划需要的时间为 $O(m \\\\cdot \\\\textit{maxTimes})$。
\\n- \\n
\\n空间复杂度:$O(n \\\\cdot \\\\textit{maxTimes})$。
\\nA代表第一种颜色,B代表第二种颜色,C代表第三种颜色。
\\n0代表红色,1代表绿色,2代表蓝色。
\\n1. m=1
\\n第一行有3种情况,且接下来的所有行,都是2种情况
\\n\\nlong long mod = 1000000007;\\n int ans=3;\\n for(int i=1;i<n;++i) \\n ans= (ans * 2LL) % mod;\\n return ans;\\n
2. m=2
\\n所有颜色排列有6种
\\n\\n01, 02, 10, 12, 20, 21\\n
可分类为AB
\\n
\\n对于第一行为AB,第二行则有BA、BC、CA三种情况,第三行同理有对应三种情况,第n行同理有三种情况。\\nlong long mod = 1000000007;\\n int ans=6;\\n for(int i=1;i<n;++i)\\n ans= (ans * 3LL) % mod;\\n return ans;\\n
3. m=3
\\n所有颜色排列有12种
\\n\\n010, 012, 020, 021, 101, 102, 120, 121, 201, 202, 210, 212\\n
可分类为ABC和ABA
\\n\\n
\\n- ABC类:共6种:012, 021, 102, 120, 201, 210;
\\n- ABA类:共6种:010, 020, 101, 121, 202, 212。
\\n则可据此根据上一行的类型递推该行的类型种数。
\\n\\n
\\n- 第 i - 1 行是 ABC 类,第 i 行是 ABC 类:以 012 为例,那么第 i 行只能是120 或 201,方案数为 2;
\\n- 第 i - 1 行是 ABC 类,第 i 行是 ABA 类:以 012 为例,那么第 i 行只能是101 或 121,方案数为 2;
\\n- 第 i - 1 行是 ABA 类,第 i 行是 ABC 类:以 010 为例,那么第 i 行只能是102 或 201,方案数为 2;
\\n- 第 i - 1 行是 ABA 类,第 i 行是 ABA 类:以 010 为例,那么第 i 行只能是101,121 或 202,方案数为 3。
\\n故有递推式
\\n\\nf[i][0] = 2 * f[i - 1][0] + 2 * f[i - 1][1];\\nf[i][1] = 2 * f[i - 1][0] + 3 * f[i - 1][1];\\n
4. m=4
\\n所有颜色排列有24种
\\n
\\n可分类为ABCA、ABCB、ABAB、ABAC
\\n(实际上ABCB和ABAC可归为一类,见评论区用户AndrewPei代码)\\n
\\n- ABCA类:共6种
\\n- ABCB类:共6种
\\n- ABAB类:共6种
\\n- ABAC类:共6种
\\n则可据此根据上一行的类型递推该行的类型种数。
\\n\\n
\\n- 第 i - 1 行是 ABCA 类,第 i 行是 ABCA 类:方案数为 3;
\\n- 第 i - 1 行是 ABCA 类,第 i 行是 ABCB 类:方案数为 2;
\\n- 第 i - 1 行是 ABCA 类,第 i 行是 ABAB 类:方案数为 1;
\\n- 第 i - 1 行是 ABCA 类,第 i 行是 ABAC 类:方案数为 2。
\\n- 第 i - 1 行是 ABCB 类,第 i 行是 ABCA 类:方案数为 2;
\\n- 第 i - 1 行是 ABCB 类,第 i 行是 ABCB 类:方案数为 2;
\\n- 第 i - 1 行是 ABCB 类,第 i 行是 ABAB 类:方案数为 1;
\\n- 第 i - 1 行是 ABCB 类,第 i 行是 ABAC 类:方案数为 2。
\\n- 第 i - 1 行是 ABAB 类,第 i 行是 ABCA 类:方案数为 1;
\\n- 第 i - 1 行是 ABAB 类,第 i 行是 ABCB 类:方案数为 1;
\\n- 第 i - 1 行是 ABAB 类,第 i 行是 ABAB 类:方案数为 2;
\\n- 第 i - 1 行是 ABAB 类,第 i 行是 ABAC 类:方案数为 1。
\\n- 第 i - 1 行是 ABAC 类,第 i 行是 ABCA 类:方案数为 2;
\\n- 第 i - 1 行是 ABAC 类,第 i 行是 ABCB 类:方案数为 2;
\\n- 第 i - 1 行是 ABAC 类,第 i 行是 ABAB 类:方案数为 1;
\\n- 第 i - 1 行是 ABAC 类,第 i 行是 ABAC 类:方案数为 2。
\\n故有递推式
\\n\\nf[i][0] = 3 * f[i - 1][0] + 2 * f[i - 1][1] + 1 * f[i - 1][2] + 2 * f[i - 1][3];\\nf[i][1] = 2 * f[i - 1][0] + 2 * f[i - 1][1] + 1 * f[i - 1][2] + 2 * f[i - 1][3];\\nf[i][2] = 1 * f[i - 1][0] + 1 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3];\\nf[i][3] = 2 * f[i - 1][0] + 2 * f[i - 1][1] + 1 * f[i - 1][2] + 2 * f[i - 1][3];\\n
5. m=5
\\n同理,实在是写不下去了,直接上递推式
\\n\\nf[i][0] = 3 * f[i - 1][0] + 2 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3] + 0 * f[i - 1][4] + 1 * f[i - 1][5] + 2 * f[i - 1][6] + 2 * f[i - 1][7];\\nf[i][1] = 2 * f[i - 1][0] + 2 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3] + 1 * f[i - 1][4] + 1 * f[i - 1][5] + 1 * f[i - 1][6] + 1 * f[i - 1][7];\\nf[i][2] = 2 * f[i - 1][0] + 2 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3] + 0 * f[i - 1][4] + 1 * f[i - 1][5] + 2 * f[i - 1][6] + 2 * f[i - 1][7];\\nf[i][3] = 1 * f[i - 1][0] + 1 * f[i - 1][1] + 1 * f[i - 1][2] + 2 * f[i - 1][3] + 1 * f[i - 1][4] + 1 * f[i - 1][5] + 1 * f[i - 1][6] + 1 * f[i - 1][7];\\nf[i][4] = 0 * f[i - 1][0] + 1 * f[i - 1][1] + 0 * f[i - 1][2] + 1 * f[i - 1][3] + 2 * f[i - 1][4] + 1 * f[i - 1][5] + 0 * f[i - 1][6] + 1 * f[i - 1][7];\\nf[i][5] = 1 * f[i - 1][0] + 1 * f[i - 1][1] + 1 * f[i - 1][2] + 1 * f[i - 1][3] + 1 * f[i - 1][4] + 2 * f[i - 1][5] + 1 * f[i - 1][6] + 1 * f[i - 1][7];\\nf[i][6] = 2 * f[i - 1][0] + 1 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3] + 0 * f[i - 1][4] + 1 * f[i - 1][5] + 2 * f[i - 1][6] + 1 * f[i - 1][7];\\nf[i][7] = 2 * f[i - 1][0] + 1 * f[i - 1][1] + 2 * f[i - 1][2] + 1 * f[i - 1][3] + 1 * f[i - 1][4] + 1 * f[i - 1][5] + 1 * f[i - 1][6] + 2 * f[i - 1][7];\\n
代码如下(貌似系数可以矩阵快速幂递推来着,等我哪天有时间再试试)
\\n\\nclass Solution {\\npublic:\\n int colorTheGrid(int m, int n) {\\n long long mod = 1000000007;\\n if(m==1)\\n {\\n int ans=3;\\n for(int i=1;i<n;++i) ans= ans * 2LL % mod;\\n return ans;\\n }\\n else if(m==2)\\n {\\n int fi = 6;\\n for(int i=1;i<n;++i) fi= 3LL * fi % mod;\\n return fi;\\n }\\n else if(m==3)\\n {\\n int fi0 = 6, fi1 = 6;\\n for (int i = 1; i < n; ++i) {\\n int new_fi0 = (2LL * fi0 + 2LL * fi1) % mod;\\n int new_fi1 = (2LL * fi0 + 3LL * fi1) % mod;\\n fi0 = new_fi0;\\n fi1 = new_fi1;\\n }\\n return ((long long)fi0 + fi1) % mod;\\n }\\n else if(m==4)\\n {\\n //ABAB//ABAC//ABCA//ABCB\\n int fi0 = 6, fi1 = 6, fi2=6, fi3=6;\\n for (int i = 1; i < n; ++i) {\\n int new_fi0 = (3LL * fi0 + 2LL * fi1+ 1LL*fi2+ 2LL*fi3) % mod;\\n int new_fi1 = (2LL * fi0 + 2LL * fi1+ 1LL*fi2+2LL*fi3) % mod;\\n int new_fi2 = (1LL * fi0 + 1LL * fi1+ 2LL*fi2 +1LL*fi3) % mod;\\n int new_fi3 = (2LL * fi0 + 2LL * fi1+ 1LL*fi2+2LL*fi3) % mod;\\n fi0 = new_fi0;\\n fi1 = new_fi1;\\n fi2 = new_fi2;\\n fi3 = new_fi3;\\n }\\n return ((long long)fi0 + fi1+ fi2+ fi3) % mod;\\n }\\n else\\n {\\n //ABABA//ABABC//ABACA//ABACB//ABCAB//ABCAC//ABCBA//ABCBC\\n int fi0 = 6, fi1 = 6, fi2=6 ,fi3 =6, fi4=6, fi5=6, fi6=6, fi7=6;\\n for (int i = 1; i < n; ++i) {\\n int new_fi0 = (3LL * fi0 + 2LL * fi1+ 2LL*fi2+ 1LL*fi3+ 0LL*fi4 +1LL*fi5 +2LL*fi6+2LL*fi7) % mod;\\n int new_fi1 = (2LL * fi0 + 2LL * fi1+ 2LL*fi2+ 1LL*fi3+ 1LL*fi4 +1LL*fi5 +1LL*fi6+1LL*fi7) % mod;\\n int new_fi2 = (2LL * fi0 + 2LL * fi1+ 2LL*fi2+ 1LL*fi3+ 0LL*fi4 +1LL*fi5 +2LL*fi6+2LL*fi7) % mod;\\n int new_fi3 = (1LL * fi0 + 1LL * fi1+ 1LL*fi2+ 2LL*fi3+ 1LL*fi4 +1LL*fi5 +1LL*fi6+1LL*fi7) % mod;\\n int new_fi4 = (0LL * fi0 + 1LL * fi1+ 0LL*fi2+ 1LL*fi3+ 2LL*fi4 +1LL*fi5 +0LL*fi6+1LL*fi7) % mod;\\n int new_fi5 = (1LL * fi0 + 1LL * fi1+ 1LL*fi2+ 1LL*fi3+ 1LL*fi4 +2LL*fi5 +1LL*fi6+1LL*fi7) % mod;\\n int new_fi6 = (2LL * fi0 + 1LL * fi1+ 2LL*fi2+ 1LL*fi3+ 0LL*fi4 +1LL*fi5 +2LL*fi6+1LL*fi7) % mod;\\n int new_fi7 = (2LL * fi0 + 1LL * fi1+ 2LL*fi2+ 1LL*fi3+ 1LL*fi4 +1LL*fi5 +1LL*fi6+2LL*fi7) % mod;\\n fi0 = new_fi0;\\n fi1 = new_fi1;\\n fi2 = new_fi2;\\n fi3 = new_fi3;\\n fi4 = new_fi4;\\n fi5 = new_fi5;\\n fi6 = new_fi6;\\n fi7 = new_fi7;\\n }\\n return ((long long)fi0 + fi1+ fi2+ fi3+ fi4 + fi5+ fi6+ fi7) % mod;\\n }\\n }\\n};\\n\\n
综上所述
\\n\\n
\\n- 对于m = 1,可分为1种情况
\\n- 对于m > 1,可分为2^(m-2)种情况
\\n故时间复杂度为O((2^m)*n)
\\n","description":"A代表第一种颜色,B代表第二种颜色,C代表第三种颜色。 0代表红色,1代表绿色,2代表蓝色。\\n\\n1. m=1\\n\\n第一行有3种情况,且接下来的所有行,都是2种情况\\n\\n long long mod = 1000000007;\\n int ans=3;\\n for(int i=1;i前言\\n 如果只有红绿两种颜色,可以把这两种颜色分别用 $0$ 和 $1$ 表示,用一个长为 $m$ 的二进制数表示一行的颜色。
\\n例如 $m=5$,二进制数 $01010_{(2)}$ 表示红绿红绿红。
\\n本题有红绿蓝三种颜色,可以分别用 $0,1,2$ 表示,用一个长为 $m$ 的三进制数表示一行的颜色。
\\n例如 $m=5$,三进制数 $01202_{(3)}$ 表示红绿蓝红蓝。
\\n\\n\\n注:本题不区分左右,三进制数从高到低读还是从低到高读都可以。
\\n思路
\\n首先预处理所有合法的(没有相邻相同颜色的)三进制数,记在数组 $\\\\textit{valid}$ 中。
\\n然后对于每个 $\\\\textit{valid}[i]$,预处理它的下一列颜色,要求左右相邻颜色不同。把 $\\\\textit{valid}$ 的下标记在数组 $\\\\textit{nxt}[i]$ 中。
\\n预处理这些数据之后,就可以 DP 了。
\\n对于 $m\\\\times n$ 的网格,如果最后一列填的是三进制数 $\\\\textit{valid}[j]$,那么问题为:对于 $m\\\\times (n-1)$ 的网格,最后一列填的是三进制数 $\\\\textit{valid}[j]$ 的情况下的涂色方案数。
\\n继续,如果倒数第二列填的是三进制数 $\\\\textit{valid}[k]$,那么接下来要解决的问题为:对于 $m\\\\times (n-2)$ 的网格,右边一列填的是三进制数 $\\\\textit{valid}[k]$ 的情况下的涂色方案数。
\\n所以定义 $\\\\textit{dfs}(i,j)$ 表示对于 $m\\\\times i$ 的网格,右边第 $i+1$ 列填的是三进制数 $\\\\textit{valid}[j]$ 的情况下的涂色方案数。
\\n枚举第 $i$ 列填颜色 $\\\\textit{valid}[k]$(其中 $k$ 是 $\\\\textit{nxt}[j]$ 中的元素),问题变成对于 $m\\\\times (i-1)$ 的网格,右边第 $i$ 列填的是三进制数 $\\\\textit{valid}[k]$ 的情况下的涂色方案数。
\\n累加得
\\n$$
\\n
\\n\\\\textit{dfs}(i,j) = \\\\sum_{k} \\\\textit{dfs}(i-1,k)
\\n$$递归边界:$\\\\textit{dfs}(0,j) = 1$,表示找到了一个合法涂色方案。
\\n递归入口:$\\\\displaystyle\\\\sum\\\\limits_{j} \\\\textit{dfs}(n-1, j)$。第 $n$ 列填颜色 $\\\\textit{valid}[j]$。
\\n细节
\\n三进制数最大为 $22\\\\ldots 2_{(3)} = 3^m-1$。枚举 $[0,3^m-1]$ 中的三进制数,怎么判断一个三进制数是否合法?
\\n我们需要取出三进制数中的每一位。
\\n回想一下十进制数 $12345$ 怎么取出百位的 $3$:$12345$ 除以 $100$ 下取整,得到 $123$,再模 $10$,得到 $3$。
\\n所以对于三进制数,可以除以 $3^i$ 下取整,再模 $3$。
\\n为什么可以在 DP 的计算过程中取模?可以看 模运算的世界:当加减乘除遇上取模。
\\n写法一:记忆化搜索
\\n\\nclass Solution:\\n def colorTheGrid(self, m: int, n: int) -> int:\\n pow3 = [3 ** i for i in range(m)]\\n valid = []\\n for color in range(3 ** m):\\n for i in range(1, m):\\n if color // pow3[i] % 3 == color // pow3[i - 1] % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n valid.append(color)\\n\\n nv = len(valid)\\n nxt = [[] for _ in range(nv)]\\n for i, color1 in enumerate(valid):\\n for j, color2 in enumerate(valid):\\n for p3 in pow3:\\n if color1 // p3 % 3 == color2 // p3 % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n nxt[i].append(j)\\n\\n MOD = 1_000_000_007\\n @cache # 缓存装饰器,避免重复计算 dfs(一行代码实现记忆化)\\n def dfs(i: int, j: int) -> int:\\n if i == 0:\\n return 1 # 找到了一个合法涂色方案\\n return sum(dfs(i - 1, k) for k in nxt[j]) % MOD\\n return sum(dfs(n - 1, j) for j in range(nv)) % MOD\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int colorTheGrid(int m, int n) {\\n int[] pow3 = new int[m];\\n pow3[0] = 1;\\n for (int i = 1; i < m; i++) {\\n pow3[i] = pow3[i - 1] * 3;\\n }\\n\\n List<Integer> valid = new ArrayList<>();\\n next:\\n for (int color = 0; color < pow3[m - 1] * 3; color++) {\\n for (int i = 1; i < m; i++) {\\n if (color / pow3[i] % 3 == color / pow3[i - 1] % 3) { // 相邻颜色相同\\n continue next;\\n }\\n }\\n valid.add(color);\\n }\\n\\n int nv = valid.size();\\n List<Integer>[] nxt = new ArrayList[nv];\\n Arrays.setAll(nxt, i -> new ArrayList<>());\\n for (int i = 0; i < nv; i++) {\\n next2:\\n for (int j = 0; j < nv; j++) {\\n for (int p3 : pow3)\\n if (valid.get(i) / p3 % 3 == valid.get(j) / p3 % 3) { // 相邻颜色相同\\n continue next2;\\n }\\n nxt[i].add(j);\\n }\\n }\\n\\n int[][] memo = new int[n][nv];\\n for (int[] row : memo) {\\n Arrays.fill(row, -1);\\n }\\n\\n long ans = 0;\\n for (int j = 0; j < nv; j++) {\\n ans += dfs(n - 1, j, nxt, memo);\\n }\\n return (int) (ans % MOD);\\n }\\n\\n private int dfs(int i, int j, List<Integer>[] nxt, int[][] memo) {\\n if (i == 0) {\\n return 1; // 找到了一个合法涂色方案\\n }\\n if (memo[i][j] != -1) { // 之前计算过\\n return memo[i][j];\\n }\\n long res = 0;\\n for (int k : nxt[j]) {\\n res += dfs(i - 1, k, nxt, memo);\\n }\\n return memo[i][j] = (int) (res % MOD); // 记忆化\\n }\\n}\\n
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int colorTheGrid(int m, int n) {\\n vector<int> pow3(m);\\n pow3[0] = 1;\\n for (int i = 1; i < m; i++) {\\n pow3[i] = pow3[i - 1] * 3;\\n }\\n\\n vector<int> valid;\\n for (int color = 0; color < pow3[m - 1] * 3; color++) {\\n bool ok = true;\\n for (int i = 1; i < m; i++) {\\n if (color / pow3[i] % 3 == color / pow3[i - 1] % 3) { // 相邻颜色相同\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n valid.push_back(color);\\n }\\n }\\n\\n int nv = valid.size();\\n vector<vector<int>> nxt(nv);\\n for (int i = 0; i < nv; i++) {\\n for (int j = 0; j < nv; j++) {\\n bool ok = true;\\n for (int k = 0; k < m; k++) {\\n if (valid[i] / pow3[k] % 3 == valid[j] / pow3[k] % 3) { // 相邻颜色相同\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n nxt[i].push_back(j);\\n }\\n }\\n }\\n\\n vector memo(n, vector<int>(nv, -1));\\n auto dfs = [&](this auto&& dfs, int i, int j) -> int {\\n if (i == 0) {\\n return 1; // 找到了一个合法涂色方案\\n }\\n int& res = memo[i][j]; // 注意这里是引用\\n if (res != -1) { // 之前计算过\\n return res;\\n }\\n res = 0;\\n for (int k : nxt[j]) {\\n res = (res + dfs(i - 1, k)) % MOD;\\n }\\n return res;\\n };\\n\\n long long ans = 0;\\n for (int j = 0; j < nv; j++) {\\n ans += dfs(n - 1, j);\\n }\\n return ans % MOD;\\n }\\n};\\n
\\nfunc colorTheGrid(m, n int) int {\\nconst mod = 1_000_000_007\\npow3 := make([]int, m)\\npow3[0] = 1\\nfor i := 1; i < m; i++ {\\npow3[i] = pow3[i-1] * 3\\n}\\n\\nvalid := []int{}\\nnext:\\nfor color := range pow3[m-1] * 3 {\\nfor i := range m - 1 {\\nif color/pow3[i+1]%3 == color/pow3[i]%3 { // 相邻颜色相同\\ncontinue next\\n}\\n}\\nvalid = append(valid, color)\\n}\\n\\nnv := len(valid)\\nnxt := make([][]int, nv)\\nfor i, color1 := range valid {\\nnext2:\\nfor j, color2 := range valid {\\nfor _, p3 := range pow3 {\\nif color1/p3%3 == color2/p3%3 { // 相邻颜色相同\\ncontinue next2\\n}\\n}\\nnxt[i] = append(nxt[i], j)\\n}\\n}\\n\\nmemo := make([][]int, n)\\nfor i := range memo {\\nmemo[i] = make([]int, nv)\\nfor j := range memo[i] {\\nmemo[i][j] = -1\\n}\\n}\\nvar dfs func(int, int) int\\ndfs = func(i, j int) (res int) {\\nif i == 0 {\\nreturn 1 // 找到了一个合法涂色方案\\n}\\np := &memo[i][j]\\nif *p != -1 { // 之前计算过\\nreturn *p\\n}\\ndefer func() { *p = res }() // 记忆化\\nfor _, k := range nxt[j] {\\nres += dfs(i-1, k)\\n}\\nreturn res % mod\\n}\\n\\nans := 0\\nfor j := range nv {\\nans += dfs(n-1, j)\\n}\\nreturn ans % mod\\n}\\n
复杂度分析
\\n有多少个状态?$\\\\textit{valid}$ 有多长?
\\n对于一列长为 $m$ 的涂色方案,第一个颜色有 $3$ 种,其余颜色不能与上一个颜色相同,所以都是 $2$ 种。所以 $\\\\textit{valid}$ 的长度为
\\n$$
\\n
\\n3\\\\cdot 2^{m-1}
\\n$$所以状态个数为 $i$ 的个数 $\\\\mathcal{O}(n)$ 乘以 $j$ 的个数 $\\\\mathcal{O}(2^m)$,一共有 $\\\\mathcal{O}(n2^m)$ 个状态。
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n4^m)$。由于每个状态只会计算一次,动态规划的时间复杂度 $=$ 状态个数 $\\\\times$ 单个状态的计算时间。本题状态个数等于 $\\\\mathcal{O}(n2^m)$,单个状态的计算时间为 $\\\\mathcal{O}(2^m)$,所以总的时间复杂度为 $\\\\mathcal{O}(n4^m)$。
\\n- 空间复杂度:$\\\\mathcal{O}(4^m + n2^m)$。其中 $\\\\mathcal{O}(4^m)$ 是 $\\\\textit{nxt}$ 需要的空间,$\\\\mathcal{O}(n2^m)$ 是记忆化搜索需要的空间。
\\n写法二:递推
\\n把记忆化搜索 1:1 翻译成递推,原理见 动态规划入门:从记忆化搜索到递推【基础算法精讲 17】。
\\n\\nclass Solution:\\n def colorTheGrid(self, m: int, n: int) -> int:\\n pow3 = [3 ** i for i in range(m)]\\n valid = []\\n for color in range(3 ** m):\\n for i in range(1, m):\\n if color // pow3[i] % 3 == color // pow3[i - 1] % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n valid.append(color)\\n\\n nv = len(valid)\\n nxt = [[] for _ in range(nv)]\\n for i, color1 in enumerate(valid):\\n for j, color2 in enumerate(valid):\\n for p3 in pow3:\\n if color1 // p3 % 3 == color2 // p3 % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n nxt[i].append(j)\\n\\n MOD = 1_000_000_007\\n f = [[0] * nv for _ in range(n)]\\n f[0] = [1] * nv # dfs 的递归边界就是 DP 数组的初始值\\n for i in range(1, n):\\n for j in range(nv):\\n f[i][j] = sum(f[i - 1][k] for k in nxt[j]) % MOD\\n return sum(f[-1]) % MOD # 递归入口就是答案\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int colorTheGrid(int m, int n) {\\n int[] pow3 = new int[m];\\n pow3[0] = 1;\\n for (int i = 1; i < m; i++) {\\n pow3[i] = pow3[i - 1] * 3;\\n }\\n\\n List<Integer> valid = new ArrayList<>();\\n next:\\n for (int color = 0; color < pow3[m - 1] * 3; color++) {\\n for (int i = 1; i < m; i++) {\\n if (color / pow3[i] % 3 == color / pow3[i - 1] % 3) { // 相邻颜色相同\\n continue next;\\n }\\n }\\n valid.add(color);\\n }\\n\\n int nv = valid.size();\\n List<Integer>[] nxt = new ArrayList[nv];\\n Arrays.setAll(nxt, i -> new ArrayList<>());\\n for (int i = 0; i < nv; i++) {\\n next2:\\n for (int j = 0; j < nv; j++) {\\n for (int p3 : pow3)\\n if (valid.get(i) / p3 % 3 == valid.get(j) / p3 % 3) { // 相邻颜色相同\\n continue next2;\\n }\\n nxt[i].add(j);\\n }\\n }\\n\\n int[][] f = new int[n][nv];\\n Arrays.fill(f[0], 1);\\n for (int i = 1; i < n; i++) {\\n for (int j = 0; j < nv; j++) {\\n for (int k : nxt[j]) {\\n f[i][j] = (f[i][j] + f[i - 1][k]) % MOD;\\n }\\n }\\n }\\n\\n long ans = 0;\\n for (int j = 0; j < nv; j++) {\\n ans += f[n - 1][j];\\n }\\n return (int) (ans % MOD);\\n }\\n}\\n
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\npublic:\\n int colorTheGrid(int m, int n) {\\n vector<int> pow3(m);\\n pow3[0] = 1;\\n for (int i = 1; i < m; i++) {\\n pow3[i] = pow3[i - 1] * 3;\\n }\\n\\n vector<int> valid;\\n for (int color = 0; color < pow3[m - 1] * 3; color++) {\\n bool ok = true;\\n for (int i = 1; i < m; i++) {\\n if (color / pow3[i] % 3 == color / pow3[i - 1] % 3) { // 相邻颜色相同\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n valid.push_back(color);\\n }\\n }\\n\\n int nv = valid.size();\\n vector<vector<int>> nxt(nv);\\n for (int i = 0; i < nv; i++) {\\n for (int j = 0; j < nv; j++) {\\n bool ok = true;\\n for (int k = 0; k < m; k++) {\\n if (valid[i] / pow3[k] % 3 == valid[j] / pow3[k] % 3) { // 相邻颜色相同\\n ok = false;\\n break;\\n }\\n }\\n if (ok) {\\n nxt[i].push_back(j);\\n }\\n }\\n }\\n\\n vector f(n, vector<int>(nv));\\n ranges::fill(f[0], 1);\\n for (int i = 1; i < n; i++) {\\n for (int j = 0; j < nv; j++) {\\n for (int k : nxt[j]) {\\n f[i][j] = (f[i][j] + f[i - 1][k]) % MOD;\\n }\\n }\\n }\\n\\n long long ans = 0;\\n for (int j = 0; j < nv; j++) {\\n ans += f[n - 1][j];\\n }\\n return ans % MOD;\\n }\\n};\\n
\\nfunc colorTheGrid(m, n int) int {\\nconst mod = 1_000_000_007\\npow3 := make([]int, m)\\npow3[0] = 1\\nfor i := 1; i < m; i++ {\\npow3[i] = pow3[i-1] * 3\\n}\\n\\nvalid := []int{}\\nnext:\\nfor color := range pow3[m-1] * 3 {\\nfor i := range m - 1 {\\nif color/pow3[i+1]%3 == color/pow3[i]%3 { // 相邻颜色相同\\ncontinue next\\n}\\n}\\nvalid = append(valid, color)\\n}\\n\\nnv := len(valid)\\nnxt := make([][]int, nv)\\nfor i, color1 := range valid {\\nnext2:\\nfor j, color2 := range valid {\\nfor _, p3 := range pow3 {\\nif color1/p3%3 == color2/p3%3 { // 相邻颜色相同\\ncontinue next2\\n}\\n}\\nnxt[i] = append(nxt[i], j)\\n}\\n}\\n\\nf := make([][]int, n)\\nfor i := range f {\\nf[i] = make([]int, nv)\\n}\\nfor j := range f[0] {\\nf[0][j] = 1\\n}\\nfor i := 1; i < n; i++ {\\nfor j := range f[i] {\\nfor _, k := range nxt[j] {\\nf[i][j] += f[i-1][k]\\n}\\nf[i][j] %= mod\\n}\\n}\\n\\nans := 0\\nfor _, fv := range f[n-1] {\\nans += fv\\n}\\nreturn ans % mod\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n4^m)$。理由同写法一。
\\n- 空间复杂度:$\\\\mathcal{O}(4^m + n2^m)$。注:用滚动数组可以优化至 $\\\\mathcal{O}(4^m)$。
\\n附:矩阵快速幂
\\n$n=10^{18}$ 也可以通过。
\\n\\n\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\n# a^n @ f0\\ndef pow(a: np.ndarray, n: int, f0: np.ndarray) -> np.ndarray:\\n res = f0\\n while n:\\n if n & 1:\\n res = a @ res % MOD\\n a = a @ a % MOD\\n n >>= 1\\n return res\\n\\nclass Solution:\\n def colorTheGrid(self, m: int, n: int) -> int:\\n pow3 = [3 ** i for i in range(m)]\\n valid = []\\n for color in range(3 ** m):\\n for i in range(1, m):\\n if color // pow3[i] % 3 == color // pow3[i - 1] % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n valid.append(color)\\n\\n nv = len(valid)\\n m = np.zeros((nv, nv), dtype=object)\\n for i, color1 in enumerate(valid):\\n for j, color2 in enumerate(valid):\\n for p3 in pow3:\\n if color1 // p3 % 3 == color2 // p3 % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n m[i, j] = 1\\n\\n f0 = np.ones((nv,), dtype=object)\\n res = pow(m, n - 1, f0)\\n return np.sum(res) % MOD\\n
\\nimport numpy as np\\n\\nMOD = 1_000_000_007\\n\\nclass Solution:\\n def colorTheGrid(self, m: int, n: int) -> int:\\n pow3 = [3 ** i for i in range(m)]\\n valid = []\\n for color in range(3 ** m):\\n for i in range(1, m):\\n if color // pow3[i] % 3 == color // pow3[i - 1] % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n valid.append(color)\\n\\n nv = len(valid)\\n m = np.zeros((nv, nv), dtype=object)\\n for i, color1 in enumerate(valid):\\n for j, color2 in enumerate(valid):\\n for p3 in pow3:\\n if color1 // p3 % 3 == color2 // p3 % 3: # 相邻颜色相同\\n break\\n else: # 没有中途 break,合法\\n m[i, j] = 1\\n\\n f0 = np.ones((nv,), dtype=object)\\n res = np.linalg.matrix_power(m, n - 1) @ f0\\n return np.sum(res) % MOD\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(2^{m\\\\omega} \\\\log n)$。矩阵长宽均为 $\\\\mathcal{O}(2^m)$,计算一次矩阵乘法需要 $\\\\mathcal{O}((2^m)^\\\\omega)$ 的时间,其中 $\\\\omega\\\\le 3$。
\\n- 空间复杂度:$\\\\mathcal{O}(4^m)$。
\\n双倍经验
\\n\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"前言 如果只有红绿两种颜色,可以把这两种颜色分别用 $0$ 和 $1$ 表示,用一个长为 $m$ 的二进制数表示一行的颜色。\\n\\n例如 $m=5$,二进制数 $01010_{(2)}$ 表示红绿红绿红。\\n\\n本题有红绿蓝三种颜色,可以分别用 $0,1,2$ 表示,用一个长为 $m$ 的三进制数表示一行的颜色。\\n\\n例如 $m=5$,三进制数 $01202_{(3)}$ 表示红绿蓝红蓝。\\n\\n注:本题不区分左右,三进制数从高到低读还是从低到高读都可以。\\n\\n思路\\n\\n首先预处理所有合法的(没有相邻相同颜色的)三进制数,记在数组 $\\\\textit{valid}$ 中。\\n\\n然后对于…","guid":"https://leetcode.cn/problems/painting-a-grid-with-three-different-colors//solution/zhuang-ya-dp-yu-chu-li-he-fa-zhuang-tai-l927s","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-11T04:09:58.686Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[Python] 费用最小堆 (200ms)","url":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/python-fei-yong-zui-xiao-dui-by-qubenhao-i68u","content":"解题思路
\\n有点儿像秋季赛的电动车
\\n
\\n考虑遍历最小费用,如果同一个城市出现更多费用,但是可以获得更多时间的时候,仍然加入堆代码
\\n###python3
\\n\\n","description":"解题思路 有点儿像秋季赛的电动车\\n 考虑遍历最小费用,如果同一个城市出现更多费用,但是可以获得更多时间的时候,仍然加入堆\\n\\n代码\\n\\n###python3\\n\\nclass Solution:\\n def minCost(self, maxTime: int, edges: List[List[int]], passingFees: List[int]) -> int:\\n n = len(passingFees)\\n connect = defaultdict(lambda:defaultdict(lambda:1001))…","guid":"https://leetcode.cn/problems/minimum-cost-to-reach-destination-in-time//solution/python-fei-yong-zui-xiao-dui-by-qubenhao-i68u","author":"himymBen","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-10T16:11:12.517Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【数据结构和算法】动态规划解决","url":"https://leetcode.cn/problems/palindrome-partitioning-iii//solution/shu-ju-jie-gou-he-suan-fa-dong-tai-gui-h-rzge","content":"class Solution:\\n def minCost(self, maxTime: int, edges: List[List[int]], passingFees: List[int]) -> int:\\n n = len(passingFees)\\n connect = defaultdict(lambda:defaultdict(lambda:1001))\\n for a,b,t in edges:\\n connect[a][b] = min(connect[a][b], t)\\n connect[b][a] = min(connect[b][a], t)\\n # fee, time, pos\\n q = [(passingFees[0], maxTime, 0)]\\n explored = {0:maxTime}\\n while q:\\n f, t, pos = heapq.heappop(q)\\n if pos == n - 1:\\n return f\\n for nxt,tm in connect[pos].items():\\n if tm > t:\\n continue\\n if nxt not in explored or t - tm > explored[nxt]:\\n explored[nxt] = t - tm\\n heapq.heappush(q, (f + passingFees[nxt], t - tm, nxt))\\n return -1\\n
动态规划解决
\\n这题是让通过修改一些字符把字符串分割为k个子串,并且每个子串都是回文的,问最少修改的字符数。
\\n
\\n最容易想到的就是把字符串s分割k个子串的所有可能组合,然后计算每个组合中子串变成回文串修改字符的数量,最后返回最小的即可。比如示例二中我们可以分割为
\\n\\n
但是当字符串s比较长的时候,运行效率是很差的。这里我们可以使用动态规划来解决,定义dp[i][j]表示表示字符串s的前i个字符分割为j个子串的修改的最小字符数。很明显i必须大于等于j,要不然不可能分割为j个子串。
\\n
\\n动态规划最关键的是找出递推公式,当我们要求dp[i][j]的时候,只需要找出前m个字符分割成j-1个子串所修改的最小字符数+最后一个子串所需要修改字符数。(最后一个子串就是前i个字符-前m个字符),看到这里大家是不是很容易想到比较经典的基础背包问题,前面也有讲过371,背包问题系列之-基础背包问题,其中还有多重背包和完全背包这些我们以后会再讲。
\\n
\\n所以递推公式我们很容易想到
\\ndp[i][j]=dp[m][j-1]+change(s,m,i-1);
\\n这里我们需要枚举m的值,但要注意m必须大于等于j-1,如下图所示
\\n\\n
这题要求的是返回最小的字符修改数,所以我们记录最小的即可
\\n\\ndp[i][j] = Math.min(dp[i][j], dp[m][j - 1] + change(s, m, i - 1));\\n
其中change(s,m,i-1)表示字符串s[m,i-1]变成回文串所需要修改的字符数。他的代码比较简单,我们来看下
\\n###java
\\n\\n//字符串的子串[left,right]变成回文串所需要修改的字符数\\nprivate int change(String s, int left, int right) {\\n int count = 0;\\n while (left < right) {\\n //如果两个指针指向的字符相同,我们不需要修改。\\n //如果不相同,只需要修改其中的一个即可,所以\\n // 修改数要加1\\n if (s.charAt(left++) != s.charAt(right--))\\n count++;\\n }\\n return count;\\n}\\n
分析到这里,这题的代码基本上就呼之欲出了,我们来看下完整代码
\\n###java
\\n\\npublic int palindromePartition(String s, int k) {\\n int length = s.length();\\n //dp[i][j]表示s的前i个字符分割成k个子串所修改的最少字符数。\\n int[][] dp = new int[length + 1][k + 1];\\n //因为这题要求的是所需要修改的最少字符数,初始值我们赋值尽可能大\\n for (int i = 0; i < dp.length; i++) {\\n Arrays.fill(dp[i], length);\\n }\\n //前i个字符,分割成j个回文子串\\n for (int i = 1; i <= length; i++) {\\n //前i个字符最大只能分割成i个子串,所以不能超过i,\\n //我们取i和k的最小值\\n int len = Math.min(i, k);\\n for (int j = 1; j <= len; j++) {\\n if (j == 1) {\\n //如果j等于1,则表示没有分割,我们直接计算\\n dp[i][j] = change(s, j - 1, i - 1);\\n } else {\\n //如果j不等于1,我们计算分割所需要修改的最小字符数,因为m的值要\\n //大于等于j-1,我们就从最小的开始枚举\\n for (int m = j - 1; m < i; m++) {\\n //递推公式\\n dp[i][j] = Math.min(dp[i][j], dp[m][j - 1] + change(s, m, i - 1));\\n }\\n }\\n }\\n }\\n //返回前length个字符分割成k个子串所需要修改的最少字符数\\n return dp[length][k];\\n}\\n\\n//字符串的子串[left,right]变成回文串所需要修改的字符数\\nprivate int change(String s, int left, int right) {\\n int count = 0;\\n while (left < right) {\\n //如果两个指针指向的字符相同,我们不需要修改。\\n //如果不相同,只需要修改其中的一个即可,所以\\n // 修改数要加1\\n if (s.charAt(left++) != s.charAt(right--))\\n count++;\\n }\\n return count;\\n}\\n
\\n动态规划代码优化
\\n上面代码中都有注释,这里就不在过多介绍,我们来看一下change方法,因为这里会涉及到字符的重复计算,导致效率不高,我们可以先计算所有的子串变成回文串需要修改的字符数,然后再使用,来看下代码
\\n###java
\\n\\npublic int palindromePartition(String s, int k) {\\n int length = s.length();\\n\\n //palindrome[i][j]表示子串[i,j]转化为回文串所需要的修改的字符数\\n int[][] palindrome = new int[length][length];\\n //2种实现方式\\n // //一列一列的从左往右(只遍历右上部分)\\n // for (int j = 1; j < length; j++) {\\n // for (int i = 0; i < j; i++) {\\n // palindrome[i][j] = palindrome[i + 1][j - 1] + (s.charAt(i) == s.charAt(j) ? 0 : 1);\\n // }\\n // }\\n\\n //一行一行的从下往上(只遍历右上部分)\\n for (int i = length - 2; i >= 0; i--) {\\n for (int j = i + 1; j < length; j++) {\\n palindrome[i][j] = palindrome[i + 1][j - 1] + (s.charAt(i) == s.charAt(j) ? 0 : 1);\\n }\\n }\\n\\n //dp[i][j]表示s的前i个字符分割成k个回文子串的最少次数,\\n //第一行和第一列应该都是0。\\n int[][] dp = new int[length + 1][k + 1];\\n for (int i = 1; i < dp.length; i++) {\\n Arrays.fill(dp[i], Integer.MAX_VALUE);\\n }\\n //前i个字符,分割成j个回文子串\\n for (int i = 1; i <= length; i++) {\\n int len = Math.min(i, k);\\n for (int j = 1; j <= len; j++) {\\n if (j == 1)//字符串的下标是从0开始的,所以这里要减1\\n dp[i][j] = palindrome[j - 1][i - 1];\\n else\\n for (int m = j - 1; m < i; m++) {\\n dp[i][j] = Math.min(dp[i][j], dp[m][j - 1] + palindrome[m][i - 1]);\\n }\\n }\\n }\\n return dp[length][k];\\n}\\n
因为dp[i][j]中i必须大于等于j,所以这里遍历的时候可以有两种方式,如下图所示
\\n\\n
\\n
LeetCode上分隔回文串总共有4题,其中只有一道中等,其他3道都是困难,这里把这4道都讲完了,也可以一起看下前面的3道题。
\\n\\n\\n\\n
\\n如果觉得有用就给个赞吧,还可以关注我的《力扣主页》查看更多的详细题解
\\n","description":"动态规划解决 这题是让通过修改一些字符把字符串分割为k个子串,并且每个子串都是回文的,问最少修改的字符数。\\n\\n\\n\\n\\n最容易想到的就是把字符串s分割k个子串的所有可能组合,然后计算每个组合中子串变成回文串修改字符的数量,最后返回最小的即可。比如示例二中我们可以分割为\\n\\n但是当字符串s比较长的时候,运行效率是很差的。这里我们可以使用动态规划来解决,定义dp[i][j]表示表示字符串s的前i个字符分割为j个子串的修改的最小字符数。很明显i必须大于等于j,要不然不可能分割为j个子串。\\n\\n\\n\\n\\n动态规划最关键的是找出递推公式,当我们要求dp[i][j]的时候,只需要找出前m个…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iii//solution/shu-ju-jie-gou-he-suan-fa-dong-tai-gui-h-rzge","author":"sdwwld","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-06T02:50:09.003Z","media":[{"url":"https://pic.leetcode-cn.com/1625539492-uJgLlz-image.png","type":"photo","width":427,"height":694,"blurhash":"LtO{BRo{}csCSKOWwz#TbvW;WVn*"},{"url":"https://pic.leetcode-cn.com/1625539578-HDAZlH-image.png","type":"photo","width":688,"height":505,"blurhash":"LNPixDP;nO^iPVvfXSbcI:V@s,S%"},{"url":"https://pic.leetcode-cn.com/1625539732-UFoWWd-image.png","type":"photo","width":446,"height":435,"blurhash":"LON0@_~8~po~Xp#6$yT0~WJCrVxV"},{"url":"https://pic.leetcode-cn.com/1625539740-UqYNLI-image.png","type":"photo","width":473,"height":437,"blurhash":"LQN0@{^h_2XnX:#7$xT0~pJDrW$e"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"1550. 存在连续三个奇数的数组","url":"https://leetcode.cn/problems/three-consecutive-odds//solution/1550-cun-zai-lian-xu-san-ge-qi-shu-de-sh-tt3w","content":"解法
\\n思路和算法
\\n这道题目要求判断给定的整数数组 $\\\\textit{arr}$ 中是否存在连续三个元素都是奇数的情况。遍历数组中的每一组连续三个元素,判断是否存在至少一组连续三个元素都是奇数即可,如果遇到连续三个元素都是奇数,即可返回 $\\\\text{true}$。如果遍历结束没有遇到连续三个元素都是奇数,则返回 $\\\\text{false}$。
\\n假设数组 $\\\\textit{arr}$ 的长度是 $n$。对于 $2 \\\\le i < n$,当 $\\\\textit{arr}[i-2],\\\\textit{arr}[i-1],\\\\textit{arr}[i]$ 都是奇数时,这三个元素就是连续的三个奇数,因此遍历上述范围内的每个 $i$,即可判断是否存在连续三个元素都是奇数。
\\n代码
\\n\\nclass Solution {\\n public boolean threeConsecutiveOdds(int[] arr) {\\n int n = arr.length;\\n for (int i = 2; i < n; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\npublic class Solution {\\n public bool ThreeConsecutiveOdds(int[] arr) {\\n int n = arr.Length;\\n for (int i = 2; i < n; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n bool threeConsecutiveOdds(vector<int>& arr) {\\n int n = arr.size();\\n for (int i = 2; i < n; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
\\nclass Solution:\\n def threeConsecutiveOdds(self, arr: List[int]) -> bool:\\n n = len(arr)\\n for i in range(2, n):\\n if arr[i - 2] % 2 == 1 and arr[i - 1] % 2 == 1 and arr[i] % 2 == 1:\\n return True\\n return False\\n
\\nbool threeConsecutiveOdds(int* arr, int arrSize) {\\n for (int i = 2; i < arrSize; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
\\nfunc threeConsecutiveOdds(arr []int) bool {\\n n := len(arr)\\n for i := 2; i < n; i++ {\\n if arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1 {\\n return true\\n }\\n }\\n return false\\n}\\n
\\nvar threeConsecutiveOdds = function(arr) {\\n let n = arr.length;\\n for (let i = 2; i < n; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
\\nfunction threeConsecutiveOdds(arr: number[]): boolean {\\n let n = arr.length;\\n for (let i = 2; i < n; i++) {\\n if (arr[i - 2] % 2 == 1 && arr[i - 1] % 2 == 1 && arr[i] % 2 == 1) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
复杂度分析
\\n\\n
\\n","description":"思路和算法 这道题目要求判断给定的整数数组 $\\\\textit{arr}$ 中是否存在连续三个元素都是奇数的情况。遍历数组中的每一组连续三个元素,判断是否存在至少一组连续三个元素都是奇数即可,如果遇到连续三个元素都是奇数,即可返回 $\\\\text{true}$。如果遍历结束没有遇到连续三个元素都是奇数,则返回 $\\\\text{false}$。\\n\\n假设数组 $\\\\textit{arr}$ 的长度是 $n$。对于 $2 \\\\le i < n$,当 $\\\\textit{arr}[i-2],\\\\textit{arr}[i-1],\\\\textit{arr}[i]$ 都是奇数时…","guid":"https://leetcode.cn/problems/three-consecutive-odds//solution/1550-cun-zai-lian-xu-san-ge-qi-shu-de-sh-tt3w","author":"stormsunshine","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-05T11:54:58.566Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"基于排列构建数组","url":"https://leetcode.cn/problems/build-array-from-permutation//solution/ji-yu-pai-lie-gou-jian-shu-zu-by-leetcod-gjcn","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{arr}$ 的长度。需要遍历数组 $\\\\textit{arr}$ 一次,对于每个位置需要判断连续三个元素是否都是奇数。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法一:按要求构建
\\n思路与算法
\\n我们可以构建一个与原数组 $\\\\textit{nums}$ 等长的新数组,同时令新数组中下标为 $i$ 的元素等于 $\\\\textit{nums}[\\\\textit{nums}[i]]$。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> buildArray(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> ans;\\n for (int i = 0; i < n; ++i){\\n ans.push_back(nums[nums[i]]);\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def buildArray(self, nums: List[int]) -> List[int]:\\n n = len(nums)\\n return [nums[nums[_]] for _ in range(n)]\\n
###Java
\\n\\nclass Solution {\\n public int[] buildArray(int[] nums) {\\n int n = nums.length;\\n int[] ans = new int[n];\\n for (int i = 0; i < n; ++i) {\\n ans[i] = nums[nums[i]];\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] BuildArray(int[] nums) {\\n int n = nums.Length;\\n int[] ans = new int[n];\\n for (int i = 0; i < n; ++i) {\\n ans[i] = nums[nums[i]];\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc buildArray(nums []int) []int {\\n n := len(nums)\\n ans := make([]int, n)\\n for i := 0; i < n; i++ {\\n ans[i] = nums[nums[i]]\\n }\\n return ans\\n}\\n
###C
\\n\\nint* buildArray(int* nums, int numsSize, int* returnSize) {\\n int* ans = (int*)malloc(numsSize * sizeof(int));\\n for (int i = 0; i < numsSize; ++i) {\\n ans[i] = nums[nums[i]];\\n }\\n *returnSize = numsSize;\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar buildArray = function(nums) {\\n const n = nums.length;\\n const ans = [];\\n for (let i = 0; i < n; ++i) {\\n ans.push(nums[nums[i]]);\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction buildArray(nums: number[]): number[] {\\n const n = nums.length;\\n const ans: number[] = [];\\n for (let i = 0; i < n; ++i) {\\n ans.push(nums[nums[i]]);\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn build_array(nums: Vec<i32>) -> Vec<i32> {\\n let n = nums.len();\\n let mut ans = Vec::with_capacity(n);\\n for i in 0..n {\\n ans.push(nums[nums[i] as usize]);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。即为构建新数组的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(1)$,输出数组不计入空间复杂度。
\\n方法二:原地构建
\\n思路与算法
\\n我们也可以直接对原数组 $\\\\textit{nums}$ 进行修改。
\\n为了使得构建过程可以完整进行,我们需要让 $\\\\textit{nums}$ 中的每个元素 $\\\\textit{nums}[i]$ 能够同时存储「当前值」(即 $\\\\textit{nums}[i]$)和「最终值」(即 $\\\\textit{nums}[\\\\textit{nums}[i]]$)。
\\n我们注意到 $\\\\textit{nums}$ 中元素的取值范围为 $[0, 999]$ 闭区间,这意味着 $\\\\textit{nums}$ 中的每个元素的「当前值」和「最终值」都在 $[0, 999]$ 闭区间内。
\\n因此,我们可以使用类似「$1000$ 进制」的思路来表示每个元素的「当前值」和「最终值」。对于每个元素,我们用它除以 $1000$ 的商数表示它的「最终值」,而用余数表示它的「当前值」。
\\n那么,我们首先遍历 $\\\\textit{nums}$,计算每个元素的「最终值」,并乘以 $1000$ 加在该元素上。随后,我们再次遍历数组,并将每个元素的值除以 $1000$ 保留其商数。此时 $\\\\textit{nums}$ 即为构建完成的数组,我们返回该数组作为答案。
\\n细节
\\n在计算 $\\\\textit{nums}[i]$ 的「最终值」并修改该元素时,我们需要计算修改前 $\\\\textit{nums}[\\\\textit{nums}[i]]$ 的值,而 $\\\\textit{nums}$ 中下标为 $\\\\textit{nums}[i]$ 的元素可能已被修改,因此我们需要将取下标得到的值对 $1000$ 取模得到「最终值」。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> buildArray(vector<int>& nums) {\\n int n = nums.size();\\n // 第一次遍历编码最终值\\n for (int i = 0; i < n; ++i){\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (int i = 0; i < n; ++i){\\n nums[i] /= 1000;\\n }\\n return nums;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def buildArray(self, nums: List[int]) -> List[int]:\\n n = len(nums)\\n # 第一次遍历编码最终值\\n for i in range(n):\\n nums[i] += 1000 * (nums[nums[i]] % 1000) \\n # 第二次遍历修改为最终值\\n for i in range(n):\\n nums[i] //= 1000\\n return nums\\n
###Java
\\n\\nclass Solution {\\n public int[] buildArray(int[] nums) {\\n int n = nums.length;\\n // 第一次遍历编码最终值\\n for (int i = 0; i < n; ++i) {\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (int i = 0; i < n; ++i) {\\n nums[i] /= 1000;\\n }\\n return nums;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] BuildArray(int[] nums) {\\n int n = nums.Length;\\n // 第一次遍历编码最终值\\n for (int i = 0; i < n; ++i) {\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (int i = 0; i < n; ++i) {\\n nums[i] /= 1000;\\n }\\n return nums;\\n }\\n}\\n
###Go
\\n\\nfunc buildArray(nums []int) []int {\\n n := len(nums)\\n // 第一次遍历编码最终值\\n for i := 0; i < n; i++ {\\n nums[i] += 1000 * (nums[nums[i]] % 1000)\\n }\\n // 第二次遍历修改为最终值\\n for i := 0; i < n; i++ {\\n nums[i] /= 1000\\n }\\n return nums\\n}\\n
###C
\\n\\nint* buildArray(int* nums, int numsSize, int* returnSize) {\\n // 第一次遍历编码最终值\\n for (int i = 0; i < numsSize; ++i) {\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (int i = 0; i < numsSize; ++i) {\\n nums[i] /= 1000;\\n }\\n *returnSize = numsSize;\\n return nums;\\n}\\n
###JavaScript
\\n\\nvar buildArray = function(nums) {\\n const n = nums.length;\\n // 第一次遍历编码最终值\\n for (let i = 0; i < n; ++i) {\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (let i = 0; i < n; ++i) {\\n nums[i] = Math.floor(nums[i] / 1000);\\n }\\n return nums;\\n};\\n
###TypeScript
\\n\\nfunction buildArray(nums: number[]): number[] {\\n const n = nums.length;\\n // 第一次遍历编码最终值\\n for (let i = 0; i < n; ++i) {\\n nums[i] += 1000 * (nums[nums[i]] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for (let i = 0; i < n; ++i) {\\n nums[i] = Math.floor(nums[i] / 1000);\\n }\\n return nums;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn build_array(mut nums: Vec<i32>) -> Vec<i32> {\\n let n = nums.len();\\n // 第一次遍历编码最终值\\n for i in 0..n {\\n nums[i] += 1000 * (nums[nums[i] as usize] % 1000);\\n }\\n // 第二次遍历修改为最终值\\n for i in 0..n {\\n nums[i] /= 1000;\\n }\\n nums\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:按要求构建 思路与算法\\n\\n我们可以构建一个与原数组 $\\\\textit{nums}$ 等长的新数组,同时令新数组中下标为 $i$ 的元素等于 $\\\\textit{nums}[\\\\textit{nums}[i]]$。\\n\\n代码\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n vector- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。我们遍历了两次 $\\\\textit{nums}$ 数组并进行修改,每次遍历并修改的时间复杂度均为 $O(n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\nbuildArray(vector & nums) {\\n int n = nums.size();\\n vector ans;\\n for (int i = 0; i < n; ++i)…","guid":"https://leetcode.cn/problems/build-array-from-permutation//solution/ji-yu-pai-lie-gou-jian-shu-zu-by-leetcod-gjcn","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T06:44:20.198Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计好数字的数目","url":"https://leetcode.cn/problems/count-good-numbers//solution/tong-ji-hao-shu-zi-de-shu-mu-by-leetcode-53jj","content":" 方法一:快速幂
\\n思路与算法
\\n对于偶数下标处的数字,它可以为 $0, 2, 4, 6, 8$ 共计 $5$ 种,而长度为 $n$ 的数字字符串有 $\\\\lfloor \\\\dfrac{n+1}{2} \\\\rfloor$ 个偶数下标,其中 $\\\\lfloor x \\\\rfloor$ 表示对 $x$ 向下取整。
\\n对于奇数下标处的数字,它可以为 $2, 3, 5, 7$ 共计 $4$ 种,而长度为 $n$ 的数字字符串有 $\\\\lfloor \\\\dfrac{n}{2} \\\\rfloor$ 个奇数下标。
\\n因此长度为 $n$ 的数字字符串中,好数字的总数即为:
\\n$$
\\n
\\n5^{\\\\lfloor \\\\frac{n+1}{2} \\\\rfloor} \\\\cdot 4^{\\\\lfloor \\\\frac{n}{2} \\\\rfloor}
\\n$$在本题中,由于 $n$ 的取值最大可以到 $10^{15}$,如果通过普通的乘法运算直接求出上式中的幂,会超出时间限制,因此我们需要使用快速幂算法对幂的求值进行优化。
\\n快速幂算法可以参考「50. Pow(x, n)」的官方题解。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n static constexpr int mod = 1000000007;\\n \\npublic:\\n int countGoodNumbers(long long n) {\\n // 快速幂求出 x^y % mod\\n auto quickmul = [](int x, long long y) -> int {\\n int ret = 1, mul = x;\\n while (y > 0) {\\n if (y % 2 == 1) {\\n ret = (long long)ret * mul % mod;\\n }\\n mul = (long long)mul * mul % mod;\\n y /= 2;\\n }\\n return ret;\\n };\\n \\n return (long long)quickmul(5, (n + 1) / 2) * quickmul(4, n / 2) % mod;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def countGoodNumbers(self, n: int) -> int:\\n mod = 10**9 + 7\\n \\n # 快速幂求出 x^y % mod\\n def quickmul(x: int, y: int) -> int:\\n ret, mul = 1, x\\n while y > 0:\\n if y % 2 == 1:\\n ret = ret * mul % mod\\n mul = mul * mul % mod\\n y //= 2\\n return ret\\n \\n return quickmul(5, (n + 1) // 2) * quickmul(4, n // 2) % mod\\n
###Java
\\n\\nclass Solution {\\n long mod = 1000000007;\\n \\n public int countGoodNumbers(long n) {\\n return (int) (quickmul(5, (n + 1) / 2) * quickmul(4, n / 2) % mod);\\n }\\n\\n // 快速幂求出 x^y % mod\\n public long quickmul(int x, long y) {\\n long ret = 1;\\n long mul = x;\\n while (y > 0) {\\n if (y % 2 == 1) {\\n ret = ret * mul % mod;\\n }\\n mul = mul * mul % mod;\\n y /= 2;\\n }\\n\\n return ret;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n long mod = 1000000007;\\n\\n public int CountGoodNumbers(long n) {\\n return (int) (Quickmul(5, (n + 1) / 2) * Quickmul(4, n / 2) % mod);\\n }\\n\\n // 快速幂求出 x^y % mod\\n public long Quickmul(int x, long y) {\\n long ret = 1;\\n long mul = x;\\n while (y > 0) {\\n if (y % 2 == 1) {\\n ret = ret * mul % mod;\\n }\\n mul = mul * mul % mod;\\n y /= 2;\\n }\\n\\n return ret;\\n }\\n}\\n
###Go
\\n\\nfunc countGoodNumbers(n int64) int {\\n mod := int64(1000000007)\\n\\n // 快速幂求出 x^y % mod\\n quickmul := func(x, y int64) int64 {\\n ret := int64(1)\\n mul := x\\n for y > 0 {\\n if y % 2 == 1 {\\n ret = ret * mul % mod\\n }\\n mul = mul * mul % mod\\n y /= 2\\n }\\n return ret\\n }\\n\\n return int(quickmul(5, (n + 1) / 2) * quickmul(4, n / 2) % mod)\\n}\\n
###C
\\n\\n#define MOD 1000000007\\n\\n// 快速幂求出 x^y % mod\\nlong long quickmul(int x, long long y) {\\n long long ret = 1;\\n long long mul = x;\\n while (y > 0) {\\n if (y % 2 == 1) {\\n ret = (ret * mul) % MOD;\\n }\\n mul = (mul * mul) % MOD;\\n y /= 2;\\n }\\n return ret;\\n}\\n\\nint countGoodNumbers(long long n) {\\n return (int)(quickmul(5, (n + 1) / 2) * quickmul(4, n / 2) % MOD);\\n}\\n
###JavaScript
\\n\\nvar countGoodNumbers = function(n) {\\n const mod = 1000000007n;\\n\\n // 快速幂求出 x^y % mod\\n function quickmul(x, y) {\\n let ret = 1n;\\n let mul = x;\\n while (y > 0) {\\n if (y % 2n === 1n) {\\n ret = (ret * mul) % mod;\\n }\\n mul = (mul * mul) % mod;\\n y = y / 2n;\\n }\\n return ret;\\n }\\n\\n return Number(quickmul(5n, BigInt(n + 1) / 2n) * quickmul(4n, BigInt(n) / 2n) % mod);\\n};\\n
###TypeScript
\\n\\nfunction countGoodNumbers(n: number): number {\\n const mod: bigint = 1000000007n;\\n\\n // 快速幂求出 x^y % mod\\n function quickmul(x: bigint, y: bigint): bigint {\\n let ret: bigint = 1n;\\n let mul: bigint = x;\\n while (y > 0n) {\\n if (y % 2n === 1n) {\\n ret = (ret * mul) % mod;\\n }\\n mul = (mul * mul) % mod;\\n y = y / 2n;\\n }\\n return ret;\\n }\\n\\n return Number(quickmul(5n, BigInt(n + 1) / 2n) * quickmul(4n, BigInt(n) / 2n) % mod);\\n};\\n
###Rust
\\n\\nconst MOD: i64 = 1000000007;\\n\\nimpl Solution {\\n pub fn count_good_numbers(n: i64) -> i32 {\\n (Self::quickmul(5, (n + 1) / 2) * Self::quickmul(4, n / 2) % MOD) as i32\\n }\\n\\n // 快速幂求出 x^y % mod\\n fn quickmul(x: i32, mut y: i64) -> i64 {\\n let mut ret = 1 as i64;\\n let mut mul = x as i64;\\n while y > 0 {\\n if y % 2 == 1 {\\n ret = ret * mul % MOD;\\n }\\n mul = mul * mul % MOD;\\n y /= 2;\\n }\\n ret\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:快速幂 思路与算法\\n\\n对于偶数下标处的数字,它可以为 $0, 2, 4, 6, 8$ 共计 $5$ 种,而长度为 $n$ 的数字字符串有 $\\\\lfloor \\\\dfrac{n+1}{2} \\\\rfloor$ 个偶数下标,其中 $\\\\lfloor x \\\\rfloor$ 表示对 $x$ 向下取整。\\n\\n对于奇数下标处的数字,它可以为 $2, 3, 5, 7$ 共计 $4$ 种,而长度为 $n$ 的数字字符串有 $\\\\lfloor \\\\dfrac{n}{2} \\\\rfloor$ 个奇数下标。\\n\\n因此长度为 $n$ 的数字字符串中,好数字的总数即为:\\n\\n$$\\n 5…","guid":"https://leetcode.cn/problems/count-good-numbers//solution/tong-ji-hao-shu-zi-de-shu-mu-by-leetcode-53jj","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T06:07:03.535Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"乘法原理+快速幂(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/count-good-numbers//solution/cheng-fa-yuan-li-kuai-su-mi-by-endlessch-btkn","content":"- \\n
\\n时间复杂度:$O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n长为 $n$ 的数字字符串:
\\n\\n
\\n- 有 $a = \\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil=\\\\left\\\\lfloor\\\\dfrac{n+1}{2}\\\\right\\\\rfloor$ 个偶数下标,每个下标可以填 $0,2,4,6,8$ 一共 $5$ 种偶数,方案数为 $5^a$。
\\n- 有 $b = \\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$ 个奇数下标,每个下标可以填 $2,3,5,7$ 一共 $4$ 种质数,方案数为 $4^b$。
\\n由于偶数下标和奇数下标互相独立,根据乘法原理,方案数相乘,答案为
\\n$$
\\n
\\n5^a4^b
\\n$$上式需要用快速幂计算,原理见【图解】一张图秒懂快速幂。
\\n注意取模。关于模运算的知识点,见 模运算的世界:当加减乘除遇上取模。
\\n\\nclass Solution:\\n def countGoodNumbers(self, n: int) -> int:\\n MOD = 1_000_000_007\\n return pow(5, (n + 1) // 2, MOD) * pow(4, n // 2, MOD) % MOD\\n
\\nclass Solution {\\n private static final int MOD = 1_000_000_007;\\n\\n public int countGoodNumbers(long n) {\\n return (int) (pow(5, (n + 1) / 2) * pow(4, n / 2) % MOD);\\n }\\n\\n private long pow(long x, long n) {\\n long res = 1;\\n while (n > 0) {\\n if ((n & 1) > 0) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n n >>= 1;\\n }\\n return res;\\n }\\n}\\n
\\nclass Solution {\\n const int MOD = 1\'000\'000\'007;\\n\\n long long qpow(long long x, long long n) {\\n long long res = 1;\\n while (n) {\\n if (n & 1) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n n >>= 1;\\n }\\n return res;\\n }\\n\\npublic:\\n int countGoodNumbers(long long n) {\\n return qpow(5, (n + 1) / 2) * qpow(4, n / 2) % MOD;\\n }\\n};\\n
\\nconst int MOD = 1000000007;\\n\\nlong long qpow(long long x, long long n) {\\n long long res = 1;\\n while (n) {\\n if (n & 1) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n n >>= 1;\\n }\\n return res;\\n}\\n\\nint countGoodNumbers(long long n) {\\n return qpow(5, (n + 1) / 2) * qpow(4, n / 2) % MOD;\\n}\\n
\\nconst mod = 1_000_000_007\\n\\nfunc countGoodNumbers(n int64) int {\\n return pow(5, (int(n)+1)/2) * pow(4, int(n)/2) % mod\\n}\\n\\nfunc pow(x, n int) int {\\n res := 1\\n for ; n > 0; n >>= 1 {\\n if n&1 > 0 {\\n res = res * x % mod\\n }\\n x = x * x % mod\\n }\\n return res\\n}\\n
\\nconst MOD = 1_000_000_007n;\\n\\nvar countGoodNumbers = function(N) {\\n const n = BigInt(N);\\n return Number(pow(5n, (n + 1n) / 2n) * pow(4n, n / 2n) % MOD);\\n};\\n\\nvar pow = function(x, n) {\\n let res = 1n;\\n while (n) {\\n if (n & 1n) {\\n res = res * x % MOD;\\n }\\n x = x * x % MOD;\\n n >>= 1n;\\n }\\n return res;\\n};\\n
\\nimpl Solution {\\n const MOD: i64 = 1_000_000_007;\\n\\n pub fn count_good_numbers(n: i64) -> i32 {\\n (Self::pow(5, (n + 1) / 2) * Self::pow(4, n / 2) % Self::MOD) as i32\\n }\\n\\n fn pow(mut x: i64, mut n: i64) -> i64 {\\n let mut res = 1;\\n while n > 0 {\\n if n & 1 > 0 {\\n res = res * x % Self::MOD;\\n }\\n x = x * x % Self::MOD;\\n n >>= 1;\\n }\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(\\\\log n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n更多相似题目,见下面数学题单中的「§2.1 乘法原理」。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 【本题相关】数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"长为 $n$ 的数字字符串: 有 $a = \\\\left\\\\lceil\\\\dfrac{n}{2}\\\\right\\\\rceil=\\\\left\\\\lfloor\\\\dfrac{n+1}{2}\\\\right\\\\rfloor$ 个偶数下标,每个下标可以填 $0,2,4,6,8$ 一共 $5$ 种偶数,方案数为 $5^a$。\\n有 $b = \\\\left\\\\lfloor\\\\dfrac{n}{2}\\\\right\\\\rfloor$ 个奇数下标,每个下标可以填 $2,3,5,7$ 一共 $4$ 种质数,方案数为 $4^b$。\\n\\n由于偶数下标和奇数下标互相独立,根据乘法原理,方案数相乘,答案为\\n\\n$$\\n 5…","guid":"https://leetcode.cn/problems/count-good-numbers//solution/cheng-fa-yuan-li-kuai-su-mi-by-endlessch-btkn","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T04:09:40.527Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java + 找规律 + 快速幂","url":"https://leetcode.cn/problems/count-good-numbers//solution/kuai-su-mi-by-quruijie-ps8r","content":"一看就是找规律的题
\\n先暴力,找到规律
\\n\\n回溯枚举n个数字的每一个数字\\n在判断数字是否为好数字\\n
\\nstatic int count = 0;\\n public static int countGoodNumbers(long n) {\\n dfs(0,(int)n,new StringBuilder());\\n return count;\\n }\\n public static void dfs(int i, int n, StringBuilder sb){\\n if(i>=n){\\n String s =sb.toString();\\n for (int j = 0; j < n; j++) {\\n int a = s.charAt(j)-\'0\';\\n if(j%2==0){\\n if( a % 2 !=0){\\n break;\\n }\\n }else{\\n if(a!=2 && a!=3 && a!=7 && a!=5){\\n break;\\n }\\n }\\n if(j==n-1){\\n count++;\\n }\\n }\\n }else{\\n int N=10;\\n for (int j = 0; j < N; j++) {\\n StringBuilder ss=new StringBuilder(sb);\\n ss.append((char)(\'0\'+j));\\n dfs(i+1,n,ss);\\n }\\n }\\n }\\n
发现以下规律
\\n\\n5 20 100 400 2000\\n1 2 3 4 5\\n一开始为5\\n每次到奇数就 * 5\\n每次到偶数就 * 4\\n所以结果为 \\n4 ^ (n中的偶数) * 5 ^ (n中的偶数)\\n
\\npublic int countGoodNumbers(long n) {\\n int N=(int)Math.pow(10,9)+7;\\n //如果是奇数,还得乘个5\\n int cheng=1;\\n if(n%2==1){\\n cheng=5;\\n //n变为偶数\\n n-=1;\\n }\\n //n中有多少个偶数\\n long o=n/2;\\n // 4*5 的 偶数 次方\\n long a = myPow(20,o,N);\\n long ans = a * cheng;\\n return (int)(ans%N);\\n }\\n //快速幂 (记得要取余N,不只是结果取余,每次乘机也要取余)\\n public long myPow(long x, long n,int N) {\\n if(n==0){\\n return 1;\\n }\\n long m=n;\\n long sum=1;\\n while(m!=0){\\n if((m&1)==1){\\n sum*=x;\\n sum%=N;\\n }\\n x*=x;\\n x%=N;\\n m>>=1;\\n }\\n return sum;\\n }\\n
\\n","description":"回溯枚举n个数字的每一个数字 在判断数字是否为好数字\\n\\nstatic int count = 0;\\n public static int countGoodNumbers(long n) {\\n dfs(0,(int)n,new StringBuilder());\\n return count;\\n }\\n public static void dfs(int i, int n, StringBuilder sb){\\n if(i>=n){\\n String s =sb…","guid":"https://leetcode.cn/problems/count-good-numbers//solution/kuai-su-mi-by-quruijie-ps8r","author":"QuRuijie","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T04:09:20.416Z","media":[{"url":"https://pic.leetcode-cn.com/1625371822-UtRWwp-image.png","type":"photo","width":753,"height":302,"blurhash":"LVQ,di-qxb-q~EWBWUWBEJoMs;ju"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"进阶问题解法:数字搬家,O(1) 空间(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/build-array-from-permutation//solution/mo-ni-by-endlesscheng-a7hu","content":"
注意 $\\\\textit{nums}$ 是一个 $0$ 到 $n-1$ 的排列。元素值的范围和下标的范围,都是 $[0,n-1]$。所以从 $i$ 开始,不断迭代 $i=\\\\textit{nums}[i]$,一定会回到起点 $i$。
\\n例如 $\\\\textit{nums}=[1,2,0]$ 的答案为 $[2,0,1]$:
\\n\\n
\\n- 把 $\\\\textit{nums}[0]$ 替换成下标为 $\\\\textit{nums}[0]=1$ 的数,即 $\\\\textit{nums}[1]=2$。
\\n- 把 $\\\\textit{nums}[1]$ 替换成下标为 $\\\\textit{nums}[1]=2$ 的数,即 $\\\\textit{nums}[2]=0$。
\\n- 把 $\\\\textit{nums}[2]$ 替换成下标为 $\\\\textit{nums}[2]=0$ 的数,即 $\\\\textit{nums}[0]=1$。
\\n我把这个过程叫做「数字搬家」:把 $\\\\textit{nums}[1]$ 搬到 $\\\\textit{nums}[0]$,把 $\\\\textit{nums}[2]$ 搬到 $\\\\textit{nums}[1]$,把 $\\\\textit{nums}[0]$ 搬到 $\\\\textit{nums}[2]$。
\\n一组数字搬完家后,还需要继续向后遍历,看看是否还有其他数字也需要搬家。例如 $\\\\textit{nums}=[1,2,0,4,3]$,把 $1,2,0$ 这一组搬家后,还有一组 $4,3$ 也需要搬家。结果为 $[2,0,1,3,4]$。
\\n问题来了,继续向后遍历,怎么知道一个数是否搬过家了?
\\n额外用一个 $\\\\textit{vis}$ 数组?这不符合题目 $\\\\mathcal{O}(1)$ 空间的要求。
\\n由于 $\\\\textit{nums}$ 中的数都是非负数,我们可以用负数标记搬过家的位置。把 $x=\\\\textit{nums}[i]$ 加一,再取相反数,就可以保证 $x<0$。于是,如果遍历到一个负数,可以直接 $\\\\texttt{continue}$。
\\n根据补码的定义,我们有
\\n$$
\\n
\\n-x=(\\\\sim x) + 1
\\n$$所以有
\\n$$
\\n
\\n-(x+1) = -x - 1 = (\\\\sim x) + 1 - 1 =\\\\ \\\\sim x
\\n$$所以把 $x$ 用位运算取反即可。
\\n所有数字搬家结束后,把 $\\\\textit{nums}$ 中的所有数字再取反(复原),即可得到最终答案。
\\n\\nclass Solution:\\n def buildArray(self, nums: List[int]) -> List[int]:\\n for i, x in enumerate(nums):\\n if x < 0: # 已搬家\\n continue\\n cur = i\\n while nums[cur] != i:\\n nxt = nums[cur]\\n nums[cur] = ~nums[nxt] # 把下一个数搬过来,同时做标记(取反)\\n cur = nxt\\n nums[cur] = ~x # 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n\\n for i, x in enumerate(nums):\\n nums[i] = ~x # 复原\\n return nums\\n
\\nclass Solution:\\n def buildArray(self, nums: List[int]) -> List[int]:\\n for i, x in enumerate(nums):\\n if x < 0: # 已搬家\\n continue\\n cur = i\\n while nums[cur] != i:\\n nums[cur], cur = ~nums[nums[cur]], nums[cur] # 把下一个数搬过来,同时做标记(取反)\\n nums[cur] = ~x # 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n\\n for i, x in enumerate(nums):\\n nums[i] = ~x # 复原\\n return nums\\n
\\nclass Solution {\\n public int[] buildArray(int[] nums) {\\n for (int i = 0; i < nums.length; i++) {\\n int x = nums[i];\\n if (x < 0) { // 已搬家\\n continue;\\n }\\n int cur = i;\\n while (nums[cur] != i) {\\n int nxt = nums[cur];\\n nums[cur] = ~nums[nxt]; // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt;\\n }\\n nums[cur] = ~x; // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for (int i = 0; i < nums.length; i++) {\\n nums[i] = ~nums[i]; // 复原\\n }\\n return nums;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n vector<int> buildArray(vector<int>& nums) {\\n for (int i = 0; i < nums.size(); i++) {\\n int x = nums[i];\\n if (x < 0) { // 已搬家\\n continue;\\n }\\n int cur = i;\\n while (nums[cur] != i) {\\n int nxt = nums[cur];\\n nums[cur] = ~nums[nxt]; // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt;\\n }\\n nums[cur] = ~x; // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for (int i = 0; i < nums.size(); i++) {\\n nums[i] = ~nums[i]; // 复原\\n }\\n return nums;\\n }\\n};\\n
\\nint* buildArray(int* nums, int numsSize, int* returnSize) {\\n for (int i = 0; i < numsSize; i++) {\\n int x = nums[i];\\n if (x < 0) { // 已搬家\\n continue;\\n }\\n int cur = i;\\n while (nums[cur] != i) {\\n int nxt = nums[cur];\\n nums[cur] = ~nums[nxt]; // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt;\\n }\\n nums[cur] = ~x; // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for (int i = 0; i < numsSize; i++) {\\n nums[i] = ~nums[i]; // 复原\\n }\\n *returnSize = numsSize;\\n return nums;\\n}\\n
\\nfunc buildArray(nums []int) []int {\\n for i, x := range nums {\\n if x < 0 { // 已搬家\\n continue\\n }\\n cur := i\\n for nums[cur] != i {\\n nxt := nums[cur]\\n nums[cur] = ^nums[nxt] // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt\\n }\\n nums[cur] = ^x // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for i, x := range nums {\\n nums[i] = ^x // 复原\\n }\\n return nums\\n}\\n
\\nvar buildArray = function(nums) {\\n for (let i = 0; i < nums.length; i++) {\\n const x = nums[i];\\n if (x < 0) { // 已搬家\\n continue;\\n }\\n let cur = i;\\n while (nums[cur] !== i) {\\n const nxt = nums[cur];\\n nums[cur] = ~nums[nxt]; // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt;\\n }\\n nums[cur] = ~x; // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for (let i = 0; i < nums.length; i++) {\\n nums[i] = ~nums[i]; // 复原\\n }\\n return nums;\\n};\\n
\\nimpl Solution {\\n pub fn build_array(mut nums: Vec<i32>) -> Vec<i32> {\\n for i in 0..nums.len() {\\n let x = nums[i];\\n if x < 0 { // 已搬家\\n continue;\\n }\\n let mut cur = i;\\n while nums[cur] as usize != i {\\n let nxt = nums[cur] as usize;\\n nums[cur] = !nums[nxt]; // 把下一个数搬过来,同时做标记(取反)\\n cur = nxt;\\n }\\n nums[cur] = !x; // 对于这一组的最后一个数,把起点 x=nums[i] 搬过来\\n }\\n\\n for i in 0..nums.len() {\\n nums[i] = !nums[i]; // 复原\\n }\\n nums\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度。虽然写了个二重循环,但每个数都恰好标记成负数一次,所以总循环次数是 $\\\\mathcal{O}(n)$ 的,所以时间复杂度是 $\\\\mathcal{O}(n)$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n思考题
\\n已知答案为 $a$,构造一个 $\\\\textit{nums}$,使得上述代码的返回结果恰好等于 $a$。
\\n比如示例 1,已知答案为 $a=[0,1,2,4,5,3]$,你需要构造 $\\\\textit{nums}=[0,2,1,5,3,4]$。
\\n\\n\\n注:这样的 $\\\\textit{nums}$ 可能不止一个,你只需构造任意一个满足要求的 $\\\\textit{nums}$。
\\n欢迎在评论区分享你的思路/代码。
\\n相似题目
\\n\\n
\\n- 442. 数组中重复的数据
\\n- 448. 找到所有数组中消失的数字
\\n- 41. 缺失的第一个正数
\\n- 287. 寻找重复数
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"注意 $\\\\textit{nums}$ 是一个 $0$ 到 $n-1$ 的排列。元素值的范围和下标的范围,都是 $[0,n-1]$。所以从 $i$ 开始,不断迭代 $i=\\\\textit{nums}[i]$,一定会回到起点 $i$。 例如 $\\\\textit{nums}=[1,2,0]$ 的答案为 $[2,0,1]$:\\n\\n把 $\\\\textit{nums}[0]$ 替换成下标为 $\\\\textit{nums}[0]=1$ 的数,即 $\\\\textit{nums}[1]=2$。\\n把 $\\\\textit{nums}[1]$ 替换成下标为 $\\\\textit{nums}[1]…","guid":"https://leetcode.cn/problems/build-array-from-permutation//solution/mo-ni-by-endlesscheng-a7hu","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T04:07:39.859Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「代码随想录」带你学透回溯算法!40. 组合总和 I【彻底理解组合问题上的去重】I","url":"https://leetcode.cn/problems/combination-sum-ii//solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-ig29","content":"《代码随想录》算法视频公开课:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II,相信结合视频再看本篇题解,更有助于大家对本题的理解。
\\n思路
\\n这道题目和39.组合总和如下区别:
\\n\\n
\\n- 本题candidates 中的每个数字在每个组合中只能使用一次。
\\n- 本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates
\\n最后本题和39.组合总和要求一样,解集不能包含重复的组合。
\\n本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。
\\n一些同学可能想了:我把所有组合求出来,再用set或者map去重,这么做很容易超时!
\\n所以要在搜索的过程中就去掉重复组合。
\\n很多同学在去重的问题上想不明白,其实很多题解也没有讲清楚,反正代码是能过的,感觉是那么回事,稀里糊涂的先把题目过了。
\\n这个去重为什么很难理解呢,所谓去重,其实就是使用过的元素不能重复选取。 这么一说好像很简单!
\\n都知道组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。没有理解这两个层面上的“使用过” 是造成大家没有彻底理解去重的根本原因。
\\n那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?
\\n回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。
\\n所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。
\\n为了理解去重我们来举一个例子,candidates = [1, 1, 2], target = 3,(方便起见candidates已经排序了)
\\n强调一下,树层去重的话,需要对数组排序!
\\n选择过程树形结构如图所示:
\\n\\n
可以看到图中,每个节点相对于 39.组合总和我多加了used数组,这个used数组下面会重点介绍。
\\n回溯三部曲
\\n\\n
\\n- 递归函数参数
\\n与39.组合总和套路相同,此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。
\\n这个集合去重的重任就是used来完成的。
\\n代码如下:
\\n###CPP
\\n\\nvector<vector<int>> result; // 存放组合集合\\nvector<int> path; // 符合条件的组合\\nvoid backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {\\n
\\n
\\n- 递归终止条件
\\n与39.组合总和相同,终止条件为
\\nsum > target
和sum == target
。代码如下:
\\n###CPP
\\n\\nif (sum > target) { // 这个条件其实可以省略\\n return;\\n}\\nif (sum == target) {\\n result.push_back(path);\\n return;\\n}\\n
\\n
sum > target
这个条件其实可以省略,因为在递归单层遍历的时候,会有剪枝的操作,下面会介绍到。\\n
\\n- 单层搜索的逻辑
\\n这里与39.组合总和最大的不同就是要去重了。
\\n前面我们提到:要去重的是“同一树层上的使用过”,如何判断同一树层上元素(相同的元素)是否使用过了呢。
\\n如果
\\ncandidates[i] == candidates[i - 1]
并且used[i - 1] == false
,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]。此时for循环里就应该做continue的操作。
\\n这块比较抽象,如图:
\\n\\n
我在图中将used的变化用橘黄色标注上,可以看出在candidates[i] == candidates[i - 1]相同的情况下:
\\n\\n
\\n- used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
\\n- used[i - 1] == false,说明同一树层candidates[i - 1]使用过
\\n可能有的录友想,为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。
\\n而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:
\\n\\n
这块去重的逻辑很抽象,网上搜的题解基本没有能讲清楚的,如果大家之前思考过这个问题或者刷过这道题目,看到这里一定会感觉通透了很多!
\\n那么单层搜索的逻辑代码如下:
\\n###CPP
\\n\\nfor (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {\\n // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过\\n // used[i - 1] == false,说明同一树层candidates[i - 1]使用过\\n // 要对同一树层使用过的元素进行跳过\\n if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {\\n continue;\\n }\\n sum += candidates[i];\\n path.push_back(candidates[i]);\\n used[i] = true;\\n backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1:这里是i+1,每个数字在每个组合中只能使用一次\\n used[i] = false;\\n sum -= candidates[i];\\n path.pop_back();\\n}\\n
注意sum + candidates[i] <= target为剪枝操作,在39.组合总和有讲解过!
\\n回溯三部曲分析完了,整体C++代码如下:
\\n###CPP
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> result;\\n vector<int> path;\\n void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {\\n if (sum == target) {\\n result.push_back(path);\\n return;\\n }\\n for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {\\n // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过\\n // used[i - 1] == false,说明同一树层candidates[i - 1]使用过\\n // 要对同一树层使用过的元素进行跳过\\n if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {\\n continue;\\n }\\n sum += candidates[i];\\n path.push_back(candidates[i]);\\n used[i] = true;\\n backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次\\n used[i] = false;\\n sum -= candidates[i];\\n path.pop_back();\\n }\\n }\\n\\npublic:\\n vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {\\n vector<bool> used(candidates.size(), false);\\n path.clear();\\n result.clear();\\n // 首先把给candidates排序,让其相同的元素都挨在一起。\\n sort(candidates.begin(), candidates.end());\\n backtracking(candidates, target, 0, 0, used);\\n return result;\\n }\\n};\\n\\n
补充
\\n这里直接用startIndex来去重也是可以的, 就不用used数组了。
\\n###CPP
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> result;\\n vector<int> path;\\n void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {\\n if (sum == target) {\\n result.push_back(path);\\n return;\\n }\\n for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {\\n // 要对同一树层使用过的元素进行跳过\\n if (i > startIndex && candidates[i] == candidates[i - 1]) {\\n continue;\\n }\\n sum += candidates[i];\\n path.push_back(candidates[i]);\\n backtracking(candidates, target, sum, i + 1); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次\\n sum -= candidates[i];\\n path.pop_back();\\n }\\n }\\n\\npublic:\\n vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {\\n path.clear();\\n result.clear();\\n // 首先把给candidates排序,让其相同的元素都挨在一起。\\n sort(candidates.begin(), candidates.end());\\n backtracking(candidates, target, 0, 0);\\n return result;\\n }\\n};\\n\\n
总结
\\n本题同样是求组合总和,但就是因为其数组candidates有重复元素,而要求不能有重复的组合,所以相对于39.组合总和难度提升了不少。
\\n关键是去重的逻辑,代码很简单,网上一搜一大把,但几乎没有能把这块代码含义讲明白的,基本都是给出代码,然后说这就是去重了,究竟怎么个去重法也是模棱两可。
\\n所以Carl有必要把去重的这块彻彻底底的给大家讲清楚,就连“树层去重”和“树枝去重”都是我自创的词汇,希望对大家理解有帮助!
\\n其他语言版本
\\n###Java
\\n\\n// **使用标记数组**\\nclass Solution {\\n LinkedList<Integer> path = new LinkedList<>();\\n List<List<Integer>> ans = new ArrayList<>();\\n boolean[] used;\\n int sum = 0;\\n\\n public List<List<Integer>> combinationSum2(int[] candidates, int target) {\\n used = new boolean[candidates.length];\\n // 加标志数组,用来辅助判断同层节点是否已经遍历\\n Arrays.fill(used, false);\\n // 为了将重复的数字都放到一起,所以先进行排序\\n Arrays.sort(candidates);\\n backTracking(candidates, target, 0);\\n return ans;\\n }\\n\\n private void backTracking(int[] candidates, int target, int startIndex) {\\n if (sum == target) {\\n ans.add(new ArrayList(path));\\n }\\n for (int i = startIndex; i < candidates.length; i++) {\\n if (sum + candidates[i] > target) {\\n break;\\n }\\n // 出现重复节点,同层的第一个节点已经被访问过,所以直接跳过\\n if (i > 0 && candidates[i] == candidates[i - 1] && !used[i - 1]) {\\n continue;\\n }\\n used[i] = true;\\n sum += candidates[i];\\n path.add(candidates[i]);\\n // 每个节点仅能选择一次,所以从下一位开始\\n backTracking(candidates, target, i + 1);\\n used[i] = false;\\n sum -= candidates[i];\\n path.removeLast();\\n }\\n }\\n}\\n\\n\\n// **不使用标记数组**\\nclass Solution {\\n List<List<Integer>> res = new ArrayList<>();\\n LinkedList<Integer> path = new LinkedList<>();\\n int sum = 0;\\n \\n public List<List<Integer>> combinationSum2( int[] candidates, int target ) {\\n //为了将重复的数字都放到一起,所以先进行排序\\n Arrays.sort( candidates );\\n backTracking( candidates, target, 0 );\\n return res;\\n }\\n \\n private void backTracking( int[] candidates, int target, int start ) {\\n if ( sum == target ) {\\n res.add( new ArrayList<>( path ) );\\n return;\\n }\\n for ( int i = start; i < candidates.length && sum + candidates[i] <= target; i++ ) {\\n //正确剔除重复解的办法\\n //跳过同一树层使用过的元素\\n if ( i > start && candidates[i] == candidates[i - 1] ) {\\n continue;\\n }\\n\\n sum += candidates[i];\\n path.add( candidates[i] );\\n // i+1 代表当前组内元素只选取一次\\n backTracking( candidates, target, i + 1 );\\n\\n int temp = path.getLast();\\n sum -= temp;\\n path.removeLast();\\n }\\n }\\n}\\n
###python
\\n\\n# **回溯+巧妙去重(省去使用used**\\nclass Solution:\\n def __init__(self):\\n self.paths = []\\n self.path = []\\n\\n def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:\\n \'\'\'\\n 类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序\\n \'\'\'\\n self.paths.clear()\\n self.path.clear()\\n # 必须提前进行数组排序,避免重复\\n candidates.sort()\\n self.backtracking(candidates, target, 0, 0)\\n return self.paths\\n\\n def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None:\\n # Base Case\\n if sum_ == target:\\n self.paths.append(self.path[:])\\n return\\n \\n # 单层递归逻辑\\n for i in range(start_index, len(candidates)):\\n # 剪枝,同39.组合总和\\n if sum_ + candidates[i] > target:\\n return\\n \\n # 跳过同一树层使用过的元素\\n if i > start_index and candidates[i] == candidates[i-1]:\\n continue\\n \\n sum_ += candidates[i]\\n self.path.append(candidates[i])\\n self.backtracking(candidates, target, sum_, i+1)\\n self.path.pop() # 回溯,为了下一轮for loop\\n sum_ -= candidates[i] # 回溯,为了下一轮for loop\\n\\n# **回溯+去重(使用used)**\\nclass Solution:\\n def __init__(self):\\n self.paths = []\\n self.path = []\\n self.used = []\\n\\n def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:\\n \'\'\'\\n 类似于求三数之和,求四数之和,为了避免重复组合,需要提前进行数组排序\\n 本题需要使用used,用来标记区别同一树层的元素使用重复情况:注意区分递归纵向遍历遇到的重复元素,和for循环遇到的重复元素,这两者的区别\\n \'\'\'\\n self.paths.clear()\\n self.path.clear()\\n self.usage_list = [False] * len(candidates)\\n # 必须提前进行数组排序,避免重复\\n candidates.sort()\\n self.backtracking(candidates, target, 0, 0)\\n return self.paths\\n\\n def backtracking(self, candidates: List[int], target: int, sum_: int, start_index: int) -> None:\\n # Base Case\\n if sum_ == target:\\n self.paths.append(self.path[:])\\n return\\n \\n # 单层递归逻辑\\n for i in range(start_index, len(candidates)):\\n # 剪枝,同39.组合总和\\n if sum_ + candidates[i] > target:\\n return\\n \\n # 检查同一树层是否出现曾经使用过的相同元素\\n # 若数组中前后元素值相同,但前者却未被使用(used == False),说明是for loop中的同一树层的相同元素情况\\n if i > 0 and candidates[i] == candidates[i-1] and self.usage_list[i-1] == False:\\n continue\\n\\n sum_ += candidates[i]\\n self.path.append(candidates[i])\\n self.usage_list[i] = True\\n self.backtracking(candidates, target, sum_, i+1)\\n self.usage_list[i] = False # 回溯,为了下一轮for loop\\n self.path.pop() # 回溯,为了下一轮for loop\\n sum_ -= candidates[i] # 回溯,为了下一轮for loop\\n
###go
\\n\\n// **使用used数组**\\nvar (\\n res [][]int\\n path []int\\n used []bool\\n)\\nfunc combinationSum2(candidates []int, target int) [][]int {\\n res, path = make([][]int, 0), make([]int, 0, len(candidates))\\n used = make([]bool, len(candidates))\\n sort.Ints(candidates) // 排序,为剪枝做准备\\n dfs(candidates, 0, target)\\n return res\\n}\\n\\nfunc dfs(candidates []int, start int, target int) {\\n if target == 0 { // target 不断减小,如果为0说明达到了目标值\\n tmp := make([]int, len(path))\\n copy(tmp, path)\\n res = append(res, tmp)\\n return\\n }\\n for i := start; i < len(candidates); i++ {\\n if candidates[i] > target { // 剪枝,提前返回\\n break\\n }\\n // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过\\n // used[i - 1] == false,说明同一树层candidates[i - 1]使用过\\n if i > 0 && candidates[i] == candidates[i-1] && used[i-1] == false { \\n continue\\n }\\n path = append(path, candidates[i])\\n used[i] = true\\n dfs(candidates, i+1, target - candidates[i])\\n used[i] = false\\n path = path[:len(path) - 1]\\n }\\n}\\n\\n// **不使用used数组**\\nvar (\\n res [][]int\\n path []int\\n)\\nfunc combinationSum2(candidates []int, target int) [][]int {\\n res, path = make([][]int, 0), make([]int, 0, len(candidates))\\n sort.Ints(candidates) // 排序,为剪枝做准备\\n dfs(candidates, 0, target)\\n return res\\n}\\n\\nfunc dfs(candidates []int, start int, target int) {\\n if target == 0 { // target 不断减小,如果为0说明达到了目标值\\n tmp := make([]int, len(path))\\n copy(tmp, path)\\n res = append(res, tmp)\\n return\\n }\\n for i := start; i < len(candidates); i++ {\\n if candidates[i] > target { // 剪枝,提前返回\\n break\\n }\\n // i != start 限制了这不对深度遍历到达的此值去重\\n if i != start && candidates[i] == candidates[i-1] { // 去重\\n continue\\n }\\n path = append(path, candidates[i])\\n dfs(candidates, i+1, target - candidates[i])\\n path = path[:len(path) - 1]\\n }\\n}\\n
###js
\\n\\n/**\\n * @param {number[]} candidates\\n * @param {number} target\\n * @return {number[][]}\\n */\\nvar combinationSum2 = function(candidates, target) {\\n const res = []; path = [], len = candidates.length;\\n candidates.sort((a,b)=>a-b);\\n backtracking(0, 0);\\n return res;\\n function backtracking(sum, i) {\\n if (sum === target) {\\n res.push(Array.from(path));\\n return;\\n }\\n for(let j = i; j < len; j++) {\\n const n = candidates[j];\\n if(j > i && candidates[j] === candidates[j-1]){\\n //若当前元素和前一个元素相等\\n //则本次循环结束,防止出现重复组合\\n continue;\\n }\\n //如果当前元素值大于目标值-总和的值\\n //由于数组已排序,那么该元素之后的元素必定不满足条件\\n //直接终止当前层的递归\\n if(n > target - sum) break;\\n path.push(n);\\n sum += n;\\n backtracking(sum, j + 1);\\n path.pop();\\n sum -= n;\\n }\\n }\\n};\\n\\n // **使用used去重**\\nvar combinationSum2 = function(candidates, target) {\\n let res = [];\\n let path = [];\\n let total = 0;\\n const len = candidates.length;\\n candidates.sort((a, b) => a - b);\\n let used = new Array(len).fill(false);\\n const backtracking = (startIndex) => {\\n if (total === target) {\\n res.push([...path]);\\n return;\\n }\\n for(let i = startIndex; i < len && total < target; i++) {\\n const cur = candidates[i];\\n if (cur > target - total || (i > 0 && cur === candidates[i - 1] && !used[i - 1])) continue;\\n path.push(cur);\\n total += cur;\\n used[i] = true;\\n backtracking(i + 1);\\n path.pop();\\n total -= cur;\\n used[i] = false;\\n }\\n }\\n backtracking(0);\\n return res;\\n};\\n
###typescript
\\n\\nfunction combinationSum2(candidates: number[], target: number): number[][] {\\n candidates.sort((a, b) => a - b);\\n const resArr: number[][] = [];\\n function backTracking(\\n candidates: number[], target: number,\\n curSum: number, startIndex: number, route: number[]\\n ) {\\n if (curSum > target) return;\\n if (curSum === target) {\\n resArr.push(route.slice());\\n return;\\n }\\n for (let i = startIndex, length = candidates.length; i < length; i++) {\\n if (i > startIndex && candidates[i] === candidates[i - 1]) {\\n continue;\\n }\\n let tempVal: number = candidates[i];\\n route.push(tempVal);\\n backTracking(candidates, target, curSum + tempVal, i + 1, route);\\n route.pop();\\n\\n }\\n }\\n backTracking(candidates, target, 0, 0, []);\\n return resArr;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, candidates: &Vec<i32>, target: i32, mut sum: i32, start_index: usize, used: &mut Vec<bool>) {\\n if sum == target {\\n result.push(path.to_vec());\\n return;\\n }\\n for i in start_index..candidates.len() {\\n if sum + candidates[i] <= target {\\n if i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false { continue; }\\n sum += candidates[i];\\n path.push(candidates[i]);\\n used[i] = true;\\n Self::backtracking(result, path, candidates, target, sum, i + 1, used);\\n used[i] = false;\\n sum -= candidates[i];\\n path.pop();\\n }\\n }\\n }\\n\\n pub fn combination_sum2(candidates: Vec<i32>, target: i32) -> Vec<Vec<i32>> {\\n let mut result: Vec<Vec<i32>> = Vec::new();\\n let mut path: Vec<i32> = Vec::new();\\n let mut used: Vec<bool> = vec![false; candidates.len()];\\n let mut candidates = candidates;\\n candidates.sort();\\n Self::backtracking(&mut result, &mut path, &candidates, target, 0, 0, &mut used);\\n result\\n }\\n}\\n
###c
\\n\\nint* path;\\nint pathTop;\\nint** ans;\\nint ansTop;\\n//记录ans中每一个一维数组的大小\\nint* length;\\nint cmp(const void* a1, const void* a2) {\\n return *((int*)a1) - *((int*)a2);\\n}\\n\\nvoid backTracking(int* candidates, int candidatesSize, int target, int sum, int startIndex) {\\n if(sum >= target) {\\n //若sum等于target,复制当前path进入\\n if(sum == target) {\\n int* tempPath = (int*)malloc(sizeof(int) * pathTop);\\n int j;\\n for(j = 0; j < pathTop; j++) {\\n tempPath[j] = path[j];\\n }\\n length[ansTop] = pathTop;\\n ans[ansTop++] = tempPath;\\n }\\n return ;\\n }\\n\\n int i;\\n for(i = startIndex; i < candidatesSize; i++) {\\n //对同一层树中使用过的元素跳过\\n if(i > startIndex && candidates[i] == candidates[i-1])\\n continue;\\n path[pathTop++] = candidates[i];\\n sum += candidates[i];\\n backTracking(candidates, candidatesSize, target, sum, i + 1);\\n //回溯\\n sum -= candidates[i];\\n pathTop--;\\n }\\n}\\n\\nint** combinationSum2(int* candidates, int candidatesSize, int target, int* returnSize, int** returnColumnSizes){\\n path = (int*)malloc(sizeof(int) * 50);\\n ans = (int**)malloc(sizeof(int*) * 100);\\n length = (int*)malloc(sizeof(int) * 100);\\n pathTop = ansTop = 0;\\n //快速排序candidates,让相同元素挨到一起\\n qsort(candidates, candidatesSize, sizeof(int), cmp);\\n\\n backTracking(candidates, candidatesSize, target, 0, 0);\\n\\n *returnSize = ansTop;\\n *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);\\n int i;\\n for(i = 0; i < ansTop; i++) {\\n (*returnColumnSizes)[i] = length[i];\\n }\\n return ans;\\n}\\n
###swift
\\n\\nfunc combinationSum2(_ candidates: [Int], _ target: Int) -> [[Int]] {\\n // 为了方便去重复,先对集合排序\\n let candidates = candidates.sorted()\\n var result = [[Int]]()\\n var path = [Int]()\\n func backtracking(sum: Int, startIndex: Int) {\\n // 终止条件\\n if sum == target {\\n result.append(path)\\n return\\n }\\n\\n let end = candidates.count\\n guard startIndex < end else { return }\\n for i in startIndex ..< end {\\n if i > startIndex, candidates[i] == candidates[i - 1] { continue } // 去重复\\n let sum = sum + candidates[i] // 使用局部变量隐藏回溯\\n if sum > target { continue } // 剪枝\\n\\n path.append(candidates[i]) // 处理\\n backtracking(sum: sum, startIndex: i + 1) // i+1避免重复访问\\n path.removeLast() // 回溯\\n }\\n }\\n backtracking(sum: 0, startIndex: 0)\\n return result\\n}\\n
###scala
\\n\\nobject Solution {\\n import scala.collection.mutable \\n def combinationSum2(candidates: Array[Int], target: Int): List[List[Int]] = {\\n var res = mutable.ListBuffer[List[Int]]()\\n var path = mutable.ListBuffer[Int]()\\n var candidate = candidates.sorted\\n\\n def backtracking(sum: Int, startIndex: Int): Unit = {\\n if (sum == target) {\\n res.append(path.toList)\\n return\\n }\\n\\n for (i <- startIndex until candidate.size if sum + candidate(i) <= target) {\\n if (!(i > startIndex && candidate(i) == candidate(i - 1))) {\\n path.append(candidate(i))\\n backtracking(sum + candidate(i), i + 1)\\n path = path.take(path.size - 1)\\n }\\n }\\n }\\n\\n backtracking(0, 0)\\n res.toList\\n }\\n}\\n
大家好,我是程序员Carl,如果你还在没有章法的刷题,建议按照代码随想录刷题路线来刷,并提供PDF下载,刷题路线同时也开源在Github上,你会发现详见很晚!
\\n如果感觉题解对你有帮助,不要吝啬给一个👍吧!
\\n","description":"《代码随想录》算法视频公开课:回溯算法中的去重,树层去重树枝去重,你弄清楚了没?| LeetCode:40.组合总和II,相信结合视频再看本篇题解,更有助于大家对本题的理解。 这道题目和39.组合总和如下区别:\\n\\n本题candidates 中的每个数字在每个组合中只能使用一次。\\n本题数组candidates的元素是有重复的,而39.组合总和是无重复元素的数组candidates\\n\\n最后本题和39.组合总和要求一样,解集不能包含重复的组合。\\n\\n本题的难点在于区别2中:集合(数组candidates)有重复元素,但还不能有重复的组合。\\n\\n一些同学可能想了…","guid":"https://leetcode.cn/problems/combination-sum-ii//solution/dai-ma-sui-xiang-lu-dai-ni-xue-tou-hui-s-ig29","author":"carlsun-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-04T03:06:07.402Z","media":[{"url":"https://pic.leetcode.cn/1672110919-KHhJDC-image.png","type":"photo","width":1734,"height":1020,"blurhash":"LASF;M^+o#~q-;ozx[V@-:o#%2j@"},{"url":"https://pic.leetcode.cn/1672110999-tOgJKW-image.png","type":"photo","width":1768,"height":1020,"blurhash":"LBSF-E^*XT_4-:W?xvV@-:kX%2s."},{"url":"https://pic.leetcode.cn/1672111042-cNXIfI-image.png","type":"photo","width":1816,"height":1074,"blurhash":"L9S6Pm^*x]_3~UtRg4M|yEtR$yRj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「代码随想录」带你学透贪心算法!135. 分发糖果","url":"https://leetcode.cn/problems/candy//solution/dai-ma-sui-xiang-lu-135-fen-fa-tang-guo-f7ezy","content":"《代码随想录》算法视频公开课:贪心算法,两者兼顾很容易顾此失彼!LeetCode:135.分发糖果,相信结合视频在看本篇题解,更有助于大家对本题的理解。
\\n思路
\\n这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。
\\n先确定右边评分大于左边的情况(也就是从前向后遍历)
\\n此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果
\\n局部最优可以推出全局最优。
\\n如果ratings[i] > ratings[i - 1] 那么[i]的糖 一定要比[i - 1]的糖多一个,所以贪心:candyVec[i] = candyVec[i - 1] + 1
\\n代码如下:
\\n###CPP
\\n\\n// 从前向后\\nfor (int i = 1; i < ratings.size(); i++) {\\n if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;\\n}\\n
如图:
\\n\\n
再确定左孩子大于右孩子的情况(从后向前遍历)
\\n遍历顺序这里有同学可能会有疑问,为什么不能从前向后遍历呢?
\\n因为 rating[5]与rating[4]的比较 要利用上 rating[5]与rating[6]的比较结果,所以 要从后向前遍历。
\\n如果从前向后遍历,rating[5]与rating[4]的比较 就不能用上 rating[5]与rating[6]的比较结果了 。如图:
\\n\\n
所以确定左孩子大于右孩子的情况一定要从后向前遍历!
\\n如果 ratings[i] > ratings[i + 1],此时candyVec[i](第i个小孩的糖果数量)就有两个选择了,一个是candyVec[i + 1] + 1(从右边这个加1得到的糖果数量),一个是candyVec[i](之前比较右孩子大于左孩子得到的糖果数量)。
\\n那么又要贪心了,局部最优:取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,保证第i个小孩的糖果数量既大于左边的也大于右边的。全局最优:相邻的孩子中,评分高的孩子获得更多的糖果。
\\n局部最优可以推出全局最优。
\\n所以就取candyVec[i + 1] + 1 和 candyVec[i] 最大的糖果数量,candyVec[i]只有取最大的才能既保持对左边candyVec[i - 1]的糖果多,也比右边candyVec[i + 1]的糖果多。
\\n如图:
\\n\\n
所以该过程代码如下:
\\n###CPP
\\n\\n// 从后向前\\nfor (int i = ratings.size() - 2; i >= 0; i--) {\\n if (ratings[i] > ratings[i + 1] ) {\\n candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);\\n }\\n}\\n
整体代码如下:
\\n###CPP
\\n\\nclass Solution {\\npublic:\\n int candy(vector<int>& ratings) {\\n vector<int> candyVec(ratings.size(), 1);\\n // 从前向后\\n for (int i = 1; i < ratings.size(); i++) {\\n if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;\\n }\\n // 从后向前\\n for (int i = ratings.size() - 2; i >= 0; i--) {\\n if (ratings[i] > ratings[i + 1] ) {\\n candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);\\n }\\n }\\n // 统计结果\\n int result = 0;\\n for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];\\n return result;\\n }\\n};\\n
\\n
\\n- 时间复杂度: O(n)
\\n- 空间复杂度: O(n)
\\n总结
\\n这在leetcode上是一道困难的题目,其难点就在于贪心的策略,如果在考虑局部的时候想两边兼顾,就会顾此失彼。
\\n那么本题我采用了两次贪心的策略:
\\n\\n
\\n- 一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
\\n- 一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
\\n这样从局部最优推出了全局最优,即:相邻的孩子中,评分高的孩子获得更多的糖果。
\\n其他语言版本
\\n###java
\\n\\nclass Solution {\\n /**\\n 分两个阶段\\n 1、起点下标1 从左往右,只要 右边 比 左边 大,右边的糖果=左边 + 1\\n 2、起点下标 ratings.length - 2 从右往左, 只要左边 比 右边 大,此时 左边的糖果应该 取本身的糖果数(符合比它左边大) 和 右边糖果数 + 1 二者的最大值,这样才符合 它比它左边的大,也比它右边大\\n */\\n public int candy(int[] ratings) {\\n int len = ratings.length;\\n int[] candyVec = new int[len];\\n candyVec[0] = 1;\\n for (int i = 1; i < len; i++) {\\n candyVec[i] = (ratings[i] > ratings[i - 1]) ? candyVec[i - 1] + 1 : 1;\\n }\\n\\n for (int i = len - 2; i >= 0; i--) {\\n if (ratings[i] > ratings[i + 1]) {\\n candyVec[i] = Math.max(candyVec[i], candyVec[i + 1] + 1);\\n }\\n }\\n\\n int ans = 0;\\n for (int num : candyVec) {\\n ans += num;\\n }\\n return ans;\\n }\\n}\\n
###python
\\n\\nclass Solution:\\n def candy(self, ratings: List[int]) -> int:\\n candyVec = [1] * len(ratings)\\n for i in range(1, len(ratings)):\\n if ratings[i] > ratings[i - 1]:\\n candyVec[i] = candyVec[i - 1] + 1\\n for j in range(len(ratings) - 2, -1, -1):\\n if ratings[j] > ratings[j + 1]:\\n candyVec[j] = max(candyVec[j], candyVec[j + 1] + 1)\\n return sum(candyVec)\\n
###go
\\n\\nfunc candy(ratings []int) int {\\n /**先确定一边,再确定另外一边\\n 1.先从左到右,当右边的大于左边的就加1\\n 2.再从右到左,当左边的大于右边的就再加1\\n **/\\n need := make([]int, len(ratings))\\n sum := 0\\n // 初始化(每个人至少一个糖果)\\n for i := 0; i < len(ratings); i++ {\\n need[i] = 1\\n }\\n // 1.先从左到右,当右边的大于左边的就加1\\n for i := 0; i < len(ratings) - 1; i++ {\\n if ratings[i] < ratings[i+1] {\\n need[i+1] = need[i] + 1\\n }\\n }\\n // 2.再从右到左,当左边的大于右边的就右边加1,但要花费糖果最少,所以需要做下判断\\n for i := len(ratings)-1; i > 0; i-- {\\n if ratings[i-1] > ratings[i] {\\n need[i-1] = findMax(need[i-1], need[i]+1)\\n }\\n }\\n //计算总共糖果\\n for i := 0; i < len(ratings); i++ {\\n sum += need[i]\\n }\\n return sum\\n}\\nfunc findMax(num1 int, num2 int) int {\\n if num1 > num2 {\\n return num1\\n }\\n return num2\\n}\\n
###Javascript
\\n\\nvar candy = function(ratings) {\\n let candys = new Array(ratings.length).fill(1)\\n\\n for(let i = 1; i < ratings.length; i++) {\\n if(ratings[i] > ratings[i - 1]) {\\n candys[i] = candys[i - 1] + 1\\n }\\n }\\n\\n for(let i = ratings.length - 2; i >= 0; i--) {\\n if(ratings[i] > ratings[i + 1]) {\\n candys[i] = Math.max(candys[i], candys[i + 1] + 1)\\n }\\n }\\n\\n let count = candys.reduce((a, b) => {\\n return a + b\\n })\\n\\n return count\\n};\\n
###rust
\\n\\npub fn candy(ratings: Vec<i32>) -> i32 {\\n let mut candies = vec![1i32; ratings.len()];\\n for i in 1..ratings.len() {\\n if ratings[i - 1] < ratings[i] {\\n candies[i] = candies[i - 1] + 1;\\n }\\n }\\n\\n for i in (0..ratings.len()-1).rev() {\\n if ratings[i] > ratings[i + 1] {\\n candies[i] = candies[i].max(candies[i + 1] + 1);\\n }\\n }\\n candies.iter().sum()\\n}\\n
###c
\\n\\n#define max(a, b) (((a) > (b)) ? (a) : (b))\\n\\nint *initCandyArr(int size) {\\n int *candyArr = (int*)malloc(sizeof(int) * size);\\n\\n int i;\\n for(i = 0; i < size; ++i)\\n candyArr[i] = 1;\\n\\n return candyArr;\\n}\\n\\nint candy(int* ratings, int ratingsSize){\\n // 初始化数组,每个小孩开始至少有一颗糖\\n int *candyArr = initCandyArr(ratingsSize);\\n\\n int i;\\n // 先判断右边是否比左边评分高。若是,右边孩子的糖果为左边孩子+1(candyArr[i] = candyArr[i - 1] + 1)\\n for(i = 1; i < ratingsSize; ++i) {\\n if(ratings[i] > ratings[i - 1])\\n candyArr[i] = candyArr[i - 1] + 1;\\n }\\n\\n // 再判断左边评分是否比右边高。\\n // 若是,左边孩子糖果为右边孩子糖果+1/自己所持糖果最大值。(若糖果已经比右孩子+1多,则不需要更多糖果)\\n // 举例:ratings为[1, 2, 3, 1]。此时评分为3的孩子在判断右边比左边大后为3,虽然它比最末尾的1(ratings[3])大,但是candyArr[3]为1。所以不必更新candyArr[2]\\n for(i = ratingsSize - 2; i >= 0; --i) {\\n if(ratings[i] > ratings[i + 1])\\n candyArr[i] = max(candyArr[i], candyArr[i + 1] + 1);\\n }\\n\\n // 求出糖果之和\\n int result = 0;\\n for(i = 0; i < ratingsSize; ++i) {\\n result += candyArr[i];\\n }\\n return result;\\n}\\n
###typescript
\\n\\nfunction candy(ratings: number[]): number {\\n const candies: number[] = [];\\n candies[0] = 1;\\n // 保证右边高分孩子一定比左边低分孩子发更多的糖果\\n for (let i = 1, length = ratings.length; i < length; i++) {\\n if (ratings[i] > ratings[i - 1]) {\\n candies[i] = candies[i - 1] + 1;\\n } else {\\n candies[i] = 1;\\n }\\n }\\n // 保证左边高分孩子一定比右边低分孩子发更多的糖果\\n for (let i = ratings.length - 2; i >= 0; i--) {\\n if (ratings[i] > ratings[i + 1]) {\\n candies[i] = Math.max(candies[i], candies[i + 1] + 1);\\n }\\n }\\n return candies.reduce((pre, cur) => pre + cur);\\n};\\n
###scala
\\n\\nobject Solution {\\n def candy(ratings: Array[Int]): Int = {\\n var candyVec = new Array[Int](ratings.length)\\n for (i <- candyVec.indices) candyVec(i) = 1\\n // 从前向后\\n for (i <- 1 until candyVec.length) {\\n if (ratings(i) > ratings(i - 1)) {\\n candyVec(i) = candyVec(i - 1) + 1\\n }\\n }\\n\\n // 从后向前\\n for (i <- (candyVec.length - 2) to 0 by -1) {\\n if (ratings(i) > ratings(i + 1)) {\\n candyVec(i) = math.max(candyVec(i), candyVec(i + 1) + 1)\\n }\\n }\\n\\n candyVec.sum // 求和\\n }\\n}\\n
大家好,我是程序员Carl,如果你还在没有章法的刷题,建议按照代码随想录刷题路线来刷,并提供PDF下载,刷题路线同时也开源在Github上,你会发现详见很晚!
\\n如果感觉题解对你有帮助,不要吝啬给一个👍吧!
\\n","description":"《代码随想录》算法视频公开课:贪心算法,两者兼顾很容易顾此失彼!LeetCode:135.分发糖果,相信结合视频在看本篇题解,更有助于大家对本题的理解。 思路\\n\\n这道题目一定是要确定一边之后,再确定另一边,例如比较每一个孩子的左边,然后再比较右边,如果两边一起考虑一定会顾此失彼。\\n\\n先确定右边评分大于左边的情况(也就是从前向后遍历)\\n\\n此时局部最优:只要右边评分比左边大,右边的孩子就多一个糖果,全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果\\n\\n局部最优可以推出全局最优。\\n\\n如果ratings[i] > ratings[i - 1] 那么[i]的糖…","guid":"https://leetcode.cn/problems/candy//solution/dai-ma-sui-xiang-lu-135-fen-fa-tang-guo-f7ezy","author":"carlsun-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-07-02T07:19:24.722Z","media":[{"url":"https://pic.leetcode.cn/1683275614-Oedkaw-image.png","type":"photo","width":1138,"height":412,"blurhash":"LDSF;LxuRj_3~qRjofs:xut7j[ay"},{"url":"https://pic.leetcode.cn/1683275632-PezogB-image.png","type":"photo","width":1124,"height":776,"blurhash":"LBR:E8_NV@?H.Sw^x]xu-pXSV@ae"},{"url":"https://pic.leetcode.cn/1683275649-QCumLZ-image.png","type":"photo","width":1160,"height":872,"blurhash":"LAS6Md_N%M?b_NRjxaWBx]soRjtR"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题双解:「朴素 BFS」&「双向 BFS(并查集)」","url":"https://leetcode.cn/problems/bus-routes//solution/gong-shui-san-xie-yi-ti-shuang-jie-po-su-1roh","content":"基本分析
\\n为了方便,我们令每个公交站为一个「车站」,由一个「车站」可以进入一条或多条「路线」。
\\n问题为从「起点车站」到「终点车站」,所进入的最少路线为多少。
\\n抽象每个「路线」为一个点,当不同「路线」之间存在「公共车站」则为其增加一条边权为 $1$ 的无向边。
\\n
\\n单向 BFS
\\n由于是在边权为 $1$ 的图上求最短路,我们直接使用
\\nBFS
即可。起始时将「起点车站」所能进入的「路线」进行入队,每次从队列中取出「路线」时,查看该路线是否包含「终点车站」:
\\n\\n
\\n- 包含「终点车站」:返回进入该线路所花费的距离
\\n- 不包含「终点车站」:遍历该路线所包含的车站,将由这些车站所能进入的路线,进行入队
\\n一些细节:由于是求最短路,同一路线重复入队是没有意义的,因此将新路线入队前需要先判断是否曾经入队。
\\n\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n int s, t;\\n int[][] rs;\\n public int numBusesToDestination(int[][] _rs, int _s, int _t) {\\n rs = _rs; s = _s; t = _t;\\n if (s == t) return 0;\\n int ans = bfs();\\n return ans;\\n }\\n int bfs() {\\n // 记录某个车站可以进入的路线\\n Map<Integer, Set<Integer>> map = new HashMap<>();\\n // 队列存的是经过的路线\\n Deque<Integer> d = new ArrayDeque<>();\\n // 哈希表记录的进入该路线所使用的距离\\n Map<Integer, Integer> m = new HashMap<>();\\n int n = rs.length;\\n for (int i = 0; i < n; i++) {\\n for (int station : rs[i]) {\\n // 将从起点可以进入的路线加入队列\\n if (station == s) {\\n d.addLast(i);\\n m.put(i, 1);\\n }\\n Set<Integer> set = map.getOrDefault(station, new HashSet<>());\\n set.add(i);\\n map.put(station, set);\\n }\\n }\\n while (!d.isEmpty()) {\\n // 取出当前所在的路线,与进入该路线所花费的距离\\n int poll = d.pollFirst();\\n int step = m.get(poll);\\n\\n // 遍历该路线所包含的车站\\n for (int station : rs[poll]) {\\n // 如果包含终点,返回进入该路线花费的距离即可\\n if (station == t) return step;\\n\\n // 将由该线路的车站发起的路线,加入队列\\n Set<Integer> lines = map.get(station);\\n if (lines == null) continue;\\n for (int nr : lines) {\\n if (!m.containsKey(nr)) {\\n m.put(nr, step + 1);\\n d.add(nr);\\n }\\n }\\n }\\n }\\n return -1;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令路线的数量为 $n$,车站的数量为 $m$。建图的时间复杂度为 $O(\\\\sum_{i=0}^{n-1} len(rs[i]))$;
\\nBFS
部分每个路线只会入队一次,最坏情况下每个路线都包含所有车站,复杂度为 $O(n * m)$。整体复杂度为 $O(n * m + \\\\sum_{i=0}^{n-1} len(rs[i]))$。- 空间复杂度:$O(n * m)$
\\n
\\n双向 BFS(并查集预处理无解情况)
\\n另外一个做法是使用双向
\\nBFS
。首先建图方式不变,将「起点」和「终点」所能进入的路线分别放入两个方向的队列,如果「遇到公共的路线」或者「当前路线包含了目标位置」,说明找到了最短路径。
\\n另外我们知道,双向
\\nBFS
在无解的情况下不如单向BFS
。因此我们可以先使用「并查集」进行预处理,判断「起点」和「终点」是否连通,如果不联通,直接返回 $-1$,有解才调用双向BFS
。由于使用「并查集」预处理的复杂度与建图是近似的,增加这样的预处理并不会越过我们时空复杂度的上限,因此这样的预处理是有益的。一定程度上可以最大化双向
\\nBFS
减少搜索空间的效益。\\n
代码:
\\n###Java
\\n\\nclass Solution {\\n static int N = (int)1e6+10;\\n static int[] p = new int[N];\\n int find(int x) {\\n if (p[x] != x) p[x] = find(p[x]);\\n return p[x];\\n }\\n void union(int a, int b) {\\n p[find(a)] = p[find(b)];\\n }\\n boolean query(int a, int b) {\\n return find(a) == find(b);\\n }\\n int s, t;\\n int[][] rs;\\n public int numBusesToDestination(int[][] _rs, int _s, int _t) {\\n rs = _rs; s = _s; t = _t;\\n if (s == t) return 0;\\n for (int i = 0; i < N; i++) p[i] = i;\\n for (int[] r : rs) {\\n for (int loc : r) {\\n union(loc, r[0]);\\n }\\n }\\n if (!query(s, t)) return -1;\\n int ans = bfs();\\n return ans;\\n }\\n // 记录某个车站可以进入的路线\\n Map<Integer, Set<Integer>> map = new HashMap<>();\\n int bfs() {\\n Deque<Integer> d1 = new ArrayDeque<>(), d2 = new ArrayDeque<>();\\n Map<Integer, Integer> m1 = new HashMap<>(), m2 = new HashMap<>();\\n \\n int n = rs.length;\\n for (int i = 0; i < n; i++) {\\n for (int station : rs[i]) {\\n // 将从起点可以进入的路线加入正向队列\\n if (station == s) {\\n d1.addLast(i);\\n m1.put(i, 1);\\n }\\n // 将从终点可以进入的路线加入反向队列\\n if (station == t) {\\n d2.addLast(i);\\n m2.put(i, 1);\\n }\\n Set<Integer> set = map.getOrDefault(station, new HashSet<>());\\n set.add(i);\\n map.put(station, set);\\n }\\n }\\n\\n // 如果「起点所发起的路线」和「终点所发起的路线」有交集,直接返回 1\\n Set<Integer> s1 = map.get(s), s2 = map.get(t);\\n Set<Integer> tot = new HashSet<>();\\n tot.addAll(s1);\\n tot.retainAll(s2);\\n if (!tot.isEmpty()) return 1;\\n\\n // 双向 BFS\\n while (!d1.isEmpty() && !d2.isEmpty()) {\\n int res = -1;\\n if (d1.size() <= d2.size()) {\\n res = update(d1, m1, m2);\\n } else {\\n res = update(d2, m2, m1);\\n }\\n if (res != -1) return res;\\n }\\n\\n return 0x3f3f3f3f; // never\\n }\\n int update(Deque<Integer> d, Map<Integer, Integer> cur, Map<Integer, Integer> other) {\\n int m = d.size();\\n while (m-- > 0) {\\n // 取出当前所在的路线,与进入该路线所花费的距离\\n int poll = d.pollFirst();\\n int step = cur.get(poll);\\n\\n // 遍历该路线所包含的车站\\n for (int station : rs[poll]) {\\n // 遍历将由该线路的车站发起的路线\\n Set<Integer> lines = map.get(station);\\n if (lines == null) continue;\\n for (int nr : lines) {\\n if (cur.containsKey(nr)) continue;\\n if (other.containsKey(nr)) return step + other.get(nr);\\n cur.put(nr, step + 1);\\n d.add(nr);\\n }\\n }\\n }\\n return -1;\\n }\\n}\\n
\\n
\\n- 时间复杂度:令路线的数量为 $n$,车站的个数为 $m$。并查集和建图的时间复杂度为 $O(\\\\sum_{i=0}^{n-1} len(rs[i]))$;
\\nBFS
求最短路径的复杂度为 $O(n * m)$。整体复杂度为 $O(n * m + \\\\sum_{i=0}^{n-1} len(rs[i]))$。- 空间复杂度:$O(n * m + \\\\sum_{i=0}^{n-1} len(rs[i]))$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"基本分析 为了方便,我们令每个公交站为一个「车站」,由一个「车站」可以进入一条或多条「路线」。\\n\\n问题为从「起点车站」到「终点车站」,所进入的最少路线为多少。\\n\\n抽象每个「路线」为一个点,当不同「路线」之间存在「公共车站」则为其增加一条边权为 $1$ 的无向边。\\n\\n单向 BFS\\n\\n由于是在边权为 $1$ 的图上求最短路,我们直接使用 BFS 即可。\\n\\n起始时将「起点车站」所能进入的「路线」进行入队,每次从队列中取出「路线」时,查看该路线是否包含「终点车站」:\\n\\n包含「终点车站」:返回进入该线路所花费的距离\\n不包含「终点车站」:遍历该路线所包含的车站…","guid":"https://leetcode.cn/problems/bus-routes//solution/gong-shui-san-xie-yi-ti-shuang-jie-po-su-1roh","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-28T03:29:04.137Z","media":[{"url":"https://pic.leetcode-cn.com/1624848368-HimklT-image.png","type":"photo","width":1098,"height":552},{"url":"https://pic.leetcode-cn.com/1624849722-IqAFXu-image.png","type":"photo","width":1116,"height":558}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"公交路线","url":"https://leetcode.cn/problems/bus-routes//solution/gong-jiao-lu-xian-by-leetcode-solution-yifz","content":"方法一:优化建图 + 广度优先搜索
\\n思路及算法
\\n由于求解的目标是最少乘坐的公交车数量,对于同一辆公交车,乘客可以在其路线中的任意车站间无代价地移动,于是我们可以把公交路线当作点。如果两条公交路线有相同车站,则可以在这两条路线间换乘公交车,那么这两条公交路线之间可视作有一条长度为 $1$ 的边。这样建出的图包含的点数即为公交路线的数量,记作 $n$。
\\n完成了建图后,我们需要先明确新的图的起点和终点,然后使用广度优先搜索,计算出的起点和终点的最短路径,从而得到最少换乘次数。
\\n注意到原本的起点车站和终点车站可能同时位于多条公交路线上,因此在新图上可能有多个起点和终点。对于这种情况,我们初始可以同时入队多个点,并在广度优先搜索结束后检查到各个终点的最短路径,取其最小值才是最少换乘次数。
\\n实际建图时,我们有以下两种方案:
\\n\\n
\\n- 方案一:我们直接枚举左右两端点,检查两点对应的两公交路线是否有公共车站。利用哈希表,我们可以将单次比较的时间复杂度优化到均摊 $O(n)$。
\\n- 方案二:我们遍历所有公交路线,记录每一个车站属于哪些公交路线。然后我们遍历每一个车站,如果有多条公交路线经过该点,则在这些公交路线之间连边。
\\n本题中我们采用方案二,据此还可以直接得到起点和终点在新图中对应的点。
\\n实际代码中,我们使用哈希映射来实时维护「车站所属公交路线列表」。假设当前枚举到公交路线 $i$ 中的车站 $\\\\textit{site}$,此时哈希映射中已记录若干条公交路线经过车站 $\\\\textit{site}$,我们只需要让点 $i$ 与这些点公交路线对应的点相连即可。完成了连线后,我们再将公交路线 $i$ 加入到「车站 $\\\\textit{site}$ 所属公交路线列表」中。
\\n特别地,起点和终点相同时,我们可以直接返回 $0$。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numBusesToDestination(vector<vector<int>>& routes, int source, int target) {\\n if (source == target) {\\n return 0;\\n }\\n\\n int n = routes.size();\\n vector<vector<int>> edge(n, vector<int>(n));\\n unordered_map<int, vector<int>> rec;\\n for (int i = 0; i < n; i++) {\\n for (int site : routes[i]) {\\n for (int j : rec[site]) {\\n edge[i][j] = edge[j][i] = true;\\n }\\n rec[site].push_back(i);\\n }\\n }\\n\\n vector<int> dis(n, -1);\\n queue<int> que;\\n for (int bus : rec[source]) {\\n dis[bus] = 1;\\n que.push(bus);\\n }\\n while (!que.empty()) {\\n int x = que.front();\\n que.pop();\\n for (int y = 0; y < n; y++) {\\n if (edge[x][y] && dis[y] == -1) {\\n dis[y] = dis[x] + 1;\\n que.push(y);\\n }\\n }\\n }\\n\\n int ret = INT_MAX;\\n for (int bus : rec[target]) {\\n if (dis[bus] != -1) {\\n ret = min(ret, dis[bus]);\\n }\\n }\\n return ret == INT_MAX ? -1 : ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int numBusesToDestination(int[][] routes, int source, int target) {\\n if (source == target) {\\n return 0;\\n }\\n\\n int n = routes.length;\\n boolean[][] edge = new boolean[n][n];\\n Map<Integer, List<Integer>> rec = new HashMap<Integer, List<Integer>>();\\n for (int i = 0; i < n; i++) {\\n for (int site : routes[i]) {\\n List<Integer> list = rec.getOrDefault(site, new ArrayList<Integer>());\\n for (int j : list) {\\n edge[i][j] = edge[j][i] = true;\\n }\\n list.add(i);\\n rec.put(site, list);\\n }\\n }\\n\\n int[] dis = new int[n];\\n Arrays.fill(dis, -1);\\n Queue<Integer> que = new LinkedList<Integer>();\\n for (int bus : rec.getOrDefault(source, new ArrayList<Integer>())) {\\n dis[bus] = 1;\\n que.offer(bus);\\n }\\n while (!que.isEmpty()) {\\n int x = que.poll();\\n for (int y = 0; y < n; y++) {\\n if (edge[x][y] && dis[y] == -1) {\\n dis[y] = dis[x] + 1;\\n que.offer(y);\\n }\\n }\\n }\\n\\n int ret = Integer.MAX_VALUE;\\n for (int bus : rec.getOrDefault(target, new ArrayList<Integer>())) {\\n if (dis[bus] != -1) {\\n ret = Math.min(ret, dis[bus]);\\n }\\n }\\n return ret == Integer.MAX_VALUE ? -1 : ret;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int NumBusesToDestination(int[][] routes, int source, int target) {\\n if (source == target) {\\n return 0;\\n }\\n\\n int n = routes.Length;\\n bool[,] edge = new bool[n, n];\\n Dictionary<int, IList<int>> rec = new Dictionary<int, IList<int>>();\\n for (int i = 0; i < n; i++) {\\n foreach (int site in routes[i]) {\\n IList<int> list = new List<int>();\\n if (rec.ContainsKey(site)) {\\n list = rec[site];\\n foreach (int j in list) {\\n edge[i, j] = edge[j, i] = true;\\n }\\n rec[site].Add(i);\\n } else {\\n list.Add(i);\\n rec.Add(site, list);\\n }\\n }\\n }\\n\\n int[] dis = new int[n];\\n Array.Fill(dis, -1);\\n Queue<int> que = new Queue<int>();\\n if (rec.ContainsKey(source)) {\\n foreach (int bus in rec[source]) {\\n dis[bus] = 1;\\n que.Enqueue(bus);\\n }\\n }\\n while (que.Count > 0) {\\n int x = que.Dequeue();\\n for (int y = 0; y < n; y++) {\\n if (edge[x, y] && dis[y] == -1) {\\n dis[y] = dis[x] + 1;\\n que.Enqueue(y);\\n }\\n }\\n }\\n\\n int ret = int.MaxValue;\\n if (rec.ContainsKey(target)) {\\n foreach (int bus in rec[target]) {\\n if (dis[bus] != -1) {\\n ret = Math.Min(ret, dis[bus]);\\n }\\n }\\n }\\n return ret == int.MaxValue ? -1 : ret;\\n }\\n}\\n
###JavaScript
\\n\\nvar numBusesToDestination = function(routes, source, target) {\\n if (source === target) {\\n return 0;\\n }\\n\\n const n = routes.length;\\n const edge = new Array(n).fill(0).map(() => new Array(n).fill(0));\\n const rec = new Map();\\n for (let i = 0; i < n; i++) {\\n for (const site of routes[i]) {\\n const list = (rec.get(site) || []);\\n for (const j of list) {\\n edge[i][j] = edge[j][i] = true;\\n }\\n list.push(i);\\n rec.set(site, list);\\n }\\n }\\n\\n const dis = new Array(n).fill(-1);\\n const que = [];\\n for (const bus of (rec.get(source) || [])) {\\n dis[bus] = 1;\\n que.push(bus);\\n }\\n while (que.length) {\\n const x = que.shift();\\n for (let y = 0; y < n; y++) {\\n if (edge[x][y] && dis[y] === -1) {\\n dis[y] = dis[x] + 1;\\n que.push(y);\\n }\\n }\\n }\\n\\n let ret = Number.MAX_VALUE;\\n for (const bus of (rec.get(target) || [])) {\\n if (dis[bus] !== -1) {\\n ret = Math.min(ret, dis[bus]);\\n }\\n }\\n return ret === Number.MAX_VALUE ? -1 : ret;\\n};\\n
###go
\\n\\nfunc numBusesToDestination(routes [][]int, source, target int) int {\\n if source == target {\\n return 0\\n }\\n\\n n := len(routes)\\n edge := make([][]bool, n)\\n for i := range edge {\\n edge[i] = make([]bool, n)\\n }\\n rec := map[int][]int{}\\n for i, route := range routes {\\n for _, site := range route {\\n for _, j := range rec[site] {\\n edge[i][j] = true\\n edge[j][i] = true\\n }\\n rec[site] = append(rec[site], i)\\n }\\n }\\n\\n dis := make([]int, n)\\n for i := range dis {\\n dis[i] = -1\\n }\\n q := []int{}\\n for _, bus := range rec[source] {\\n dis[bus] = 1\\n q = append(q, bus)\\n }\\n for len(q) > 0 {\\n x := q[0]\\n q = q[1:]\\n for y, b := range edge[x] {\\n if b && dis[y] == -1 {\\n dis[y] = dis[x] + 1\\n q = append(q, y)\\n }\\n }\\n }\\n\\n ans := math.MaxInt32\\n for _, bus := range rec[target] {\\n if dis[bus] != -1 && dis[bus] < ans {\\n ans = dis[bus]\\n }\\n }\\n if ans < math.MaxInt32 {\\n return ans\\n }\\n return -1\\n}\\n
###C
\\n\\nstruct Vector {\\n int* arr;\\n int capacity;\\n int size;\\n};\\n\\nvoid init(struct Vector* vec) {\\n vec->arr = malloc(sizeof(int));\\n vec->capacity = 1;\\n vec->size = 0;\\n}\\n\\nvoid push_back(struct Vector* vec, int val) {\\n if (vec->size == vec->capacity) {\\n vec->capacity <<= 1;\\n vec->arr = realloc(vec->arr, sizeof(int) * vec->capacity);\\n }\\n vec->arr[vec->size++] = val;\\n}\\n\\nint numBusesToDestination(int** routes, int routesSize, int* routesColSize, int source, int target) {\\n if (source == target) {\\n return 0;\\n }\\n\\n int n = routesSize;\\n int edge[n][n];\\n memset(edge, 0, sizeof(edge));\\n struct Vector rec[100001];\\n for (int i = 0; i < 100001; i++) {\\n init(&rec[i]);\\n }\\n for (int i = 0; i < n; i++) {\\n for (int j = 0; j < routesColSize[i]; j++) {\\n int site = routes[i][j];\\n for (int k = 0; k < rec[site].size; k++) {\\n edge[i][rec[site].arr[k]] = edge[rec[site].arr[k]][i] = true;\\n }\\n push_back(&rec[site], i);\\n }\\n }\\n int dis[n];\\n memset(dis, -1, sizeof(dis));\\n int que[n];\\n int left = 0, right = 0;\\n\\n for (int i = 0; i < rec[source].size; i++) {\\n dis[rec[source].arr[i]] = 1;\\n que[right++] = rec[source].arr[i];\\n }\\n while (left < right) {\\n int x = que[left++];\\n for (int y = 0; y < n; y++) {\\n if (edge[x][y] && dis[y] == -1) {\\n dis[y] = dis[x] + 1;\\n que[right++] = y;\\n }\\n }\\n }\\n\\n int ret = INT_MAX;\\n\\n for (int i = 0; i < rec[target].size; i++) {\\n if (dis[rec[target].arr[i]] != -1) {\\n ret = fmin(ret, dis[rec[target].arr[i]]);\\n }\\n }\\n return ret == INT_MAX ? -1 : ret;\\n}\\n
###TypeScript
\\n\\nfunction numBusesToDestination(routes: number[][], source: number, target: number): number {\\n if (source === target) {\\n return 0;\\n }\\n\\n const n = routes.length;\\n const edge: boolean[][] = Array.from({ length: n }, () => Array(n).fill(false));\\n const rec: Map<number, number[]> = new Map();\\n for (let i = 0; i < n; i++) {\\n for (const site of routes[i]) {\\n if (!rec.has(site)) {\\n rec.set(site, []);\\n }\\n for (const j of rec.get(site)!) {\\n edge[i][j] = edge[j][i] = true;\\n }\\n rec.get(site)!.push(i);\\n }\\n }\\n\\n const dis: number[] = Array(n).fill(-1);\\n const queue: number[] = [];\\n for (const bus of rec.get(source) || []) {\\n dis[bus] = 1;\\n queue.push(bus);\\n }\\n\\n while (queue.length > 0) {\\n const x = queue.shift()!;\\n for (let y = 0; y < n; y++) {\\n if (edge[x][y] && dis[y] === -1) {\\n dis[y] = dis[x] + 1;\\n queue.push(y);\\n }\\n }\\n }\\n\\n let ret = Number.MAX_SAFE_INTEGER;\\n for (const bus of rec.get(target) || []) {\\n if (dis[bus] !== -1) {\\n ret = Math.min(ret, dis[bus]);\\n }\\n }\\n\\n return ret === Number.MAX_SAFE_INTEGER ? -1 : ret;\\n};\\n
###Rust
\\n\\nuse std::collections::{HashMap, VecDeque};\\nuse std::i32::MAX as INT_MAX;\\n\\nimpl Solution {\\n pub fn num_buses_to_destination(routes: Vec<Vec<i32>>, source: i32, target: i32) -> i32 {\\n if source == target {\\n return 0;\\n }\\n\\n let n = routes.len();\\n let mut edge = vec![vec![false; n]; n];\\n let mut rec: HashMap<i32, Vec<usize>> = HashMap::new();\\n for i in 0..n {\\n for &site in &routes[i] {\\n if !rec.contains_key(&site) {\\n rec.insert(site, Vec::new());\\n }\\n for &j in rec.get(&site).unwrap() {\\n edge[i][j] = true;\\n edge[j][i] = true;\\n }\\n rec.get_mut(&site).unwrap().push(i);\\n }\\n }\\n\\n let mut dis = vec![-1; n];\\n let mut queue = VecDeque::new();\\n\\n if let Some(buses) = rec.get(&source) {\\n for &bus in buses {\\n dis[bus] = 1;\\n queue.push_back(bus);\\n }\\n }\\n while let Some(x) = queue.pop_front() {\\n for y in 0..n {\\n if edge[x][y] && dis[y] == -1 {\\n dis[y] = dis[x] + 1;\\n queue.push_back(y);\\n }\\n }\\n }\\n\\n let mut ret = INT_MAX;\\n if let Some(buses) = rec.get(&target) {\\n for &bus in buses {\\n if dis[bus] != -1 {\\n ret = ret.min(dis[bus]);\\n }\\n }\\n }\\n\\n if ret == INT_MAX {\\n -1\\n } else {\\n ret\\n }\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func numBusesToDestination(routes: Array<Array<Int64>>, source: Int64, target: Int64): Int64 {\\n if (source == target) {\\n return 0\\n }\\n\\n let n = routes.size\\n var edge = Array<Array<Int64>>(n, { _ => Array<Int>(n, item: 0)})\\n var rec = HashMap<Int64, ArrayList<Int64>>()\\n for (i in 0..n) {\\n for (site in routes[i]) {\\n if (rec.contains(site)) {\\n for (j in rec.get(site).getOrThrow()) {\\n edge[i][j] = 1\\n edge[j][i] = 1\\n }\\n rec.entryView(site).getValue().getOrThrow().append(i)\\n } else {\\n rec.put(site, ArrayList<Int64>([i]))\\n }\\n }\\n }\\n var dis = Array<Int64>(n, item: -1)\\n var que = ArrayList<Int64>()\\n if (rec.contains(source)) {\\n for (bus in rec.get(source).getOrThrow()) {\\n dis[bus] = 1\\n que.append(bus)\\n }\\n }\\n\\n while (!que.isEmpty()) {\\n let x = que[0]\\n que.remove(0)\\n for (y in 0..n) {\\n if (edge[x][y] != 0 && dis[y] == -1) {\\n dis[y] = dis[x] + 1\\n que.append(y)\\n }\\n }\\n }\\n var ret = 1000000007\\n if (rec.contains(target)) {\\n for (bus in rec.get(target).getOrThrow()) {\\n if (dis[bus] != -1) {\\n ret = min(ret, dis[bus])\\n }\\n }\\n }\\n if (ret == 1000000007) {\\n return -1\\n } \\n return ret\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:优化建图 + 广度优先搜索 思路及算法\\n\\n由于求解的目标是最少乘坐的公交车数量,对于同一辆公交车,乘客可以在其路线中的任意车站间无代价地移动,于是我们可以把公交路线当作点。如果两条公交路线有相同车站,则可以在这两条路线间换乘公交车,那么这两条公交路线之间可视作有一条长度为 $1$ 的边。这样建出的图包含的点数即为公交路线的数量,记作 $n$。\\n\\n完成了建图后,我们需要先明确新的图的起点和终点,然后使用广度优先搜索,计算出的起点和终点的最短路径,从而得到最少换乘次数。\\n\\n注意到原本的起点车站和终点车站可能同时位于多条公交路线上…","guid":"https://leetcode.cn/problems/bus-routes//solution/gong-jiao-lu-xian-by-leetcode-solution-yifz","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-27T15:11:58.180Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】BFS 模拟运用题","url":"https://leetcode.cn/problems/snakes-and-ladders//solution/gong-shui-san-xie-bfs-mo-ni-by-ac_oier-woh6","content":"- \\n
\\n时间复杂度:$O(nm+n^2)$,其中 $n$ 是公交路线的数量,$m$ 是车站的总数量。建图时最坏的情况是,所有的公交路线都经过相同的车站,而本题中限制了所有公交路线的车站总数。因此最坏的情况为,每条公交路都经过相同的 $O(\\\\frac{m}{n})$ 个车站,建图的时间复杂度为 $O(\\\\frac{m}{n}) \\\\times O(n) \\\\times O(n) = O(nm)$。同时广度优先搜索的时间复杂度为 $O(n^2)$,因此总时间复杂度为 $O(nm+n^2)$。
\\n- \\n
\\n空间复杂度:$O(n^2+m)$,其中 $n$ 是公交路线的数量,$m$ 是车站的总数量。记录「经过任意车站的公交路线的列表」的空间复杂度为 $O(m)$,使用邻接矩阵存图的空间复杂度为 $O(n^2)$。
\\nBFS
\\n最多有 $20 \\\\times 20$ 个格子,直接使用常规的单向
\\nBFS
进行求解即可。为了方便我们可以按照题目给定的意思,将二维的矩阵「扁平化」为一维的矩阵,然后再按照规则进行
\\nBFS
。代码:
\\n###Java
\\n\\nclass Solution {\\n int n;\\n int[] nums;\\n public int snakesAndLadders(int[][] board) {\\n n = board.length;\\n if (board[0][0] != -1) return -1;\\n nums = new int[n * n + 1];\\n boolean isRight = true;\\n for (int i = n - 1, idx = 1; i >= 0; i--) {\\n for (int j = (isRight ? 0 : n - 1); isRight ? j < n : j >= 0; j += isRight ? 1 : -1) {\\n nums[idx++] = board[i][j];\\n }\\n isRight = !isRight;\\n }\\n int ans = bfs();\\n return ans;\\n }\\n int bfs() {\\n Deque<Integer> d = new ArrayDeque<>();\\n Map<Integer, Integer> m = new HashMap<>();\\n d.addLast(1);\\n m.put(1, 0);\\n while (!d.isEmpty()) {\\n int poll = d.pollFirst();\\n int step = m.get(poll);\\n if (poll == n * n) return step;\\n for (int i = 1; i <= 6; i++) {\\n int np = poll + i;\\n if (np <= 0 || np > n * n) continue;\\n if (nums[np] != -1) np = nums[np];\\n if (m.containsKey(np)) continue;\\n m.put(np, step + 1);\\n d.addLast(np);\\n }\\n }\\n return -1;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n^2)$
\\n- 空间复杂度:$O(n^2)$
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"BFS 最多有 $20 \\\\times 20$ 个格子,直接使用常规的单向 BFS 进行求解即可。\\n\\n为了方便我们可以按照题目给定的意思,将二维的矩阵「扁平化」为一维的矩阵,然后再按照规则进行 BFS。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n int n;\\n int[] nums;\\n public int snakesAndLadders(int[][] board) {\\n n = board.length;\\n if (board[0][0] != -1) return -1;…","guid":"https://leetcode.cn/problems/snakes-and-ladders//solution/gong-shui-san-xie-bfs-mo-ni-by-ac_oier-woh6","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-27T02:08:36.878Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"蛇梯棋","url":"https://leetcode.cn/problems/snakes-and-ladders//solution/she-ti-qi-by-leetcode-solution-w0vl","content":"方法一:广度优先搜索
\\n我们可以将棋盘抽象成一个包含 $N^2$ 个节点的有向图,对于每个节点 $x$,若 $x+i\\\\ (1\\\\le i \\\\le 6)$ 上没有蛇或梯子,则连一条从 $x$ 到 $x+i$ 的有向边;否则记蛇梯的目的地为 $y$,连一条从 $x$ 到 $y$ 的有向边。
\\n如此转换后,原问题等价于在这张有向图上求出从 $1$ 到 $N^2$ 的最短路长度。
\\n对于该问题,我们可以使用广度优先搜索。将节点编号和到达该节点的移动次数作为搜索状态,顺着该节点的出边扩展新状态,直至到达终点 $N^2$,返回此时的移动次数。若无法到达终点则返回 $-1$。
\\n代码实现时,我们可以用一个队列来存储搜索状态,初始时将起点状态 $(1,0)$ 加入队列,表示当前位于起点 $1$,移动次数为 $0$。然后不断取出队首,每次取出队首元素时扩展新状态,即遍历该节点的出边,若出边对应节点未被访问,则将该节点和移动次数加一的结果作为新状态,加入队列。如此循环直至到达终点或队列为空。
\\n此外,我们需要计算出编号在棋盘中的对应行列,以便从 $\\\\textit{board}$ 中得到目的地。设编号为 $\\\\textit{id}$,由于每行有 $n$ 个数字,其位于棋盘从下往上数的第 $\\\\left \\\\lfloor \\\\dfrac{\\\\textit{id}-1}{n} \\\\right \\\\rfloor$ 行,记作 $r$。由于棋盘的每一行会交替方向,若 $r$ 为偶数,则编号方向从左向右,列号为 $(\\\\textit{id}-1) \\\\bmod n$;若 $r$ 为奇数,则编号方向从右向左,列号为 $n-1-((\\\\textit{id}-1) \\\\bmod n)$。
\\n###C++
\\n\\nclass Solution {\\n pair<int, int> id2rc(int id, int n) {\\n int r = (id - 1) / n, c = (id - 1) % n;\\n if (r % 2 == 1) {\\n c = n - 1 - c;\\n }\\n return {n - 1 - r, c};\\n }\\n\\npublic:\\n int snakesAndLadders(vector<vector<int>> &board) {\\n int n = board.size();\\n vector<int> vis(n * n + 1);\\n queue<pair<int, int>> q;\\n q.emplace(1, 0);\\n while (!q.empty()) {\\n auto p = q.front();\\n q.pop();\\n for (int i = 1; i <= 6; ++i) {\\n int nxt = p.first + i;\\n if (nxt > n * n) { // 超出边界\\n break;\\n }\\n auto rc = id2rc(nxt, n); // 得到下一步的行列\\n if (board[rc.first][rc.second] > 0) { // 存在蛇或梯子\\n nxt = board[rc.first][rc.second];\\n }\\n if (nxt == n * n) { // 到达终点\\n return p.second + 1;\\n }\\n if (!vis[nxt]) {\\n vis[nxt] = true;\\n q.emplace(nxt, p.second + 1); // 扩展新状态\\n }\\n }\\n }\\n return -1;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int snakesAndLadders(int[][] board) {\\n int n = board.length;\\n boolean[] vis = new boolean[n * n + 1];\\n Queue<int[]> queue = new LinkedList<int[]>();\\n queue.offer(new int[]{1, 0});\\n while (!queue.isEmpty()) {\\n int[] p = queue.poll();\\n for (int i = 1; i <= 6; ++i) {\\n int nxt = p[0] + i;\\n if (nxt > n * n) { // 超出边界\\n break;\\n }\\n int[] rc = id2rc(nxt, n); // 得到下一步的行列\\n if (board[rc[0]][rc[1]] > 0) { // 存在蛇或梯子\\n nxt = board[rc[0]][rc[1]];\\n }\\n if (nxt == n * n) { // 到达终点\\n return p[1] + 1;\\n }\\n if (!vis[nxt]) {\\n vis[nxt] = true;\\n queue.offer(new int[]{nxt, p[1] + 1}); // 扩展新状态\\n }\\n }\\n }\\n return -1;\\n }\\n\\n public int[] id2rc(int id, int n) {\\n int r = (id - 1) / n, c = (id - 1) % n;\\n if (r % 2 == 1) {\\n c = n - 1 - c;\\n }\\n return new int[]{n - 1 - r, c};\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int SnakesAndLadders(int[][] board) {\\n int n = board.Length;\\n bool[] vis = new bool[n * n + 1];\\n Queue<Tuple<int, int>> queue = new Queue<Tuple<int, int>>();\\n queue.Enqueue(new Tuple<int, int>(1, 0));\\n while (queue.Count > 0) {\\n Tuple<int, int> p = queue.Dequeue();\\n for (int i = 1; i <= 6; ++i) {\\n int nxt = p.Item1 + i;\\n if (nxt > n * n) { // 超出边界\\n break;\\n }\\n Tuple<int, int> rc = Id2Rc(nxt, n); // 得到下一步的行列\\n if (board[rc.Item1][rc.Item2] > 0) { // 存在蛇或梯子\\n nxt = board[rc.Item1][rc.Item2];\\n }\\n if (nxt == n * n) { // 到达终点\\n return p.Item2 + 1;\\n }\\n if (!vis[nxt]) {\\n vis[nxt] = true;\\n queue.Enqueue(new Tuple<int, int>(nxt, p.Item2 + 1)); // 扩展新状态\\n }\\n }\\n }\\n return -1;\\n }\\n\\n public Tuple<int, int> Id2Rc(int id, int n) {\\n int r = (id - 1) / n, c = (id - 1) % n;\\n if (r % 2 == 1) {\\n c = n - 1 - c;\\n }\\n return new Tuple<int, int>(n - 1 - r, c);\\n }\\n}\\n
###go
\\n\\nfunc id2rc(id, n int) (r, c int) {\\n r, c = (id-1)/n, (id-1)%n\\n if r%2 == 1 {\\n c = n - 1 - c\\n }\\n r = n - 1 - r\\n return\\n}\\n\\nfunc snakesAndLadders(board [][]int) int {\\n n := len(board)\\n vis := make([]bool, n*n+1)\\n type pair struct{ id, step int }\\n q := []pair{{1, 0}}\\n for len(q) > 0 {\\n p := q[0]\\n q = q[1:]\\n for i := 1; i <= 6; i++ {\\n nxt := p.id + i\\n if nxt > n*n { // 超出边界\\n break\\n }\\n r, c := id2rc(nxt, n) // 得到下一步的行列\\n if board[r][c] > 0 { // 存在蛇或梯子\\n nxt = board[r][c]\\n }\\n if nxt == n*n { // 到达终点\\n return p.step + 1\\n }\\n if !vis[nxt] {\\n vis[nxt] = true\\n q = append(q, pair{nxt, p.step + 1}) // 扩展新状态\\n }\\n }\\n }\\n return -1\\n}\\n
###JavaScript
\\n\\nvar snakesAndLadders = function(board) {\\n const n = board.length;\\n const vis = new Array(n * n + 1).fill(0);\\n const queue = [[1, 0]];\\n while (queue.length) {\\n const p = queue.shift();\\n for (let i = 1; i <= 6; ++i) {\\n let nxt = p[0] + i;\\n if (nxt > n * n) { // 超出边界\\n break;\\n }\\n const rc = id2rc(nxt, n); // 得到下一步的行列\\n if (board[rc[0]][rc[1]] > 0) { // 存在蛇或梯子\\n nxt = board[rc[0]][rc[1]];\\n }\\n if (nxt === n * n) { // 到达终点\\n return p[1] + 1;\\n }\\n if (!vis[nxt]) {\\n vis[nxt] = true;\\n queue.push([nxt, p[1] + 1]); // 扩展新状态\\n }\\n }\\n }\\n return -1;\\n};\\n\\nconst id2rc = (id, n) => {\\n let r = Math.floor((id - 1) / n), c = (id - 1) % n;\\n if (r % 2 === 1) {\\n c = n - 1 - c;\\n }\\n return [n - 1 - r, c];\\n}\\n
###Python
\\n\\nclass Solution:\\n def snakesAndLadders(self, board: List[List[int]]) -> int:\\n n = len(board)\\n\\n def id2rc(idx: int) -> (int, int):\\n r, c = (idx - 1) // n, (idx - 1) % n\\n if r % 2 == 1:\\n c = n - 1 - c\\n return n - 1 - r, c\\n \\n vis = set()\\n q = deque([(1, 0)])\\n while q:\\n idx, step = q.popleft()\\n for i in range(1, 6 + 1):\\n idx_nxt = idx + i\\n if idx_nxt > n * n: # 超出边界\\n break\\n \\n x_nxt, y_nxt = id2rc(idx_nxt) # 得到下一步的行列\\n if board[x_nxt][y_nxt] > 0: # 存在蛇或梯子\\n idx_nxt = board[x_nxt][y_nxt]\\n if idx_nxt == n * n: # 到达终点\\n return step + 1\\n if idx_nxt not in vis:\\n vis.add(idx_nxt)\\n q.append((idx_nxt, step + 1)) # 扩展新状态\\n \\n return -1\\n
###C
\\n\\nstruct Pair {\\n int first;\\n int second;\\n};\\n\\nstruct Pair id2rc(int id, int n) {\\n int r = (id - 1) / n, c = (id - 1) % n;\\n if (r % 2 == 1) {\\n c = n - 1 - c;\\n }\\n return (struct Pair){n - 1 - r, c};\\n}\\n\\nint snakesAndLadders(int** board, int boardSize, int* boardColSize) {\\n int n = boardSize;\\n int vis[n * n + 1];\\n memset(vis, 0, sizeof(vis));\\n struct Pair que[n * n];\\n int left = 0, right = 0;\\n que[right].first = 1, que[right++].second = 0;\\n while (left < right) {\\n struct Pair p = que[left++];\\n for (int i = 1; i <= 6; ++i) {\\n int nxt = p.first + i;\\n if (nxt > n * n) { // 超出边界\\n break;\\n }\\n struct Pair rc = id2rc(nxt, n); // 得到下一步的行列\\n if (board[rc.first][rc.second] > 0) { // 存在蛇或梯子\\n nxt = board[rc.first][rc.second];\\n }\\n if (nxt == n * n) { // 到达终点\\n return p.second + 1;\\n }\\n if (!vis[nxt]) {\\n vis[nxt] = true;\\n que[right].first = nxt, que[right++].second = p.second + 1; // 扩展新状态\\n }\\n }\\n }\\n return -1;\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:广度优先搜索 我们可以将棋盘抽象成一个包含 $N^2$ 个节点的有向图,对于每个节点 $x$,若 $x+i\\\\ (1\\\\le i \\\\le 6)$ 上没有蛇或梯子,则连一条从 $x$ 到 $x+i$ 的有向边;否则记蛇梯的目的地为 $y$,连一条从 $x$ 到 $y$ 的有向边。\\n\\n如此转换后,原问题等价于在这张有向图上求出从 $1$ 到 $N^2$ 的最短路长度。\\n\\n对于该问题,我们可以使用广度优先搜索。将节点编号和到达该节点的移动次数作为搜索状态,顺着该节点的出边扩展新状态,直至到达终点 $N^2$,返回此时的移动次数。若无法到达终点则返回 $-1$。…","guid":"https://leetcode.cn/problems/snakes-and-ladders//solution/she-ti-qi-by-leetcode-solution-w0vl","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-26T15:39:39.037Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划O(n²) + 数学推导O(1)","url":"https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors//solution/dong-tai-gui-hua-shu-xue-tui-dao-by-tang-1zz1","content":"- \\n
\\n时间复杂度:$O(N^2)$,其中 $N$ 为棋盘 $\\\\textit{board}$ 的边长。棋盘的每个格子至多入队一次,因此时间复杂度为 $O(N^2)$。
\\n- \\n
\\n空间复杂度:$O(N^2)$。我们需要 $O(N^2)$ 的空间来存储每个格子是否被访问过。
\\n解法一:动态规划
\\n
\\n本题比较直观的解法可以采用动态规划,用 dp[i][j] 表示有 i + 1 枚鸡蛋时,验证 j 层楼需要的最少操作次数, 我们可以分开分析 i = 0 和 i = 1 的情况:\\n
\\n- i = 0 即只剩一枚鸡蛋,此时我们需要从 1 层开始逐层验证才能确保获取确切的 f 值,因此对于任意的 j 都有 dp[0][j] = j
\\n- i = 1,对于任意 j ,第一次操作可以选择在 [1, j] 范围内的任一楼层 k,如果鸡蛋在 k 层丢下后破碎,接下来问题转化成 i = 0 时验证 k - 1 层需要的次数,即 dp[0][k - 1], 总操作次数为 dp[0][k - 1] + 1; 如果鸡蛋在 k 层丢下后没碎,接下来问题转化成 i = 1 时验证 j - k 层需要的次数, 即 dp[1][j - k], 总操作次数为 dp[1][j - k] + 1,考虑最坏的情况,两者取最大值则有 dp[1][j] = min(dp[1][j], max(dp[0][k - 1] + 1, dp[1][j - k] + 1))
\\n\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n vector<vector<int>> dp(2, vector<int>(n + 1, INT_MAX));\\n dp[0][0] = dp[1][0] = 0;\\n for (int j = 1; j <= n; ++j) {\\n dp[0][j] = j;\\n }\\n\\n for (int j = 1; j <= n; ++j) {\\n for (int k = 1; k <= j; ++k) {\\n dp[1][j] = min(dp[1][j], max(dp[0][k - 1] + 1, dp[1][j - k] + 1));\\n }\\n }\\n\\n return dp[1][n];\\n }\\n};\\n
显然上面的 dp[0][j] 可以优化掉转为一维dp
\\n\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n vector<int> dp(n + 1, INT_MAX);\\n dp[0] = 0;\\n for (int j = 1; j <= n; ++j) {\\n for (int k = 1; k <= j; ++k) {\\n dp[j] = min(dp[j], max(k, dp[j - k] + 1));\\n }\\n }\\n return dp[n];\\n }\\n};\\n
复杂度分析
\\n\\n
\\n- 时间复杂度: O(n²), n为楼层数
\\n- 空间复杂度: O(n)
\\n
\\n解法二:数学规律
\\n首先分开看2枚鸡蛋的使用:
\\n
\\n第1枚鸡蛋可以视为大范围的覆盖验证,\\n
\\n- 在任意步操作后第1枚鸡蛋仍没有碎,待验证楼层区间为[bottom, n]
\\n- 下一步在任意 i (bottom <= i <= n) 层丢下后,可将验证范围缩小到 [bottom, i - 1] (碎了) 或 [i, n] (没碎)
\\n- 如果一直没碎则可以一直向上覆盖待验证区间,直到 i == n
\\n第2枚鸡蛋视为细粒度逐层验证
\\n\\n
\\n- 第1枚鸡蛋破碎后由第2枚鸡蛋检验 [bottom, i - 1] 区间
\\n- 只能按 bottom, bottom + 1 ... i - 1 顺序逐层验证才能确保获得 f 确切的值
\\n有了上面的鸡蛋操作规范,我们可以反向推导,假设对于 n 层楼计算并返回要确定 f 确切的值的最小操作次数为 M , 我们可以有以下结论:
\\n\\n
\\n- 第一次操作必然选择在 x ≤ M 层,这里使用反证法:当 x > M ,如果第一次操作后鸡蛋破碎,则转入第2枚鸡蛋任务,需要 x - 1 次操作逐层验证,总操作次数为 1 + (x - 1) = x > M ,违背总操作次数为 M 的假设
\\n- 第 k 次操作第1枚鸡蛋的覆盖层数必须小于等于 M - k + 1 ,原因同 1
\\n- 综合(1, 2)的限制,可以得出 M 次操作可以覆盖的最大楼层数量为 Sum = M + (M - 1) + (M - 2) + ... + 1 = (M + 1) * M / 2
\\n- 得到关系:(M + 1) * M / 2 ≥ n,则满足条件的 M 最小值即为最小操作次数,用数学方法求解即可:
\\n\\nclass Solution {\\npublic:\\n int twoEggDrop(int n) {\\n return ceil((-1.0 + sqrt(n * 8 + 1)) / 2);\\n }\\n};\\n
复杂度分析
\\n\\n
\\n","description":"解法一:动态规划 本题比较直观的解法可以采用动态规划,用 dp[i][j] 表示有 i + 1 枚鸡蛋时,验证 j 层楼需要的最少操作次数, 我们可以分开分析 i = 0 和 i = 1 的情况:\\n\\ni = 0 即只剩一枚鸡蛋,此时我们需要从 1 层开始逐层验证才能确保获取确切的 f 值,因此对于任意的 j 都有 dp[0][j] = j\\ni = 1,对于任意 j ,第一次操作可以选择在 [1, j] 范围内的任一楼层 k,如果鸡蛋在 k 层丢下后破碎,接下来问题转化成 i = 0 时验证 k - 1 层需要的次数,即 dp[0][k - 1], 总操作…","guid":"https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors//solution/dong-tai-gui-hua-shu-xue-tui-dao-by-tang-1zz1","author":"tang-bo-hu-dian-feng-xiang","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-05T05:51:13.307Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"来自美国站双100%,时间复杂度O(1)的方法。","url":"https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors//solution/lai-zi-mei-guo-zhan-shuang-100shi-jian-f-y8m0","content":"- 时间复杂度、空间复杂度均为 O(1)
\\n解题思路
\\n大致的想法就是我们要找到让公式
\\n1 + 2 + 3 + 4 + .... + x >= n
成立的最小的x
就是最后找到f
的最小次数。所以也就是首项为1, 公差为1的等差数列的求和公式(x * (x + 1) / 2) >= n
,然后解方程,求出x
。
\\n原因是什么呢,假设我们随机从15层开始往下丢一个鸡蛋,如果鸡蛋碎了,我们接下来要尝试14次,总共要15次才能找到。如果鸡蛋没碎,接下来我们会尝试15 + (15 - 1) = 29
层楼,如果在29楼掉下去碎了,我们接着要尝试16 ~ 28 = 13
次,总共要1 + 1 + 13 = 15
次,如果29楼掉下去没碎,那么我们会尝试15 + (15 - 1) + (15 - 2) = 42
层楼,如果在42楼碎了,那么接下来我们要尝试30 ~ 41 = 12
次,总共要1 + 1 + 1 + 12 = 15
次,
\\n诶,规律出来了,那么什么情况下(当n = ?
)是15次呢,或者说我们怎么知道我们第一次随机往下丢是从15层,还是多少层呢?显然我们知道有15 + 14 + 13 + 12 + 11 + ... + 1 = 120
所以当n = 120
的时候,最小的丢鸡蛋次数就是15次,当n = 100
的时候,同样的,我们要找到使得1 + 2 + 3 + 4 + .... + x >= n
成立的最小的x
,我们求得x = 14
时,总和等于105,x = 13
时,总和等于91,这也意味着,当n
在92 ~ 105
之间,最少的丢鸡蛋的次数都是14次。当n
在106 ~ 120
之间,最少的丢鸡蛋的次数都是15次。
\\n所以,已知n
的大小,其实就是在解方程。
\\n大致就是这样的,Java O(1) | Intuition and Detailed Reasoning | 100% time, 100% space代码
\\n###cpp
\\n\\n","description":"解题思路 大致的想法就是我们要找到让公式1 + 2 + 3 + 4 + .... + x >= n成立的最小的x就是最后找到f的最小次数。所以也就是首项为1, 公差为1的等差数列的求和公式(x * (x + 1) / 2) >= n,然后解方程,求出x。\\n 原因是什么呢,假设我们随机从15层开始往下丢一个鸡蛋,如果鸡蛋碎了,我们接下来要尝试14次,总共要15次才能找到。如果鸡蛋没碎,接下来我们会尝试15 + (15 - 1) = 29层楼,如果在29楼掉下去碎了,我们接着要尝试16 ~ 28 = 13次,总共要1 + 1 + 13 = 15次…","guid":"https://leetcode.cn/problems/egg-drop-with-2-eggs-and-n-floors//solution/lai-zi-mei-guo-zhan-shuang-100shi-jian-f-y8m0","author":"Johnson_1998","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-06-03T04:08:40.903Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"准时到达的列车最小时速","url":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/zhun-shi-dao-da-de-lie-che-zui-xiao-shi-tl9df","content":"class Solution {\\npublic:\\n int twoEggDrop(int n) {\\n return (int)ceil((-1.+sqrt(1+8*n))/2.); \\n }\\n};\\n
方法一:二分查找
\\n提示 $1$
\\n随着火车时速增加,到达终点的时间会减小。
\\n思路与算法
\\n根据 提示 $1$,我们可以用二分的方法寻找到能够按时到达的最小时速。
\\n由于时速必须为正整数,因此二分的下界为 $1$;对于二分的上界,我们考虑 $\\\\textit{hours}$ 为两位小数,因此对于最后一段路程,最小的时限为 $0.01$,那么最高的时速要求即为 $\\\\textit{dist}[i]/0.01 \\\\le 10^7$,同时为二分时速的上界。
\\n在二分过程中,假设当前时速为 $\\\\textit{mid}$,我们计算对应时速下到达终点的时间 $t$,并与 $\\\\textit{hour}$ 比较以判断能否按时到达。
\\n假设 $\\\\textit{dist}$ 的长度为 $n$,我们考虑第 $i$ 段花费的时间。对于前 $n - 1$ 段,我们需要加上等待通向下一个地点的火车的时间,因此花费的时间为 $\\\\lceil \\\\textit{dist}[i] / \\\\textit{mid} \\\\rceil$。而对于最后一段,花费的时间为 $\\\\textit{dist}[n-1] / \\\\textit{mid}$。
\\n显然,前 $n - 1$ 段至少需要 $n - 1$ 时间完成,同时最后一段的花费时间必定为正数。因此如果时限 $\\\\textit{hour} \\\\le n - 1$,那么显然无法完成,此时应返回 $-1$。而只要 $\\\\textit{hour} > n - 1$,那么一定存在符合要求的时速。
\\n细节
\\n在代码实现中,为了避免浮点数造成的潜在误差,我们需要转化为整数之间的比较。
\\n假设当前时速为 $\\\\textit{mid}$,前 $n - 1$ 段花费的时间为 $t$,那么如果能够准时到达终点,必定有:
\\n$$
\\n
\\nt + \\\\frac{\\\\textit{dist}[n-1]}{\\\\textit{mid}} \\\\le \\\\textit{hour}.
\\n$$首先,考虑不等式左边,$t$ 为整数,但 $\\\\textit{dist}[n-1]/\\\\textit{mid}$ 为分数,因此我们需要在不等式两边同时乘 $\\\\textit{mid}$,即可将不等式左边转化为整数:
\\n$$
\\n
\\n\\\\textit{mid}\\\\cdot t + \\\\textit{dist}[n-1] \\\\le \\\\textit{mid}\\\\cdot\\\\textit{hour}.
\\n$$其次,考虑不等式右边,由于时限 $\\\\textit{hour}$ 为两位小数,因此我们引入 $\\\\textit{hr} = 100 \\\\textit{hour}$ 以将其转为整数,并在不等式两边同时乘 $100$:
\\n$$
\\n
\\n100(\\\\textit{mid}\\\\cdot t + \\\\textit{dist}[n-1]) \\\\le \\\\textit{mid}\\\\cdot\\\\textit{hr}.
\\n$$此时,不等式两边均为整数。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minSpeedOnTime(vector<int>& dist, double hour) {\\n int n = dist.size();\\n // 将 hour 乘 100 以转为整数\\n long long hr = llround(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100){\\n return -1;\\n }\\n // 二分\\n int l = 1;\\n int r = 1e7;\\n while (l < r){\\n int mid = l + (r - l) / 2;\\n // 判断当前时速是否满足时限\\n long long t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (int i = 0; i < n - 1; ++i){\\n t += (dist[i] - 1) / mid + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n-1];\\n if (t * 100 <= hr * mid){ // 通分以转化为整数比较\\n r = mid;\\n }\\n else{\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int minSpeedOnTime(int[] dist, double hour) {\\n int n = dist.length;\\n // 将 hour 乘 100 以转为整数\\n long hr = Math.round(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n int l = 1;\\n int r = 10000000;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n // 判断当前时速是否满足时限\\n long t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (int i = 0; i < n - 1; ++i) {\\n t += (dist[i] - 1) / mid + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n - 1];\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinSpeedOnTime(int[] dist, double hour) {\\n int n = dist.Length;\\n // 将 hour 乘 100 以转为整数\\n long hr = (long)Math.Round(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n int l = 1;\\n int r = 10000000;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n // 判断当前时速是否满足时限\\n long t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (int i = 0; i < n - 1; ++i) {\\n t += (dist[i] - 1) / mid + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n - 1];\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n }\\n}\\n
###Go
\\n\\nfunc minSpeedOnTime(dist []int, hour float64) int {\\n n := len(dist)\\n // 将 hour 乘 100 以转为整数\\n hr := int(math.Round(hour * 100))\\n // 时间必须要大于路程段数减 1\\n if hr <= (n - 1) * 100 {\\n return -1\\n }\\n // 二分\\n l, r := 1, 10000000\\n for l < r {\\n mid := l + (r - l) / 2\\n // 判断当前时速是否满足时限\\n t := 0\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for i := 0; i < n - 1; i++ {\\n t += (dist[i] - 1) / mid + 1\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid\\n t += dist[n - 1]\\n if t * 100 <= hr * mid { // 通分以转化为整数比较\\n r = mid\\n } else {\\n l = mid + 1\\n }\\n }\\n return l // 满足条件的最小时速\\n}\\n
###Python
\\n\\nclass Solution:\\n def minSpeedOnTime(self, dist: List[int], hour: float) -> int:\\n n = len(dist)\\n hr = round(hour * 100)\\n # 时间必须要大于路程段数减 1\\n if hr <= 100 * (n - 1):\\n return -1\\n # 判断当前时速是否满足时限\\n def check(speed: int) -> bool:\\n t = 0\\n # 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for i in range(n - 1):\\n t += (dist[i] - 1) // speed + 1\\n # 最后一段贡献的时间: dist[n-1] / mid\\n t *= speed\\n t += dist[-1]\\n return t * 100 <= hr * speed # 通分以转化为整数比较\\n \\n # 二分\\n l, r = 1, 10 ** 7\\n while l < r:\\n mid = l + (r - l) // 2\\n if check(mid):\\n r = mid\\n else:\\n l = mid + 1\\n return l # 满足条件的最小时速\\n
###C
\\n\\nint minSpeedOnTime(int* dist, int distSize, double hour) {\\n int n = distSize;\\n // 将 hour 乘 100 以转为整数\\n long long hr = llround(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n int l = 1;\\n int r = 10000000;\\n while (l < r) {\\n int mid = l + (r - l) / 2;\\n // 判断当前时速是否满足时限\\n long long t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (int i = 0; i < n - 1; ++i) {\\n t += (dist[i] - 1) / mid + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n - 1];\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n}\\n
###JavaScript
\\n\\nvar minSpeedOnTime = function(dist, hour) {\\n const n = dist.length;\\n // 将 hour 乘 100 以转为整数\\n const hr = Math.round(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n let l = 1;\\n let r = 10000000;\\n while (l < r) {\\n const mid = l + Math.floor((r - l) / 2);\\n // 判断当前时速是否满足时限\\n let t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (let i = 0; i < n - 1; ++i) {\\n t += Math.floor((dist[i] - 1) / mid) + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n - 1];\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n};\\n
###TypeScript
\\n\\nfunction minSpeedOnTime(dist: number[], hour: number): number {\\n const n = dist.length;\\n // 将 hour 乘 100 以转为整数\\n const hr = Math.round(hour * 100);\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n let l = 1;\\n let r = 10000000;\\n while (l < r) {\\n const mid = l + Math.floor((r - l) / 2);\\n // 判断当前时速是否满足时限\\n let t = 0;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for (let i = 0; i < n - 1; ++i) {\\n t += Math.floor((dist[i] - 1) / mid) + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid;\\n t += dist[n - 1];\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return l; // 满足条件的最小时速\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn min_speed_on_time(dist: Vec<i32>, hour: f64) -> i32 {\\n let n = dist.len();\\n // 将 hour 乘 100 以转为整数\\n let hr = (hour * 100.0).round() as i64;\\n // 时间必须要大于路程段数减 1\\n if hr <= ((n as i64 - 1) * 100) {\\n return -1;\\n }\\n // 二分\\n let mut l = 1;\\n let mut r = 10_000_000;\\n while l < r {\\n let mid = l + (r - l) / 2;\\n // 判断当前时速是否满足时限\\n let mut t = 0i64;\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for i in 0..n - 1 {\\n t += (dist[i] as i64 - 1) / mid as i64 + 1;\\n }\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid as i64;\\n t += dist[n - 1] as i64;\\n if t * 100 <= hr * mid as i64 { // 通分以转化为整数比较\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n l // 满足条件的最小时速\\n }\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n func minSpeedOnTime(dist: Array<Int64>, hour: Float64): Int64 {\\n let n = dist.size\\n // 将 hour 乘 100 以转为整数\\n let eps = 1e-9\\n let hr = Int64((hour + eps) * 100.0)\\n // 时间必须要大于路程段数减 1\\n if (hr <= (n - 1) * 100) {\\n return -1\\n }\\n // 二分\\n var l = 1\\n var r = 10000000\\n while (l < r) {\\n let mid = l + (r - l) / 2\\n // 判断当前时速是否满足时限\\n var t = 0\\n // 前 n-1 段中第 i 段贡献的时间: floor(dist[i] / mid)\\n for(i in 0..(n - 1)) {\\n t += (dist[i] - 1) / mid + 1\\n }\\n println(\\"mid = ${mid}\\")\\n // 最后一段贡献的时间: dist[n-1] / mid\\n t *= mid\\n t += dist[n - 1]\\n if (t * 100 <= hr * mid) { // 通分以转化为整数比较\\n r = mid\\n } else {\\n l = mid + 1\\n }\\n }\\n return l // 满足条件的最小时速\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二分查找 提示 $1$\\n\\n随着火车时速增加,到达终点的时间会减小。\\n\\n思路与算法\\n\\n根据 提示 $1$,我们可以用二分的方法寻找到能够按时到达的最小时速。\\n\\n由于时速必须为正整数,因此二分的下界为 $1$;对于二分的上界,我们考虑 $\\\\textit{hours}$ 为两位小数,因此对于最后一段路程,最小的时限为 $0.01$,那么最高的时速要求即为 $\\\\textit{dist}[i]/0.01 \\\\le 10^7$,同时为二分时速的上界。\\n\\n在二分过程中,假设当前时速为 $\\\\textit{mid}$,我们计算对应时速下到达终点的时间 $t$,并与…","guid":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/zhun-shi-dao-da-de-lie-che-zui-xiao-shi-tl9df","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-23T06:28:03.368Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二分答案,避免浮点误差的写法(Python/Java/C++/C/Go/JS/Rust)","url":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/bi-mian-fu-dian-yun-suan-de-xie-fa-by-en-9fc6","content":"- \\n
\\n时间复杂度:$O(n\\\\log(C))$,其中 $n$ 为 $\\\\textit{dist}$ 的长度,$C$ 为二分的上下界之差。每一次二分都需要 $O(n)$ 的时间计算花费的总时间。
\\n- \\n
\\n空间复杂度:$O(1)$,我们只使用了常数个变量。
\\n思路
\\n首先,由于除了最后一趟列车,前面的每趟列车至少花费 $1$ 小时(算上等待时间),且最后一趟列车花费的时间严格大于 $0$,因此 $\\\\textit{hour}$ 必须严格大于 $n-1$。若不满足则返回 $-1$。
\\n由于时速越大,花费的时间越少,有单调性,可以二分时速 $v$。关于二分算法的原理,请看【基础算法精讲 04】。
\\n现在问题变成:
\\n\\n
\\n- 已知时速 $v$,计算花费的总时间是否 $\\\\le \\\\textit{hour}$。
\\n除了最后一趟列车,前面每趟列车花费的时间为 $\\\\left\\\\lceil \\\\dfrac{\\\\textit{dist}[i]}{v} \\\\right\\\\rceil$。把前 $n-1$ 趟列车的时间之和记为 $t$。
\\n$t$ 加上最后一趟列车的用时即为总时间,需要满足
\\n$$
\\n
\\nt+\\\\dfrac{\\\\textit{dist}[n-1]}{v}\\\\le\\\\textit{hour}
\\n$$即
\\n$$
\\n
\\nt\\\\cdot v+\\\\textit{dist}[n-1]\\\\le\\\\textit{hour}\\\\cdot v
\\n$$由于 $\\\\textit{hour}$ 至多有两位小数,不妨将其乘上 $100$,得到整数 $\\\\textit{h}_{100}$。上式两边同乘 $100$ 得
\\n$$
\\n
\\n(t\\\\cdot v+\\\\textit{dist}[n-1])\\\\cdot 100\\\\le\\\\textit{h}_{100}\\\\cdot v
\\n$$这样就和浮点数说拜拜了。
\\n细节
\\n1)
\\n为了简化二分边界的计算,我们可以先特判 $\\\\textit{hour}\\\\le n$ 的情况。
\\n这种情况,除了最后一趟列车,前面的每趟列车一定都花费恰好 $1$ 小时(算上等待时间)。所以时速至少是 $\\\\textit{dist}[0]$ 到 $\\\\textit{dist}[n-2]$ 的最大值。
\\n留给最后一趟列车的时间是 $\\\\textit{hour} - (n-1)$ 小时,那么有
\\n$$
\\n
\\n(\\\\textit{hour} - (n-1))\\\\cdot v \\\\ge \\\\textit{dist}[n-1]
\\n$$即
\\n$$
\\n
\\n(h_{100} - (n-1)\\\\cdot 100)\\\\cdot v \\\\ge \\\\textit{dist}[n-1]\\\\cdot 100
\\n$$解得
\\n$$
\\n
\\nv \\\\ge \\\\left\\\\lceil\\\\dfrac{\\\\textit{dist}[n-1]\\\\cdot 100}{h_{100} - (n-1)\\\\cdot 100}\\\\right\\\\rceil\\\\ge \\\\textit{dist}[n-1]
\\n$$\\n\\n这里注明 $\\\\ge \\\\textit{dist}[n-1]$ 是想说时速至少是 $\\\\max(\\\\textit{dist})$,我们可以取整个 $\\\\textit{dist}$ 数组的最大值,而不是 $\\\\textit{dist}[0]$ 到 $\\\\textit{dist}[n-2]$ 的最大值。
\\n综上所述,当 $\\\\textit{hour}\\\\le n$ 时,$v$ 的最小值为
\\n$$
\\n
\\n\\\\max\\\\left{ \\\\max(\\\\textit{dist}), \\\\left\\\\lceil\\\\dfrac{\\\\textit{dist}[n-1]\\\\cdot 100}{h_{100} - (n-1)\\\\cdot 100}\\\\right\\\\rceil \\\\right}
\\n$$2)
\\n下面代码采用开区间二分,这仅仅是二分的一种写法,使用闭区间或者半闭半开区间都是可以的。
\\n\\n
\\n- 开区间左端点初始值:$0$。时速为 $0$,一定无法到达终点。
\\n- 开区间右端点初始值:$\\\\max(\\\\textit{dist})$。至多花费 $n$ 小时。由于我们前面特判了 $\\\\textit{hour}\\\\le n$ 的情况,所以这里 $v=\\\\max(\\\\textit{dist})$ 是一定可以到达终点的。
\\n3)
\\n关于上取整的计算,当 $a$ 和 $b$ 均为正整数时,我们有
\\n$$
\\n
\\n\\\\left\\\\lceil\\\\dfrac{a}{b}\\\\right\\\\rceil = \\\\left\\\\lfloor\\\\dfrac{a-1}{b}\\\\right\\\\rfloor + 1
\\n$$讨论 $a$ 被 $b$ 整除,和不被 $b$ 整除两种情况,可以证明上式的正确性。
\\n4)
\\n力扣有多台评测机,如果你发现运行时间长,可能是运行在比较慢的那台机子上,可以尝试多提交几次。
\\n\\nclass Solution:\\n def minSpeedOnTime(self, dist: List[int], hour: float) -> int:\\n n = len(dist)\\n h100 = round(hour * 100) # 下面不会用到任何浮点数\\n delta = h100 - (n - 1) * 100\\n if delta <= 0: # 无法到达终点\\n return -1\\n\\n max_dist = max(dist)\\n if h100 <= n * 100: # 特判\\n # 见题解中的公式\\n return max(max_dist, (dist[-1] * 100 - 1) // delta + 1)\\n\\n def check(v: int) -> bool:\\n t = n - 1 # n-1 个上取整中的 +1 先提出来\\n for d in dist[:-1]:\\n t += (d - 1) // v\\n return (t * v + dist[-1]) * 100 <= h100 * v\\n\\n left = 0\\n right = max_dist\\n while left + 1 < right:\\n mid = (left + right) // 2\\n if check(mid):\\n right = mid\\n else:\\n left = mid\\n return right\\n
\\nclass Solution:\\n def minSpeedOnTime(self, dist: List[int], hour: float) -> int:\\n n = len(dist)\\n h100 = round(hour * 100) # 下面不会用到任何浮点数\\n delta = h100 - (n - 1) * 100\\n if delta <= 0: # 无法到达终点\\n return -1\\n\\n max_dist = max(dist)\\n if h100 <= n * 100: # 特判\\n # 见题解中的公式\\n return max(max_dist, (dist[-1] * 100 - 1) // delta + 1)\\n\\n def check(v: int) -> bool:\\n t = n - 1 # n-1 个上取整中的 +1 先提出来\\n for d in dist[:-1]:\\n t += (d - 1) // v\\n return (t * v + dist[-1]) * 100 <= h100 * v\\n\\n return bisect_left(range(max_dist), True, 1, key=check)\\n
\\nclass Solution {\\n public int minSpeedOnTime(int[] dist, double hour) {\\n int n = dist.length;\\n long h100 = Math.round(hour * 100); // 下面不会用到任何浮点数\\n long delta = h100 - (n - 1) * 100;\\n if (delta <= 0) { // 无法到达终点\\n return -1;\\n }\\n\\n int maxDist = 0;\\n for (int d : dist) {\\n maxDist = Math.max(maxDist, d);\\n }\\n if (h100 <= n * 100) { // 特判\\n // 见题解中的公式\\n return Math.max(maxDist, (int) ((dist[n - 1] * 100 - 1) / delta + 1));\\n }\\n\\n int left = 0;\\n int right = maxDist;\\n while (left + 1 < right) {\\n int mid = (left + right) >>> 1;\\n if (check(mid, dist, h100)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n }\\n\\n private boolean check(int v, int[] dist, long h100) {\\n int n = dist.length;\\n long t = 0;\\n for (int i = 0; i < n - 1; i++) {\\n t += (dist[i] - 1) / v + 1;\\n }\\n return (t * v + dist[n - 1]) * 100 <= h100 * v;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int minSpeedOnTime(vector<int>& dist, double hour) {\\n int n = dist.size();\\n long long h100 = round(hour * 100); // 下面不会用到任何浮点数\\n long long delta = h100 - (n - 1) * 100;\\n if (delta <= 0) { // 无法到达终点\\n return -1;\\n }\\n\\n int max_dist = ranges::max(dist);\\n if (h100 <= n * 100) { // 特判\\n // 见题解中的公式\\n return max(max_dist, (int) ((dist.back() * 100 - 1) / delta + 1));\\n }\\n\\n auto check = [&](int v) -> bool {\\n long long t = 0;\\n for (int i = 0; i < n - 1; i++) {\\n t += (dist[i] - 1) / v + 1;\\n }\\n return (t * v + dist.back()) * 100 <= h100 * v;\\n };\\n\\n int left = 0, right = max_dist;\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n (check(mid) ? right : left) = mid;\\n }\\n return right;\\n }\\n};\\n
\\n#define MAX(a, b) ((b) > (a) ? (b) : (a))\\n\\nint check(int v, int* dist, int n, long long h100) {\\n long long t = 0;\\n for (int i = 0; i < n - 1; i++) {\\n t += (dist[i] - 1) / v + 1;\\n }\\n return (t * v + dist[n - 1]) * 100 <= h100 * v;\\n}\\n\\nint minSpeedOnTime(int* dist, int n, double hour) {\\n long long h100 = round(hour * 100); // 下面不会用到任何浮点数\\n long long delta = h100 - (n - 1) * 100;\\n if (delta <= 0) { // 无法到达终点\\n return -1;\\n }\\n\\n int max_dist = 0;\\n for (int i = 0; i < n; i++) {\\n max_dist = MAX(max_dist, dist[i]);\\n }\\n if (h100 <= n * 100) { // 特判\\n // 见题解中的公式\\n return MAX(max_dist, (int) ((dist[n - 1] * 100 - 1) / delta + 1));\\n }\\n\\n int left = 0, right = max_dist;\\n while (left + 1 < right) {\\n int mid = (left + right) / 2;\\n if (check(mid, dist, n, h100)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n}\\n
\\nfunc minSpeedOnTime(dist []int, hour float64) int {\\n n := len(dist)\\n h100 := int(math.Round(hour * 100)) // 下面不会用到任何浮点数\\n delta := h100 - (n-1)*100\\n if delta <= 0 { // 无法到达终点\\n return -1\\n }\\n\\n maxDist := slices.Max(dist)\\n if h100 <= n*100 { // 特判\\n // 见题解中的公式\\n return max(maxDist, (dist[n-1]*100-1)/delta+1)\\n }\\n\\n return 1 + sort.Search(maxDist-1, func(v int) bool {\\n v++\\n t := 0\\n for _, d := range dist[:n-1] {\\n t += (d-1)/v + 1\\n }\\n return (t*v+dist[n-1])*100 <= h100*v\\n })\\n}\\n
\\nvar minSpeedOnTime = function(dist, hour) {\\n const n = dist.length;\\n const h100 = Math.round(hour * 100);\\n const delta = h100 - (n - 1) * 100;\\n if (delta <= 0) { // 无法到达终点\\n return -1;\\n }\\n\\n const maxDist = Math.max(...dist);\\n if (h100 <= n * 100) { // 特判\\n // 见题解中的公式\\n return Math.max(maxDist, Math.ceil(dist[n - 1] * 100 / delta));\\n }\\n\\n function check(v) {\\n let t = 0;\\n for (let i = 0; i < n - 1; i++) {\\n t += Math.ceil(dist[i] / v);\\n }\\n return (t * v + dist[n - 1]) * 100 <= h100 * v;\\n }\\n\\n let left = 0, right = maxDist;\\n while (left + 1 < right) {\\n const mid = Math.floor((left + right) / 2);\\n if (check(mid)) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n return right;\\n};\\n
\\nimpl Solution {\\n pub fn min_speed_on_time(dist: Vec<i32>, hour: f64) -> i32 {\\n let n = dist.len();\\n let h100 = (hour * 100.0).round() as i64; // 下面不会用到任何浮点数\\n let delta = h100 - (n as i64 - 1) * 100;\\n if delta <= 0 { // 无法到达终点\\n return -1;\\n }\\n\\n let max_dist = *dist.iter().max().unwrap();\\n if h100 <= n as i64 * 100 { // 特判\\n // 见题解中的公式\\n return max_dist.max(((dist[n - 1] * 100 - 1) as i64 / delta) as i32 + 1);\\n }\\n\\n let check = |v: i32| -> bool {\\n let mut t = 0i64;\\n for &d in &dist[..n - 1] {\\n t += ((d - 1) / v + 1) as i64;\\n }\\n (t * v as i64 + dist[n - 1] as i64) * 100 <= h100 * v as i64\\n };\\n\\n let mut left = 0;\\n let mut right = max_dist;\\n while left + 1 < right {\\n let mid = (left + right) / 2;\\n if check(mid) {\\n right = mid;\\n } else {\\n left = mid;\\n }\\n }\\n right\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}(n\\\\log U)$,其中 $n$ 是 $\\\\textit{dist}$ 的长度,$U=\\\\max(\\\\textit{dist})$。
\\n- 空间复杂度:$\\\\mathcal{O}(1)$。
\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/最短路/最小生成树/二分图/基环树/欧拉路径)
\\n- 动态规划(入门/背包/状态机/划分/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与一般树(前后指针/快慢指针/DFS/BFS/直径/LCA)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"思路 首先,由于除了最后一趟列车,前面的每趟列车至少花费 $1$ 小时(算上等待时间),且最后一趟列车花费的时间严格大于 $0$,因此 $\\\\textit{hour}$ 必须严格大于 $n-1$。若不满足则返回 $-1$。\\n\\n由于时速越大,花费的时间越少,有单调性,可以二分时速 $v$。关于二分算法的原理,请看【基础算法精讲 04】。\\n\\n现在问题变成:\\n\\n已知时速 $v$,计算花费的总时间是否 $\\\\le \\\\textit{hour}$。\\n\\n除了最后一趟列车,前面每趟列车花费的时间为 $\\\\left\\\\lceil \\\\dfrac{\\\\textit{dist}[i]}{v}…","guid":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/bi-mian-fu-dian-yun-suan-de-xie-fa-by-en-9fc6","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-23T04:22:22.078Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java 二分法,逐行注释(101ms,52.8MB)","url":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/java-er-fen-fa-zhu-xing-zhu-shi-101ms528-zab8","content":"解题思路
\\n首先判断特殊情况:当所需时间向上取整后仍然小于
\\ndist
长度,那么注定到不了,就直接返回false
。
\\n然后进行二分搜索,范围是[1, Integer.MAX_VALUE]
。代码
\\n###java
\\n\\n","description":"解题思路 首先判断特殊情况:当所需时间向上取整后仍然小于 dist 长度,那么注定到不了,就直接返回 false。\\n 然后进行二分搜索,范围是 [1, Integer.MAX_VALUE]。\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int minSpeedOnTime(int[] dist, double hour) {\\n if (dist.length > Math.ceil(hour)) return -1;\\n // 搜索边界\\n int left = 1, right…","guid":"https://leetcode.cn/problems/minimum-speed-to-arrive-on-time//solution/java-er-fen-fa-zhu-xing-zhu-shi-101ms528-zab8","author":"hxz1998","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-23T04:11:35.787Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"找出所有子集的异或总和再求和","url":"https://leetcode.cn/problems/sum-of-all-subset-xor-totals//solution/sum-of-all-subset-xor-totals-by-leetcode-o5aa","content":"class Solution {\\n public int minSpeedOnTime(int[] dist, double hour) {\\n if (dist.length > Math.ceil(hour)) return -1;\\n // 搜索边界\\n int left = 1, right = Integer.MAX_VALUE;\\n while (left < right) {\\n int mid = left + (right - left) / 2;\\n // 如果以 mid 速度可达,那么就尝试减小速度\\n if (check(dist, hour, mid)) right = mid;\\n // 否则就需要加了\\n else left = mid + 1;\\n }\\n return left;\\n }\\n\\n private boolean check(int[] dist, double hour, int speed) {\\n double cnt = 0.0;\\n // 对除了最后一个站点以外的时间进行向上取整累加\\n for (int i = 0; i < dist.length - 1; ++i) {\\n // 除法的向上取整\\n cnt += (dist[i] + speed - 1) / speed;\\n }\\n // 加上最后一个站点所需的时间\\n cnt += (double) dist[dist.length - 1] / speed;\\n return cnt <= hour;\\n }\\n}\\n
方法一:递归法枚举子集
\\n思路与算法
\\n我们用函数 $\\\\textit{dfs}(\\\\textit{val}, \\\\textit{idx})$ 来递归枚举数组 $\\\\textit{nums}$ 的子集。其中 $\\\\textit{val}$ 代表当前选取部分的异或值,$\\\\textit{idx}$ 代表递归的当前位置。
\\n我们用 $n$ 来表示 $\\\\textit{nums}$ 的长度。在进入 $\\\\textit{dfs}(\\\\textit{val}, \\\\textit{idx})$ 时,数组中 $[0,\\\\textit{idx} - 1]$ 部分的选取情况是已经确定的,而 $[\\\\textit{idx}, n)$ 部分的选取情况还未确定。我们需要确定 $\\\\textit{idx}$ 位置的选取情况,然后求解子问题 $\\\\textit{dfs}(\\\\textit{val\'}, \\\\textit{idx} + 1)$。
\\n此时选取情况有两种:
\\n\\n
\\n- \\n
\\n选取,此时 $\\\\textit{val\'} = \\\\textit{val} \\\\oplus \\\\textit{nums}[\\\\textit{idx}]$,其中 $\\\\oplus$ 代表异或运算;
\\n- \\n
\\n不选取,此时 $\\\\textit{val\'} = \\\\textit{val}$。
\\n当 $\\\\textit{idx} = n$ 时,递归结束。与此同时,我们维护这些子集异或总和 $\\\\textit{val}$ 的和。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int res;\\n int n;\\n \\n void dfs(int val, int idx, vector<int>& nums){\\n if (idx == n){\\n // 终止递归\\n res += val;\\n return;\\n }\\n // 考虑选择当前数字\\n dfs(val ^ nums[idx], idx + 1, nums);\\n // 考虑不选择当前数字\\n dfs(val, idx + 1, nums);\\n }\\n \\n int subsetXORSum(vector<int>& nums) {\\n res = 0;\\n n = nums.size();\\n dfs(0, 0, nums);\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def subsetXORSum(self, nums: List[int]) -> int:\\n res = 0\\n n = len(nums)\\n def dfs(val, idx):\\n nonlocal res\\n if idx == n:\\n # 终止递归\\n res += val\\n return\\n # 考虑选择当前数字\\n dfs(val ^ nums[idx], idx + 1)\\n # 考虑不选择当前数字\\n dfs(val, idx + 1)\\n \\n dfs(0, 0)\\n return res\\n
###Java
\\n\\nclass Solution {\\n int res;\\n int n;\\n\\n // 深度优先搜索\\n void dfs(int val, int idx, int[] nums) {\\n if (idx == n) {\\n // 终止递归\\n res += val;\\n return;\\n }\\n // 考虑选择当前数字\\n dfs(val ^ nums[idx], idx + 1, nums);\\n // 考虑不选择当前数字\\n dfs(val, idx + 1, nums);\\n }\\n\\n public int subsetXORSum(int[] nums) {\\n res = 0;\\n n = nums.length;\\n dfs(0, 0, nums);\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n int res;\\n int n;\\n\\n // 深度优先搜索\\n void Dfs(int val, int idx, int[] nums) {\\n if (idx == n) {\\n // 终止递归\\n res += val;\\n return;\\n }\\n // 考虑选择当前数字\\n Dfs(val ^ nums[idx], idx + 1, nums);\\n // 考虑不选择当前数字\\n Dfs(val, idx + 1, nums);\\n }\\n\\n public int SubsetXORSum(int[] nums) {\\n res = 0;\\n n = nums.Length;\\n Dfs(0, 0, nums);\\n return res;\\n }\\n}\\n
###Go
\\n\\nfunc subsetXORSum(nums []int) int {\\n return dfs(0, 0, nums)\\n}\\n\\nfunc dfs(val, idx int, nums []int) int {\\n if idx == len(nums) {\\n // 终止递归\\n return val\\n }\\n // 考虑选择当前数字, 考虑不选择当前数字\\n return dfs(val ^ nums[idx], idx + 1, nums) + dfs(val, idx + 1, nums)\\n}\\n
###C
\\n\\nint dfs(int val, int idx, int* nums, int numsSize) {\\n if (idx == numsSize) {\\n // 终止递归\\n return val;\\n }\\n // 考虑选择当前数字, 考虑不选择当前数字\\n return dfs(val ^ nums[idx], idx + 1, nums, numsSize) + dfs(val, idx + 1, nums, numsSize);\\n}\\n\\nint subsetXORSum(int* nums, int numsSize) {\\n return dfs(0, 0, nums, numsSize);\\n}\\n
###JavaScript
\\n\\nvar subsetXORSum = function(nums) {\\n return dfs(0, 0, nums);\\n};\\n\\n// 深度优先搜索\\nfunction dfs(val, idx, nums) {\\n if (idx === nums.length) {\\n // 终止递归\\n return val;\\n }\\n // 考虑选择当前数字, 考虑不选择当前数字\\n return dfs(val ^ nums[idx], idx + 1, nums) + dfs(val, idx + 1, nums);\\n}\\n
###JavaScript
\\n\\nfunction subsetXORSum(nums: number[]): number {\\n return dfs(0, 0, nums);\\n};\\n\\n// 深度优先搜索\\nfunction dfs(val: number, idx: number, nums: number[]): number {\\n if (idx === nums.length) {\\n // 终止递归\\n return val;\\n }\\n // 考虑选择当前数字, 考虑不选择当前数字\\n return dfs(val ^ nums[idx], idx + 1, nums) + dfs(val, idx + 1, nums);\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn subset_xor_sum(nums: Vec<i32>) -> i32 {\\n fn dfs(val: i32, idx: usize, nums: &[i32]) -> i32 {\\n if idx == nums.len() {\\n // 终止递归\\n return val;\\n }\\n // 考虑选择当前数字, 考虑不选择当前数字\\n dfs(val ^ nums[idx], idx + 1, nums) + dfs(val, idx + 1, nums)\\n }\\n \\n dfs(0, 0, &nums)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(2^n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。
\\n第 $\\\\textit{idx}$ 层的递归函数共有 $2^\\\\textit{idx}$ 个,总计共会调用 $\\\\sum_{i = 0}^n 2^i = 2^{n+1} - 1$ 次递归函数。而每个递归函数的时间复杂度均为 $O(1)$。
\\n- \\n
\\n空间复杂度:$O(n)$,即为递归时的栈空间开销。
\\n方法二:迭代法枚举子集
\\n提示 $1$
\\n一个长度为 $n$ 的数组 $\\\\textit{nums}$ 有 $2^n$ 个子集(包括空集与自身)。我们可以将这些子集一一映射到 $[0, 2^n-1]$ 中的整数。
\\n提示 $2$
\\n数组中的每个元素都有「选取」与「未选取」两个状态,可以对应一个二进制位的 $1$ 与 $0$。那么对于一个长度为 $n$ 的数组 $\\\\textit{nums}$,我们也可以用 $n$ 个二进制位的整数来唯一表示每个元素的选取情况。此时该整数第 $j$ 位的取值表示数组第 $j$ 个元素是否包含在对应的子集中。
\\n思路与算法
\\n我们也可以用迭代来实现子集枚举。
\\n根据 提示 $1$ 与 提示 $2$,我们枚举 $[0, 2^n-1]$ 中的整数 $i$,其第 $j$ 位的取值表示 $\\\\textit{nums}$ 的第 $j$ 个元素是否包含在对应的子集中。
\\n对于每个整数 $i$,我们遍历它的每一位计算对应子集的异或总和,并维护这些值之和。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int subsetXORSum(vector<int>& nums) {\\n int res = 0;\\n int n = nums.size();\\n for (int i = 0; i < (1 << n); ++i){ // 遍历所有子集\\n int tmp = 0;\\n for (int j = 0; j < n; ++j){ // 遍历每个元素\\n if (i & (1 << j)){\\n tmp ^= nums[j];\\n }\\n }\\n res += tmp;\\n }\\n return res;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def subsetXORSum(self, nums: List[int]) -> int:\\n res = 0\\n n = len(nums)\\n for i in range(1 << n): # 遍历所有子集\\n tmp = 0\\n for j in range(n): # 遍历每个元素\\n if i & (1 << j):\\n tmp ^= nums[j]\\n res += tmp\\n return res\\n
###Java
\\n\\nclass Solution {\\n public long mostPoints(int[][] questions) {\\n int n = questions.length;\\n long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数\\n for (int i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public long MostPoints(int[][] questions) {\\n int n = questions.Length;\\n long[] dp = new long[n + 1]; // 解决每道题及以后题目的最高分数\\n for (int i = n - 1; i >= 0; i--) {\\n dp[i] = Math.Max(dp[i + 1], questions[i][0] + dp[Math.Min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n }\\n}\\n
###Go
\\n\\nfunc mostPoints(questions [][]int) int64 {\\n n := len(questions)\\n dp := make([]int64, n + 1) // 解决每道题及以后题目的最高分数\\n for i := n - 1; i >= 0; i-- {\\n dp[i] = max(dp[i + 1], int64(questions[i][0]) + dp[min(n, i + questions[i][1] + 1)])\\n }\\n return dp[0]\\n}\\n
###C
\\n\\nlong long max(long long a, long long b) {\\n return a > b ? a : b;\\n}\\n\\nlong long min(long long a, long long b) {\\n return a < b ? a : b;\\n}\\n\\nlong long mostPoints(int** questions, int questionsSize, int* questionsColSize) {\\n long long dp[questionsSize + 1]; // 解决每道题及以后题目的最高分数\\n memset(dp, 0, sizeof(dp));\\n for (int i = questionsSize - 1; i >= 0; --i) {\\n dp[i] = max(dp[i + 1], questions[i][0] + dp[min(questionsSize, i + questions[i][1] + 1)]);\\n }\\n long long result = dp[0];\\n return result;\\n}\\n
###JavaScript
\\n\\nvar mostPoints = function(questions) {\\n const n = questions.length;\\n const dp = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数\\n for (let i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n};\\n
###JavaScript
\\n\\nfunction mostPoints(questions: number[][]): number {\\n const n = questions.length;\\n const dp: number[] = new Array(n + 1).fill(0); // 解决每道题及以后题目的最高分数\\n for (let i = n - 1; i >= 0; i--) {\\n dp[i] = Math.max(dp[i + 1], questions[i][0] + dp[Math.min(n, i + questions[i][1] + 1)]);\\n }\\n return dp[0];\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn most_points(questions: Vec<Vec<i32>>) -> i64 {\\n let n = questions.len();\\n let mut dp = vec![0i64; n + 1]; // 解决每道题及以后题目的最高分数\\n for i in (0..n).rev() {\\n dp[i] = dp[i + 1].max(questions[i][0] as i64 + dp[(n).min(i + questions[i][1] as usize + 1)]);\\n }\\n dp[0]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n2^n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度。我们遍历了 $\\\\textit{nums}$ 的 $2^n$ 个子集,每个子集需要 $O(n)$ 的时间计算异或总和。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法三:按位考虑 + 二项式展开
\\n提示 $1$
\\n由于异或运算本质上是按位操作,因此我们可以按位考虑取值情况。
\\n提示 $2$
\\n对于数组中所有元素的某一位,存在两种可能:
\\n\\n
\\n- \\n
\\n第一种,所有元素该位都为 $0$;
\\n- \\n
\\n第二种,至少有一个元素该位为 $1$。
\\n假设数组元素个数为 $n$,那么第一种情况下,所有子集异或总和中该位均为 $0$;第二种情况下,所有子集异或总和中该位为 $0$ 的个数与为 $1$ 的个数相等,均为 $2^{n-1}$。
\\n提示 $2$ 解释
\\n首先,一个子集的异或总和中某位为 $0$ 当且仅当子集内该位为 $1$ 的元素数量为偶数(包括 $0$),某位为 $1$ 当且仅当子集内该位为 $1$ 的元素数量为奇数。那么第一种情况时显然所有子集的异或总和中该位都为 $0$。
\\n其次,假设数组内某一位为 $1$ 的元素个数为 $m$,那么它的子集里面包含 $k$ 个 $1$ 的数量为($k \\\\le m \\\\le n$):
\\n$$
\\n
\\n2^{n-m}\\\\binom{k}{m},
\\n$$那么包含奇数个 $1$ 的子集数量为:
\\n$$
\\n
\\n\\\\sum_{k\\\\ \\\\text{is odd}, 0\\\\le k\\\\le m}2^{n-m}\\\\binom{k}{m} = 2^{n-m}\\\\sum_{k\\\\ \\\\text{is odd}, 0\\\\le k\\\\le m}\\\\binom{k}{m},
\\n$$同理,包含偶数个 $1$ 的子集数量为:
\\n$$
\\n
\\n\\\\sum_{k\\\\ \\\\text{is even}, 0\\\\le k\\\\le m}2^{n-m}\\\\binom{k}{m} = 2^{n-m}\\\\sum_{k\\\\ \\\\text{is even}, 0\\\\le k\\\\le m}\\\\binom{k}{m}.
\\n$$事实上,我们通过对于 $(x + 1)^m$ 二项式展开并取 $x = -1$ 时,有:
\\n$$
\\n
\\n(-1+1)^m = \\\\sum_{k = 0}^{m} \\\\binom{k}{m} (-1)^k 1^{m-k} = \\\\sum_{k\\\\ \\\\text{is even}, 0\\\\le k\\\\le m}\\\\binom{k}{m} - \\\\sum_{k\\\\ \\\\text{is odd}, 0\\\\le k\\\\le m}\\\\binom{k}{m} = 0.
\\n$$这也就说明,包含奇数个 $1$ 的子集数量与包含偶数个 $1$ 的子集数量相等,均为全体子集数量的一半,即 $2^{n-1}$。
\\n思路与算法
\\n根据 提示 $2$,我们用 $\\\\textit{res}$ 来维护数组全体元素的按位或,使得 $\\\\textit{res}$ 的某一位为 $1$ 当且仅当数组中存在该位为 $1$ 的元素。
\\n那么,对于 $\\\\textit{res}$ 中为 $1$ 的任何一位,其对于结果的贡献均为该位对应的值乘上异或总和为 $1$ 的子集数量 $2^{n-1}$;对于为 $0$ 的任何一位,乘上 $2^{n-1}$ 也不会对结果产生影响。因此我们可以直接将 $\\\\textit{res}$ 算术左移 $n - 1$ 位作为结果返回。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int subsetXORSum(vector<int>& nums) {\\n int res = 0;\\n int n = nums.size();\\n for (auto num: nums){\\n res |= num;\\n }\\n return res << (n - 1);\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def subsetXORSum(self, nums: List[int]) -> int:\\n res = 0\\n n = len(nums)\\n for num in nums:\\n res |= num\\n return res << (n - 1)\\n
###Java
\\n\\nclass Solution {\\n public int subsetXORSum(int[] nums) {\\n int res = 0;\\n int n = nums.length;\\n for (int num : nums) {\\n res |= num;\\n }\\n return res << (n - 1);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int SubsetXORSum(int[] nums) {\\n int res = 0;\\n int n = nums.Length;\\n foreach (int num in nums) {\\n res |= num;\\n }\\n return res << (n - 1);\\n }\\n}\\n
###Go
\\n\\nfunc subsetXORSum(nums []int) int {\\n res := 0\\n n := len(nums)\\n for _, num := range nums {\\n res |= num\\n }\\n return res << (n - 1)\\n}\\n
###C
\\n\\nint subsetXORSum(int* nums, int numsSize) {\\n int res = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n res |= nums[i];\\n }\\n return res << (numsSize - 1);\\n}\\n
###JavaScript
\\n\\nvar subsetXORSum = function(nums) {\\n let res = 0;\\n const n = nums.length;\\n for (let num of nums) {\\n res |= num;\\n }\\n return res << (n - 1);\\n};\\n
###JavaScript
\\n\\nfunction subsetXORSum(nums: number[]): number {\\n let res = 0;\\n const n = nums.length;\\n for (let num of nums) {\\n res |= num;\\n }\\n return res << (n - 1);\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn subset_xor_sum(nums: Vec<i32>) -> i32 {\\n let mut res = 0;\\n let n = nums.len();\\n for &num in &nums {\\n res |= num;\\n }\\n res << (n - 1)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:递归法枚举子集 思路与算法\\n\\n我们用函数 $\\\\textit{dfs}(\\\\textit{val}, \\\\textit{idx})$ 来递归枚举数组 $\\\\textit{nums}$ 的子集。其中 $\\\\textit{val}$ 代表当前选取部分的异或值,$\\\\textit{idx}$ 代表递归的当前位置。\\n\\n我们用 $n$ 来表示 $\\\\textit{nums}$ 的长度。在进入 $\\\\textit{dfs}(\\\\textit{val}, \\\\textit{idx})$ 时,数组中 $[0,\\\\textit{idx} - 1]$ 部分的选取情况是已经确定的,而…","guid":"https://leetcode.cn/problems/sum-of-all-subset-xor-totals//solution/sum-of-all-subset-xor-totals-by-leetcode-o5aa","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-18T16:24:36.741Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"O(n)算法简明讲解","url":"https://leetcode.cn/problems/sum-of-all-subset-xor-totals//solution/onsuan-fa-jian-ming-jiang-jie-by-yuyinsl-9sod","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为 $\\\\textit{nums}$ 的长度,即为一遍遍历数组的时间复杂度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n\\n
\\n//AC代码\\nint subsetXORSum(int* nums, int numsSize) {\\n int ans = 0;\\n for(int i=0; i<numsSize; i++)\\n {\\n ans |= nums[i];\\n }\\n return ans << (numsSize - 1);\\n}\\n
如果感觉对你有用,烦请点个赞!
\\n","description":"//AC代码 int subsetXORSum(int* nums, int numsSize) {\\n int ans = 0;\\n for(int i=0; i方法一:拓扑排序 + 动态规划\\n 提示 $1$
\\n我们需要求出的答案等价于:
\\n\\n\\n对于一种颜色 $c$,以及一条路径 $\\\\textit{path}$,其中颜色为 $c$ 的节点有 $\\\\textit{path}_c$ 个。我们希望挑选 $c$ 以及 $\\\\textit{path}$,使得 $\\\\textit{path}_c$ 的值最大。
\\n提示 $2$
\\n根据提示 $1$,我们可以枚举颜色 $c$,随后选出可以使得 $\\\\textit{path}_c$ 达到最大值的 $\\\\textit{path}$。这些 $\\\\textit{path}_c$ 中的最大值即为答案。
\\n提示 $3$
\\n如果给定的有向图包含环,那么它不存在拓扑排序。
\\n如果给定的有向图不包含环,那么这个有向图是一个「有向无环图」,它一定存在拓扑排序。
\\n根据拓扑排序的性质,如果节点 $a$ 有一条有向边指向节点 $b$,那么 $b$ 在拓扑排序中一定出现在 $a$ 之后。因此,一条路径上点的顺序与它们在拓扑排序中出现的顺序是一致的。
\\n提示 $4$
\\n我们可以根据拓扑排序来进行动态规划。
\\n设 $f(v, c)$ 表示以节点 $v$ 为终点的所有路径中,包含颜色 $c$ 的节点数量的最大值。在进行状态转移时,我们考虑所有 $v$ 的前驱节点(即有一条有向边指向 $v$ 的节点)$\\\\textit{prev}_v$:
\\n$$
\\n
\\nf(v, c) = \\\\left( \\\\max_{u \\\\in \\\\textit{prev}_j} f(u, c) \\\\right) + \\\\mathbb{I}(v, c)
\\n$$即找出前驱节点中包含颜色 $c$ 的节点数量最多的那个节点进行转移,并且如果 $v$ 本身的颜色为 $c$,$f(v, c)$ 的值就增加 $1$。这里 $\\\\mathbb{I}(v, c)$ 为示性函数,当节点 $v$ 的颜色为 $c$ 时,函数值为 $1$,否则为 $0$。
\\n那么 $\\\\textit{path}_c$ 的值即为 $f(v, c)$ 中的最大值。
\\n思路与算法
\\n我们可以将状态转移融入使用广度优先搜索的方法求解拓扑排序的过程中。当我们遍历到节点 $u$ 时:
\\n\\n
\\n- \\n
\\n如果 $u$ 的颜色为 $c$,那么将 $f(u, c)$ 的值增加 $1$;
\\n- \\n
\\n枚举 $u$ 的所有后继节点(即从 $u$ 出发经过一条有向边可以到达的节点),对于后继节点 $v$,将 $f(v, c)$ 更新为其与 $f(u, c)$ 的较大值。
\\n这样的操作与上文描述的状态转移方程是一致的。它的好处在于,如果使用广度优先搜索的方法求解拓扑排序,那么我们需要使用邻接表存储所有的有向边,而上文的动态规划是通过「枚举 $v$ $\\\\to$ 枚举前驱节点 $u$」进行状态转移的,这就需要我们额外存储所有边的反向边,才能通过 $v$ 找到所有的前驱节点。如果我们通过「枚举 $u$ $\\\\to$ 枚举后继节点 $v$」进行状态转移,这样就与拓扑排序存储的边保持一致了。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int largestPathValue(string colors, vector<vector<int>>& edges) {\\n int n = colors.size();\\n // 邻接表\\n vector<vector<int>> g(n);\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n vector<int> indeg(n);\\n for (auto&& edge: edges) {\\n ++indeg[edge[1]];\\n g[edge[0]].push_back(edge[1]);\\n }\\n \\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n int found = 0;\\n vector<array<int, 26>> f(n);\\n queue<int> q;\\n for (int i = 0; i < n; ++i) {\\n if (!indeg[i]) {\\n q.push(i);\\n }\\n }\\n \\n while (!q.empty()) {\\n ++found;\\n int u = q.front();\\n q.pop();\\n // 将节点 u 对应的颜色增加 1\\n ++f[u][colors[u] - \'a\'];\\n // 枚举 u 的后继节点 v\\n for (int v: g[u]) {\\n --indeg[v];\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (int c = 0; c < 26; ++c) {\\n f[v][c] = max(f[v][c], f[u][c]);\\n }\\n if (!indeg[v]) {\\n q.push(v);\\n }\\n }\\n }\\n\\n if (found != n) {\\n return -1;\\n }\\n \\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n ans = max(ans, *max_element(f[i].begin(), f[i].end()));\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def largestPathValue(self, colors: str, edges: List[List[int]]) -> int:\\n n = len(colors)\\n # 邻接表\\n g = collections.defaultdict(list)\\n # 节点的入度统计,用于找出拓扑排序中最开始的节点\\n indeg = [0] * n\\n\\n for x, y in edges:\\n indeg[y] += 1\\n g[x].append(y)\\n \\n # 记录拓扑排序过程中遇到的节点个数\\n # 如果最终 found 的值不为 n,说明图中存在环\\n found = 0\\n f = [[0] * 26 for _ in range(n)]\\n q = collections.deque()\\n for i in range(n):\\n if indeg[i] == 0:\\n q.append(i)\\n \\n while q:\\n found += 1\\n u = q.popleft()\\n # 将节点 u 对应的颜色增加 1\\n f[u][ord(colors[u]) - ord(\\"a\\")] += 1\\n # 枚举 u 的后继节点 v\\n for v in g[u]:\\n indeg[v] -= 1\\n # 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for c in range(26):\\n f[v][c] = max(f[v][c], f[u][c])\\n if indeg[v] == 0:\\n q.append(v)\\n \\n if found != n:\\n return -1\\n \\n ans = 0\\n for i in range(n):\\n ans = max(ans, max(f[i]))\\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int largestPathValue(String colors, int[][] edges) {\\n int n = colors.length();\\n // 邻接表\\n List<List<Integer>> g = new ArrayList<>();\\n for (int i = 0; i < n; ++i) {\\n g.add(new ArrayList<>());\\n }\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n int[] indeg = new int[n];\\n for (int[] edge : edges) {\\n ++indeg[edge[1]];\\n g.get(edge[0]).add(edge[1]);\\n }\\n \\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n int found = 0;\\n int[][] f = new int[n][26];\\n Queue<Integer> q = new LinkedList<>();\\n for (int i = 0; i < n; ++i) {\\n if (indeg[i] == 0) {\\n q.offer(i);\\n }\\n }\\n while (!q.isEmpty()) {\\n ++found;\\n int u = q.poll();\\n // 将节点 u 对应的颜色增加 1\\n ++f[u][colors.charAt(u) - \'a\'];\\n // 枚举 u 的后继节点 v\\n for (int v : g.get(u)) {\\n --indeg[v];\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (int c = 0; c < 26; ++c) {\\n f[v][c] = Math.max(f[v][c], f[u][c]);\\n }\\n if (indeg[v] == 0) {\\n q.offer(v);\\n }\\n }\\n }\\n if (found != n) {\\n return -1;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n ans = Math.max(ans, Arrays.stream(f[i]).max().getAsInt());\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int LargestPathValue(string colors, int[][] edges) {\\n int n = colors.Length;\\n // 邻接表\\n List<List<int>> g = new List<List<int>>();\\n for (int i = 0; i < n; ++i) {\\n g.Add(new List<int>());\\n }\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n int[] indeg = new int[n];\\n foreach (var edge in edges) {\\n ++indeg[edge[1]];\\n g[edge[0]].Add(edge[1]);\\n }\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n int found = 0;\\n int[][] f = new int[n][];\\n for (int i = 0; i < n; ++i) {\\n f[i] = new int[26];\\n }\\n Queue<int> q = new Queue<int>();\\n for (int i = 0; i < n; ++i) {\\n if (indeg[i] == 0) {\\n q.Enqueue(i);\\n }\\n }\\n while (q.Count > 0) {\\n ++found;\\n int u = q.Dequeue();\\n // 将节点 u 对应的颜色增加 1\\n ++f[u][colors[u] - \'a\'];\\n // 枚举 u 的后继节点 v\\n foreach (int v in g[u]) {\\n --indeg[v];\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (int c = 0; c < 26; ++c) {\\n f[v][c] = Math.Max(f[v][c], f[u][c]);\\n }\\n if (indeg[v] == 0) {\\n q.Enqueue(v);\\n }\\n }\\n }\\n if (found != n) {\\n return -1;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n ans = Math.Max(ans, f[i].Max());\\n }\\n return ans;\\n }\\n}\\n\\n
###Go
\\n\\nfunc largestPathValue(colors string, edges [][]int) int {\\n n := len(colors)\\n // 邻接表\\n g := make([][]int, n)\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n indeg := make([]int, n)\\n for _, edge := range edges {\\n indeg[edge[1]]++\\n g[edge[0]] = append(g[edge[0]], edge[1])\\n }\\n\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n found := 0\\n f := make([][26]int, n)\\n q := []int{}\\n for i := 0; i < n; i++ {\\n if indeg[i] == 0 {\\n q = append(q, i)\\n }\\n }\\n\\n for len(q) > 0 {\\n found++\\n u := q[0]\\n q = q[1:]\\n // 将节点 u 对应的颜色增加 1\\n f[u][colors[u]-\'a\']++\\n // 枚举 u 的后继节点 v\\n for _, v := range g[u] {\\n indeg[v]--\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for c := 0; c < 26; c++ {\\n f[v][c] = max(f[v][c], f[u][c])\\n }\\n if indeg[v] == 0 {\\n q = append(q, v)\\n }\\n }\\n }\\n if found != n {\\n return -1\\n }\\n ans := 0\\n for i := 0; i < n; i++ {\\n for c := 0; c < 26; c++ {\\n ans = max(ans, f[i][c])\\n }\\n }\\n return ans\\n}\\n
###C
\\n\\nstruct ListNode *createListNode(int val) {\\n struct ListNode *obj = (struct ListNode *)malloc(sizeof(struct ListNode));\\n obj->val = val;\\n obj->next = NULL;\\n return obj;\\n}\\n\\nvoid freeList(struct ListNode *list) {\\n while (list) {\\n struct ListNode *p = list;\\n list = list->next;\\n free(p);\\n }\\n}\\n\\nint largestPathValue(char* colors, int** edges, int edgesSize, int* edgesColSize) {\\n int n = strlen(colors);\\n // 邻接表\\n struct ListNode *g[n];\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n int indeg[n];\\n for (int i = 0; i < n; i++) {\\n g[i] = NULL;\\n indeg[i] = 0;\\n }\\n for (int i = 0; i < edgesSize; ++i) {\\n int x = edges[i][0], y = edges[i][1];\\n ++indeg[y];\\n struct ListNode *node = createListNode(y);\\n node->next = g[x];\\n g[x] = node;\\n }\\n\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n int found = 0;\\n int f[n][26];\\n int queue[n];\\n int head = 0, tail = 0;\\n memset(f, 0, sizeof(f));\\n for (int i = 0; i < n; ++i) {\\n if (indeg[i] == 0) {\\n queue[tail++] = i;\\n }\\n }\\n\\n while (head != tail) {\\n ++found;\\n int u = queue[head];\\n head++;\\n // 将节点 u 对应的颜色增加 1\\n ++f[u][colors[u] - \'a\'];\\n // 枚举 u 的后继节点 v\\n for (struct ListNode *p = g[u]; p != NULL; p = p->next) {\\n int v = p->val;\\n --indeg[v];\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (int c = 0; c < 26; ++c) {\\n f[v][c] = (f[v][c] > f[u][c]) ? f[v][c] : f[u][c];\\n }\\n if (indeg[v] == 0) {\\n queue[tail++] = v;\\n }\\n }\\n }\\n\\n if (found != n) {\\n return -1;\\n }\\n int ans = 0;\\n for (int i = 0; i < n; ++i) {\\n for (int c = 0; c < 26; ++c) {\\n if (f[i][c] > ans) {\\n ans = f[i][c];\\n }\\n }\\n freeList(g[i]);\\n }\\n \\n return ans;\\n}\\n
###JavaScript
\\n\\nvar largestPathValue = function(colors, edges) {\\n const n = colors.length;\\n // 邻接表\\n const g = Array.from({ length: n }, () => []);\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n const indeg = Array(n).fill(0);\\n for (const [u, v] of edges) {\\n indeg[v]++;\\n g[u].push(v);\\n }\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n let found = 0;\\n const f = Array.from({ length: n }, () => Array(26).fill(0));\\n const q = [];\\n for (let i = 0; i < n; i++) {\\n if (indeg[i] === 0) {\\n q.push(i);\\n }\\n }\\n while (q.length > 0) {\\n found++;\\n const u = q.shift();\\n // 将节点 u 对应的颜色增加 1\\n f[u][colors.charCodeAt(u) - \'a\'.charCodeAt(0)]++;\\n // 枚举 u 的后继节点 v\\n for (const v of g[u]) {\\n indeg[v]--;\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (let c = 0; c < 26; c++) {\\n f[v][c] = Math.max(f[v][c], f[u][c]);\\n }\\n if (indeg[v] === 0) {\\n q.push(v);\\n }\\n }\\n }\\n if (found !== n) {\\n return -1;\\n }\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n ans = Math.max(ans, ...f[i]);\\n }\\n return ans;\\n}\\n\\n
###TypeScript
\\n\\nfunction largestPathValue(colors: string, edges: number[][]): number {\\n const n = colors.length;\\n // 邻接表\\n const g: number[][] = Array.from({ length: n }, () => []);\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n const indeg: number[] = Array(n).fill(0);\\n for (const [u, v] of edges) {\\n indeg[v]++;\\n g[u].push(v);\\n }\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n let found = 0;\\n const f: number[][] = Array.from({ length: n }, () => Array(26).fill(0));\\n const q: number[] = [];\\n for (let i = 0; i < n; i++) {\\n if (indeg[i] === 0) {\\n q.push(i);\\n }\\n }\\n while (q.length > 0) {\\n found++;\\n const u = q.shift()!;\\n // 将节点 u 对应的颜色增加 1\\n f[u][colors.charCodeAt(u) - \'a\'.charCodeAt(0)]++;\\n // 枚举 u 的后继节点 v\\n for (const v of g[u]) {\\n indeg[v]--;\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for (let c = 0; c < 26; c++) {\\n f[v][c] = Math.max(f[v][c], f[u][c]);\\n }\\n if (indeg[v] === 0) {\\n q.push(v);\\n }\\n }\\n }\\n\\n if (found !== n) {\\n return -1;\\n }\\n\\n let ans = 0;\\n for (let i = 0; i < n; i++) {\\n ans = Math.max(ans, ...f[i]);\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::collections::{VecDeque, HashMap};\\n\\nimpl Solution {\\n pub fn largest_path_value(colors: String, edges: Vec<Vec<i32>>) -> i32 {\\n let n = colors.len();\\n // 邻接表\\n let mut g = vec![vec![]; n];\\n // 节点的入度统计,用于找出拓扑排序中最开始的节点\\n let mut indeg = vec![0; n];\\n for edge in edges {\\n indeg[edge[1] as usize] += 1;\\n g[edge[0] as usize].push(edge[1] as usize);\\n }\\n\\n // 记录拓扑排序过程中遇到的节点个数\\n // 如果最终 found 的值不为 n,说明图中存在环\\n let mut found = 0;\\n let mut f = vec![vec![0; 26]; n];\\n let mut q = VecDeque::new();\\n for i in 0..n {\\n if indeg[i] == 0 {\\n q.push_back(i);\\n }\\n }\\n while let Some(u) = q.pop_front() {\\n found += 1;\\n // 将节点 u 对应的颜色增加 1\\n f[u][colors.as_bytes()[u] as usize - b\'a\' as usize] += 1;\\n // 枚举 u 的后继节点 v\\n for &v in &g[u] {\\n indeg[v] -= 1;\\n // 将 f(v,c) 更新为其与 f(u,c) 的较大值\\n for c in 0..26 {\\n f[v][c] = f[v][c].max(f[u][c]);\\n }\\n if indeg[v] == 0 {\\n q.push_back(v);\\n }\\n }\\n }\\n if found != n {\\n return -1;\\n }\\n let mut ans = 0;\\n for i in 0..n {\\n ans = ans.max(*f[i].iter().max().unwrap());\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:拓扑排序 + 动态规划 提示 $1$\\n\\n我们需要求出的答案等价于:\\n\\n对于一种颜色 $c$,以及一条路径 $\\\\textit{path}$,其中颜色为 $c$ 的节点有 $\\\\textit{path}_c$ 个。我们希望挑选 $c$ 以及 $\\\\textit{path}$,使得 $\\\\textit{path}_c$ 的值最大。\\n\\n提示 $2$\\n\\n根据提示 $1$,我们可以枚举颜色 $c$,随后选出可以使得 $\\\\textit{path}_c$ 达到最大值的 $\\\\textit{path}$。这些 $\\\\textit{path}_c$ 中的最大值即为答案。\\n\\n提示…","guid":"https://leetcode.cn/problems/largest-color-value-in-a-directed-graph//solution/you-xiang-tu-zhong-zui-da-yan-se-zhi-by-dmtaa","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-09T05:35:51.077Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"拓扑排序 + 动态规划 详细解析","url":"https://leetcode.cn/problems/largest-color-value-in-a-directed-graph//solution/tuo-bu-pai-xu-dong-tai-gui-hua-xiang-xi-b4try","content":"- \\n
\\n时间复杂度:$O((n+m)|\\\\Sigma|)$,其中 $|\\\\Sigma|$ 表示颜色的数量,在本题中 $|\\\\Sigma|=26$。
\\n\\n
\\n- 一般的拓扑排序需要的时间为 $O(n+m)$。而在本题中,我们在拓扑排序的过程中加入了状态转移,由于一条有向边对应着 $|\\\\Sigma|$ 次状态转移,因此拓扑排序的时间复杂度实际为 $O(n + m|\\\\Sigma|)$;
\\n- 我们需要在 $O(n |\\\\Sigma|)$ 个状态中找出最大值,时间复杂度为 $O(n |\\\\Sigma|)$。
\\n将它们相加即可得到总时间复杂度为 $O(n + m|\\\\Sigma|) + O(n |\\\\Sigma|) = O((n+m)|\\\\Sigma|)$。
\\n- \\n
\\n空间复杂度:$O(n|\\\\Sigma| + m)$。
\\n\\n
\\n- 我们需要 $O(n |\\\\Sigma|)$ 的空间存储对应数量的状态;
\\n- 我们需要 $O(n+m)$ 的空间存储邻接表;
\\n- 我们需要 $O(n)$ 的队列空间进行拓扑排序。
\\n将它们相加即可得到总时间复杂度为 $O(n |\\\\Sigma| + m)$。
\\n思路
\\n本题的图是一个有向图,且可以排除有环的情况,故可以使用拓扑排序,按拓扑序进行动态规划。
\\n拓扑排序流程
\\n①将所有入度为0的点加入队列中
\\n②每次出队一个入度为0的点,然后将该点删除(意思是将所有与该点相连的边都删掉,即将边另一端对应的点的入度减1),若删除该点后与该点相连的点入度变为了0,则将该点加入队列。
\\n③重复②过程直到队列中的元素被删完
\\n排除有环的情况
\\n因为只有入度为 0 的点才能入队,故若存在环,环上的点一定无法入队。
\\n所以只需统计入过队的点数之和是否等于点的总数 $n$ 即可。
\\n拓扑序DP
\\n拓扑序可以理解为上述队列的出队顺序,一个点 $x$ 拓扑序小于 $y$ 当且仅当 $x$在 $y$ 之前出队,在拓扑序列上DP就是拓扑序DP
\\n实现上,可以在步骤②中,删除一个点时进行动态规划转移。
\\n本题中DP状态设为 $f[i][j]$ 表示到点 $i$ 的所有路径中,颜色为 $j$ 的点的个数的最大值。
\\n设 $u$ 为拓扑序在 $i$ 后面且存在边 $(i,u)$ , 则:
\\n$f[u][j] = max(f[u][j], f[i][j] + (colors[u] - \'a\' == j));$
\\n答案 $ans = max(f[i][j]), i=0,..,n-1,j=0,..,25$
\\n时间复杂度分析
\\n状态数为 $O(n*C)$ , $C$ 为字符集大小,转移为 $O(1)$。
\\n因此总的时间复杂度 $T(n) = O(nC)$,空间复杂度也为 $O(nC)$
\\n\\n","description":"思路 本题的图是一个有向图,且可以排除有环的情况,故可以使用拓扑排序,按拓扑序进行动态规划。\\n\\n拓扑排序流程\\n\\n①将所有入度为0的点加入队列中\\n\\n②每次出队一个入度为0的点,然后将该点删除(意思是将所有与该点相连的边都删掉,即将边另一端对应的点的入度减1),若删除该点后与该点相连的点入度变为了0,则将该点加入队列。\\n\\n③重复②过程直到队列中的元素被删完\\n\\n排除有环的情况\\n\\n因为只有入度为 0 的点才能入队,故若存在环,环上的点一定无法入队。\\n\\n所以只需统计入过队的点数之和是否等于点的总数 $n$ 即可。\\n\\n拓扑序DP\\n\\n拓扑序可以理解为上述队列的出队顺序,一个点 $x…","guid":"https://leetcode.cn/problems/largest-color-value-in-a-directed-graph//solution/tuo-bu-pai-xu-dong-tai-gui-hua-xiang-xi-b4try","author":"Hankpipi","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-09T04:15:02.889Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种方法:拓扑排序 / 记忆化搜索(Python/Java/C++/Go)","url":"https://leetcode.cn/problems/largest-color-value-in-a-directed-graph//solution/an-zhao-tuo-bu-xu-dp-by-endlesscheng-2n4g","content":"class Solution {\\npublic:\\n const static int N = 100010;\\n int n;\\n int d[N], f[N][27];\\n int head[N], to[N], nxt[N], num = 0; //邻接表\\n void link(int x, int y) {\\n nxt[++num] = head[x], to[num] = y, head[x] = num;\\n }\\n queue<int>Q;\\n int largestPathValue(string colors, vector<vector<int>>& edges) {\\n n = colors.length();\\n for(auto& t : edges) {\\n ++d[t[1]];\\n link(t[0], t[1]);\\n }\\n for(int i = 0; i < n; ++i)\\n if(d[i] == 0)\\n Q.push(i), f[i][colors[i] - \'a\'] = 1;\\n int cnt = 0, ans = 0;\\n while(!Q.empty()) {\\n int x = Q.front();\\n Q.pop();\\n ++cnt;\\n for(int i = head[x]; i; i = nxt[i]) {\\n int u = to[i];\\n --d[u];\\n if(d[u] == 0)Q.push(u); //入数为0则加入\\n for(int j = 0; j < 26; ++j) //状态转移\\n f[u][j] = max(f[u][j], f[x][j] + (colors[u] - \'a\' == j));\\n }\\n for(int j = 0; j < 26; ++j)\\n ans = max(ans, f[x][j]);\\n }\\n if(cnt < n)return -1;\\n return ans;\\n }\\n};\\n
方法一:记忆化搜索 + 三色标记法
\\n判断图中是否有环,可以用 三色标记法 解决。
\\n如果图中无环,那么从一个节点 $x$ 出发,不可能又重新回到 $x$。任意路径中也不会有环。
\\n核心思路:设 $x$ 的邻居为 $y$,即 $x\\\\to y$。如果 $x$ 的颜色是 $i$,那么从 $x$ 开始的路径中颜色 $i$ 的最大出现次数,等于从 $y$ 开始的路径中颜色 $i$ 的最大出现次数,加上 $1$。如果 $x$ 的颜色不是 $i$,则不加 $1$。
\\n据此定义 $\\\\textit{dfs}(x)$ 表示从节点 $x$ 开始的路径中,每种颜色的最大出现次数。返回值是一个长为 $26$ 的数组 $\\\\textit{res}$。
\\n遍历 $x$ 的邻居 $y$,设 $\\\\textit{dfs}(y)$ 的返回值为 $\\\\textit{cy}$,用 $\\\\textit{cy}[i]$ 更新 $\\\\textit{res}[i]$ 的最大值。最后加上节点 $x$ 的颜色 $\\\\textit{colors}[x]$,也就是把 $\\\\textit{res}[\\\\textit{colors}[x]]$ 加一。
\\n状态转移方程为
\\n$$
\\n
\\n\\\\textit{dfs}(x)[i] = \\\\max_{y} \\\\textit{dfs}(y)[i] + [\\\\textit{colors}[x] = i]
\\n$$答案为 $\\\\textit{dfs}(x)[i]$ 的最大值。
\\n实现时,可以在记忆化搜索的同时,使用三色标记法判环。具体见代码。
\\n实现时,可以只取 $\\\\textit{dfs}(x)[\\\\textit{colors}[x]]$ 的最大值。因为其他颜色 $j$ 的最大出现次数等于某个其他的 $\\\\textit{dfs}(y)[j]$,其中 $\\\\textit{color}[y]=j$。
\\n\\nclass Solution:\\n def largestPathValue(self, colors: str, edges: List[List[int]]) -> int:\\n n = len(colors)\\n g = [[] for _ in range(n)]\\n for x, y in edges:\\n if x == y: # 自环\\n return -1\\n g[x].append(y)\\n\\n memo = [None] * n\\n def dfs(x: int) -> Dict[str, int]:\\n if memo[x] is not None: # x 计算中或者计算过\\n return memo[x] # 如果是 0,表示有环\\n memo[x] = 0 # 用 0 表示计算中\\n res = defaultdict(int)\\n for y in g[x]:\\n cy = dfs(y)\\n if not cy: # 有环\\n return cy\\n for ch, c in cy.items():\\n res[ch] = max(res[ch], c)\\n res[colors[x]] += 1\\n memo[x] = res # 记忆化,同时也表示 x 计算完毕\\n return res\\n\\n ans = 0\\n for x, c in enumerate(colors):\\n res = dfs(x)\\n if not res: # 有环\\n return -1\\n ans = max(ans, res[c])\\n return ans\\n
\\nclass Solution {\\n public int largestPathValue(String colors, int[][] edges) {\\n int n = colors.length();\\n List<Integer>[] g = new ArrayList[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n if (x == y) { // 自环\\n return -1;\\n }\\n g[x].add(y);\\n }\\n\\n int ans = 0;\\n char[] cs = colors.toCharArray();\\n int[][] memo = new int[n][];\\n for (int x = 0; x < n; x++) {\\n int[] res = dfs(x, g, cs, memo);\\n if (res.length == 0) { // 有环\\n return -1;\\n }\\n ans = Math.max(ans, res[cs[x] - \'a\']);\\n }\\n return ans;\\n }\\n\\n private int[] dfs(int x, List<Integer>[] g, char[] colors, int[][] memo) {\\n if (memo[x] != null) { // x 计算中或者计算过\\n return memo[x]; // 如果是空数组,表示有环\\n }\\n memo[x] = new int[]{}; // 表示计算中\\n int[] res = new int[26];\\n for (int y : g[x]) {\\n int[] cy = dfs(y, g, colors, memo);\\n if (cy.length == 0) { // 有环\\n return cy;\\n }\\n for (int i = 0; i < 26; i++) {\\n res[i] = Math.max(res[i], cy[i]);\\n }\\n }\\n res[colors[x] - \'a\']++;\\n return memo[x] = res; // 记忆化,同时也表示 x 计算完毕\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestPathValue(string colors, vector<vector<int>>& edges) {\\n int n = colors.size();\\n vector<vector<int>> g(n);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n if (x == y) { // 自环\\n return -1;\\n }\\n g[x].push_back(y);\\n }\\n\\n vector<vector<int>> memo(n);\\n auto dfs = [&](this auto&& dfs, int x) -> vector<int> {\\n if (!memo[x].empty()) { // x 计算中或者计算过\\n return memo[x]; // 如果是空 vector,表示有环\\n }\\n memo[x] = {0}; // 表示计算中\\n vector<int> res(26);\\n for (int y : g[x]) {\\n auto cy = dfs(y);\\n if (cy.size() <= 1) { // 有环\\n return cy;\\n }\\n for (int i = 0; i < 26; i++) {\\n res[i] = max(res[i], cy[i]);\\n }\\n }\\n res[colors[x] - \'a\']++;\\n return memo[x] = res; // 记忆化,同时也表示 x 计算完毕\\n };\\n\\n int ans = 0;\\n for (int x = 0; x < n; x++) {\\n auto res = dfs(x);\\n if (res.size() <= 1) { // 有环\\n return -1;\\n }\\n ans = max(ans, res[colors[x] - \'a\']);\\n }\\n return ans;\\n }\\n};\\n
\\nfunc largestPathValue(colors string, edges [][]int) (ans int) {\\nn := len(colors)\\ng := make([][]int, n)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\nif x == y { // 自环\\nreturn -1\\n}\\ng[x] = append(g[x], y)\\n}\\n\\nmemo := make([][]int, n)\\nvar dfs func(int) []int\\ndfs = func(x int) []int {\\nif memo[x] != nil { // x 计算中或者计算过\\nreturn memo[x] // 如果 memo[x] 是空列表,返回空列表,表示有环\\n}\\nmemo[x] = []int{} // 用空列表表示计算中\\nres := make([]int, 26)\\nfor _, y := range g[x] {\\ncy := dfs(y)\\nif len(cy) == 0 { // 有环\\nreturn cy\\n}\\nfor i, c := range cy {\\nres[i] = max(res[i], c)\\n}\\n}\\nres[colors[x]-\'a\']++\\nmemo[x] = res // 记忆化,同时也表示 x 计算完毕\\nreturn res\\n}\\n\\nfor x, ch := range colors {\\nres := dfs(x)\\nif len(res) == 0 { // 有环\\nreturn -1\\n}\\nans = max(ans, res[ch-\'a\'])\\n}\\nreturn\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}((n+m)|\\\\Sigma|)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度,$|\\\\Sigma|=26$ 是字符集合的大小。有 $\\\\mathcal{O}(n|\\\\Sigma|)$ 个状态,$\\\\mathcal{O}(m)$ 次状态转移,每次状态转移需要 $\\\\mathcal{O}(|\\\\Sigma|)$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(m + n|\\\\Sigma|)$。
\\n方法二:拓扑排序 + 刷表法
\\n方法一是以 $x$ 为起点定义状态,还可以以 $x$ 为终点定义状态。
\\n定义 $f[x][i]$ 表示终点为 $x$ 的路径中,颜色 $i$ 的最大出现次数。
\\n为了正确计算转移,必须先把 $x$ 的所有转移来源算出来,也就是按照拓扑序转移:
\\n\\n
\\n- 把入度为 $0$ 的点入队。
\\n- 节点 $x$ 出队时,把 $f[x][\\\\textit{colors}[x]]$ 增加 $1$。
\\n- 遍历 $x$ 的邻居 $y$,考虑刷表法,用 $f[x][i]$ 更新 $f[y][i]$ 的最大值。
\\n- 把 $y$ 的入度减一。如果 $y$ 的入度变成 $0$,则把 $y$ 入队。
\\n\\n\\n注:在动态规划中,用转移来源更新当前状态叫查表法,用当前状态更新其他状态叫刷表法。
\\n答案为所有 $f[x][i]$ 的最大值。
\\n如果有节点没有入队,说明有环,返回 $-1$。
\\n实现时,可以只取 $f[x][\\\\textit{colors}[x]]$ 的最大值。因为其他颜色 $j$ 的最大出现次数等于某个其他的 $f[y][j]$,其中 $\\\\textit{color}[y]=j$。
\\n\\nclass Solution:\\n def largestPathValue(self, colors: str, edges: List[List[int]]) -> int:\\n n = len(colors)\\n g = [[] for _ in range(n)]\\n deg = [0] * n\\n for x, y in edges:\\n if x == y: # 自环\\n return -1\\n g[x].append(y)\\n deg[y] += 1\\n\\n ans = visited = 0\\n q = deque(i for i, d in enumerate(deg) if d == 0) # 入度为 0 的点入队\\n f = [defaultdict(int) for _ in range(n)]\\n while q:\\n x = q.popleft() # x 的所有转移来源都计算完毕,也都更新到 f[x] 中\\n visited += 1\\n ch = colors[x]\\n f[x][ch] += 1\\n ans = max(ans, f[x][ch])\\n for y in g[x]:\\n for ch, c in f[x].items():\\n f[y][ch] = max(f[y][ch], c) # 刷表法,更新邻居的最大值\\n deg[y] -= 1\\n if deg[y] == 0:\\n q.append(y)\\n\\n return ans if visited == n else -1\\n
\\nclass Solution {\\n public int largestPathValue(String colors, int[][] edges) {\\n char[] cs = colors.toCharArray();\\n int n = cs.length;\\n\\n List<Integer>[] g = new ArrayList[n];\\n Arrays.setAll(g, i -> new ArrayList<>());\\n int[] deg = new int[n];\\n\\n for (int[] e : edges) {\\n int x = e[0];\\n int y = e[1];\\n if (x == y) { // 自环\\n return -1;\\n }\\n g[x].add(y);\\n deg[y]++;\\n }\\n\\n Queue<Integer> q = new ArrayDeque<>();\\n for (int i = 0; i < n; i++) {\\n if (deg[i] == 0) {\\n q.add(i); // 入度为 0 的点入队\\n }\\n }\\n\\n int ans = 0;\\n int visited = 0;\\n int[][] f = new int[n][26];\\n\\n while (!q.isEmpty()) {\\n int x = q.poll(); // x 的所有转移来源都计算完毕,也都更新到 f[x] 中\\n visited++;\\n int ch = cs[x] - \'a\';\\n f[x][ch]++;\\n ans = Math.max(ans, f[x][ch]);\\n for (int y : g[x]) {\\n for (int i = 0; i < 26; i++) {\\n f[y][i] = Math.max(f[y][i], f[x][i]); // 刷表法,更新邻居的最大值\\n }\\n if (--deg[y] == 0) {\\n q.add(y);\\n }\\n }\\n }\\n\\n return visited < n ? -1 : ans;\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n int largestPathValue(string colors, vector<vector<int>>& edges) {\\n int n = colors.size();\\n vector<vector<int>> g(n);\\n vector<int> deg(n);\\n for (auto& e : edges) {\\n int x = e[0], y = e[1];\\n if (x == y) { // 自环\\n return -1;\\n }\\n g[x].push_back(y);\\n deg[y]++;\\n }\\n\\n vector<int> q;\\n for (int i = 0; i < n; i++) {\\n if (deg[i] == 0) {\\n q.push_back(i); // 入度为 0 的点入队\\n }\\n }\\n\\n int ans = 0;\\n vector<array<int, 26>> f(n);\\n for (int i = 0; i < q.size(); i++) { // 注意 q.size() 会变大\\n int x = q[i]; // x 的所有转移来源都计算完毕,也都更新到 f[x] 中\\n int ch = colors[x] - \'a\';\\n f[x][ch]++;\\n ans = max(ans, f[x][ch]);\\n for (int y : g[x]) {\\n for (int j = 0; j < 26; j++) {\\n f[y][j] = max(f[y][j], f[x][j]); // 刷表法,更新邻居的最大值\\n }\\n if (--deg[y] == 0) {\\n q.push_back(y);\\n }\\n }\\n }\\n\\n return q.size() < n ? -1 : ans;\\n }\\n};\\n
\\nfunc largestPathValue(colors string, edges [][]int) (ans int) {\\nn := len(colors)\\ng := make([][]int, n)\\ndeg := make([]int, n)\\nfor _, e := range edges {\\nx, y := e[0], e[1]\\nif x == y { // 自环\\nreturn -1\\n}\\ng[x] = append(g[x], y)\\ndeg[y]++\\n}\\n\\nq := make([]int, 0, n)\\nfor i, d := range deg {\\nif d == 0 {\\nq = append(q, i) // 入度为 0 的点入队\\n}\\n}\\n\\nf := make([][26]int, n)\\nfor len(q) > 0 {\\nx := q[0] // x 的所有转移来源都计算完毕,也都更新到 f[x] 中\\nq = q[1:]\\nch := colors[x] - \'a\'\\nf[x][ch]++\\nans = max(ans, f[x][ch])\\nfor _, y := range g[x] {\\nfor i, cnt := range f[x] {\\nf[y][i] = max(f[y][i], cnt) // 刷表法,更新邻居的最大值\\n}\\ndeg[y]--\\nif deg[y] == 0 {\\nq = append(q, y)\\n}\\n}\\n}\\n\\nif cap(q) > 0 { // 有节点没入队,说明有环\\nreturn -1\\n}\\nreturn\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$\\\\mathcal{O}((n+m)|\\\\Sigma|)$,其中 $n$ 是 $\\\\textit{nums}$ 的长度,$|\\\\Sigma|=26$ 是字符集合的大小。有 $\\\\mathcal{O}(n|\\\\Sigma|)$ 个状态,$\\\\mathcal{O}(m)$ 次状态转移,每次状态转移需要 $\\\\mathcal{O}(|\\\\Sigma|)$ 的时间。
\\n- 空间复杂度:$\\\\mathcal{O}(m + n|\\\\Sigma|)$。
\\n相似题目
\\n\\n分类题单
\\n\\n\\n
\\n\\n- 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
\\n- 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
\\n- 单调栈(基础/矩形面积/贡献法/最小字典序)
\\n- 网格图(DFS/BFS/综合应用)
\\n- 位运算(基础/性质/拆位/试填/恒等式/思维)
\\n- 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
\\n- 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
\\n- 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
\\n- 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
\\n- 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
\\n- 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
\\n- 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)
\\n欢迎关注 B站@灵茶山艾府
\\n","description":"方法一:记忆化搜索 + 三色标记法 判断图中是否有环,可以用 三色标记法 解决。\\n\\n如果图中无环,那么从一个节点 $x$ 出发,不可能又重新回到 $x$。任意路径中也不会有环。\\n\\n核心思路:设 $x$ 的邻居为 $y$,即 $x\\\\to y$。如果 $x$ 的颜色是 $i$,那么从 $x$ 开始的路径中颜色 $i$ 的最大出现次数,等于从 $y$ 开始的路径中颜色 $i$ 的最大出现次数,加上 $1$。如果 $x$ 的颜色不是 $i$,则不加 $1$。\\n\\n据此定义 $\\\\textit{dfs}(x)$ 表示从节点 $x$ 开始的路径中,每种颜色的最大出现次数…","guid":"https://leetcode.cn/problems/largest-color-value-in-a-directed-graph//solution/an-zhao-tuo-bu-xu-dp-by-endlesscheng-2n4g","author":"endlesscheng","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-09T04:05:53.788Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最近的房间","url":"https://leetcode.cn/problems/closest-room//solution/zui-jin-de-fang-jian-by-leetcode-solutio-9ylf","content":"方法一:离线算法
\\n提示 $1$
\\n如果我们可以将给定的房间和询问重新排序,那么是否可以使得问题更加容易解决?
\\n提示 $2$
\\n我们可以将房间以及询问都看成一个「事件」,如果我们将这些「事件」按照大小(房间的 $\\\\textit{size}$ 或者询问的 $\\\\textit{minSize}$)进行降序排序,那么:
\\n\\n
\\n- 如果我们遇到一个表示房间的「事件」,那么可以将该房间的 $\\\\textit{roomId}$ 加入某一「数据结构」中;
\\n- 如果我们遇到一个表示询问的「事件」,那么需要在「数据结构」中寻找与 $\\\\textit{preferred}$ 最接近的 $\\\\textit{roomId}$。
\\n提示 $3$
\\n你能想出一种合适的「数据结构」吗?
\\n思路与算法
\\n我们使用「有序集合」作为提示中的「数据结构」。
\\n根据提示 $2$,我们将每一个房间以及询问对应一个「事件」,放入数组中进行降序排序。随后我们遍历这些「事件」:
\\n\\n
\\n- 如果我们遇到一个表示房间的「事件」,那么将该该房间的 $\\\\textit{roomId}$ 加入「有序集合」中;
\\n- 如果我们遇到一个表示询问的「事件」,那么答案即为「有序集合」中与询问的 $\\\\textit{preferred}$ 最接近的那个 $\\\\textit{roomId}$。在大部分语言的「有序集合」的 API 中,提供了例如「在有序集合中查找最小的大于等于 $x$ 的元素」或者「在有序集合中查找最小的严格大于 $x$ 的元素」,我们可以利用这些 API 找出「有序集合」中与 $\\\\textit{preferred}$ 最接近的两个元素,其中一个小于 $\\\\textit{preferred}$,另一个大于等于 $\\\\textit{preferred}$。通过比较这两个元素,我们即可得到该询问对应的答案。
\\n细节
\\n如果不同类型的「事件」的位置相同,那么我们应当按照先处理表示房间的「事件」,再处理表示询问的「事件」,这是因为房间的 $\\\\textit{size}$ 只要大于等于询问的 $\\\\textit{minSize}$ 就是满足要求的。
\\n代码
\\n###C++
\\n\\nstruct Event {\\n // 事件的类型,0 表示房间,1 表示询问\\n int type;\\n // 房间的 size 或者询问的 minSize\\n int size;\\n // 房间的 roomId 或者询问的 preferred\\n int id;\\n // 房间在数组 room 中的原始编号或者询问在数组 queries 中的原始编号\\n int origin;\\n \\n Event(int _type, int _size, int _id, int _origin): type(_type), size(_size), id(_id), origin(_origin) {}\\n \\n // 自定义比较函数,按照事件的 size 降序排序\\n // 如果 size 相同,优先考虑房间\\n bool operator< (const Event& that) const {\\n return size > that.size || (size == that.size && type < that.type);\\n }\\n};\\n\\nclass Solution {\\npublic:\\n vector<int> closestRoom(vector<vector<int>>& rooms, vector<vector<int>>& queries) {\\n int m = rooms.size();\\n int n = queries.size();\\n \\n vector<Event> events;\\n for (int i = 0; i < m; ++i) {\\n // 房间事件\\n events.emplace_back(0, rooms[i][1], rooms[i][0], i);\\n }\\n for (int i = 0; i < n; ++i) {\\n // 询问事件\\n events.emplace_back(1, queries[i][1], queries[i][0], i);\\n }\\n\\n sort(events.begin(), events.end());\\n vector<int> ans(n, -1);\\n // 存储房间 roomId 的有序集合\\n set<int> valid;\\n for (const auto& event: events) {\\n if (event.type == 0) {\\n // 房间事件,将 roomId 加入有序集合\\n valid.insert(event.id);\\n }\\n else {\\n // 询问事件\\n int dist = INT_MAX;\\n // 查找最小的大于等于 preferred 的元素\\n auto it = valid.lower_bound(event.id);\\n if (it != valid.end() && *it - event.id < dist) {\\n dist = *it - event.id;\\n ans[event.origin] = *it;\\n }\\n if (it != valid.begin()) {\\n // 查找最大的严格小于 preferred 的元素\\n it = prev(it);\\n if (event.id - *it <= dist) {\\n dist = event.id - *it;\\n ans[event.origin] = *it;\\n }\\n }\\n }\\n }\\n \\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Event:\\n \\"\\"\\"\\n op: 事件的类型,0 表示房间,1 表示询问\\n size: 房间的 size 或者询问的 minSize\\n idx: 房间的 roomId 或者询问的 preferred\\n origin: 房间在数组 room 中的原始编号或者询问在数组 queries 中的原始编号\\n \\"\\"\\"\\n def __init__(self, op: int, size: int, idx: int, origin: int):\\n self.op = op\\n self.size = size\\n self.idx = idx\\n self.origin = origin\\n\\n \\"\\"\\"\\n 自定义比较函数,按照事件的 size 降序排序\\n 如果 size 相同,优先考虑房间\\n \\"\\"\\"\\n def __lt__(self, other: \\"Event\\") -> bool:\\n return self.size > other.size or (self.size == other.size and self.op < other.op)\\n\\nclass Solution:\\n def closestRoom(self, rooms: List[List[int]], queries: List[List[int]]) -> List[int]:\\n n = len(queries)\\n\\n events = list()\\n for i, (roomId, size) in enumerate(rooms):\\n # 房间事件\\n events.append(Event(0, size, roomId, i))\\n\\n for i, (minSize, preferred) in enumerate(queries):\\n # 询问事件\\n events.append(Event(1, preferred, minSize, i))\\n\\n events.sort()\\n\\n ans = [-1] * n\\n # 存储房间 roomId 的有序集合\\n # 需要导入 sortedcontainers 库\\n valid = sortedcontainers.SortedList()\\n for event in events:\\n if event.op == 0:\\n # 房间事件,将 roomId 加入有序集合\\n valid.add(event.idx)\\n else:\\n # 询问事件\\n dist = float(\\"inf\\")\\n # 查找最小的大于等于 preferred 的元素\\n x = valid.bisect_left(event.idx)\\n if x != len(valid) and valid[x] - event.idx < dist:\\n dist = valid[x] - event.idx\\n ans[event.origin] = valid[x]\\n if x != 0:\\n # 查找最大的严格小于 preferred 的元素\\n x -= 1\\n if event.idx - valid[x] <= dist:\\n dist = event.idx - valid[x]\\n ans[event.origin] = valid[x]\\n \\n return ans\\n
###Java
\\n\\nclass Event implements Comparable<Event> {\\n int type, size, id, origin;\\n\\n public Event(int type, int size, int id, int origin) {\\n this.type = type;\\n this.size = size;\\n this.id = id;\\n this.origin = origin;\\n }\\n\\n @Override\\n public int compareTo(Event that) {\\n // 自定义比较函数,按照事件的 size 降序排序\\n // 如果 size 相同,优先考虑房间\\n if (this.size != that.size) {\\n return Integer.compare(that.size, this.size);\\n } else {\\n return Integer.compare(this.type, that.type);\\n }\\n }\\n}\\n\\nclass Solution {\\n public int[] closestRoom(int[][] rooms, int[][] queries) {\\n int m = rooms.length;\\n int n = queries.length;\\n\\n // 创建事件列表,存储房间和询问事件\\n List<Event> events = new ArrayList<>();\\n for (int i = 0; i < m; ++i) {\\n // 房间事件\\n events.add(new Event(0, rooms[i][1], rooms[i][0], i));\\n }\\n for (int i = 0; i < n; ++i) {\\n // 询问事件\\n events.add(new Event(1, queries[i][1], queries[i][0], i));\\n }\\n // 对事件列表进行排序\\n Collections.sort(events);\\n int[] ans = new int[n];\\n Arrays.fill(ans, -1);\\n // 使用 TreeSet 存储房间 roomId 的有序集合\\n TreeSet<Integer> valid = new TreeSet<>();\\n\\n for (Event event : events) {\\n if (event.type == 0) {\\n // 房间事件,将 roomId 加入有序集合\\n valid.add(event.id);\\n } else {\\n // 询问事件,查找最近的房间\\n Integer higher = valid.ceiling(event.id);\\n Integer lower = valid.floor(event.id);\\n int dist = Integer.MAX_VALUE;\\n\\n // 查找最小的大于等于 preferred 的元素\\n if (higher != null && higher - event.id < dist) {\\n dist = higher - event.id;\\n ans[event.origin] = higher;\\n }\\n // 查找最大的严格小于 preferred 的元素\\n if (lower != null && event.id - lower <= dist) {\\n ans[event.origin] = lower;\\n }\\n }\\n }\\n\\n return ans;\\n }\\n}\\n
###C#
\\n\\n// 定义事件类,实现 IComparable 接口用于排序\\nclass Event : IComparable<Event> {\\n public int Type, Size, Id, Origin;\\n public Event(int type, int size, int id, int origin) {\\n // 初始化事件的类型、size、id 和 origin\\n Type = type;\\n Size = size;\\n Id = id;\\n Origin = origin;\\n }\\n\\n public int CompareTo(Event that) {\\n // 自定义比较函数,按照事件的 size 降序排序\\n // 如果 size 相同,优先考虑房间\\n if (this.Size != that.Size) {\\n return that.Size.CompareTo(this.Size);\\n } else {\\n return this.Type.CompareTo(that.Type);\\n }\\n }\\n}\\n\\npublic class Solution {\\n public int[] ClosestRoom(int[][] rooms, int[][] queries) {\\n int m = rooms.Length;\\n int n = queries.Length;\\n\\n // 创建事件列表,存储房间和询问事件\\n List<Event> events = new List<Event>();\\n for (int i = 0; i < m; ++i) {\\n // 房间事件\\n events.Add(new Event(0, rooms[i][1], rooms[i][0], i));\\n }\\n for (int i = 0; i < n; ++i) {\\n // 询问事件\\n events.Add(new Event(1, queries[i][1], queries[i][0], i));\\n }\\n\\n // 对事件列表进行排序\\n events.Sort();\\n int[] ans = new int[n];\\n Array.Fill(ans, -1);\\n // 使用 SortedSet 存储房间 roomId 的有序集合\\n SortedSet<int> valid = new SortedSet<int>();\\n foreach (var ev in events) {\\n if (ev.Type == 0) {\\n // 房间事件,将 roomId 加入有序集合\\n valid.Add(ev.Id);\\n } else {\\n // 询问事件,查找最近的房间\\n int dist = int.MaxValue;\\n var ceiling = valid.GetViewBetween(ev.Id, int.MaxValue).Min;\\n if (ceiling != default && ceiling - ev.Id < dist) {\\n dist = ceiling - ev.Id;\\n ans[ev.Origin] = ceiling;\\n }\\n var floor = valid.GetViewBetween(0, ev.Id).Max;\\n if (floor != default && ev.Id - floor <= dist) {\\n ans[ev.Origin] = floor;\\n }\\n }\\n }\\n\\n return ans;\\n }\\n}\\n
###Rust
\\n\\nuse std::collections::BTreeSet;\\n\\n#[derive(Debug)]\\nstruct Event {\\n // 事件的类型,0 表示房间,1 表示询问\\n event_type: i32,\\n // 房间的 size 或者询问的 minSize\\n size: i32,\\n // 房间的 roomId 或者询问的 preferred\\n id: i32,\\n // 房间在数组 room 中的原始编号或者询问在数组 queries 中的原始编号\\n origin: usize,\\n}\\n\\nimpl Event {\\n fn new(event_type: i32, size: i32, id: i32, origin: usize) -> Self {\\n Event {\\n event_type,\\n size,\\n id,\\n origin,\\n }\\n }\\n}\\n\\n\\n// 自定义比较函数,按照事件的 size 降序排序\\n// 如果 size 相同,优先考虑房间\\nimpl PartialEq for Event {\\n fn eq(&self, other: &Self) -> bool {\\n self.size == other.size && self.event_type == other.event_type\\n }\\n}\\n\\nimpl Eq for Event {}\\n\\nimpl PartialOrd for Event {\\n fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {\\n Some(self.cmp(other))\\n }\\n}\\n\\nimpl Ord for Event {\\n fn cmp(&self, other: &Self) -> std::cmp::Ordering {\\n other\\n .size\\n .cmp(&self.size)\\n .then(self.event_type.cmp(&other.event_type))\\n }\\n}\\n\\n\\nimpl Solution {\\n pub fn closest_room(rooms: Vec<Vec<i32>>, queries: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut events = Vec::new();\\n for (i, room) in rooms.iter().enumerate() {\\n // 房间事件\\n events.push(Event::new(0, room[1], room[0], i));\\n }\\n for (i, query) in queries.iter().enumerate() {\\n // 询问事件\\n events.push(Event::new(1, query[1], query[0], i));\\n }\\n\\n // 对事件进行排序\\n events.sort();\\n // 用于存储房间的 roomId 的有序集合\\n let mut valid_rooms = BTreeSet::new();\\n let mut ans = vec![-1; queries.len()];\\n for event in events {\\n if event.event_type == 0 {\\n // 房间事件,将 roomId 加入有序集合\\n valid_rooms.insert(event.id);\\n } else {\\n // 询问事件\\n let mut dist = i32::MAX;\\n // 查找大于等于 preferred 的最小房间\\n if let Some(&ceiling) = valid_rooms.range(event.id..).next() {\\n if ceiling - event.id < dist {\\n dist = ceiling - event.id;\\n ans[event.origin] = ceiling;\\n }\\n }\\n\\n // 查找小于 preferred 的最大房间\\n if let Some(&floor) = valid_rooms.range(..event.id).next_back() {\\n if event.id - floor <= dist {\\n ans[event.origin] = floor;\\n }\\n }\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:离线算法 提示 $1$\\n\\n如果我们可以将给定的房间和询问重新排序,那么是否可以使得问题更加容易解决?\\n\\n提示 $2$\\n\\n我们可以将房间以及询问都看成一个「事件」,如果我们将这些「事件」按照大小(房间的 $\\\\textit{size}$ 或者询问的 $\\\\textit{minSize}$)进行降序排序,那么:\\n\\n如果我们遇到一个表示房间的「事件」,那么可以将该房间的 $\\\\textit{roomId}$ 加入某一「数据结构」中;\\n如果我们遇到一个表示询问的「事件」,那么需要在「数据结构」中寻找与 $\\\\textit{preferred}$ 最接近的…","guid":"https://leetcode.cn/problems/closest-room//solution/zui-jin-de-fang-jian-by-leetcode-solutio-9ylf","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-02T01:46:50.416Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"座位预约管理系统","url":"https://leetcode.cn/problems/seat-reservation-manager//solution/zuo-wei-yu-yue-guan-li-xi-tong-by-leetco-wj45","content":"- \\n
\\n时间复杂度:$O((n+q) \\\\log (n+q))$,其中 $n$ 是数组 $\\\\textit{rooms}$ 的长度,$q$ 是数组 $\\\\textit{queries}$ 的长度。「事件」的数量为 $n+q = O(n+q)$,因此需要 $O((n+q) \\\\log (n+q))$ 的时间进行排序。在这之后,我们需要 $O(n+q)$ 的时间对事件进行遍历,而对有序集合进行操作的单次时间复杂度为 $O(\\\\log n)$,总时间复杂度为 $O((n+q) \\\\log n)$,在渐进意义下小于前者,可以忽略。
\\n- \\n
\\n空间复杂度:$O(n+q)$。我们需要 $O(n+q)$ 的空间存储「事件」,以及 $O(n)$ 的空间分配给有序集合,因此总空间复杂度为 $O(n+q)$。
\\n方法一:最小堆(优先队列)
\\n提示 $1$
\\n考虑 $\\\\textit{reserve}$ 与 $\\\\textit{unreserve}$ 方法对应的需求。什么样的数据结构能够在较好的时间复杂度下支持这些操作?
\\n思路与算法
\\n根据 提示 $1$,假设我们使用数据结构 $\\\\textit{available}$ 来维护所有可以预约的座位,我们需要分析 $\\\\textit{reserve}$ 与 $\\\\textit{unreserve}$ 的具体需求:
\\n\\n
\\n- \\n
\\n对于 $\\\\textit{reserve}$ 方法,我们需要弹出并返回 $\\\\textit{available}$ 中的最小元素;
\\n- \\n
\\n对于 $\\\\textit{unreserve}$ 方法,我们需要将 $\\\\textit{seatNumber}$ 添加至 $\\\\textit{available}$ 中。
\\n因此我们可以使用二叉堆实现的优先队列作为 $\\\\textit{available}$。对于一个最小堆,可以在 $O(\\\\log n)$ 的时间复杂度内完成单次「添加元素」与「弹出最小值」的操作。
\\n需要注意的是,$\\\\texttt{Python}$ 的二叉堆默认为最小堆,但 $\\\\texttt{C++}$ 的二叉堆默认为最大堆。
\\n代码
\\n###C++
\\n\\nclass SeatManager {\\npublic:\\n vector<int> available;\\n\\n SeatManager(int n) {\\n for (int i = 1; i <= n; ++i){\\n available.push_back(i);\\n }\\n }\\n \\n int reserve() {\\n pop_heap(available.begin(), available.end(), greater<int>());\\n int tmp = available.back();\\n available.pop_back();\\n return tmp;\\n }\\n \\n void unreserve(int seatNumber) {\\n available.push_back(seatNumber);\\n push_heap(available.begin(), available.end(), greater<int>());\\n }\\n};\\n
###Python
\\n\\nfrom heapq import heappush, heappop\\n\\nclass SeatManager:\\n\\n def __init__(self, n: int):\\n self.available = list(range(1, n + 1))\\n\\n def reserve(self) -> int:\\n return heappop(self.available)\\n\\n def unreserve(self, seatNumber: int) -> None:\\n heappush(self.available, seatNumber)\\n\\n
###Java
\\n\\nclass SeatManager {\\n private PriorityQueue<Integer> available;\\n public SeatManager(int n) {\\n available = new PriorityQueue<>();\\n for (int i = 1; i <= n; i++) {\\n available.offer(i);\\n }\\n }\\n \\n public int reserve() {\\n return available.poll();\\n }\\n \\n public void unreserve(int seatNumber) {\\n available.offer(seatNumber);\\n }\\n}\\n
###C
\\n\\n#define MIN_QUEUE_SIZE 64\\n\\ntypedef struct Element {\\n int data[1];\\n} Element;\\n\\ntypedef bool (*compare)(const void *, const void *);\\n\\ntypedef struct PriorityQueue {\\n Element *arr;\\n int capacity;\\n int queueSize;\\n compare lessFunc;\\n} PriorityQueue;\\n\\nElement *createElement(int x) {\\n Element *obj = (Element *)malloc(sizeof(Element));\\n obj->data[0] = x;\\n return obj;\\n}\\n\\nstatic bool less(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] > e2->data[0];\\n}\\n\\nstatic bool greater(const void *a, const void *b) {\\n Element *e1 = (Element *)a;\\n Element *e2 = (Element *)b;\\n return e1->data[0] < e2->data[0];\\n}\\n\\nstatic void memswap(void *m1, void *m2, size_t size){\\n unsigned char *a = (unsigned char*)m1;\\n unsigned char *b = (unsigned char*)m2;\\n while (size--) {\\n *b ^= *a ^= *b ^= *a;\\n a++;\\n b++;\\n }\\n}\\n\\nstatic void swap(Element *arr, int i, int j) {\\n memswap(&arr[i], &arr[j], sizeof(Element));\\n}\\n\\nstatic void down(Element *arr, int size, int i, compare cmpFunc) {\\n for (int k = 2 * i + 1; k < size; k = 2 * k + 1) {\\n if (k + 1 < size && cmpFunc(&arr[k], &arr[k + 1])) {\\n k++;\\n }\\n if (cmpFunc(&arr[k], &arr[(k - 1) / 2])) {\\n break;\\n }\\n swap(arr, k, (k - 1) / 2);\\n }\\n}\\n\\nPriorityQueue *createPriorityQueue(compare cmpFunc) {\\n PriorityQueue *obj = (PriorityQueue *)malloc(sizeof(PriorityQueue));\\n obj->capacity = MIN_QUEUE_SIZE;\\n obj->arr = (Element *)malloc(sizeof(Element) * obj->capacity);\\n obj->queueSize = 0;\\n obj->lessFunc = cmpFunc;\\n return obj;\\n}\\n\\nvoid heapfiy(PriorityQueue *obj) {\\n for (int i = obj->queueSize / 2 - 1; i >= 0; i--) {\\n down(obj->arr, obj->queueSize, i, obj->lessFunc);\\n }\\n}\\n\\nvoid enQueue(PriorityQueue *obj, Element *e) {\\n // we need to alloc more space, just twice space size\\n if (obj->queueSize == obj->capacity) {\\n obj->capacity *= 2;\\n Element *mem = (Element *)malloc(sizeof(Element) * obj->capacity);\\n memcpy(mem, obj->arr, sizeof(Element) * obj->queueSize);\\n free(obj->arr);\\n obj->arr = mem;\\n }\\n memcpy(&obj->arr[obj->queueSize], e, sizeof(Element));\\n for (int i = obj->queueSize; i > 0 && obj->lessFunc(&obj->arr[(i - 1) / 2], &obj->arr[i]); i = (i - 1) / 2) {\\n swap(obj->arr, i, (i - 1) / 2);\\n }\\n obj->queueSize++;\\n}\\n\\nElement* deQueue(PriorityQueue *obj) {\\n swap(obj->arr, 0, obj->queueSize - 1);\\n down(obj->arr, obj->queueSize - 1, 0, obj->lessFunc);\\n Element *e = &obj->arr[obj->queueSize - 1];\\n obj->queueSize--;\\n return e;\\n}\\n\\nbool isEmpty(const PriorityQueue *obj) {\\n return obj->queueSize == 0;\\n}\\n\\nElement* front(const PriorityQueue *obj) {\\n if (obj->queueSize == 0) {\\n return NULL;\\n } else {\\n return &obj->arr[0];\\n }\\n}\\n\\nvoid clear(PriorityQueue *obj) {\\n obj->queueSize = 0;\\n}\\n\\nint size(const PriorityQueue *obj) {\\n return obj->queueSize;\\n}\\n\\nvoid freeQueue(PriorityQueue *obj) {\\n free(obj->arr);\\n free(obj);\\n}\\n\\ntypedef struct {\\n PriorityQueue *available;\\n} SeatManager;\\n\\nSeatManager* seatManagerCreate(int n) {\\n SeatManager *obj = (SeatManager *)malloc(sizeof(SeatManager));\\n obj->available = createPriorityQueue(less);\\n Element e;\\n for (int i = 1; i <= n; i++) {\\n e.data[0] = i;\\n enQueue(obj->available, &e);\\n }\\n return obj;\\n}\\n\\nint seatManagerReserve(SeatManager* obj) {\\n int ret = front(obj->available)->data[0];\\n deQueue(obj->available);\\n return ret;\\n}\\n\\nvoid seatManagerUnreserve(SeatManager* obj, int seatNumber) {\\n Element e;\\n e.data[0] = seatNumber;\\n enQueue(obj->available, &e);\\n}\\n\\nvoid seatManagerFree(SeatManager* obj) {\\n freeQueue(obj->available);\\n free(obj);\\n}\\n
###Go
\\n\\ntype SeatManager struct {\\n available *Heap\\n}\\n\\nfunc Constructor(n int) SeatManager {\\n h := &Heap{}\\nheap.Init(h)\\nfor i := 1; i <= n; i++ {\\nheap.Push(h, i)\\n}\\nreturn SeatManager{available: h}\\n}\\n\\nfunc (this *SeatManager) Reserve() int {\\n return heap.Pop(this.available).(int)\\n}\\n\\nfunc (this *SeatManager) Unreserve(seatNumber int) {\\n heap.Push(this.available, seatNumber)\\n}\\n\\ntype Heap []int\\n\\nfunc (h Heap) Len() int { return len(h) }\\nfunc (h Heap) Less(i, j int) bool { return h[i] < h[j] }\\nfunc (h Heap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\n\\nfunc (h *Heap) Push(x interface{}) {\\n*h = append(*h, x.(int))\\n}\\n\\nfunc (h *Heap) Pop() interface{} {\\nold := *h\\nn := len(old)\\nx := old[n-1]\\n*h = old[0 : n-1]\\nreturn x\\n}\\n
###JavaScript
\\n\\nvar SeatManager = function(n) {\\n this.available = new MinPriorityQueue();\\n for (let i = 1; i <= n; i++) {\\n this.available.enqueue(i, i);\\n }\\n};\\n\\nSeatManager.prototype.reserve = function() {\\n return this.available.dequeue().element;\\n};\\n\\nSeatManager.prototype.unreserve = function(seatNumber) {\\n this.available.enqueue(seatNumber);\\n};\\n
###TypeScript
\\n\\nclass SeatManager {\\n private available;\\n\\n constructor(n: number) {\\n this.available = new MinPriorityQueue();\\n for (let i = 1; i <= n; i++) {\\n this.available.enqueue(i, i);\\n }\\n }\\n\\n reserve(): number {\\n return this.available.dequeue().element;\\n }\\n\\n unreserve(seatNumber: number): void {\\n this.available.enqueue(seatNumber);\\n }\\n}\\n
###Rust
\\n\\nuse std::collections::BinaryHeap;\\nuse std::cmp::Reverse;\\n\\nstruct SeatManager {\\n available: BinaryHeap<Reverse<i32>>,\\n}\\n\\nimpl SeatManager {\\n fn new(n: i32) -> Self {\\n let mut available = BinaryHeap::new();\\n for i in 1..=n {\\n available.push(Reverse(i));\\n }\\n SeatManager { available }\\n }\\n \\n fn reserve(&mut self) -> i32 {\\n self.available.pop().unwrap().0\\n }\\n \\n fn unreserve(&mut self, seat_number: i32) {\\n self.available.push(Reverse(seat_number));\\n }\\n}\\n
###Cangjie
\\n\\nclass PriorityQueue<T> {\\n private let queue: ArrayList<T>\\n private let comparator: (T, T) -> Bool\\n\\n init(comp: (T, T) -> Bool) {\\n this.queue = ArrayList<T>()\\n this.comparator = comp\\n }\\n\\n public func enqueue(item: T): Unit {\\n this.queue.append(item)\\n var i = queue.size - 1\\n while (i > 0 && this.comparator(this.queue.get((i - 1) / 2).getOrThrow(), \\n this.queue.get(i).getOrThrow())) {\\n swap(i, (i - 1) / 2)\\n i = (i - 1) / 2\\n }\\n }\\n\\n public func dequeue(): Option<T> {\\n if (isEmpty()) {\\n return None\\n }\\n swap(0, this.queue.size - 1)\\n let val = this.queue.remove(this.queue.size - 1)\\n down(0)\\n return Some(val)\\n }\\n\\n public func peek(): Option<T> {\\n return this.queue.get(0)\\n }\\n\\n public func isEmpty(): Bool {\\n return this.queue.isEmpty()\\n }\\n\\n public func size(): Int64 {\\n return this.queue.size\\n }\\n\\n private func swap(i: Int64, j: Int64) {\\n let val = this.queue.get(i).getOrThrow()\\n this.queue.set(i, this.queue.get(j).getOrThrow())\\n this.queue.set(j, val)\\n }\\n\\n private func down(i: Int64): Unit {\\n var k = 2 * i + 1\\n while (k < this.queue.size) {\\n if (k + 1 < this.queue.size && this.comparator(this.queue.get(k).getOrThrow(), \\n this.queue.get(k + 1).getOrThrow())) {\\n k++\\n }\\n if (this.comparator(this.queue.get(k).getOrThrow(),\\n this.queue.get((k - 1) / 2).getOrThrow())) {\\n break\\n }\\n swap(k, (k - 1) / 2)\\n k = k * 2 + 1\\n }\\n }\\n}\\n\\nclass SeatManager {\\n let available: PriorityQueue<Int64>\\n init(n: Int64) {\\n let maxComparator = {a: Int64, b: Int64 => a > b }\\n this.available = PriorityQueue<Int64>(maxComparator)\\n for (i in 1..= n) {\\n this.available.enqueue(i)\\n }\\n }\\n \\n func reserve(): Int64 {\\n return this.available.dequeue().getOrThrow()\\n }\\n \\n func unreserve(seatNumber: Int64): Unit {\\n this.available.enqueue(seatNumber)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:最小堆(优先队列) 提示 $1$\\n\\n考虑 $\\\\textit{reserve}$ 与 $\\\\textit{unreserve}$ 方法对应的需求。什么样的数据结构能够在较好的时间复杂度下支持这些操作?\\n\\n思路与算法\\n\\n根据 提示 $1$,假设我们使用数据结构 $\\\\textit{available}$ 来维护所有可以预约的座位,我们需要分析 $\\\\textit{reserve}$ 与 $\\\\textit{unreserve}$ 的具体需求:\\n\\n对于 $\\\\textit{reserve}$ 方法,我们需要弹出并返回 $\\\\textit{available…","guid":"https://leetcode.cn/problems/seat-reservation-manager//solution/zuo-wei-yu-yue-guan-li-xi-tong-by-leetco-wj45","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-02T01:38:18.899Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"你永远可以相信分块算法","url":"https://leetcode.cn/problems/closest-room//solution/ni-yong-yuan-ke-yi-xiang-xin-fen-kuai-su-88a4","content":"- \\n
\\n时间复杂度:$O(n + (q_1 + q_2)\\\\log n)$,其中 $n$ 为座位的数量,$q_1$ 为 $\\\\textit{reserve}$ 操作的次数,$q_2$ 为 $\\\\textit{unreserve}$ 的次数。初始化的时间复杂度为 $O(n)$,二叉堆实现的优先队列单次添加元素与弹出最小值操作的复杂度均为 $O(\\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(n)$,二叉堆的空间开销。
\\n核心思路:
\\n\\n
\\n- 将房间按照 $\\\\textit{size}$ 升序排序后,每 $\\\\text{BLOCK_SIZE}$ 个分成一个块,块内存放原始数据 $(\\\\textit{id}, \\\\textit{size})$ 以及排好序的 $\\\\textit{id}$;
\\n- 对于每个询问进行暴力求解。按照逆序遍历每一个块:\\n
\\n\\n
\\n- 如果当前块原始数据的第一个 $\\\\textit{size}$ 大于等于 $\\\\textit{minSize}$,说明块内所有的房间都满足要求,我们在 $\\\\textit{id}$ 上二分查找即可;
\\n- 否则,我们遍历块内的原始数据,对每个房间依次进行判断并更新答案。并且这说明再之前的块都是不满足要求的,可以忽略。
\\n记 $n$ 为房间数量,$q$ 为询问数量,$s$ 为 $\\\\text{BLOCK_SIZE}$。上述做法的时间复杂度为
\\n$$
\\n
\\nO(n\\\\log s) + O(q((n/s)\\\\log s+s))
\\n$$取 $s=\\\\sqrt{n}$ 一定是没啥问题的,时间复杂度为 $O((n+q\\\\sqrt{n})\\\\log n)$。
\\n
\\n在代码里面直接取常数 $s=300$ 即可。###C++
\\n\\nstruct Block {\\n // block 中最小的房间 size\\n int min_size = INT_MAX;\\n // block 中的房间 id\\n vector<int> ids;\\n // 原始数据\\n vector<pair<int, int>> origin;\\n\\n Block() = default;\\n\\n // 加入一个房间\\n void insert(int id, int size) {\\n origin.emplace_back(id, size);\\n ids.push_back(id);\\n min_size = min(min_size, size);\\n }\\n\\n // 添加完所有房间后,将房间 id 排序,便于后续二分\\n void sort() {\\n ::sort(ids.begin(), ids.end());\\n }\\n\\n // 封装一下二分函数,找最小的大于等于它的\\n int geq(int preferred) {\\n auto it = lower_bound(ids.begin(), ids.end(), preferred);\\n return it == ids.end() ? -1 : *it;\\n }\\n\\n // 封装一下二分函数,找最大的严格小于它的\\n int lt(int preferred) {\\n auto it = upper_bound(ids.begin(), ids.end(), preferred);\\n return it == ids.begin() ? -1 : *prev(it);\\n }\\n};\\n\\nclass Solution {\\nprivate:\\n static constexpr int BLOCK_SIZE = 300;\\n\\npublic:\\n vector<int> closestRoom(vector<vector<int>>& rooms, vector<vector<int>>& queries) {\\n int n = rooms.size();\\n int q = queries.size();\\n\\n // 按照 size 升序排序\\n sort(rooms.begin(), rooms.end(), [](const auto& r1, const auto& r2) { return r1[1] < r2[1]; });\\n\\n // 每 BLOCK_SIZE 个房间放进一个 block\\n vector<Block> blocks;\\n for (int i = 0; i < n; ++i) {\\n if (i % BLOCK_SIZE == 0) {\\n blocks.emplace_back();\\n }\\n blocks.back().insert(rooms[i][0], rooms[i][1]);\\n }\\n for (auto&& block: blocks) {\\n block.sort();\\n }\\n\\n vector<int> ans(q, -1);\\n for (int i = 0; i < q; ++i) {\\n int preferred = queries[i][0];\\n int minsize = queries[i][1];\\n int mindiff = INT_MAX;\\n for (auto it_block = blocks.rbegin(); it_block != blocks.rend(); ++it_block) {\\n auto&& block = *it_block;\\n // block 中最小 size 的房间大于等于 minsize,整个 block 都可以选择\\n if (block.origin[0].second >= minsize) {\\n int c1 = block.geq(preferred);\\n if (c1 != -1 && c1 - preferred < mindiff) {\\n mindiff = c1 - preferred;\\n ans[i] = c1;\\n }\\n int c2 = block.lt(preferred);\\n if (c2 != -1 && preferred - c2 <= mindiff) {\\n mindiff = preferred - c2;\\n ans[i] = c2;\\n }\\n }\\n else {\\n // 只有部分都可以选择,遍历一下所有的房间\\n auto&& rooms = block.origin;\\n for (auto it_room = rooms.rbegin(); it_room != rooms.rend(); ++it_room) {\\n if (it_room->second >= minsize) {\\n int diff = abs(it_room->first - preferred);\\n if (diff < mindiff || (diff == mindiff && it_room->first < ans[i])) {\\n mindiff = diff;\\n ans[i] = it_room->first;\\n }\\n }\\n else {\\n break;\\n }\\n }\\n // 再之前的 block 一定都严格小于 minsize,可以直接退出\\n break;\\n }\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\n","description":"核心思路: 将房间按照 $\\\\textit{size}$ 升序排序后,每 $\\\\text{BLOCK_SIZE}$ 个分成一个块,块内存放原始数据 $(\\\\textit{id}, \\\\textit{size})$ 以及排好序的 $\\\\textit{id}$;\\n对于每个询问进行暴力求解。按照逆序遍历每一个块:\\n如果当前块原始数据的第一个 $\\\\textit{size}$ 大于等于 $\\\\textit{minSize}$,说明块内所有的房间都满足要求,我们在 $\\\\textit{id}$ 上二分查找即可;\\n否则,我们遍历块内的原始数据,对每个房间依次进行判断并更新答案…","guid":"https://leetcode.cn/problems/closest-room//solution/ni-yong-yuan-ke-yi-xiang-xin-fen-kuai-su-88a4","author":"zerotrac2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-01T16:51:59.602Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"离线处理+TreeSet JAVA双百","url":"https://leetcode.cn/problems/closest-room//solution/chi-xian-xun-wen-javashuang-bai-by-garet-rxws","content":"class Block:\\n def __init__(self):\\n # block 中最小的房间 size\\n self.min_size = float(\\"inf\\")\\n # block 中的房间 id\\n self.ids = list()\\n # 原始数据\\n self.origin = list()\\n\\n # 加入一个房间\\n def insert(self, idx: int, size: int):\\n self.origin.append((idx, size))\\n self.ids.append(idx)\\n self.min_size = min(self.min_size, size)\\n\\n # 添加完所有房间后,将房间 id 排序,便于后续二分\\n def sort(self):\\n self.ids.sort()\\n\\n # 封装一下二分函数,找最小的大于等于它的\\n def geq(self, preferred: int) -> int:\\n _ids = self.ids\\n\\n it = bisect_left(_ids, preferred);\\n return -1 if it == len(_ids) else _ids[it]\\n\\n # 封装一下二分函数,找最大的严格小于它的\\n def lt(self, preferred: int) -> int:\\n _ids = self.ids\\n\\n it = bisect_right(_ids, preferred)\\n return -1 if it == 0 else _ids[it - 1]\\n\\n\\nclass Solution:\\n def closestRoom(self, rooms: List[List[int]], queries: List[List[int]]) -> List[int]:\\n BLOCK_SIZE = 300\\n \\n # 按照 size 升序排序\\n rooms.sort(key=lambda room: room[1])\\n\\n # 每 BLOCK_SIZE 个房间放进一个 block\\n blocks = list()\\n for i, (roomid, size) in enumerate(rooms):\\n if i % BLOCK_SIZE == 0:\\n blocks.append(Block())\\n blocks[-1].insert(roomid, size)\\n \\n for block in blocks:\\n block.sort()\\n\\n ans = [-1] * len(queries)\\n for i, (preferred, minsize) in enumerate(queries):\\n mindiff = float(\\"inf\\")\\n\\n for block in blocks[::-1]:\\n rooms = block.origin\\n # block 中最小 size 的房间大于等于 minsize,整个 block 都可以选择\\n if rooms[0][1] >= minsize:\\n c1 = block.geq(preferred)\\n if c1 != -1 and c1 - preferred < mindiff:\\n mindiff = c1 - preferred\\n ans[i] = c1\\n \\n c2 = block.lt(preferred)\\n if c2 != -1 and preferred - c2 <= mindiff:\\n mindiff = preferred - c2\\n ans[i] = c2\\n else:\\n # 只有部分都可以选择,遍历一下所有的房间\\n for room in rooms[::-1]:\\n if room[1] >= minsize:\\n diff = abs(room[0] - preferred)\\n if diff < mindiff or (diff == mindiff and room[0] < ans[i]):\\n mindiff = diff\\n ans[i] = room[0]\\n else:\\n break\\n # 再之前的 block 一定都严格小于 minsize,可以直接退出\\n break\\n \\n return ans\\n
\\n
\\n","description":"class Solution { public int[] closestRoom(int[][] rooms, int[][] queries) {\\n int[][] q=new int[queries.length][3];\\n for(int i=0;iclass Solution {\\n public int[] closestRoom(int[][] rooms, int[][] queries) {\\n int[][] q=new int[queries.length][3];\\n for(int i=0;i<q.length;i++){\\n q[i][0]=queries[i][0];\\n q[i][1]=queries[i][1];\\n q[i][2]=i;\\n }\\n Arrays.sort(q,(x,y)->y[1]-x[1]);\\n Arrays.sort(rooms,(x,y)->y[1]-x[1]);\\n TreeSet<Integer> set=new TreeSet<>();\\n int idx=0;\\n int[] ans=new int[q.length];\\n Arrays.fill(ans,-1);\\n for(int i=0;i<q.length;i++){\\n while(idx<rooms.length&&rooms[idx][1]>=q[i][1]){\\n set.add(rooms[idx][0]);\\n idx+=1;\\n }\\n Integer a=set.floor(q[i][0]);\\n Integer b=set.ceiling(q[i][0]);\\n if(a==null&&b==null){\\n ans[q[i][2]]=-1;\\n }else if(b==null||a==null){\\n ans[q[i][2]]=(a==null)?b:a;\\n }else{\\n ans[q[i][2]]=((q[i][0]-a)<=(b-q[i][0]))?a:b;\\n }\\n }\\n return ans;\\n }\\n}\\n
1845.座位预约管理系统\\n \\n\\nhttps://leetcode-cn.com/problems/seat-reservation-manager/solution/5731zuo-wei-yu-yue-guan-li-xi-tong-jian-tlmzu/
\\n难度:中等
\\n题目:
\\n请你设计一个管理 n 个座位预约的系统,座位编号从 1 到 n 。
\\n请你实现 SeatManager 类:
\\n\\n
\\n- SeatManager(int n) 初始化一个 SeatManager 对象,它管理从 1 到 n 编号的 n 个座位。所有座位初始都是可预约的。
\\n- int reserve() 返回可以预约座位的 最小编号 ,此座位变为不可预约。
\\n- void unreserve(int seatNumber) 将给定编号 seatNumber 对应的座位变成可以预约。
\\n提示:
\\n\\n
\\n- 1 <= n <= 105
\\n- 1 <= seatNumber <= n
\\n- 每一次对reserve的调用,题目保证至少存在一个可以预约的座位。
\\n- 每一次对unreserve的调用,题目保证seatNumber在调用函数前都是被预约状态。
\\n- 对reserve 和unreserve的调用总共不超过105次。
\\n示例:
\\n\\n示例 1:\\n\\n输入:\\n[\\"SeatManager\\", \\"reserve\\", \\"reserve\\", \\"unreserve\\", \\"reserve\\", \\"reserve\\", \\"reserve\\", \\"reserve\\", \\"unreserve\\"]\\n[[5], [], [], [2], [], [], [], [], [5]]\\n输出:\\n[null, 1, 2, null, 2, 3, 4, 5, null]\\n\\n解释:\\nSeatManager seatManager = new SeatManager(5); // 初始化 SeatManager ,有 5 个座位。\\nseatManager.reserve(); // 所有座位都可以预约,所以返回最小编号的座位,也就是 1 。\\nseatManager.reserve(); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。\\nseatManager.unreserve(2); // 将座位 2 变为可以预约,现在可预约的座位为 [2,3,4,5] 。\\nseatManager.reserve(); // 可以预约的座位为 [2,3,4,5] ,返回最小编号的座位,也就是 2 。\\nseatManager.reserve(); // 可以预约的座位为 [3,4,5] ,返回最小编号的座位,也就是 3 。\\nseatManager.reserve(); // 可以预约的座位为 [4,5] ,返回最小编号的座位,也就是 4 。\\nseatManager.reserve(); // 唯一可以预约的是座位 5 ,所以返回 5 。\\nseatManager.unreserve(5); // 将座位 5 变为可以预约,现在可预约的座位为 [5] 。\\n
分析
\\n类似这种简单类设计题,在日常面试还是比较多的。
\\n
\\n这道题我们使用小根堆,解题简直不要太简单。解题:
\\n###python
\\n\\nimport heapq\\n\\nclass SeatManager:\\n\\n def __init__(self, n: int):\\n self.ret = [i for i in range(1, n + 1)]\\n\\n def reserve(self):\\n return heapq.heappop(self.ret)\\n\\n def unreserve(self, seatNumber):\\n heapq.heappush(self.ret, seatNumber)\\n
欢迎关注我的公众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。
\\n有喜欢力扣刷题的小伙伴可以加我微信(King_Uranus)互相鼓励,共同进步,一起玩转超级码力!
\\n我的个人博客:https://qingfengpython.cn
\\n力扣解题合集:https://github.com/BreezePython/AlgorithmMarkdown
\\n","description":"https://leetcode-cn.com/problems/seat-reservation-manager/solution/5731zuo-wei-yu-yue-guan-li-xi-tong-jian-tlmzu/ 难度:中等\\n\\n题目:\\n\\n请你设计一个管理 n 个座位预约的系统,座位编号从 1 到 n 。\\n\\n请你实现 SeatManager 类:\\n\\nSeatManager(int n) 初始化一个 SeatManager 对象,它管理从 1 到 n 编号的 n 个座位。所有座位初始都是可预约的。\\nint reserve() 返回可以预约座位的…","guid":"https://leetcode.cn/problems/seat-reservation-manager//solution/5731zuo-wei-yu-yue-guan-li-xi-tong-jian-tlmzu","author":"qingfengpython","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-05-01T16:17:47.815Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】一题三解:「枚举」&「双指针」&「费马平方和」","url":"https://leetcode.cn/problems/sum-of-square-numbers//solution/gong-shui-san-xie-yi-ti-san-jie-mei-ju-s-7qi5","content":"基本分析
\\n根据等式 $a^2 + b^2 = c$,可得知
\\na
和b
的范围均为 $[0,\\\\sqrt{c}]$。基于此我们会有以下几种做法。
\\n
\\n枚举
\\n我们可以枚举
\\na
,边枚举边检查是否存在b
使得等式成立。这样做的复杂度为 $O(\\\\sqrt{c})$。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n int max = (int)Math.sqrt(c);\\n for (int a = 0; a <= max; a++) {\\n int b = (int)Math.sqrt(c - a * a);\\n if (a * a + b * b == c) return true;\\n }\\n return false;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(\\\\sqrt{c})$
\\n- 空间复杂度:$O(1)$
\\n
\\n双指针
\\n由于
\\na
和b
的范围均为 $[0,\\\\sqrt{c}]$,因此我们可以使用「双指针」在 $[0,\\\\sqrt{c}]$ 范围进行扫描:\\n
\\n- $a^2 + b^2 == c$ : 找到符合条件的
\\na
和b
,返回 $true$- $a^2 + b^2 < c$ : 当前值比目标值要小,
\\na++
- $a^2 + b^2 > c$ : 当前值比目标值要大,
\\nb--
代码:
\\n###Java
\\n\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n long a = 0, b = (long) Math.sqrt(c);\\n while (a <= b) {\\n long cur = a * a + b * b;\\n if (cur == c) {\\n return true;\\n } else if (cur > c) {\\n b--;\\n } else {\\n a++;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(\\\\sqrt{c})$
\\n- 空间复杂度:$O(1)$
\\n
\\n费马平方和
\\n费马平方和 : 奇质数能表示为两个平方数之和的充分必要条件是该质数被 4 除余 1 。
\\n翻译过来就是:当且仅当一个自然数的质因数分解中,满足
\\n4k+3
形式的质数次方数均为偶数时,该自然数才能被表示为两个平方数之和。因此我们对
\\nc
进行质因数分解,再判断满足4k+3
形式的质因子的次方数是否均为偶数即可。代码:
\\n###Java
\\n\\npublic class Solution {\\n public boolean judgeSquareSum(int c) {\\n for (int i = 2, cnt = 0; i * i <= c; i++, cnt = 0) {\\n while (c % i == 0 && ++cnt > 0) c /= i;\\n if (i % 4 == 3 && cnt % 2 != 0) return false;\\n }\\n return c % 4 != 3;\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(\\\\sqrt{c})$
\\n- 空间复杂度:$O(1)$
\\n
\\n我猜你问
\\n\\n
\\n- 三种解法复杂度都一样,哪个才是最优解呀?
\\n前两套解法是需要「真正掌握」的,而「费马平方和」更多的是作为一种拓展。
\\n你会发现从复杂度上来说,其实「费马平方和」并没有比前两种解法更好,但由于存在对
\\nc
除质因数操作,导致「费马平方和」实际表现效果要优于同样复杂度的其他做法。但这仍然不成为我们必须掌握「费马平方和」的理由。三者从复杂度上来说,都是 $O(\\\\sqrt{c})$ 算法,不存在最不最优的问题。
\\n\\n
\\n- 是否有关于「费马平方和」的证明呢?
\\n想要看 莱昂哈德·欧拉 对于「费马平方和」的证明在 这里,我这里直接引用 费马 本人的证明:
\\n\\n\\n我确实发现了一个美妙的证明,但这里空白太小写不下。
\\n\\n
\\n- 我就是要学「费马平方和」,有没有可读性更高的代码?
\\n有的,在这里。喜欢的话可以考虑背过:
\\n###Java
\\n\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n long a = 0, b = (long) Math.sqrt(c);\\n while (a <= b) {\\n long cur = a * a + b * b;\\n if (cur == c) {\\n return true;\\n } else if (cur > c) {\\n b--;\\n } else {\\n a++;\\n }\\n }\\n return false;\\n }\\n}\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解
\\n","description":"基本分析 根据等式 $a^2 + b^2 = c$,可得知 a 和 b 的范围均为 $[0,\\\\sqrt{c}]$。\\n\\n基于此我们会有以下几种做法。\\n\\n枚举\\n\\n我们可以枚举 a ,边枚举边检查是否存在 b 使得等式成立。\\n\\n这样做的复杂度为 $O(\\\\sqrt{c})$。\\n\\n代码:\\n\\n###Java\\n\\nclass Solution {\\n public boolean judgeSquareSum(int c) {\\n int max = (int)Math.sqrt(c);\\n for (int a = 0; a <= max; a++)…","guid":"https://leetcode.cn/problems/sum-of-square-numbers//solution/gong-shui-san-xie-yi-ti-san-jie-mei-ju-s-7qi5","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-28T00:45:07.922Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"为什么双指针不会错过正确答案?双指针的本质。","url":"https://leetcode.cn/problems/sum-of-square-numbers//solution/shuang-zhi-zhen-de-ben-zhi-er-wei-ju-zhe-ebn3","content":"解题思路:
\\n看了官方题解的双指针算法,不免产生一个疑问,假设初始化时,左指针
\\nlow = 0
,右指针high = sqrt(c)
。
\\n为什么 $low^2+high^2<c$ 时,要让low++
而不是high++
呢?或者说为什么让low++
可以保证不错过正确答案呢?
\\n同理,为什么$low^2+high^2>c$ 时,要让high--
而不是low--
呢?或者说为什么让high--
可以保证不错过正确答案呢?
\\n其实我们可以把双指针的过程看成在一个矩阵中搜寻的过程。举个例子,c = 18
,初始化low = 0
,high = 4
,那么看如下矩阵:\\n
矩阵沿主对角线对称 $(low<=high)$,其中的元素表示 $low^2+high^2$ 的值,黄色格子表示当前的 $low^2+high^2$ ,绿色格子表示目标
\\nc
,low++
相当于让黄色格子下移,high--
则相当于让黄色格子左移。这里矩阵的性质和搜索的过程其实和240. 搜索二维矩阵 II是一样的。每一列从上到下升序,每一行从左到右升序。
\\n查找的过程具有如下性质:\\n
\\n- 初始化时黄色格子必定在矩阵的右上角。
\\n- 每次比较 $low^2+high^2$ 和 $c$ 可以排除矩阵的一行或一列。
\\n如下图:
\\n
\\n由于以上性质,当前黄色格子的上方和右边的所有元素一定是已经被排除的,所以黄色格子在搜索过程中只有两种行为:
\\n\\n
\\n- 小于 $c$ :左边的元素都小于当前元素,只能下移,相当于
\\nlow++
。此时排除的是黄色格子以及左边同行的元素,都小于 $c$ ,所以不会错过正确答案。- 大于 $c$ :下面的元素都大于当前元素,只能左移,相当于
\\nhigh--
。此时排除的是黄色格子以及下方同列的元素,都大于 $c$ ,所以不会错过正确答案。如此一来,双指针这个操作就十分自然了。
\\n\\nclass Solution:\\n def judgeSquareSum(self, c: int) -> bool:\\n low, high = 0, int(c**0.5)\\n while low<=high:\\n sumOf = low*low+high*high\\n if sumOf==c: return True\\n elif sumOf<c: low += 1\\n else: high -= 1\\n return False\\n
矩阵的行数和列数都是$\\\\sqrt{c}$,所以时间复杂度为 $O(\\\\sqrt{c})$。
\\n推广到一般情况:双指针的本质
\\n我们可以看到,二维矩阵实际上是枚举了所有可能的组合。本题中 $c=a^2+b^2$ ,可以简单推广到 $c=f(a,b)$ ,只要 $f(a,b)$ 满足随 $a, b$ 递增,且要搜索的是一个有序序列,就可以使用这种双指针法,只要把二维矩阵中的元素换成 $f(a,b)$ 就可以了。例如15. 三数之和。
\\n但是有些双指针的题目并不好用二维矩阵来解释,比如11. 盛最多水的容器。二维矩阵中每个格子,代表的是两个指针的一种组合,从这里我们可以看到,双指针更一般的本质,其实就是在每次移动指针的时候,排除掉一部分不包含正确答案的组合,只要能证明这点,就可以使用双指针。
\\n例如本题,如果 $low^2+high^2>c$ ,要进行
\\nhigh--
,本质就是排除掉下标区间 $[low, high-1]$ 中每个元素,和当前high
所指元素的组合,由于该区间中每个元素 $x$ 都大于等于 $low$,都满足 $x^2+high^2\\\\ge low^2+high^2>c$, 所以可以证明排除掉的集合不包含正确答案。low++
同理,也就证明了双指针的正确性。所以用双指针要考虑两个问题:每次移动指针排除掉了哪些情况?这些情况中是否可能包含正确答案?
\\n","description":"解题思路: 看了官方题解的双指针算法,不免产生一个疑问,假设初始化时,左指针low = 0,右指针high = sqrt(c)。\\n 为什么 $low^2+high^2c$ 时,要让high--而不是low--呢?或者说为什么让high--可以保证不错过正确答案呢?\\n 其实我们可以把双指针的过程看成在一个矩阵中搜寻的过程。举个例子,c = 18,初始化low = 0,high = 4,那么看如下矩阵:\\n\\n矩阵沿主对角线对称…","guid":"https://leetcode.cn/problems/sum-of-square-numbers//solution/shuang-zhi-zhen-de-ben-zhi-er-wei-ju-zhe-ebn3","author":"ggt55ng6","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-27T19:37:52.900Z","media":[{"url":"https://pic.leetcode-cn.com/1619566844-VUoSAs-image.png","type":"photo","width":379,"height":321,"blurhash":"LLRp8-~qt6-;?bM{RjWBxvNFoMRj"},{"url":"https://pic.leetcode-cn.com/1623185846-LKBLqt-image.png","type":"photo","width":1351,"height":681,"blurhash":"LURW0a%Loh~q?bj]RkRiaxoff9az"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶の相信科学系列】详解为何能转换为序列 DP 问题","url":"https://leetcode.cn/problems/largest-divisible-subset//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-0a3jc","content":" 基本分析
\\n根据题意:对于符合要求的「整除子集」中的任意两个值,必然满足「较大数」是「较小数」的倍数。
\\n数据范围是 $10^3$,我们不可能采取获取所有子集,再检查子集是否合法的爆搜解法。
\\n通常「递归」做不了,我们就往「递推」方向去考虑。
\\n由于存在「整除子集」中任意两个值必然存在倍数/约数关系的性质,我们自然会想到对
\\nnums
进行排序,然后从集合nums
中从大到小进行取数,每次取数只考虑当前决策的数是否与「整除子集」中的最后一个数成倍数关系即可。这时候你可能会想枚举每个数作为「整除子集」的起点,然后从前往后遍历一遍,每次都将符合「与当前子集最后一个元素成倍数」关系的数加入答案。
\\n举个🌰,假设有原数组
\\n[1,2,4,8]
,“或许”我们期望的决策过程是:\\n
\\n- 遍历到数字
\\n1
,此时「整除子集」为空,加到「整除子集」中;- 遍历到数字
\\n2
,与「整除子集」的最后一个元素(1
)成倍数关系,加到「整除子集」中;- 遍历到数字
\\n4
,与「整除子集」的最后一个元素(2
)成倍数关系,自然也与2
之前的元素成倍数关系,加到「整除子集」中;- 遍历到数字
\\n8
,与「整除子集」的最后一个元素(4
)成倍数关系,自然也与4
之前的元素成倍数关系,加到「整除子集」中。但这样的做法只能够确保得到「合法解」,无法确保得到的是「最长整除子集」。
\\n当时担心本题数据太弱,上述错误的解法也能够通过,所以还特意实现了一下,还好被卡住了(🤣
\\n同时也得到这个反例:
\\n[9,18,54,90,108,180,360,540,720]
,如果按照我们上述逻辑,我们得到的是[9,18,54,108,540]
答案(长度为 5),但事实上存在更长的「整除子集」:[9,18,90,180,360,720]
(长度为 6)。其本质是因为同一个数的不同倍数之间不存在必然的「倍数/约数关系」,而只存在「具有公约数」的性质,这会导致我们「模拟解法」错过最优解。
\\n比如上述 🌰,
\\n54
&90
和18
存在倍数关系,但两者本身不存在倍数关系。因此当我们决策到某一个数
\\nnums[i]
时(nums
已排好序),我们无法直接将nums[i]
直接接在符合「约数关系」的、最靠近位置i
的数后面,而是要检查位置i
前面的所有符合「约数关系」的位置,找一个已经形成「整除子集」长度最大的数。换句话说,当我们对
\\nnums
排好序并从前往后处理时,在处理到nums[i]
时,我们希望知道位置i
之前的下标已经形成的「整除子集」长度是多少,然后从中选一个最长的「整除子集」,将nums[i]
接在后面(前提是符合「倍数关系」)。
\\n动态规划
\\n基于上述分析,我们不难发现这其实是一个序列 DP 问题:某个状态的转移依赖于与前一个状态的关系。即
\\nnums[i]
能否接在nums[j]
后面,取决于是否满足nums[i] % nums[j] == 0
条件。可看做是「最长上升子序列」问题的变形题。
\\n定义 $f[i]$ 为考虑前
\\ni
个数字,且以第i
个数为结尾的最长「整除子集」长度。我们不失一般性的考虑任意位置
\\ni
,存在两种情况:\\n
\\n- 如果在
\\ni
之前找不到符合条件nums[i] % nums[j] == 0
的位置j
,那么nums[i]
不能接在位置i
之前的任何数的后面,只能自己独立作为「整除子集」的第一个数,此时状态转移方程为 $f[i] = 1$;- 如果在
\\ni
之前能够找到符合条件的位置j
,则取所有符合条件的f[j]
的最大值,代表如果希望找到以nums[i]
为结尾的最长「整除子集」,需要将nums[i]
接到符合条件的最长的nums[j]
后面,此时状态转移方程为 $f[i] = f[j] + 1$。同时由于我们需要输出具体方案,需要额外使用
\\ng[]
数组来记录每个状态是由哪个状态转移而来。定义 $g[i]$ 为记录 $f[i]$ 是由哪个下标的状态转移而来,如果 $f[i] = f[j] + 1$, 则有 $g[i] = j$。
\\n对于求方案数的题目,多开一个数组来记录状态从何转移而来是最常见的手段。
\\n当我们求得所有的状态值之后,可以对
\\nf[]
数组进行遍历,取得具体的最长「整除子集」长度和对应下标,然后使用g[]
数组进行回溯,取得答案。代码(感谢 @Benhao 和 @007 两位同学提供的其他语言版本):
\\n###Java
\\n\\nclass Solution {\\n public List<Integer> largestDivisibleSubset(int[] nums) {\\n Arrays.sort(nums);\\n int n = nums.length;\\n int[] f = new int[n];\\n int[] g = new int[n];\\n for (int i = 0; i < n; i++) {\\n // 至少包含自身一个数,因此起始长度为 1,由自身转移而来\\n int len = 1, prev = i;\\n for (int j = 0; j < i; j++) {\\n if (nums[i] % nums[j] == 0) {\\n // 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」\\n if (f[j] + 1 > len) {\\n len = f[j] + 1;\\n prev = j;\\n }\\n }\\n }\\n // 记录「最终长度」&「从何转移而来」\\n f[i] = len;\\n g[i] = prev;\\n }\\n \\n // 遍历所有的 f[i],取得「最大长度」和「对应下标」\\n int max = -1, idx = -1;\\n for (int i = 0; i < n; i++) {\\n if (f[i] > max) {\\n idx = i;\\n max = f[i];\\n }\\n }\\n\\n // 使用 g[] 数组回溯出具体方案\\n List<Integer> ans = new ArrayList<>();\\n while (ans.size() != max) {\\n ans.add(nums[idx]);\\n idx = g[idx];\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> largestDivisibleSubset(vector<int>& nums) {\\n sort(nums.begin(), nums.end());\\n int n = nums.size();\\n vector<int> f(n, 0);\\n vector<int> g(n ,0);\\n \\n for(int i = 0; i < n; i++) {\\n // 至少包含自身一个数,因此起始长度为 1,由自身转移而来\\n int len = 1, prev = i;\\n for(int j = 0; j < i; j++) {\\n if(nums[i] % nums[j] == 0) {\\n // 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」\\n if(f[j] + 1 > len) {\\n len = f[j] + 1;\\n prev = j;\\n }\\n }\\n }\\n f[i] = len;\\n g[i] = prev;\\n }\\n\\n // 遍历所有的 f[i],取得「最大长度」和「对应下标」\\n int idx = max_element(f.begin(), f.end()) - f.begin();\\n int max = f[idx];\\n\\n // 使用 g[] 数组回溯出具体方案\\n vector<int> ans;\\n while(ans.size() != max) {\\n ans.push_back(nums[idx]);\\n idx = g[idx];\\n }\\n return ans;\\n }\\n};\\n
###Python3
\\n\\nclass Solution:\\n def largestDivisibleSubset(self, nums: List[int]) -> List[int]:\\n nums.sort()\\n n = len(nums)\\n f, g = [0] * n, [0] * n\\n for i in range(n):\\n # 至少包含自身一个数,因此起始长度为 1,由自身转移而来\\n length, prev = 1, i\\n for j in range(i):\\n if nums[i] % nums[j] == 0:\\n # 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」\\n if f[j] + 1 > length:\\n length = f[j] + 1\\n prev = j\\n # 记录「最终长度」&「从何转移而来」\\n f[i] = length\\n g[i] = prev\\n\\n # 遍历所有的 f[i],取得「最大长度」和「对应下标」\\n max_len = idx = -1\\n for i in range(n):\\n if f[i] > max_len:\\n idx = i\\n max_len = f[i]\\n \\n # 使用 g[] 数组回溯出具体方案\\n ans = []\\n while len(ans) < max_len:\\n ans.append(nums[idx])\\n idx = g[idx]\\n ans.reverse()\\n return ans\\n
###Go
\\n\\nfunc largestDivisibleSubset(nums []int) []int {\\nsort.Ints(nums)\\nn := len(nums)\\n// 定义 f[i] 为考虑前 i 个数字,且以第 i 个数为结尾的最长「整除子集」长度。\\nf := make([]int, n)\\n// 定义 g[i] 为记录 f[i] 是由哪个下标的状态转移而来,如果 f[i] = f[j] + 1, 则有 g[i] = j。\\ng := make([]int, n)\\n\\nfor i := 0; i < n; i++ {\\n// 至少包含自身一个数,因此起始长度为 1,由自身转移而来\\nl := 1\\nprev := i\\nfor j := 0; j < i; j++ {\\nif nums[i]%nums[j] == 0 {\\n// 如果能接在更长的序列后面,则更新「最大长度」&「从何转移而来」\\nif f[j]+1 > l {\\nl = f[j] + 1\\nprev = j\\n}\\n}\\n}\\n\\n// 记录「最终长度」&「从何转移而来」\\nf[i] = l\\ng[i] = prev\\n}\\n\\n// 遍历所有的 f[i],取得「最大长度」和「对应下标」\\nmax := -1\\nidx := -1\\nfor i := 0; i < n; i++ {\\nif f[i] > max {\\nidx = i\\nmax = f[i]\\n}\\n}\\n\\n// 使用 g[] 数组回溯出具体方案\\nvar ans []int\\nfor len(ans) != max {\\nans = append(ans, nums[idx])\\n\\nidx = g[idx]\\n}\\nreturn ans\\n}\\n
\\n
\\n- 时间复杂度:$O(n^2)$
\\n- 空间复杂度:$O(n)$
\\n
\\n证明
\\n之所以上述解法能够成立,问题能够转化为「最长上升子序列(LIS)」问题进行求解,本质是利用了「全序关系」中的「可传递性」。
\\n在 LIS 问题中,我们是利用了「关系运算符 $\\\\geqslant$ 」的传递性,因此当我们某个数
\\na
能够接在b
后面,只需要确保 $a \\\\geqslant b$ 成立,即可确保a
大于等于b
之前的所有值。那么同理,如果我们想要上述解法成立,我们还需要证明如下内容:
\\n\\n
\\n- \\n
\\n「倍数/约数关系」具有传递性
\\n由于我们将
\\nnums[i]
往某个数字后面接时(假设为nums[j]
),只检查了其与nums[j]
的关系,并没有去检查nums[i]
与nums[j]
之前的数值是否具有「倍数/约数关系」。换句话说,我们只确保了最终答案
\\n[a1, a2, a3, ..., an]
相邻两数值之间具有「倍数/约数关系」,并不明确任意两值之间具有「倍数/约数关系」。因此需要证得由 $a | b$ 和 $b | c$,可推导出 $a | c$ 的传递性:
\\n由 $a | b$ 可得 $b = x * a$
\\n
\\n由 $b | c$ 可得 $c = y * b$最终有 $c = y * b = y * x * a$,由于 $x$ 和 $y$ 都是整数,因此可得 $a | c$。
\\n得证「倍数/约数关系」具有传递性。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解
\\n","description":"基本分析 根据题意:对于符合要求的「整除子集」中的任意两个值,必然满足「较大数」是「较小数」的倍数。\\n\\n数据范围是 $10^3$,我们不可能采取获取所有子集,再检查子集是否合法的爆搜解法。\\n\\n通常「递归」做不了,我们就往「递推」方向去考虑。\\n\\n由于存在「整除子集」中任意两个值必然存在倍数/约数关系的性质,我们自然会想到对 nums 进行排序,然后从集合 nums 中从大到小进行取数,每次取数只考虑当前决策的数是否与「整除子集」中的最后一个数成倍数关系即可。\\n\\n这时候你可能会想枚举每个数作为「整除子集」的起点,然后从前往后遍历一遍,每次都将符合…","guid":"https://leetcode.cn/problems/largest-divisible-subset//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-0a3jc","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-23T00:52:08.058Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最大整除子集","url":"https://leetcode.cn/problems/largest-divisible-subset//solution/zui-da-zheng-chu-zi-ji-by-leetcode-solut-t4pz","content":"前言
\\n首先需要理解什么叫「整除子集」。根据题目的描述,如果一个所有元素互不相同的集合中的任意元素存在整除关系,就称为整除子集。为了得到「最大整除子集」,我们需要考虑如何从一个小的整除子集扩充成为更大的整除子集。
\\n根据整除关系具有传递性,即如果 $a\\\\big|b$,并且 $b\\\\big|c$,那么 $a\\\\big|c$,可知:
\\n\\n
\\n- \\n
\\n如果整数 $a$ 是整除子集 $S_1$ 的最小整数 $b$ 的约数(即 $a\\\\big|b$),那么可以将 $a$ 添加到 $S_1$ 中得到一个更大的整除子集;
\\n- \\n
\\n如果整数 $c$ 是整除子集 $S_2$ 的最大整数 $d$ 的倍数(即 $d\\\\big|c$),那么可以将 $c$ 添加到 $S_2$ 中得到一个更大的整除子集。
\\n这两点揭示了当前问题状态转移的特点,因此可以使用动态规划的方法求解。题目只要求我们得到多个目标子集的其中一个,根据求解动态规划问题的经验,我们需要将子集的大小定义为状态,然后根据结果倒推得到一个目标子集。事实上,当前问题和使用动态规划解决的经典问题「300. 最长递增子序列」有相似之处。
\\n方法一:动态规划
\\n根据前言的分析,我们需要将输入数组 $\\\\textit{nums}$ 按照升序排序,以便获得一个子集的最小整数或者最大整数。又根据动态规划的「无后效性」状态设计准则,我们需要将状态定义成「某个元素必须选择」。
\\n状态定义:$\\\\textit{dp}[i]$ 表示在输入数组 $\\\\textit{nums}$ 升序排列的前提下,以 $\\\\textit{nums}[i]$ 为最大整数的「整除子集」的大小(在这种定义下 $\\\\textit{nums}[i]$ 必须被选择)。
\\n状态转移方程:枚举 $j = 0 \\\\ldots i-1$ 的所有整数 $\\\\textit{nums}[j]$,如果 $\\\\textit{nums}[j]$ 能整除 $\\\\textit{nums}[i]$,说明 $\\\\textit{nums}[i]$ 可以扩充在以 $\\\\textit{nums}[j]$ 为最大整数的整除子集里成为一个更大的整除子集。
\\n初始化:由于 $\\\\textit{nums}[i]$ 必须被选择,因此对于任意 $i = 0 \\\\ldots n-1$,初始的时候 $\\\\textit{dp}[i] = 1$,这里 $n$ 是输入数组的长度。
\\n输出:由于最大整除子集不一定包含 $\\\\textit{nums}$ 中最大的整数,所以我们需要枚举所有的 $\\\\textit{dp}[i]$,选出最大整除子集的大小 $\\\\textit{maxSize}$,以及该最大子集中的最大整数 $\\\\textit{maxVal}$。按照如下方式倒推获得一个目标子集:
\\n\\n
\\n- \\n
\\n倒序遍历数组 $\\\\textit{dp}$,直到找到 $\\\\textit{dp}[i] = \\\\textit{maxSize}$ 为止,把此时对应的 $\\\\textit{nums}[i]$ 加入结果集,此时 $\\\\textit{maxVal} = \\\\textit{nums}[i]$;
\\n- \\n
\\n然后将 $\\\\textit{maxSize}$ 的值减 $1$,继续倒序遍历找到 $\\\\textit{dp}[i] = \\\\textit{maxSize}$,且 $\\\\textit{nums}[i]$ 能整除 $\\\\textit{maxVal}$ 的 $i$ 为止,将此时的 $\\\\textit{nums}[i]$ 加入结果集,$\\\\textit{maxVal}$ 更新为此时的 $num[i]$;
\\n- \\n
\\n重复上述操作,直到 $\\\\textit{maxSize}$ 的值变成 $0$,此时的结果集即为一个目标子集。
\\n下面用一个例子说明如何得到最大整除子集。假设输入数组为 $[2,4,7,8,9,12,16,18]$(已经有序),得到的动态规划表格如下:
\\n\\n\\n
\\n\\n \\n\\n\\n$\\\\textit{nums}$ \\n$2$ \\n$4$ \\n$7$ \\n$8$ \\n$9$ \\n$12$ \\n$16$ \\n$20$ \\n\\n \\n\\n$\\\\textit{dp}$ \\n$1$ \\n$2$ \\n$1$ \\n$3$ \\n$1$ \\n$3$ \\n$4$ \\n$3$ \\n得到最大整除子集的做法如下:
\\n\\n
\\n- \\n
\\n根据 $\\\\textit{dp}$ 的计算结果,$\\\\textit{maxSize}=4$,$\\\\textit{maxVal}=16$,因此大小为 $4$ 的最大整除子集包含的最大整数为 $16$;
\\n- \\n
\\n然后查找大小为 $3$ 的最大整除子集,我们看到 $8$ 和 $12$ 对应的状态值都是 $3$,最大整除子集一定包含 $8$,这是因为 $8 \\\\big| 16$;
\\n- \\n
\\n然后查找大小为 $2$ 的最大整除子集,我们看到 $4$ 对应的状态值是 $2$,最大整除子集一定包含 $4$;
\\n- \\n
\\n然后查找大小为 $1$ 的最大整除子集,我们看到 $2$ 对应的状态值是 $1$,最大整除子集一定包含 $2$。
\\n通过这样的方式,我们就找到了满足条件的某个最大整除子集 $[16,8,4,2]$。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public List<Integer> largestDivisibleSubset(int[] nums) {\\n int len = nums.length;\\n Arrays.sort(nums);\\n\\n // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数\\n int[] dp = new int[len];\\n Arrays.fill(dp, 1);\\n int maxSize = 1;\\n int maxVal = dp[0];\\n for (int i = 1; i < len; i++) {\\n for (int j = 0; j < i; j++) {\\n // 题目中说「没有重复元素」很重要\\n if (nums[i] % nums[j] == 0) {\\n dp[i] = Math.max(dp[i], dp[j] + 1);\\n }\\n }\\n\\n if (dp[i] > maxSize) {\\n maxSize = dp[i];\\n maxVal = nums[i];\\n }\\n }\\n\\n // 第 2 步:倒推获得最大子集\\n List<Integer> res = new ArrayList<Integer>();\\n if (maxSize == 1) {\\n res.add(nums[0]);\\n return res;\\n }\\n \\n for (int i = len - 1; i >= 0 && maxSize > 0; i--) {\\n if (dp[i] == maxSize && maxVal % nums[i] == 0) {\\n res.add(nums[i]);\\n maxVal = nums[i];\\n maxSize--;\\n }\\n }\\n return res;\\n }\\n}\\n
###JavaScript
\\n\\nvar largestDivisibleSubset = function(nums) {\\n const len = nums.length;\\n nums.sort((a, b) => a - b);\\n\\n // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数\\n const dp = new Array(len).fill(1);\\n let maxSize = 1;\\n let maxVal = dp[0];\\n for (let i = 1; i < len; i++) {\\n for (let j = 0; j < i; j++) {\\n // 题目中说「没有重复元素」很重要\\n if (nums[i] % nums[j] === 0) {\\n dp[i] = Math.max(dp[i], dp[j] + 1);\\n }\\n }\\n\\n if (dp[i] > maxSize) {\\n maxSize = dp[i];\\n maxVal = nums[i];\\n }\\n }\\n\\n // 第 2 步:倒推获得最大子集\\n const res = [];\\n if (maxSize === 1) {\\n res.push(nums[0]);\\n return res;\\n }\\n \\n for (let i = len - 1; i >= 0 && maxSize > 0; i--) {\\n if (dp[i] === maxSize && maxVal % nums[i] === 0) {\\n res.push(nums[i]);\\n maxVal = nums[i];\\n maxSize--;\\n }\\n }\\n return res;\\n};\\n
###go
\\n\\nfunc largestDivisibleSubset(nums []int) (res []int) {\\n sort.Ints(nums)\\n\\n // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数\\n n := len(nums)\\n dp := make([]int, n)\\n for i := range dp {\\n dp[i] = 1\\n }\\n maxSize, maxVal := 1, 1\\n for i := 1; i < n; i++ {\\n for j, v := range nums[:i] {\\n if nums[i]%v == 0 && dp[j]+1 > dp[i] {\\n dp[i] = dp[j] + 1\\n }\\n }\\n if dp[i] > maxSize {\\n maxSize, maxVal = dp[i], nums[i]\\n }\\n }\\n\\n if maxSize == 1 {\\n return []int{nums[0]}\\n }\\n\\n // 第 2 步:倒推获得最大子集\\n for i := n - 1; i >= 0 && maxSize > 0; i-- {\\n if dp[i] == maxSize && maxVal%nums[i] == 0 {\\n res = append(res, nums[i])\\n maxVal = nums[i]\\n maxSize--\\n }\\n }\\n return\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> largestDivisibleSubset(vector<int>& nums) {\\n int len = nums.size();\\n sort(nums.begin(), nums.end());\\n\\n // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数\\n vector<int> dp(len, 1);\\n int maxSize = 1;\\n int maxVal = dp[0];\\n for (int i = 1; i < len; i++) {\\n for (int j = 0; j < i; j++) {\\n // 题目中说「没有重复元素」很重要\\n if (nums[i] % nums[j] == 0) {\\n dp[i] = max(dp[i], dp[j] + 1);\\n }\\n }\\n\\n if (dp[i] > maxSize) {\\n maxSize = dp[i];\\n maxVal = nums[i];\\n }\\n }\\n\\n // 第 2 步:倒推获得最大子集\\n vector<int> res;\\n if (maxSize == 1) {\\n res.push_back(nums[0]);\\n return res;\\n }\\n\\n for (int i = len - 1; i >= 0 && maxSize > 0; i--) {\\n if (dp[i] == maxSize && maxVal % nums[i] == 0) {\\n res.push_back(nums[i]);\\n maxVal = nums[i];\\n maxSize--;\\n }\\n }\\n return res;\\n }\\n};\\n
###C
\\n\\nint cmp(int* a, int* b) {\\n return *a - *b;\\n}\\n\\nint* largestDivisibleSubset(int* nums, int numsSize, int* returnSize) {\\n int len = numsSize;\\n qsort(nums, numsSize, sizeof(int), cmp);\\n\\n // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数\\n int dp[len];\\n for (int i = 0; i < len; i++) {\\n dp[i] = 1;\\n }\\n int maxSize = 1;\\n int maxVal = dp[0];\\n for (int i = 1; i < len; i++) {\\n for (int j = 0; j < i; j++) {\\n // 题目中说「没有重复元素」很重要\\n if (nums[i] % nums[j] == 0) {\\n dp[i] = fmax(dp[i], dp[j] + 1);\\n }\\n }\\n\\n if (dp[i] > maxSize) {\\n maxSize = dp[i];\\n maxVal = nums[i];\\n }\\n }\\n\\n // 第 2 步:倒推获得最大子集\\n int* res = malloc(sizeof(int) * len);\\n *returnSize = 0;\\n if (maxSize == 1) {\\n res[(*returnSize)++] = nums[0];\\n return res;\\n }\\n\\n for (int i = len - 1; i >= 0 && maxSize > 0; i--) {\\n if (dp[i] == maxSize && maxVal % nums[i] == 0) {\\n res[(*returnSize)++] = nums[i];\\n maxVal = nums[i];\\n maxSize--;\\n }\\n }\\n return res;\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 为输入数组的长度。对数组 $\\\\textit{nums}$ 排序的时间复杂度为 $O(n \\\\log n)$,计算数组 $\\\\textit{dp}$ 元素的时间复杂度为 $O(n^2)$,倒序遍历得到一个目标子集,时间复杂度为 $O(n)$。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 为输入数组的长度。需要创建长度为 $n$ 的数组 $\\\\textit{dp}$。
\\n
\\n📚 好读书?读好书!让时间更有价值| 世界读书日
\\n4 月 22 日至 4 月 28 日,进入「学习」,完成页面右上角的「让时间更有价值」限时阅读任务,可获得「2021 读书日纪念勋章」。更多活动详情戳上方标题了解更多👆
\\n今日学习任务:
\\n\\n
\\n","description":"前言 首先需要理解什么叫「整除子集」。根据题目的描述,如果一个所有元素互不相同的集合中的任意元素存在整除关系,就称为整除子集。为了得到「最大整除子集」,我们需要考虑如何从一个小的整除子集扩充成为更大的整除子集。\\n\\n根据整除关系具有传递性,即如果 $a\\\\big|b$,并且 $b\\\\big|c$,那么 $a\\\\big|c$,可知:\\n\\n如果整数 $a$ 是整除子集 $S_1$ 的最小整数 $b$ 的约数(即 $a\\\\big|b$),那么可以将 $a$ 添加到 $S_1$ 中得到一个更大的整除子集;\\n\\n如果整数 $c$ 是整除子集 $S_2$ 的最大整数 $d…","guid":"https://leetcode.cn/problems/largest-divisible-subset//solution/zui-da-zheng-chu-zi-ji-by-leetcode-solut-t4pz","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-22T14:54:53.666Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】详解为何元素相同会导致 O(n),一起看清二分的本质 ...","url":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii//solution/gong-shui-san-xie-xiang-jie-wei-he-yuan-xtam4","content":"- 图解分治思想
\\n
\\n完成阅读 4.1 分而治之- 图解快速排序复杂度
\\n
\\n完成阅读 4.3 再谈大 O 表示法二分解法
\\n根据题意,我们知道,所谓的旋转其实就是「将某个下标前面的所有数整体移到后面,使得数组从整体有序变为分段有序」。
\\n但和 33. 搜索旋转排序数组 不同的是,本题元素并不唯一。
\\n这意味着我们无法直接根据与 $nums[0]$ 的大小关系,将数组划分为两段,即无法通过「二分」来找到旋转点。
\\n因为「二分」的本质是二段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。
\\n如果你有看过我 【宫水三叶】严格 O(logN),一起看清二分的本质 这篇题解,你应该很容易就理解上句话的意思。如果没有也没关系,我们可以先解决本题,在理解后你再去做 33. 搜索旋转排序数组,我认为这两题都是一样的,不存在先后关系。
\\n举个🌰,我们使用数据 [0,1,2,2,2,3,4,5] 来理解为什么不同的旋转点会导致「二段性丢失」:
\\n\\n
代码(感谢 @Alfredo、 @007 和 @天 三位同学提供的其他语言版本):
\\n###java
\\n\\nclass Solution {\\n public boolean search(int[] nums, int t) {\\n int n = nums.length;\\n int l = 0, r = n - 1;\\n // 恢复二段性\\n while (l < r && nums[0] == nums[r]) r--;\\n\\n // 第一次二分,找旋转点\\n while (l < r) {\\n int mid = l + r + 1 >> 1;\\n if (nums[mid] >= nums[0]) {\\n l = mid;\\n } else {\\n r = mid - 1;\\n }\\n }\\n \\n int idx = n;\\n if (nums[r] >= nums[0] && r + 1 < n) idx = r + 1;\\n\\n // 第二次二分,找目标值\\n int ans = find(nums, 0, idx - 1, t);\\n if (ans != -1) return true;\\n ans = find(nums, idx, n - 1, t);\\n return ans != -1;\\n }\\n int find(int[] nums, int l, int r, int t) {\\n while (l < r) {\\n int mid = l + r >> 1;\\n if (nums[mid] >= t) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return nums[r] == t ? r : -1;\\n }\\n}\\n
###c++
\\n\\nclass Solution {\\npublic:\\n bool search(vector<int>& nums, int t) {\\n int n = nums.size();\\n int l = 0, r = n - 1;\\n // 恢复二段性\\n while (l < r && nums[0] == nums[r]) r--;\\n\\n // 第一次二分,找旋转点\\n while (l < r) {\\n int mid = (l + r + 1) >> 1;\\n if (nums[mid] >= nums[0]) {\\n l = mid;\\n } else {\\n r = mid - 1;\\n }\\n }\\n \\n int idx = n;\\n if (nums[r] >= nums[0] && r + 1 < n) idx = r + 1;\\n\\n // 第二次二分,找目标值\\n int ans = find(nums, 0, idx - 1, t);\\n if (ans != -1) return true;\\n ans = find(nums, idx, n - 1, t);\\n return ans != -1;\\n }\\n\\n int find(vector<int>& nums, int l, int r, int t) {\\n while (l < r) {\\n int mid = l + r >> 1;\\n if (nums[mid] >= t) {\\n r = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return nums[r] == t ? r : -1;\\n }\\n};\\n
###golang
\\n\\nfunc search(nums []int, target int) bool {\\nn := len(nums)\\nleft := 0\\nright := n - 1\\n// 恢复二段性\\nfor left < right && nums[0] == nums[right] {\\nright--\\n}\\n\\n// 第一次二分,找旋转点\\nfor left < right {\\nmid := (left + right + 1) >> 1\\nif nums[mid] >= nums[0] {\\nleft = mid\\n} else {\\nright = mid - 1\\n}\\n}\\n\\nidx := n\\nif nums[right] >= nums[0] && right+1 < n {\\nidx = right + 1\\n}\\n// 第二次二分,找目标值\\nans := find(nums, 0, idx-1, target)\\nif ans != -1 {\\nreturn true\\n}\\nans = find(nums, idx, n-1, target)\\nreturn ans != -1\\n}\\nfunc find(nums []int, l int, r int, t int) int {\\nfor l < r {\\nmid := (l + r) >> 1\\nif nums[mid] >= t {\\nr = mid\\n} else {\\nl = mid + 1\\n}\\n}\\nif nums[r] == t {\\nreturn t\\n}\\nreturn -1\\n}\\n
###c#
\\n\\npublic class Solution {\\n public bool Search(int[] nums, int target)\\n {\\n int n = nums.Length;\\n int l = 0, r = n - 1;\\n while (l < r && nums[0] == nums[r]) r--;\\n\\n while (l < r)\\n {\\n int mid = (l + r + 1) >> 1;\\n if (nums[mid] >= nums[0])\\n {\\n l = mid;\\n }\\n else\\n {\\n r = mid - 1;\\n }\\n }\\n int idx = n;\\n if (nums[r] >= nums[0] && r + 1 < n) idx = r + 1;\\n\\n int ans = find(nums, 0, idx - 1, target);\\n if (ans != -1) return true;\\n ans = find(nums, idx, n - 1, target);\\n return ans != -1;\\n\\n }\\n int find(int[] nums, int l, int r, int t)\\n {\\n while (l < r)\\n {\\n int mid = (l + r ) >> 1;\\n if (nums[mid] >= t)\\n {\\n r = mid;\\n }\\n else\\n {\\n l = mid + 1;\\n }\\n }\\n return nums[r] == t ? r : -1;\\n }\\n}\\n
\\n
\\n- 时间复杂度:恢复二段性处理中,最坏的情况下(考虑整个数组都是同一个数)复杂度是 $O(n)$,而之后的找旋转点和目标值都是「二分」,复杂度为 $O(log{n})$。整体复杂度为 $O(n)$ 的。
\\n- 空间复杂度:$O(1)$。
\\n
\\n进阶
\\n如果真正理解「二分」的话,本题和 33. 搜索旋转排序数组 区别不大。
\\n建议大家在完成两题的基础上试试 面试题 10.03. 搜索旋转数组 。
\\n
\\n其他「二分」相关题解
\\n\\n
\\n- \\n
\\n二分模板
\\n
\\n29. 两数相除 : 二分 + 倍增乘法解法(含模板)- \\n
\\n二分本质 & 恢复二段性处理
\\n33. 搜索旋转排序数组(找目标值) : 严格 O(logN),一起看清二分的本质
\\n81. 搜索旋转排序数组 II(找目标值) : 详解为何元素相同会导致 O(n),一起看清二分的本质
\\n\\n\\n- \\n
\\n二分 check 函数如何确定
\\n
\\n34. 在排序数组中查找元素的第一个和最后一个位置 : 考察对「二分」的理解,以及 check 函数的「大于 小于」怎么写- \\n
\\n二分答案的题目
\\n
\\n1482. 制作 m 束花所需的最少天数 : 利用「二段性」找分割点,以及优化 check 函数
\\n1011. 在 D 天内送达包裹的能力 : 利用「二段性」找分割点,以及如何确定「二分范围」
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n如有不理解的地方,欢迎你在评论区给我留言,我都会逐一回复 ~
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解
\\n","description":"二分解法 根据题意,我们知道,所谓的旋转其实就是「将某个下标前面的所有数整体移到后面,使得数组从整体有序变为分段有序」。\\n\\n但和 33. 搜索旋转排序数组 不同的是,本题元素并不唯一。\\n\\n这意味着我们无法直接根据与 $nums[0]$ 的大小关系,将数组划分为两段,即无法通过「二分」来找到旋转点。\\n\\n因为「二分」的本质是二段性,并非单调性。只要一段满足某个性质,另外一段不满足某个性质,就可以用「二分」。\\n\\n如果你有看过我 【宫水三叶】严格 O(logN),一起看清二分的本质 这篇题解,你应该很容易就理解上句话的意思。如果没有也没关系,我们可以先解决本题…","guid":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii//solution/gong-shui-san-xie-xiang-jie-wei-he-yuan-xtam4","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-07T01:56:41.253Z","media":[{"url":"https://pic.leetcode-cn.com/1617852745-LoBNPK-image.png","type":"photo","width":2468,"height":1876,"blurhash":"LTO4h;}sIoX.-=xaWCaekEt2W?n%"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】关于「删除有序数组重复项」的通解","url":"https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii//solution/gong-shui-san-xie-guan-yu-shan-chu-you-x-glnq","content":"通用解法
\\n为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。
\\n对于此类问题,我们应该进行如下考虑:
\\n\\n
\\n- 由于是保留
\\nk
个相同数字,对于前k
个数字,我们可以直接保留- 对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第
\\nk
个元素进行比较,不相同则保留举个🌰,我们令
\\nk=2
,假设有如下样例[1,1,1,1,1,1,2,2,2,2,2,2,3]
\\n\\n
\\n- 首先我们先让前 2 位直接保留,得到 1,1
\\n- 对后面的每一位进行继续遍历,能够保留的前提是与当前位置的前面
\\nk
个元素不同(答案中的第一个 1),因此我们会跳过剩余的 1,将第一个 2 追加,得到 1,1,2- 继续这个过程,这时候是和答案中的第 2 个 1 进行对比,因此可以得到 1,1,2,2
\\n- 这时候和答案中的第 1 个 2 比较,只有与其不同的元素能追加到答案,因此剩余的 2 被跳过,3 被追加到答案:1,1,2,2,3
\\n代码(感谢 @Qian 、@宫水三叶的小迷妹 和 @007 三位同学提供的其他语言版本):
\\n###Java
\\n\\nclass Solution {\\n public int removeDuplicates(int[] nums) { \\n return process(nums, 2);\\n }\\n int process(int[] nums, int k) {\\n int u = 0; \\n for (int x : nums) {\\n if (u < k || nums[u - k] != x) nums[u++] = x;\\n }\\n return u;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int work(vector<int>& nums, int k) {\\n int len = 0;\\n for(auto num : nums)\\n if(len < k || nums[len-k] != num)\\n nums[len++] = num;\\n return len;\\n }\\n int removeDuplicates(vector<int>& nums) {\\n return work(nums, 2);\\n }\\n};\\n
###Python3
\\n\\nclass Solution:\\n def removeDuplicates(self, nums: List[int]) -> int:\\n def solve(k):\\n u = 0\\n for x in nums:\\n if u < k or nums[u - k] != x:\\n nums[u] = x\\n u += 1\\n return u\\n return solve(2)\\n
###Golang
\\n\\nfunc removeDuplicates(nums []int) int {\\nvar process func(k int) int\\nprocess = func(k int) int {\\nu := 0\\nfor _, v := range nums {\\nif u < k || nums[u-k] != v {\\nnums[u] = v\\nu++\\n}\\n}\\nreturn u\\n}\\nreturn process(2)\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(1)$
\\n
\\n其他
\\n这是一种针对「数据有序,相同元素保留
\\nk
位」问题更加本质的解法,该解法是从性质出发提炼的,利用了「数组有序 & 保留逻辑」两大主要性质。当你掌握这种通解之后,要解决 26. 删除有序数组中的重复项 ,只需要改上述代码一个数字即可(将相同数字保留 2 个修改为保留 1 个)。
\\n这种通解最早我也在 【宫水三叶】「双指针」&「通用」解法 讲过。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"通用解法 为了让解法更具有一般性,我们将原问题的「保留 2 位」修改为「保留 k 位」。\\n\\n对于此类问题,我们应该进行如下考虑:\\n\\n由于是保留 k 个相同数字,对于前 k 个数字,我们可以直接保留\\n对于后面的任意数字,能够保留的前提是:与当前写入的位置前面的第 k 个元素进行比较,不相同则保留\\n\\n举个🌰,我们令 k=2,假设有如下样例\\n\\n[1,1,1,1,1,1,2,2,2,2,2,2,3]\\n\\n首先我们先让前 2 位直接保留,得到 1,1\\n对后面的每一位进行继续遍历,能够保留的前提是与当前位置的前面 k 个元素不同(答案中的第一个 1…","guid":"https://leetcode.cn/problems/remove-duplicates-from-sorted-array-ii//solution/gong-shui-san-xie-guan-yu-shan-chu-you-x-glnq","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-06T01:23:55.785Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶の相信科学系列】同一贪心思路的两种求解方式","url":"https://leetcode.cn/problems/rabbits-in-forest//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-v17p5","content":"基本分析
\\n首先,兔子它不会说谎 (`・ω・´),因此我们可以得出以下结论:
\\n\\n
\\n- 同一颜色的兔子回答的数值必然是一样的
\\n- 但回答同样数值的,不一定就是同颜色兔子
\\n举个🌰,假如有 3 只白兔,每只白兔的回答必然都是 2(对应结论 1);但假如有兔子回答了数值 2,可能只是三只白兔,也可能是三只白兔和三只灰兔同时进行了回答(对应结论 2)。
\\n答案要我们求最少的兔子数量。
\\n不妨设有某种颜色的兔子 $m$ 只,它们回答的答案数值为 $cnt$,那么 $m$ 和 $cnt$ 满足什么关系?
\\n显然两者关系为 $m = cnt + 1$。
\\n但如果是在 $answers$ 数组里,回答 $cnt$ 的数量为 $t$ 的话呢?这时候我们需要分情况讨论:
\\n\\n
\\n- $t \\\\leqslant cnt + 1$ : 为达到「最少的兔子数量」,我们可以假定这 $t$ 只兔子为同一颜色,这样能够满足题意,同时不会导致「额外」兔子数量增加(颜色数量最少)。
\\n- $t > cnt + 1$ : 我们知道回答 $cnt$ 的兔子应该有 $cnt + 1$ 只。这时候说明有数量相同的不同颜色的兔子进行了回答。为达到「最少的兔子数量」,我们应当将 $t$ 分为若干种颜色,并尽可能让某一种颜色的兔子为 $cnt + 1$ 只,这样能够满足题意,同时不会导致「额外」兔子数量增加(颜色数量最少)。
\\n换句话说,我们应该让「同一颜色的兔子数量」尽量多,从而实现「总的兔子数量」最少。
\\n
\\n证明
\\n我们来证明一下,为什么这样的贪心思路是对的:
\\n基于上述分析,我们其实是在处理 $answers$ 数组中每一个 $cnt$ ,使得满足题意的前提下,数值 $cnt$ 带来的影响(总的兔子数量,或者说总的颜色数量)最小。
\\n首先 $answers$ 中的每个数都会导致我们总的兔子数量增加,因此我们应该「让 $answers$ 的数字尽可能变少」,也就是我们需要去抵消掉 $answers$ 中的一些数字。
\\n对于 $answers$ 中的某个 $cnt$ 而言(注意是某个 $cnt$,含义为一只兔子的回答),必然代表了有 $cnt + 1$ 只兔子,同时也代表了数值 $cnt$ 最多在 $answers$ 数组中出现 $cnt + 1$ 次(与其颜色相同的兔子都进行了回答)。
\\n这时候我们可以从数组中移走 $cnt + 1$ 个数值 $cnt$(如果有的话)。
\\n当每次处理 $cnt$ 的时候,我们都执行这样的抵消操作。最后得到的 $answers$ 数值数量必然最少(而固定),抵消完成后的 $answers$ 中的每个 $cnt$ 对答案的影响也固定(增加 $cnt + 1$),从而实现「总的兔子数量」最少。
\\n相反,如果不执行这样的操作的话,得到的 $answers$ 数值数量必然会更多,「总的兔子数量」也必然会更多,也必然不会比这样做法更优。
\\n
\\n模拟解法
\\n按照上述思路,我们可以先对 $answers$ 进行排序,然后根据遍历到某个 $cnt$ 时,将其对答案的影响应用到 $ans$ 中(
\\nans += cnt + 1
),并将后面的 $cnt$ 个 $cnt$ 进行忽略。代码(感谢 @Qian、@宫水三叶的小迷妹 和 @007 三位同学提供的其他语言版本):
\\n###Java
\\n\\nclass Solution {\\n public int numRabbits(int[] cs) {\\n Arrays.sort(cs);\\n int n = cs.length;\\n int ans = 0;\\n for (int i = 0; i < n; i++) {\\n int cnt = cs[i];\\n ans += cnt + 1;\\n // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 \\n int k = cnt;\\n while (k-- > 0 && i + 1 < n && cs[i] == cs[i + 1]) i++;\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int numRabbits(vector<int>& answers) {\\n sort(answers.begin(), answers.end());\\n int n = answers.size();\\n int ans = 0;\\n for(int i = 0; i < n; i++) {\\n int cnt = answers[i];\\n ans += cnt + 1;\\n // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」\\n int k = cnt;\\n while (k-- && i+1 < n && answers[i] == answers[i+1])\\n i++;\\n }\\n return ans;\\n }\\n};\\n
###Python3
\\n\\nclass Solution:\\n def numRabbits(self, answers: List[int]) -> int:\\n answers.sort()\\n i, ans, n = 0, 0, len(answers)\\n while i < n:\\n cur = answers[i]\\n ans += (cur + 1)\\n while cur > 0 and i + 1 < len(answers) and answers[i] == answers[i+1]:\\n cur -= 1\\n i += 1\\n i += 1\\n return ans\\n
###Golang
\\n\\nfunc numRabbits(answers []int) int {\\nsort.Ints(answers)\\nn := len(answers)\\nans := 0\\nfor i := 0; i < n; i++ {\\ncnt := answers[i]\\nans += cnt + 1\\n// 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 \\nk := cnt\\nfor k > 0 && i+1 < n && answers[i] == answers[i+1] {\\nk--\\ni++\\n}\\n}\\nreturn ans\\n}\\n
\\n
\\n- 时间复杂度:$O(n\\\\log{n})$
\\n- 空间复杂度:$O(1)$
\\n
\\n统计分配
\\n我们也可以先对所有出现过的数字进行统计,然后再对数值进行(颜色)分配。
\\n代码(感谢 @Qian 和 @007 两位同学提供的其他语言版本):
\\n###Java
\\n\\nclass Solution {\\n int N = 1009;\\n int[] counts = new int[N];\\n public int numRabbits(int[] cs) {\\n // counts[x] = cnt 代表在 cs 中数值 x 的数量为 cnt\\n for (int i : cs) counts[i]++;\\n int ans = counts[0];\\n for (int i = 1; i < N; i++) {\\n int per = i + 1;\\n int cnt = counts[i];\\n int k = cnt / per;\\n if (k * per < cnt) k++;\\n ans += k * per;\\n }\\n return ans;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n static constexpr int N = 1009;\\n int numRabbits(vector<int>& answers) {\\n vector<int> counts(N,0);\\n for(auto i : answers)\\n counts[i]++;\\n int ans = counts[0];\\n for(int i = 1; i < N; i++) {\\n int per = i + 1;\\n int cnt = counts[i];\\n int k = cnt/per;\\n if (k * per < cnt) k++;\\n ans += k * per;\\n }\\n return ans;\\n }\\n};\\n
###Golang
\\n\\nfunc numRabbits(answers []int) int {\\nN := 1009\\ncounts := make([]int, N)\\nfor _, v := range answers {\\ncounts[v]++\\n}\\n// counts[x] = cnt 代表在 cs 中数值 x 的数量为 cnt\\nans := counts[0]\\nfor i := 1; i < N; i++ {\\nper := i + 1\\ncnt := counts[i]\\nk := cnt / per\\nif k*per < cnt {\\nk++\\n}\\nans += k * per\\n}\\nreturn ans\\n}\\n
\\n
\\n- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n
\\n拓展
\\n保持题目现有的条件不变,假定颜色相同的兔子至少有一只发声,问题改为「问兔子颜色数量可能有多少种」,又该如何求解呢?
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解
\\n","description":"基本分析 首先,兔子它不会说谎 (`・ω・´),因此我们可以得出以下结论:\\n\\n同一颜色的兔子回答的数值必然是一样的\\n但回答同样数值的,不一定就是同颜色兔子\\n\\n举个🌰,假如有 3 只白兔,每只白兔的回答必然都是 2(对应结论 1);但假如有兔子回答了数值 2,可能只是三只白兔,也可能是三只白兔和三只灰兔同时进行了回答(对应结论 2)。\\n\\n答案要我们求最少的兔子数量。\\n\\n不妨设有某种颜色的兔子 $m$ 只,它们回答的答案数值为 $cnt$,那么 $m$ 和 $cnt$ 满足什么关系?\\n\\n显然两者关系为 $m = cnt + 1$。\\n\\n但如果是在 $answers$…","guid":"https://leetcode.cn/problems/rabbits-in-forest//solution/gong-shui-san-xie-noxiang-xin-ke-xue-xi-v17p5","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-04T00:58:37.778Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【负雪明烛】找规律,附「向上取整」转「向下取整」的证明","url":"https://leetcode.cn/problems/rabbits-in-forest//solution/fu-xue-ming-zhu-zhao-gui-lu-fu-xiang-sha-1yk3","content":"各位题友大家好! 今天是 @负雪明烛 坚持日更的第 70 天。今天力扣上的每日一题是「781. 森林中的兔子」。
\\n解题思路
\\n重点:当某个兔子回答
\\nx
的时候,那么数组中最多允许x+1
个同花色的兔子🐰同时回答x
。我们先举个例子进行理解:
\\n\\n
\\n- 比如有一个红色的兔子回答了 2,那么数组中最多有 3 个红色的兔子。
\\n- 如果数组是
\\n[2,2,2]
,那么至少有一种颜色的兔子。- 如果数组是
\\n[2,2,2,2]
,那么说明至少有两种颜色的兔子,比如说前 3 个兔子构成一种颜色;那么最后一个兔子说的必须是其他颜色。- 如果数组是
\\n[2,2,2,2,2,2]
,那么说明至少有两种颜色的兔子,比如说前 3 个兔子构成一种颜色;那么后 3 个兔子说的必须是其他颜色。通过上面的这个例子可以得出以下的规律。
\\n我们统计数组中所有回答 $x$ 的兔子的数量 $n$:
\\n\\n
\\n- 若
\\nn % (x+1)==0
,说明我们此时只需要 $n/(x+1)$ 种不同颜色的兔子,每种颜色兔子的个数为 $x+1$ 。- 若
\\nn % (x+1) != 0
,说明我们此时只需要 $n/(x+1) + 1$ 种不同颜色的兔子,每种颜色兔子的个数为 $x+1$ 。那么这两种情况可以通过 $ceil(n/(x+1))$ 来整合(其中 $ceil()$ 是向上取整函数),即 $n / (x + 1)$ 向上取整 种不同颜色的兔子。 向上取整的函数可以自己实现,也可以转化为 $(n + x) / (x + 1)$,这个公式中的除法是 向下取整。证明在代码后面。
\\n我们还把上面的例子拿来,看一下计算的对不对。
\\n\\n
\\n- 如果数组是
\\n[2,2,2]
,那么有 $ceil(n/(x+1)) = ceil(3/3) = 1$ 种颜色的兔子,也可以通过 $(n + x) / (x + 1) = (3 + 2) / (2 + 1) = 1$ 计算得到。- 如果数组是
\\n[2,2,2,2]
,那么有 $ceil(n/(x+1)) = ceil(4/3) = 2$ 种颜色的兔子,也可以通过 $(n + x) / (x + 1) = (4 + 2) / (2 + 1) = 2$ 计算得到。- 如果数组是
\\n[2,2,2,2,2,2]
,那么有 $ceil(n/(x+1)) = ceil(6/3) = 2$ 种颜色的兔子,也可以通过 $(n + x) / (x + 1) = (6 + 2) / (2 + 1) = 2$ 计算得到。代码如下。
\\n###Python
\\n\\nclass Solution(object):\\n def numRabbits(self, answers):\\n count = collections.Counter(answers)\\n return sum((count[x] + x) / (x + 1) * (x + 1) for x in count)\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int numRabbits(vector<int>& answers) {\\n int res = 0;\\n unordered_map<int, int> m;\\n for (int a : answers) m[a]++;\\n for (auto a : m) {\\n res += (a.second + a.first) / (a.first + 1) * (a.first + 1);\\n }\\n return res;\\n }\\n};\\n
\\n
\\n- 时间复杂度:$O(N)$
\\n- 空间复杂度:$O(N)$
\\n证明
\\n下面证明 $ceil(n/(x+1)) = (n + x) / (x + 1)$ 。
\\n\\n
\\n- 当 $n = k * (x + 1)$ 的时候,即 $n$ 是 $x + 1$ 的整数倍时。
\\n\\n
\\n- 左边 $ceil(n / (x + 1)) = ceil(k * (x + 1) / (x + 1)) = ceil(k) = k$。
\\n- 右边 $(n + x) / (x + 1) = (k * (x + 1) + x) / (x + 1) = k + x / (x + 1) = k + 0 = k$
\\n- 左边等于右边。
\\n\\n
\\n- 当 $n = k * (x + 1) + a$ 的时候,即 $n$ 除以 $x + 1$ 得 k,余数为 a 时 ($1 <= a < (x + 1)$)。
\\n\\n
\\n- 左边 $ceil(n / (x + 1)) = ceil((k * (x + 1) + a) / (x + 1)) = ceil(k + a / (x + 1)) = k + 1$。
\\n- 右边 $(n + x) / (x + 1) = (k * (x + 1) + a + x) / (x + 1) = ((k + 1)*(x + 1) + a - 1) / (x + 1) = k + 1 + (a - 1) / (x + 1) = k + 1$。注意这里的 $1<= a < (x + 1)$,所以 $(a - 1) / (x + 1)$ 最小为 $(1 - 1) / (x + 1) = 0$;最大为 $(x - 1) / (x + 1) = 0$。
\\n- 左边等于右边。
\\n刷题心得
\\n今天的题目代码简单,但是思考起来挺费劲,有点像脑筋急转弯。
\\n
\\nOK,以上就是 @负雪明烛 写的今天题解的全部内容了,如果你觉得有帮助的话,求赞、求关注、求收藏。如果有疑问的话,请在下面评论,我会及时解答。
\\n关注我,你将不会错过我的精彩动画题解、面试题分享、组队刷题活动,进入主页 @负雪明烛 右侧有刷题组织,从此刷题不再孤单。
\\n祝大家 AC 多多,Offer 多多!我们明天再见!
\\n","description":"各位题友大家好! 今天是 @负雪明烛 坚持日更的第 70 天。今天力扣上的每日一题是「781. 森林中的兔子」。 重点:当某个兔子回答 x 的时候,那么数组中最多允许 x+1 个同花色的兔子🐰同时回答 x。\\n\\n我们先举个例子进行理解:\\n\\n比如有一个红色的兔子回答了 2,那么数组中最多有 3 个红色的兔子。\\n如果数组是 [2,2,2] ,那么至少有一种颜色的兔子。\\n如果数组是 [2,2,2,2] ,那么说明至少有两种颜色的兔子,比如说前 3 个兔子构成一种颜色;那么最后一个兔子说的必须是其他颜色。\\n如果数组是 [2,2,2,2,2,2] ,那么说明至少有…","guid":"https://leetcode.cn/problems/rabbits-in-forest//solution/fu-xue-ming-zhu-zhao-gui-lu-fu-xiang-sha-1yk3","author":"fuxuemingzhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-04T00:49:49.224Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"森林中的兔子","url":"https://leetcode.cn/problems/rabbits-in-forest//solution/sen-lin-zhong-de-tu-zi-by-leetcode-solut-kvla","content":"方法一:贪心
\\n思路
\\n两只相同颜色的兔子看到的其他同色兔子数必然是相同的。反之,若两只兔子看到的其他同色兔子数不同,那么这两只兔子颜色也不同。
\\n因此,将 $\\\\textit{answers}$ 中值相同的元素分为一组,对于每一组,计算出兔子的最少数量,然后将所有组的计算结果累加,就是最终的答案。
\\n例如,现在有 $13$ 只兔子回答 $5$。假设其中有一只红色的兔子,那么森林中必然有 $6$ 只红兔子。再假设其中还有一只蓝色的兔子,同样的道理森林中必然有 $6$ 只蓝兔子。为了最小化可能的兔子数量,我们假设这 $12$ 只兔子都在这 $13$ 只兔子中。那么还有一只额外的兔子回答 $5$,这只兔子只能是其他的颜色,这一颜色的兔子也有 $6$ 只。因此这种情况下最少会有 $18$ 只兔子。
\\n一般地,如果有 $x$ 只兔子都回答 $y$,则至少有 $\\\\lceil\\\\dfrac{x}{y+1}\\\\rceil$ 种不同的颜色,且每种颜色有 $y+1$ 只兔子,因此兔子数至少为
\\n$$\\\\lceil\\\\dfrac{x}{y+1}\\\\rceil\\\\cdot(y+1)$$
\\n我们可以用哈希表统计 $\\\\textit{answers}$ 中各个元素的出现次数,对每个元素套用上述公式计算,并将计算结果累加,即为最终答案。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numRabbits(vector<int> &answers) {\\n unordered_map<int, int> count;\\n for (int y : answers) {\\n ++count[y];\\n }\\n int ans = 0;\\n for (auto &[y, x] : count) {\\n ans += (x + y) / (y + 1) * (y + 1);\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int numRabbits(int[] answers) {\\n Map<Integer, Integer> count = new HashMap<Integer, Integer>();\\n for (int y : answers) {\\n count.put(y, count.getOrDefault(y, 0) + 1);\\n }\\n int ans = 0;\\n for (Map.Entry<Integer, Integer> entry : count.entrySet()) {\\n int y = entry.getKey(), x = entry.getValue();\\n ans += (x + y) / (y + 1) * (y + 1);\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc numRabbits(answers []int) (ans int) {\\n count := map[int]int{}\\n for _, y := range answers {\\n count[y]++\\n }\\n for y, x := range count {\\n ans += (x + y) / (y + 1) * (y + 1)\\n }\\n return\\n}\\n
###JavaScript
\\n\\nvar numRabbits = function(answers) {\\n const count = new Map();\\n for (const y of answers) {\\n count.set(y, (count.get(y) || 0) + 1);\\n }\\n let ans = 0;\\n for (const [y, x] of count.entries()) {\\n ans += Math.floor((x + y) / (y + 1)) * (y + 1);\\n }\\n return ans;\\n};\\n
###Python
\\n\\nclass Solution:\\n def numRabbits(self, answers: List[int]) -> int:\\n count = Counter(answers)\\n ans = sum((x + y) // (y + 1) * (y + 1) for y, x in count.items())\\n return ans\\n
###C
\\n\\nstruct HashTable {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n};\\n\\nint numRabbits(int* answers, int answersSize) {\\n struct HashTable* hashTable = NULL;\\n for (int i = 0; i < answersSize; i++) {\\n struct HashTable* tmp;\\n HASH_FIND_INT(hashTable, &answers[i], tmp);\\n if (tmp == NULL) {\\n tmp = malloc(sizeof(struct HashTable));\\n tmp->key = answers[i];\\n tmp->val = 1;\\n HASH_ADD_INT(hashTable, key, tmp);\\n } else {\\n tmp->val++;\\n }\\n }\\n int ans = 0;\\n struct HashTable *iter, *tmp;\\n HASH_ITER(hh, hashTable, iter, tmp) {\\n int x = iter->val, y = iter->key;\\n ans += (x + y) / (y + 1) * (y + 1);\\n }\\n return ans;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int NumRabbits(int[] answers) {\\n Dictionary<int, int> count = new Dictionary<int, int>();\\n foreach (int y in answers) {\\n if (count.ContainsKey(y)) {\\n count[y]++;\\n } else {\\n count[y] = 1;\\n }\\n }\\n int ans = 0;\\n foreach (var pair in count) {\\n int y = pair.Key;\\n int x = pair.Value;\\n ans += (x + y) / (y + 1) * (y + 1);\\n }\\n return ans;\\n }\\n}\\n
###TypeScript
\\n\\nfunction numRabbits(answers: number[]): number {\\n const count: Map<number, number> = new Map();\\n for (const y of answers) {\\n count.set(y, (count.get(y) || 0) + 1);\\n }\\n let ans = 0;\\n for (const [y, x] of count) {\\n ans += Math.floor((x + y) / (y + 1)) * (y + 1);\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn num_rabbits(answers: Vec<i32>) -> i32 {\\n let mut count: HashMap<i32, i32> = HashMap::new();\\n for &y in &answers {\\n *count.entry(y).or_insert(0) += 1;\\n }\\n let mut ans = 0;\\n for (&y, &x) in &count {\\n ans += ((x + y) / (y + 1)) * (y + 1);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心 思路\\n\\n两只相同颜色的兔子看到的其他同色兔子数必然是相同的。反之,若两只兔子看到的其他同色兔子数不同,那么这两只兔子颜色也不同。\\n\\n因此,将 $\\\\textit{answers}$ 中值相同的元素分为一组,对于每一组,计算出兔子的最少数量,然后将所有组的计算结果累加,就是最终的答案。\\n\\n例如,现在有 $13$ 只兔子回答 $5$。假设其中有一只红色的兔子,那么森林中必然有 $6$ 只红兔子。再假设其中还有一只蓝色的兔子,同样的道理森林中必然有 $6$ 只蓝兔子。为了最小化可能的兔子数量,我们假设这 $12$ 只兔子都在这 $13$ 只兔子中…","guid":"https://leetcode.cn/problems/rabbits-in-forest//solution/sen-lin-zhong-de-tu-zi-by-leetcode-solut-kvla","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-04-03T16:10:32.682Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「代码随想录」带你学透回溯算法!90. 子集 II:【彻底理解子集问题如何去重】","url":"https://leetcode.cn/problems/subsets-ii//solution/90-zi-ji-iiche-di-li-jie-zi-ji-wen-ti-ru-djmf","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{answers}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(n)$。最坏情况下,哈希表中含有 $n$ 个元素。
\\n算法公开课
\\n《代码随想录》算法视频公开课:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II,相信结合视频再看本篇题解,更有助于大家对本题的理解。
\\n思路
\\n做本题之前一定要先做78.子集。
\\n这道题目和78.子集区别就是集合里有重复元素了,而且求取的子集要去重。
\\n那么关于回溯算法中的去重问题,在40.组合总和II中已经详细讲解过了,和本题是一个套路。
\\n剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要。
\\n用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)
\\n\\n
从图中可以看出,同一树层上重复取2 就要过滤掉,同一树枝上就可以重复取2,因为同一树枝上元素的集合才是唯一子集!
\\n本题就是其实就是回溯算法:求子集问题!的基础上加上了去重,去重我们在回溯算法:求组合总和(三)也讲过了,所以我就直接给出代码了:
\\nC++代码如下:
\\n###CPP
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> result;\\n vector<int> path;\\n void backtracking(vector<int>& nums, int startIndex, vector<bool>& used) {\\n result.push_back(path);\\n for (int i = startIndex; i < nums.size(); i++) {\\n // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过\\n // used[i - 1] == false,说明同一树层candidates[i - 1]使用过\\n // 而我们要对同一树层使用过的元素进行跳过\\n if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) {\\n continue;\\n }\\n path.push_back(nums[i]);\\n used[i] = true;\\n backtracking(nums, i + 1, used);\\n used[i] = false;\\n path.pop_back();\\n }\\n }\\n\\npublic:\\n vector<vector<int>> subsetsWithDup(vector<int>& nums) {\\n result.clear();\\n path.clear();\\n vector<bool> used(nums.size(), false);\\n sort(nums.begin(), nums.end()); // 去重需要排序\\n backtracking(nums, 0, used);\\n return result;\\n }\\n};\\n
使用set去重的版本。
\\n###CPP
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> result;\\n vector<int> path;\\n void backtracking(vector<int>& nums, int startIndex) {\\n result.push_back(path);\\n unordered_set<int> uset;\\n for (int i = startIndex; i < nums.size(); i++) {\\n if (uset.find(nums[i]) != uset.end()) {\\n continue;\\n }\\n uset.insert(nums[i]);\\n path.push_back(nums[i]);\\n backtracking(nums, i + 1);\\n path.pop_back();\\n }\\n }\\n\\npublic:\\n vector<vector<int>> subsetsWithDup(vector<int>& nums) {\\n result.clear();\\n path.clear();\\n sort(nums.begin(), nums.end()); // 去重需要排序\\n backtracking(nums, 0);\\n return result;\\n }\\n};\\n
补充
\\n本题也可以不使用used数组来去重,因为递归的时候下一个startIndex是i+1而不是0。
\\n如果要是全排列的话,每次要从0开始遍历,为了跳过已入栈的元素,需要使用used。
\\n代码如下:
\\n###CPP
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> result;\\n vector<int> path;\\n void backtracking(vector<int>& nums, int startIndex) {\\n result.push_back(path);\\n for (int i = startIndex; i < nums.size(); i++) {\\n // 而我们要对同一树层使用过的元素进行跳过\\n if (i > startIndex && nums[i] == nums[i - 1] ) { // 注意这里使用i > startIndex\\n continue;\\n }\\n path.push_back(nums[i]);\\n backtracking(nums, i + 1);\\n path.pop_back();\\n }\\n }\\n\\npublic:\\n vector<vector<int>> subsetsWithDup(vector<int>& nums) {\\n result.clear();\\n path.clear();\\n sort(nums.begin(), nums.end()); // 去重需要排序\\n backtracking(nums, 0);\\n return result;\\n }\\n};\\n
总结
\\n其实这道题目的知识点,我们之前都讲过了,如果之前讲过的子集问题和去重问题都掌握的好,这道题目应该分分钟AC。
\\n当然本题去重的逻辑,也可以这么写
\\n###cpp
\\n\\nif (i > startIndex && nums[i] == nums[i - 1] ) {\\ncontinue;\\n}\\n
其他语言版本
\\n###java
\\n\\n// 使用used数组\\nclass Solution {\\n List<List<Integer>> result = new ArrayList<>();// 存放符合条件结果的集合\\n LinkedList<Integer> path = new LinkedList<>();// 用来存放符合条件结果\\n boolean[] used;\\n public List<List<Integer>> subsetsWithDup(int[] nums) {\\n if (nums.length == 0){\\n result.add(path);\\n return result;\\n }\\n Arrays.sort(nums);\\n used = new boolean[nums.length];\\n subsetsWithDupHelper(nums, 0);\\n return result;\\n }\\n \\n private void subsetsWithDupHelper(int[] nums, int startIndex){\\n result.add(new ArrayList<>(path));\\n if (startIndex >= nums.length){\\n return;\\n }\\n for (int i = startIndex; i < nums.length; i++){\\n if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]){\\n continue;\\n }\\n path.add(nums[i]);\\n used[i] = true;\\n subsetsWithDupHelper(nums, i + 1);\\n path.removeLast();\\n used[i] = false;\\n }\\n }\\n}\\n\\n// 不使用used数组\\nclass Solution {\\n\\n List<List<Integer>> res = new ArrayList<>();\\n LinkedList<Integer> path = new LinkedList<>();\\n \\n public List<List<Integer>> subsetsWithDup( int[] nums ) {\\n Arrays.sort( nums );\\n subsetsWithDupHelper( nums, 0 );\\n return res;\\n }\\n\\n\\n private void subsetsWithDupHelper( int[] nums, int start ) {\\n res.add( new ArrayList<>( path ) );\\n\\n for ( int i = start; i < nums.length; i++ ) {\\n // 跳过当前树层使用过的、相同的元素\\n if ( i > start && nums[i - 1] == nums[i] ) {\\n continue;\\n }\\n path.add( nums[i] );\\n subsetsWithDupHelper( nums, i + 1 );\\n path.removeLast();\\n }\\n }\\n\\n}\\n
###python
\\n\\nclass Solution:\\n def __init__(self):\\n self.paths = []\\n self.path = []\\n\\n def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:\\n nums.sort()\\n self.backtracking(nums, 0)\\n return self.paths\\n\\n def backtracking(self, nums: List[int], start_index: int) -> None:\\n # ps.空集合仍符合要求\\n self.paths.append(self.path[:])\\n # Base Case\\n if start_index == len(nums):\\n return\\n \\n # 单层递归逻辑\\n for i in range(start_index, len(nums)):\\n if i > start_index and nums[i] == nums[i-1]:\\n # 当前后元素值相同时,跳入下一个循环,去重\\n continue\\n self.path.append(nums[i])\\n self.backtracking(nums, i+1)\\n self.path.pop()\\n\\n# 不使用used数组\\nclass Solution:\\n def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:\\n res = []\\n path = []\\n nums.sort() # 去重需要先对数组进行排序\\n\\n def backtracking(nums, startIndex):\\n # 终止条件\\n res.append(path[:])\\n if startIndex == len(nums):\\n return\\n \\n # for循环\\n for i in range(startIndex, len(nums)):\\n # 数层去重\\n if i > startIndex and nums[i] == nums[i-1]: # 去重\\n continue\\n path.append(nums[i])\\n backtracking(nums, i+1)\\n path.pop()\\n \\n backtracking(nums, 0)\\n return res\\n\\n# 使用used数组\\nclass Solution:\\n def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:\\n result = []\\n path = []\\n nums.sort()\\n used = [0] * len(nums)\\n def backtrack(nums, startIdx):\\n result.append(path[:])\\n for i in range(startIdx, len(nums)):\\n if i > startIdx and nums[i] == nums[i-1] and used[i-1] == 0:\\n continue\\n used[i] = 1\\n path.append(nums[i])\\n backtrack(nums, i+1)\\n path.pop()\\n used[i] = 0\\n backtrack(nums, 0)\\n return result\\n
###Go
\\n\\nvar (\\n path []int\\n res [][]int\\n)\\nfunc subsetsWithDup(nums []int) [][]int {\\n path, res = make([]int, 0, len(nums)), make([][]int, 0)\\n sort.Ints(nums)\\n dfs(nums, 0)\\n return res\\n}\\n\\nfunc dfs(nums []int, start int) {\\n tmp := make([]int, len(path))\\n copy(tmp, path)\\n res = append(res, tmp)\\n\\n for i := start; i < len(nums); i++ {\\n if i != start && nums[i] == nums[i-1] {\\n continue\\n }\\n path = append(path, nums[i])\\n dfs(nums, i+1)\\n path = path[:len(path)-1]\\n }\\n}\\n
###Javascript
\\n\\n\\nvar subsetsWithDup = function(nums) {\\n let result = []\\n let path = []\\n let sortNums = nums.sort((a, b) => {\\n return a - b\\n })\\n function backtracing(startIndex, sortNums) {\\n result.push([...path])\\n if(startIndex > nums.length - 1) {\\n return\\n }\\n for(let i = startIndex; i < nums.length; i++) {\\n if(i > startIndex && nums[i] === nums[i - 1]) {\\n continue\\n }\\n path.push(nums[i])\\n backtracing(i + 1, sortNums)\\n path.pop()\\n }\\n }\\n backtracing(0, sortNums)\\n return result\\n};\\n\\n
###typescript
\\n\\nfunction subsetsWithDup(nums: number[]): number[][] {\\n nums.sort((a, b) => a - b);\\n const resArr: number[][] = [];\\n backTraking(nums, 0, []);\\n return resArr;\\n function backTraking(nums: number[], startIndex: number, route: number[]): void {\\n resArr.push([...route]);\\n let length: number = nums.length;\\n if (startIndex === length) return;\\n for (let i = startIndex; i < length; i++) {\\n if (i > startIndex && nums[i] === nums[i - 1]) continue;\\n route.push(nums[i]);\\n backTraking(nums, i + 1, route);\\n route.pop();\\n }\\n }\\n};\\n\\n// 使用set去重版本\\nfunction subsetsWithDup(nums: number[]): number[][] {\\n const result: number[][] = [];\\n const path: number[] = [];\\n // 去重之前先排序\\n nums.sort((a, b) => a - b);\\n function backTracking(startIndex: number) {\\n // 收集结果\\n result.push([...path])\\n // 此处不返回也可以因为,每次递归都会使startIndex + 1,当这个数大到nums.length的时候就不会进入递归了。\\n if (startIndex === nums.length) {\\n return\\n }\\n // 定义每一个树层的set集合\\n const set: Set<number> = new Set()\\n for (let i = startIndex; i < nums.length; i++) {\\n // 去重\\n if (set.has(nums[i])) {\\n continue\\n }\\n set.add(nums[i])\\n path.push(nums[i])\\n backTracking(i + 1)\\n // 回溯\\n path.pop()\\n }\\n }\\n backTracking(0)\\n return result\\n};\\n
###Rust
\\n\\nimpl Solution {\\n fn backtracking(result: &mut Vec<Vec<i32>>, path: &mut Vec<i32>, nums: &Vec<i32>, start_index: usize, used: &mut Vec<bool>) {\\n result.push(path.clone());\\n let len = nums.len();\\n // if start_index >= len { return; }\\n for i in start_index..len {\\n if i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false { continue; }\\n path.push(nums[i]);\\n used[i] = true;\\n Self::backtracking(result, path, nums, i + 1, used);\\n used[i] = false;\\n path.pop();\\n }\\n }\\n\\n pub fn subsets_with_dup(nums: Vec<i32>) -> Vec<Vec<i32>> {\\n let mut result: Vec<Vec<i32>> = Vec::new();\\n let mut path: Vec<i32> = Vec::new();\\n let mut used = vec![false; nums.len()];\\n let mut nums = nums;\\n nums.sort();\\n Self::backtracking(&mut result, &mut path, &nums, 0, &mut used);\\n result\\n }\\n}\\n
###c
\\n\\nint* path;\\nint pathTop;\\nint** ans;\\nint ansTop;\\n//负责存放二维数组中每个数组的长度\\nint* lengths;\\n//快排cmp函数\\nint cmp(const void* a, const void* b) {\\n return *((int*)a) - *((int*)b);\\n}\\n\\n//复制函数,将当前path中的元素复制到ans中。同时记录path长度\\nvoid copy() {\\n int* tempPath = (int*)malloc(sizeof(int) * pathTop);\\n int i;\\n for(i = 0; i < pathTop; i++) {\\n tempPath[i] = path[i];\\n }\\n ans = (int**)realloc(ans, sizeof(int*) * (ansTop + 1));\\n lengths[ansTop] = pathTop;\\n ans[ansTop++] = tempPath;\\n}\\n\\nvoid backTracking(int* nums, int numsSize, int startIndex, int* used) {\\n //首先将当前path复制\\n copy();\\n //若startIndex大于数组最后一位元素的位置,返回\\n if(startIndex >= numsSize)\\n return ;\\n \\n int i;\\n for(i = startIndex; i < numsSize; i++) {\\n //对同一树层使用过的元素进行跳过\\n if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false) \\n continue;\\n path[pathTop++] = nums[i];\\n used[i] = true;\\n backTracking(nums, numsSize, i + 1, used);\\n used[i] = false;\\n pathTop--;\\n }\\n}\\n\\nint** subsetsWithDup(int* nums, int numsSize, int* returnSize, int** returnColumnSizes){\\n //声明辅助变量\\n path = (int*)malloc(sizeof(int) * numsSize);\\n ans = (int**)malloc(0);\\n lengths = (int*)malloc(sizeof(int) * 1500);\\n int* used = (int*)malloc(sizeof(int) * numsSize);\\n pathTop = ansTop = 0;\\n\\n //排序后查重才能生效\\n qsort(nums, numsSize, sizeof(int), cmp);\\n backTracking(nums, numsSize, 0, used);\\n\\n //设置一维数组和二维数组的返回大小\\n *returnSize = ansTop;\\n *returnColumnSizes = (int*)malloc(sizeof(int) * ansTop);\\n int i;\\n for(i = 0; i < ansTop; i++) {\\n (*returnColumnSizes)[i] = lengths[i];\\n }\\n return ans;\\n}\\n
###swift
\\n\\nfunc subsetsWithDup(_ nums: [Int]) -> [[Int]] {\\n let nums = nums.sorted()\\n var result = [[Int]]()\\n var path = [Int]()\\n func backtracking(startIndex: Int) {\\n // 直接收集结果\\n result.append(path)\\n\\n let end = nums.count\\n guard startIndex < end else { return } // 终止条件\\n for i in startIndex ..< end {\\n if i > startIndex, nums[i] == nums[i - 1] { continue } // 跳过重复元素\\n path.append(nums[i]) // 处理:收集元素\\n backtracking(startIndex: i + 1) // 元素不重复访问\\n path.removeLast() // 回溯\\n }\\n }\\n backtracking(startIndex: 0)\\n return result\\n}\\n
###scala
\\n\\n// 不使用used数组:\\nobject Solution {\\n import scala.collection.mutable\\n def subsetsWithDup(nums: Array[Int]): List[List[Int]] = {\\n var result = mutable.ListBuffer[List[Int]]()\\n var path = mutable.ListBuffer[Int]()\\n var num = nums.sorted // 排序\\n\\n def backtracking(startIndex: Int): Unit = {\\n result.append(path.toList)\\n if (startIndex >= num.size){\\n return\\n }\\n for (i <- startIndex until num.size) {\\n // 同一树层重复的元素不进入回溯\\n if (!(i > startIndex && num(i) == num(i - 1))) {\\n path.append(num(i))\\n backtracking(i + 1)\\n path.remove(path.size - 1)\\n }\\n }\\n }\\n\\n backtracking(0)\\n result.toList\\n }\\n}\\n\\n// 使用Set去重:\\nobject Solution {\\n import scala.collection.mutable\\n def subsetsWithDup(nums: Array[Int]): List[List[Int]] = {\\n var result = mutable.Set[List[Int]]()\\n var num = nums.sorted\\n def backtracking(path: mutable.ListBuffer[Int], startIndex: Int): Unit = {\\n if (startIndex == num.length) {\\n result.add(path.toList)\\n return\\n }\\n path.append(num(startIndex))\\n backtracking(path, startIndex + 1) // 选择\\n path.remove(path.size - 1)\\n backtracking(path, startIndex + 1) // 不选择\\n }\\n\\n backtracking(mutable.ListBuffer[Int](), 0)\\n\\n result.toList\\n }\\n}\\n
回溯算法力扣题目总结
\\n按照如下顺序刷力扣上的题目,相信会帮你在学习回溯算法的路上少走很多弯路。
\\n\\n
\\n- 关于回溯算法,你该了解这些!
\\n- 组合问题\\n
\\n\\n
\\n- 77.组合
\\n- 216.组合总和III
\\n- 17.电话号码的字母组合
\\n- 39.组合总和
\\n- 40.组合总和II
\\n- 分割问题\\n\\n
\\n- 子集问题\\n\\n
\\n- 排列问题\\n\\n
\\n- 棋盘问题\\n\\n
\\n- 其他\\n
\\n\\n
\\n- 491.递增子序列
\\n- 332.重新安排行程
\\n- 回溯算法总结篇
\\n
\\n大家好,我是程序员Carl,如果你还在没有章法的刷题,建议按照代码随想录刷题路线来刷,并提供PDF下载,刷题路线同时也开源在Github上,你会发现详见很晚!
\\n如果感觉题解对你有帮助,不要吝啬给一个👍吧!
\\n","description":"《代码随想录》算法视频公开课:回溯算法解决子集问题,如何去重?| LeetCode:90.子集II,相信结合视频再看本篇题解,更有助于大家对本题的理解。 思路\\n\\n做本题之前一定要先做78.子集。\\n\\n这道题目和78.子集区别就是集合里有重复元素了,而且求取的子集要去重。\\n\\n那么关于回溯算法中的去重问题,在40.组合总和II中已经详细讲解过了,和本题是一个套路。\\n\\n剧透一下,后期要讲解的排列问题里去重也是这个套路,所以理解“树层去重”和“树枝去重”非常重要。\\n\\n用示例中的[1, 2, 2] 来举例,如图所示: (注意去重需要先对集合排序)\\n\\n从图中可以看出…","guid":"https://leetcode.cn/problems/subsets-ii//solution/90-zi-ji-iiche-di-li-jie-zi-ji-wen-ti-ru-djmf","author":"carlsun-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-31T01:12:24.710Z","media":[{"url":"https://pic.leetcode.cn/1674874725-XYWjam-image.png","type":"photo","width":2556,"height":1510,"blurhash":"LAS6Me~qRk?a^*%N%LRmx^t7V@V@"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【双指针】两个数组的交集 II","url":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/ha-xi-biao-liang-ge-shu-zu-de-jiao-ji-ii-fkwo","content":"\\n
\\n🧠 解题思路
\\n通过题意,寻找两数组是否有相同项,并且提示中说可以不要求交集的顺序。
\\n既然如此,我们便可以先行将数组排序,方便我们查找,然后正式流程如下:
\\n\\n
\\n- 创建一个指针 $i$ 指向 $nums1$ 数组首位,指针 $j$ 指向 $nums2$ 数组首位。
\\n- 创建一个临时栈,用于存放结果集。
\\n- 开始比较指针 $i$ 和指针 $j$ 的值大小,若两个值不等,则数字小的指针,往右移一位。
\\n- 若指针 $i$ 和指针 $j$ 的值相等,则将交集压入栈。
\\n- 若 $nums$ 或 $nums2$ 有一方遍历结束,代表另一方的剩余值,都是唯一存在,且不会与之产生交集的。
\\n
\\n🎨 图解演示
\\n<
\\n,
,
,
,
,
,
,
,
,
>
\\n🍭 示例代码
\\n###Javascript
\\n\\nlet intersect = function (nums1, nums2) {\\n nums1.sort((a, b) => a - b);\\n nums2.sort((a, b) => a - b);\\n let l = 0, r = 0, ans = [];\\n while (l < nums1.length && r < nums2.length) {\\n if (nums1[l] === nums2[r]) {\\n ans.push(nums1[l]);\\n l++;\\n r++;\\n } else nums1[l] < nums2[r] ? l++ : r++;\\n }\\n return ans;\\n};\\n
###Java
\\n\\nclass Solution {\\n public int[] intersect(int[] nums1, int[] nums2) {\\n Arrays.sort(nums1);\\n Arrays.sort(nums2);\\n int length1 = nums1.length, length2 = nums2.length;\\n int[] intersection = new int[Math.min(length1, length2)];\\n int index1 = 0, index2 = 0, index = 0;\\n while (index1 < length1 && index2 < length2) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection[index] = nums1[index1];\\n index1++;\\n index2++;\\n index++;\\n }\\n }\\n return Arrays.copyOfRange(intersection, 0, index);\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n sort(nums1.begin(), nums1.end());\\n sort(nums2.begin(), nums2.end());\\n int length1 = nums1.size(), length2 = nums2.size();\\n vector<int> intersection;\\n int index1 = 0, index2 = 0;\\n while (index1 < length1 && index2 < length2) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection.push_back(nums1[index1]);\\n index1++;\\n index2++;\\n }\\n }\\n return intersection;\\n }\\n};\\n
###Python3
\\n\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:\\n nums1.sort()\\n nums2.sort()\\n\\n length1, length2 = len(nums1), len(nums2)\\n intersection = list()\\n index1 = index2 = 0\\n while index1 < length1 and index2 < length2:\\n if nums1[index1] < nums2[index2]:\\n index1 += 1\\n elif nums1[index1] > nums2[index2]:\\n index2 += 1\\n else:\\n intersection.append(nums1[index1])\\n index1 += 1\\n index2 += 1\\n \\n return intersection\\n
###Golang
\\n\\nfunc intersect(nums1 []int, nums2 []int) []int {\\n sort.Ints(nums1)\\n sort.Ints(nums2)\\n length1, length2 := len(nums1), len(nums2)\\n index1, index2 := 0, 0\\n\\n intersection := []int{}\\n for index1 < length1 && index2 < length2 {\\n if nums1[index1] < nums2[index2] {\\n index1++\\n } else if nums1[index1] > nums2[index2] {\\n index2++\\n } else {\\n intersection = append(intersection, nums1[index1])\\n index1++\\n index2++\\n }\\n }\\n return intersection\\n}\\n
###C
\\n\\nint cmp(const void* _a, const void* _b) {\\n int *a = _a, *b = (int*)_b;\\n return *a == *b ? 0 : *a > *b ? 1 : -1;\\n}\\n\\nint* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size,\\n int* returnSize) {\\n qsort(nums1, nums1Size, sizeof(int), cmp);\\n qsort(nums2, nums2Size, sizeof(int), cmp);\\n *returnSize = 0;\\n int* intersection = (int*)malloc(sizeof(int) * fmin(nums1Size, nums2Size));\\n int index1 = 0, index2 = 0;\\n while (index1 < nums1Size && index2 < nums2Size) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection[(*returnSize)++] = nums1[index1];\\n index1++;\\n index2++;\\n }\\n }\\n return intersection;\\n}\\n
\\n转身挥手
\\n嘿,少年,做图不易,留下个赞或评论再走吧!谢啦~ 💐
\\n差点忘了,祝你牛年大吉 🐮 ,AC 和 Offer 📑 多多益善~
\\n⛲⛲⛲ 期待下次再见~
\\n","description":"🧠 解题思路 通过题意,寻找两数组是否有相同项,并且提示中说可以不要求交集的顺序。\\n\\n既然如此,我们便可以先行将数组排序,方便我们查找,然后正式流程如下:\\n\\n创建一个指针 $i$ 指向 $nums1$ 数组首位,指针 $j$ 指向 $nums2$ 数组首位。\\n创建一个临时栈,用于存放结果集。\\n开始比较指针 $i$ 和指针 $j$ 的值大小,若两个值不等,则数字小的指针,往右移一位。\\n若指针 $i$ 和指针 $j$ 的值相等,则将交集压入栈。\\n若 $nums$ 或 $nums2$ 有一方遍历结束,代表另一方的剩余值,都是唯一存在,且不会与之产生交集的。…","guid":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/ha-xi-biao-liang-ge-shu-zu-de-jiao-ji-ii-fkwo","author":"demigodliu","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-27T13:00:22.985Z","media":[{"url":"https://pic.leetcode-cn.com/1615817903-fzmpwZ-%E5%9B%BE%E8%A7%A3%E6%AF%8F%E6%97%A5%E4%B8%80%E7%BB%83.jpg","type":"photo","width":760,"height":340,"blurhash":"LRB.r#oL9boJI[a|n$WV0#ay-UWX"},{"url":"https://pic.leetcode-cn.com/1617032404-VMHJLm-1.jpg","type":"photo","width":760,"height":440,"blurhash":"LESigQ~q?b-;-;ofxuWBt7WBWBof"},{"url":"https://pic.leetcode-cn.com/1617032406-qYuIXB-2.jpg","type":"photo","width":760,"height":440,"blurhash":"LDS$ov~q?b-;%Mt7%MRj%MRjWBt7"},{"url":"https://pic.leetcode-cn.com/1617032409-XWGjeM-3.jpg","type":"photo","width":760,"height":440,"blurhash":"LFSidH~q?c-;-:ogxvRit6WBWEt6"},{"url":"https://pic.leetcode-cn.com/1617032411-XPBbgQ-4.jpg","type":"photo","width":760,"height":440,"blurhash":"LFSigP~q?c-:-okCx^V@tQWBWCof"},{"url":"https://pic.leetcode-cn.com/1617032414-mWVzyk-5.jpg","type":"photo","width":760,"height":440,"blurhash":"LGSigO_N?c%K%KogxvV?xaWBWCoe"},{"url":"https://pic.leetcode-cn.com/1617032416-oIOrwu-6.jpg","type":"photo","width":760,"height":440,"blurhash":"LGSidH~q?c-o-okDx]Rit6WBWCoe"},{"url":"https://pic.leetcode-cn.com/1617032419-zbCOiu-7.jpg","type":"photo","width":760,"height":440,"blurhash":"LESidH_4_4?a-.WY%Nadt7WBWCoy"},{"url":"https://pic.leetcode-cn.com/1617032421-tbzIxG-8.jpg","type":"photo","width":760,"height":440,"blurhash":"LESidH_4_4?a-oa$%Nact6WCWCoe"},{"url":"https://pic.leetcode-cn.com/1617032426-JhSqSr-9.jpg","type":"photo","width":760,"height":440,"blurhash":"LESigO_4_4-:%Jfm%NjWxtWCV]oe"},{"url":"https://pic.leetcode-cn.com/1617032423-vxhgDz-10.jpg","type":"photo","width":760,"height":440,"blurhash":"LDSPX^_4_3?a?Ht8-;WAt7R%Rjt7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最简洁的双指针解法!一次遍历,无交换操作","url":"https://leetcode.cn/problems/sort-colors//solution/zui-jian-ji-de-shuang-zhi-zhen-jie-fa-yi-gtfz","content":"\\n","description":"public void sortColors(int[] nums) { int n0 = 0, n1 = 0;\\n for(int i = 0; i < nums.length; i++){\\n int num = nums[i];\\n nums[i] = 2;\\n if(num < 2){\\n nums[n1++] = 1;\\n }\\n if(num < 1){…","guid":"https://leetcode.cn/problems/sort-colors//solution/zui-jian-ji-de-shuang-zhi-zhen-jie-fa-yi-gtfz","author":"butterfly1024","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-24T02:14:02.729Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"螺旋矩阵 II","url":"https://leetcode.cn/problems/spiral-matrix-ii//solution/luo-xuan-ju-zhen-ii-by-leetcode-solution-f7fp","content":"public void sortColors(int[] nums) {\\n int n0 = 0, n1 = 0;\\n for(int i = 0; i < nums.length; i++){\\n int num = nums[i];\\n nums[i] = 2;\\n if(num < 2){\\n nums[n1++] = 1;\\n }\\n if(num < 1){\\n nums[n0++] = 0;\\n }\\n }\\n }\\n
方法一:模拟
\\n模拟矩阵的生成。按照要求,初始位置设为矩阵的左上角,初始方向设为向右。若下一步的位置超出矩阵边界,或者是之前访问过的位置,则顺时针旋转,进入下一个方向。如此反复直至填入 $n^2$ 个元素。
\\n记 $\\\\textit{matrix}$ 为生成的矩阵,其初始元素设为 $0$。由于填入的元素均为正数,我们可以判断当前位置的元素值,若不为 $0$,则说明已经访问过此位置。
\\n###Java
\\n\\nclass Solution {\\n public int[][] generateMatrix(int n) {\\n int maxNum = n * n;\\n int curNum = 1;\\n int[][] matrix = new int[n][n];\\n int row = 0, column = 0;\\n int[][] directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\n int directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row = row + directions[directionIndex][0];\\n column = column + directions[directionIndex][1];\\n }\\n return matrix;\\n }\\n}\\n
###JavaScript
\\n\\nvar generateMatrix = function(n) {\\n const maxNum = n * n;\\n let curNum = 1;\\n const matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));\\n let row = 0, column = 0;\\n const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; // 右下左上\\n let directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n const nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] !== 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row = row + directions[directionIndex][0];\\n column = column + directions[directionIndex][1];\\n }\\n return matrix;\\n};\\n
###go
\\n\\ntype pair struct{ x, y int }\\nvar dirs = []pair{{0, 1}, {1, 0}, {0, -1}, {-1, 0}} // 右下左上\\n\\nfunc generateMatrix(n int) [][]int {\\n matrix := make([][]int, n)\\n for i := range matrix {\\n matrix[i] = make([]int, n)\\n }\\n row, col, dirIdx := 0, 0, 0\\n for i := 1; i <= n*n; i++ {\\n matrix[row][col] = i\\n dir := dirs[dirIdx]\\n if r, c := row+dir.x, col+dir.y; r < 0 || r >= n || c < 0 || c >= n || matrix[r][c] > 0 {\\n dirIdx = (dirIdx + 1) % 4 // 顺时针旋转至下一个方向\\n dir = dirs[dirIdx]\\n }\\n row += dir.x\\n col += dir.y\\n }\\n return matrix\\n}\\n
###Python
\\n\\nclass Solution:\\n def generateMatrix(self, n: int) -> List[List[int]]:\\n dirs = [(0, 1), (1, 0), (0, -1), (-1, 0)]\\n matrix = [[0] * n for _ in range(n)]\\n row, col, dirIdx = 0, 0, 0\\n for i in range(n * n):\\n matrix[row][col] = i + 1\\n dx, dy = dirs[dirIdx]\\n r, c = row + dx, col + dy\\n if r < 0 or r >= n or c < 0 or c >= n or matrix[r][c] > 0:\\n dirIdx = (dirIdx + 1) % 4 # 顺时针旋转至下一个方向\\n dx, dy = dirs[dirIdx]\\n row, col = row + dx, col + dy\\n \\n return matrix\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> generateMatrix(int n) {\\n int maxNum = n * n;\\n int curNum = 1;\\n vector<vector<int>> matrix(n, vector<int>(n));\\n int row = 0, column = 0;\\n vector<vector<int>> directions = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\n int directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row = row + directions[directionIndex][0];\\n column = column + directions[directionIndex][1];\\n }\\n return matrix;\\n }\\n};\\n
###C
\\n\\nint** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {\\n int maxNum = n * n;\\n int curNum = 1;\\n int** matrix = malloc(sizeof(int*) * n);\\n *returnSize = n;\\n *returnColumnSizes = malloc(sizeof(int) * n);\\n for (int i = 0; i < n; i++) {\\n matrix[i] = malloc(sizeof(int) * n);\\n memset(matrix[i], 0, sizeof(int) * n);\\n (*returnColumnSizes)[i] = n;\\n }\\n int row = 0, column = 0;\\n int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}}; // 右下左上\\n int directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row = row + directions[directionIndex][0];\\n column = column + directions[directionIndex][1];\\n }\\n return matrix;\\n}\\n
###TypeScript
\\n\\nfunction generateMatrix(n: number): number[][] {\\n const maxNum = n * n;\\n let curNum = 1;\\n const matrix: number[][] = Array.from({ length: n }, () => Array(n).fill(0));\\n let row = 0, column = 0;\\n const directions = [[0, 1], [1, 0], [0, -1], [-1, 0]]; // 右下左上\\n let directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n const nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] !== 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row += directions[directionIndex][0];\\n column += directions[directionIndex][1];\\n }\\n return matrix;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[][] GenerateMatrix(int n) {\\n int maxNum = n * n;\\n int curNum = 1;\\n int[][] matrix = new int[n][];\\n for (int i = 0; i < n; i++) {\\n matrix[i] = new int[n];\\n }\\n int row = 0, column = 0;\\n int[][] directions = new int[][] { new int[] { 0, 1 }, new int[] { 1, 0 }, new int[] { 0, -1 }, new int[] { -1, 0 } }; // 右下左上\\n int directionIndex = 0;\\n while (curNum <= maxNum) {\\n matrix[row][column] = curNum;\\n curNum++;\\n int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];\\n if (nextRow < 0 || nextRow >= n || nextColumn < 0 || nextColumn >= n || matrix[nextRow][nextColumn] != 0) {\\n directionIndex = (directionIndex + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row += directions[directionIndex][0];\\n column += directions[directionIndex][1];\\n }\\n return matrix;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn generate_matrix(n: i32) -> Vec<Vec<i32>> {\\n let max_num = n * n;\\n let mut cur_num = 1;\\n let mut matrix = vec![vec![0; n as usize]; n as usize];\\n let (mut row, mut column) = (0, 0);\\n let directions = vec![[0, 1], [1, 0], [0, -1], [-1, 0]]; // 右下左上\\n let mut direction_index = 0;\\n while cur_num <= max_num {\\n matrix[row as usize][column as usize] = cur_num;\\n cur_num += 1;\\n let next_row = row + directions[direction_index][0];\\n let next_column = column + directions[direction_index][1];\\n if next_row < 0 || next_row >= n || next_column < 0 || next_column >= n || matrix[next_row as usize][next_column as usize] != 0 {\\n direction_index = (direction_index + 1) % 4; // 顺时针旋转至下一个方向\\n }\\n row += directions[direction_index][0];\\n column += directions[direction_index][1];\\n }\\n matrix\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是给定的正整数。矩阵的大小是 $n \\\\times n$,需要填入矩阵中的每个元素。
\\n- \\n
\\n空间复杂度:$O(1)$。除了返回的矩阵以外,空间复杂度是常数。
\\n方法二:按层模拟
\\n可以将矩阵看成若干层,首先填入矩阵最外层的元素,其次填入矩阵次外层的元素,直到填入矩阵最内层的元素。
\\n定义矩阵的第 $k$ 层是到最近边界距离为 $k$ 的所有顶点。例如,下图矩阵最外层元素都是第 $1$ 层,次外层元素都是第 $2$ 层,最内层元素都是第 $3$ 层。
\\n\\n[[1, 1, 1, 1, 1, 1],\\n [1, 2, 2, 2, 2, 1],\\n [1, 2, 3, 3, 2, 1],\\n [1, 2, 3, 3, 2, 1],\\n [1, 2, 2, 2, 2, 1],\\n [1, 1, 1, 1, 1, 1]]\\n
对于每层,从左上方开始以顺时针的顺序填入所有元素。假设当前层的左上角位于 $(\\\\textit{top}, \\\\textit{left})$,右下角位于 $(\\\\textit{bottom}, \\\\textit{right})$,按照如下顺序填入当前层的元素。
\\n\\n
\\n- \\n
\\n从左到右填入上侧元素,依次为 $(\\\\textit{top}, \\\\textit{left})$ 到 $(\\\\textit{top}, \\\\textit{right})$。
\\n- \\n
\\n从上到下填入右侧元素,依次为 $(\\\\textit{top} + 1, \\\\textit{right})$ 到 $(\\\\textit{bottom}, \\\\textit{right})$。
\\n- \\n
\\n如果 $\\\\textit{left} < \\\\textit{right}$ 且 $\\\\textit{top} < \\\\textit{bottom}$,则从右到左填入下侧元素,依次为 $(\\\\textit{bottom}, \\\\textit{right} - 1)$ 到 $(\\\\textit{bottom}, \\\\textit{left} + 1)$,以及从下到上填入左侧元素,依次为 $(\\\\textit{bottom}, \\\\textit{left})$ 到 $(\\\\textit{top} + 1, \\\\textit{left})$。
\\n填完当前层的元素之后,将 $\\\\textit{left}$ 和 $\\\\textit{top}$ 分别增加 $1$,将 $\\\\textit{right}$ 和 $\\\\textit{bottom}$ 分别减少 $1$,进入下一层继续填入元素,直到填完所有元素为止。
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
,
,
,
>
###Java
\\n\\nclass Solution {\\n public int[][] generateMatrix(int n) {\\n int num = 1;\\n int[][] matrix = new int[n][n];\\n int left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (int column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (int row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (int column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (int row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n }\\n}\\n
###JavaScript
\\n\\nvar generateMatrix = function(n) {\\n let num = 1;\\n const matrix = new Array(n).fill(0).map(() => new Array(n).fill(0));\\n let left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (let column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (let row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (let column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (let row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n};\\n
###go
\\n\\nfunc generateMatrix(n int) [][]int {\\n matrix := make([][]int, n)\\n for i := range matrix {\\n matrix[i] = make([]int, n)\\n }\\n num := 1\\n left, right, top, bottom := 0, n-1, 0, n-1\\n for left <= right && top <= bottom {\\n for column := left; column <= right; column++ {\\n matrix[top][column] = num\\n num++\\n }\\n for row := top + 1; row <= bottom; row++ {\\n matrix[row][right] = num\\n num++\\n }\\n if left < right && top < bottom {\\n for column := right - 1; column > left; column-- {\\n matrix[bottom][column] = num\\n num++\\n }\\n for row := bottom; row > top; row-- {\\n matrix[row][left] = num\\n num++\\n }\\n }\\n left++\\n right--\\n top++\\n bottom--\\n }\\n return matrix\\n}\\n
###Python
\\n\\nclass Solution:\\n def generateMatrix(self, n: int) -> List[List[int]]:\\n matrix = [[0] * n for _ in range(n)]\\n num = 1\\n left, right, top, bottom = 0, n - 1, 0, n - 1\\n\\n while left <= right and top <= bottom:\\n for col in range(left, right + 1):\\n matrix[top][col] = num\\n num += 1\\n for row in range(top + 1, bottom + 1):\\n matrix[row][right] = num\\n num += 1\\n if left < right and top < bottom:\\n for col in range(right - 1, left, -1):\\n matrix[bottom][col] = num\\n num += 1\\n for row in range(bottom, top, -1):\\n matrix[row][left] = num\\n num += 1\\n left += 1\\n right -= 1\\n top += 1\\n bottom -= 1\\n\\n return matrix\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<int>> generateMatrix(int n) {\\n int num = 1;\\n vector<vector<int>> matrix(n, vector<int>(n));\\n int left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (int column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (int row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (int column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (int row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n }\\n};\\n
###C
\\n\\nint** generateMatrix(int n, int* returnSize, int** returnColumnSizes) {\\n int num = 1;\\n int** matrix = malloc(sizeof(int*) * n);\\n *returnSize = n;\\n *returnColumnSizes = malloc(sizeof(int) * n);\\n for (int i = 0; i < n; i++) {\\n matrix[i] = malloc(sizeof(int) * n);\\n memset(matrix[i], 0, sizeof(int) * n);\\n (*returnColumnSizes)[i] = n;\\n }\\n int left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (int column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (int row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (int column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (int row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n}\\n
###TypeScript
\\n\\nfunction generateMatrix(n: number): number[][] {\\n let num = 1;\\n const matrix: number[][] = Array.from({ length: n }, () => Array(n).fill(0));\\n let left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (let column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (let row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (let column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (let row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[][] GenerateMatrix(int n) {\\n int num = 1;\\n int[][] matrix = new int[n][];\\n for (int i = 0; i < n; i++) {\\n matrix[i] = new int[n];\\n }\\n int left = 0, right = n - 1, top = 0, bottom = n - 1;\\n while (left <= right && top <= bottom) {\\n for (int column = left; column <= right; column++) {\\n matrix[top][column] = num;\\n num++;\\n }\\n for (int row = top + 1; row <= bottom; row++) {\\n matrix[row][right] = num;\\n num++;\\n }\\n if (left < right && top < bottom) {\\n for (int column = right - 1; column > left; column--) {\\n matrix[bottom][column] = num;\\n num++;\\n }\\n for (int row = bottom; row > top; row--) {\\n matrix[row][left] = num;\\n num++;\\n }\\n }\\n left++;\\n right--;\\n top++;\\n bottom--;\\n }\\n return matrix;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn generate_matrix(n: i32) -> Vec<Vec<i32>> {\\n let mut num = 1;\\n let mut matrix = vec![vec![0; n as usize]; n as usize];\\n let (mut left, mut right, mut top, mut bottom) = (0, n - 1, 0, n - 1);\\n while left <= right && top <= bottom {\\n for column in left..=right {\\n matrix[top as usize][column as usize] = num;\\n num += 1;\\n }\\n for row in (top + 1)..=bottom {\\n matrix[row as usize][right as usize] = num;\\n num += 1;\\n }\\n if left < right && top < bottom {\\n for column in ((left + 1)..right).rev() {\\n matrix[bottom as usize][column as usize] = num;\\n num += 1;\\n }\\n for row in ((top + 1)..=bottom).rev() {\\n matrix[row as usize][left as usize] = num;\\n num += 1;\\n }\\n }\\n left += 1;\\n right -= 1;\\n top += 1;\\n bottom -= 1;\\n }\\n matrix\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:模拟 模拟矩阵的生成。按照要求,初始位置设为矩阵的左上角,初始方向设为向右。若下一步的位置超出矩阵边界,或者是之前访问过的位置,则顺时针旋转,进入下一个方向。如此反复直至填入 $n^2$ 个元素。\\n\\n记 $\\\\textit{matrix}$ 为生成的矩阵,其初始元素设为 $0$。由于填入的元素均为正数,我们可以判断当前位置的元素值,若不为 $0$,则说明已经访问过此位置。\\n\\n###Java\\n\\nclass Solution {\\n public int[][] generateMatrix(int n) {\\n int maxNum = n…","guid":"https://leetcode.cn/problems/spiral-matrix-ii//solution/luo-xuan-ju-zhen-ii-by-leetcode-solution-f7fp","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-15T13:39:09.493Z","media":[{"url":"https://assets.leetcode.cn/solution-static/59/1.png","type":"photo","width":2000,"height":1125,"blurhash":"LnHV9wRj00oeIUj[xaay00of.8WC"},{"url":"https://assets.leetcode.cn/solution-static/59/2.png","type":"photo","width":2000,"height":1125,"blurhash":"LoGI+XRj4ToeE2j[xtaz4mof-=WB"},{"url":"https://assets.leetcode.cn/solution-static/59/3.png","type":"photo","width":2000,"height":1125,"blurhash":"LoF6kMR44nt7E2ofxtWC8^o}-;Rj"},{"url":"https://assets.leetcode.cn/solution-static/59/4.png","type":"photo","width":2000,"height":1125,"blurhash":"LnE3*rR48^o#M_bbt7jY4mtR-;Rj"},{"url":"https://assets.leetcode.cn/solution-static/59/5.png","type":"photo","width":2000,"height":1125,"blurhash":"LqC%~,WA8^s:ROfkt7ay8^of-;WB"},{"url":"https://assets.leetcode.cn/solution-static/59/6.png","type":"photo","width":2000,"height":1125,"blurhash":"LpCQ.EWA8^s.ROj[t7az8^of-;WC"},{"url":"https://assets.leetcode.cn/solution-static/59/7.png","type":"photo","width":2000,"height":1125,"blurhash":"LrBEj{adDgs:M{j[t7azDgkC%NWC"},{"url":"https://assets.leetcode.cn/solution-static/59/8.png","type":"photo","width":2000,"height":1125,"blurhash":"LsA2P,n#H;WYROa}t7j?H;W=x^oI"},{"url":"https://assets.leetcode.cn/solution-static/59/9.png","type":"photo","width":2000,"height":1125,"blurhash":"Lt9Irhn#H;WYV=a}ozoJH;W=x^oI"},{"url":"https://assets.leetcode.cn/solution-static/59/10.png","type":"photo","width":2000,"height":1125,"blurhash":"Lu7:S8e-MHofV=fkkDayMHfltmWC"},{"url":"https://assets.leetcode.cn/solution-static/59/11.png","type":"photo","width":2000,"height":1125,"blurhash":"Lr7pM-VqQ*t8V=kCkDayMHkXtTWB"},{"url":"https://assets.leetcode.cn/solution-static/59/12.png","type":"photo","width":2000,"height":1125,"blurhash":"Ls6d5$Z}VBo#aIj]kDayQ*g4ksf5"},{"url":"https://assets.leetcode.cn/solution-static/59/13.png","type":"photo","width":2000,"height":1125,"blurhash":"Ls5tXcZyVWo$aIf,kDf5VBb_kYn#"},{"url":"https://assets.leetcode.cn/solution-static/59/14.png","type":"photo","width":2000,"height":1125,"blurhash":"Lw5HT9jEZekCelfkbcjsZebIXojZ"},{"url":"https://assets.leetcode.cn/solution-static/59/15.png","type":"photo","width":2000,"height":1125,"blurhash":"Lu2b,We-d.fli]fkf,fPiFflgPf5"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【宫水三叶】详解「快速判断是否回文」&「递推最小分割次数」两遍 DP 解法","url":"https://leetcode.cn/problems/palindrome-partitioning-ii//solution/xiang-jie-liang-bian-dong-tai-gui-hua-ji-s5xr","content":"- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是给定的正整数。矩阵的大小是 $n \\\\times n$,需要填入矩阵中的每个元素。
\\n- \\n
\\n空间复杂度:$O(1)$。除了返回的矩阵以外,空间复杂度是常数。
\\n动态规划
\\n如果在 131. 分割回文串 有使用到 DP 进行预处理的话。
\\n这道题就很简单了,就是一道常规的动态规划题。
\\n为了方便,我们约定所有下标从 $1$ 开始。
\\n即对于长度为 $n$ 的字符串,我们使用 $[1,n]$ 进行表示。
\\n估计不少看过三叶题解的同学都知道,这样做的目的是为了减少边界情况判断,这本身也是对于「哨兵」思想的运用。
\\n递推「最小分割次数」思路
\\n我们定义 $f[r]$ 为将 $[1,r]$ 这一段字符分割为若干回文串的最小分割次数,那么最终答案为 $f[n]$。
\\n不失一般性的考虑 $f[r]$ 如何转移:
\\n\\n
\\n- 从「起点字符」到「第 $r$ 个字符」能形成回文串。那么最小分割次数为 0,此时有 $f[r] = 0$;
\\n- 从「起点字符」到「第 $r$ 个字符」不能形成回文串。此时我们需要枚举左端点 $l$,如果 $[l,r]$ 这一段是回文串的话,那么有 $f[r] = f[l - 1] + 1$。
\\n在 $2$ 中满足回文要求的左端点位置 $l$ 可能有很多个,我们在所有方案中取一个 $\\\\min$ 即可。
\\n快速判断「任意一段子串是否回文」思路
\\n剩下的问题是,我们如何快速判断连续一段 $[l, r]$ 是否为回文串,做法和昨天的 131. 分割回文串 一模一样。
\\n\\n\\nPS. 在 131. 分割回文串,数据范围只有 $16$,因此我们可以不使用 DP 进行预处理,而是使用双指针来判断是否回文也能过。但是该题数据范围为 $2000$(数量级为 $10^3$),使用朴素做法判断是否回文的话,复杂度会去到 $O(n^3)$(计算量为 $10^9$),必然超时。
\\n因此我们不可能每次都使用双指针去线性扫描一遍 $[l, r]$ 判断是否回文。
\\n一个合理的做法是,我们先预处理出所有的 $g[l][r]$,$g[l][r]$ 代表 $[l,r]$ 这一段是否为回文串。
\\n预处理 $g[l][r]$ 的过程可以用递推去做。
\\n要想 $g[l][r] = true$ ,必须满足以下两个条件:
\\n\\n
\\n- $g[l + 1][r - 1] = true$
\\n- $s[l] = s[r]$
\\n由于状态 $g[l][r]$ 依赖于状态 $g[l + 1][r - 1]$,因此需要我们左端点 $l$ 是「从大到小」进行遍历;而右端点 $r$ 是「从小到大」进行遍历。
\\n因此最终的遍历过程可以整理为:右端点 $r$ 一直往右移动(从小到大),在 $r$ 固定情况下,左端点 $l$ 在 $r$ 在左边开始,一直往左移动(从大到小)。
\\n代码:
\\n###Java
\\n\\nclass Solution {\\n public int minCut(String s) {\\n int n = s.length();\\n char[] cs = s.toCharArray();\\n\\n // g[l][r] 代表 [l,r] 这一段是否为回文串\\n boolean[][] g = new boolean[n + 1][n + 1];\\n for (int r = 1; r <= n; r++) {\\n for (int l = r; l >= 1; l--) {\\n // 如果只有一个字符,则[l,r]属于回文\\n if (l == r) {\\n g[l][r] = true;\\n } else {\\n // 在 l 和 r 字符相同的前提下\\n if (cs[l - 1] == cs[r - 1]) {\\n // 如果 l 和 r 长度只有 2;或者 [l+1,r-1] 这一段满足回文,则[l,r]属于回文\\n if (r - l == 1 || g[l + 1][r - 1]) {\\n g[l][r] = true;\\n }\\n }\\n }\\n }\\n }\\n\\n // f[r] 代表将 [1,r] 这一段分割成若干回文子串所需要的最小分割次数\\n int[] f = new int[n + 1];\\n for (int r = 1; r <= n; r++) {\\n // 如果 [1,r] 满足回文,不需要分割\\n if (g[1][r]) {\\n f[r] = 0;\\n } else {\\n // 先设定一个最大分割次数(r 个字符最多消耗 r - 1 次分割)\\n f[r] = r - 1;\\n // 在所有符合 [l,r] 回文的方案中取最小值\\n for (int l = 1; l <= r; l++) {\\n if (g[l][r]) f[r] = Math.min(f[r], f[l - 1] + 1);\\n } \\n }\\n }\\n\\n return f[n];\\n }\\n}\\n
\\n
\\n- 时间复杂度:$O(n^2)$
\\n- 空间复杂度:$O(n^2)$
\\n
\\n关于「如何确定状态定义」
\\n有同学会对「如何确定 DP 的状态定义」有疑问,觉得自己总是定不下 DP 的状态定义。
\\n首先,十分正常,不用担心。
\\nDP 的状态定义,基本上是考经验的(猜的),猜对了 DP 的状态定义,基本上「状态转移方程」就是呼之欲出。
\\n虽然大多数情况都是猜的,但也不是毫无规律,相当一部分是定义是与「结尾」和「答案」有所关联的。
\\n例如本题定义 $f[i]$ 为以下标为 $i$ 的字符作为结尾(结尾)的最小分割次数(答案)。
\\n因此对于那些你没见过的 DP 模型题,可以从这两方面去「猜」。
\\n
\\nManacher(非重要补充)
\\n如果你还学有余力的话,可以看看下面这篇题解。
\\n提供了「回文串」问题的究极答案:Manacher 算法。
\\n由于 Manacher 算法较为局限,只能解决「回文串」问题,远不如 KMP 算法使用广泛,不建议大家深究原理,而是直接当做「模板」背过。
\\n背过这样的算法的意义在于:相当于大脑里有了一个时间复杂度为 $O(n)$ 的 api 可以使用,这个 api 传入一个字符串,返回该字符串的最大回文子串。
\\n\\n如果觉得自己背不下来,也没有问题。事实上我还没有见过必须使用 Manacher 算法才能过的回文串题。
\\n
\\n最后
\\n如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (\\"▔□▔)/
\\n也欢迎你 关注我 和 加入我们的「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。
\\n所有题解已经加入 刷题指南,欢迎 star 哦 ~
\\n","description":"动态规划 如果在 131. 分割回文串 有使用到 DP 进行预处理的话。\\n\\n这道题就很简单了,就是一道常规的动态规划题。\\n\\n为了方便,我们约定所有下标从 $1$ 开始。\\n\\n即对于长度为 $n$ 的字符串,我们使用 $[1,n]$ 进行表示。\\n\\n估计不少看过三叶题解的同学都知道,这样做的目的是为了减少边界情况判断,这本身也是对于「哨兵」思想的运用。\\n\\n递推「最小分割次数」思路\\n\\n我们定义 $f[r]$ 为将 $[1,r]$ 这一段字符分割为若干回文串的最小分割次数,那么最终答案为 $f[n]$。\\n\\n不失一般性的考虑 $f[r]$ 如何转移:\\n\\n从「起点字符」到「第 $r…","guid":"https://leetcode.cn/problems/palindrome-partitioning-ii//solution/xiang-jie-liang-bian-dong-tai-gui-hua-ji-s5xr","author":"AC_OIer","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-08T01:15:37.687Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分割回文串","url":"https://leetcode.cn/problems/palindrome-partitioning//solution/fen-ge-hui-wen-chuan-by-leetcode-solutio-6jkv","content":"方法一:回溯 + 动态规划预处理
\\n思路与算法
\\n由于需要求出字符串 $s$ 的所有分割方案,因此我们考虑使用搜索 + 回溯的方法枚举所有可能的分割方法并进行判断。
\\n假设我们当前搜索到字符串的第 $i$ 个字符,且 $s[0..i-1]$ 位置的所有字符已经被分割成若干个回文串,并且分割结果被放入了答案数组 $\\\\textit{ans}$ 中,那么我们就需要枚举下一个回文串的右边界 $j$,使得 $s[i..j]$ 是一个回文串。
\\n因此,我们可以从 $i$ 开始,从小到大依次枚举 $j$。对于当前枚举的 $j$ 值,我们使用双指针的方法判断 $s[i..j]$ 是否为回文串:如果 $s[i..j]$ 是回文串,那么就将其加入答案数组 $\\\\textit{ans}$ 中,并以 $j+1$ 作为新的 $i$ 进行下一层搜索,并在未来的回溯时将 $s[i..j]$ 从 $\\\\textit{ans}$ 中移除。
\\n如果我们已经搜索完了字符串的最后一个字符,那么就找到了一种满足要求的分割方法。
\\n细节
\\n当我们在判断 $s[i..j]$ 是否为回文串时,常规的方法是使用双指针分别指向 $i$ 和 $j$,每次判断两个指针指向的字符是否相同,直到两个指针相遇。然而这种方法会产生重复计算,例如下面这个例子:
\\n\\n\\n当 $s = \\\\texttt{aaba}$ 时,对于前 $2$ 个字符 $\\\\texttt{aa}$,我们有 $2$ 种分割方法 $[\\\\texttt{aa}]$ 和 $[\\\\texttt{a}, \\\\texttt{a}]$,当我们每一次搜索到字符串的第 $i=2$ 个字符 $\\\\texttt{b}$ 时,都需要对于每个 $s[i..j]$ 使用双指针判断其是否为回文串,这就产生了重复计算。
\\n因此,我们可以将字符串 $s$ 的每个子串 $s[i..j]$ 是否为回文串预处理出来,使用动态规划即可。设 $f(i, j)$ 表示 $s[i..j]$ 是否为回文串,那么有状态转移方程:
\\n$$
\\n
\\nf(i, j) = \\\\begin{cases}
\\n\\\\texttt{True}, & \\\\quad i \\\\geq j \\\\
\\nf(i+1,j-1) \\\\wedge (s[i]=s[j]), & \\\\quad \\\\text{otherwise}
\\n\\\\end{cases}
\\n$$其中 $\\\\wedge$ 表示逻辑与运算,即 $s[i..j]$ 为回文串,当且仅当其为空串($i>j$),其长度为 $1$($i=j$),或者首尾字符相同且 $s[i+1..j-1]$ 为回文串。
\\n预处理完成之后,我们只需要 $O(1)$ 的时间就可以判断任意 $s[i..j]$ 是否为回文串了。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> f;\\n vector<vector<string>> ret;\\n vector<string> ans;\\n int n;\\n\\npublic:\\n void dfs(const string& s, int i) {\\n if (i == n) {\\n ret.push_back(ans);\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (f[i][j]) {\\n ans.push_back(s.substr(i, j - i + 1));\\n dfs(s, j + 1);\\n ans.pop_back();\\n }\\n }\\n }\\n\\n vector<vector<string>> partition(string s) {\\n n = s.size();\\n f.assign(n, vector<int>(n, true));\\n\\n for (int i = n - 1; i >= 0; --i) {\\n for (int j = i + 1; j < n; ++j) {\\n f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];\\n }\\n }\\n\\n dfs(s, 0);\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n boolean[][] f;\\n List<List<String>> ret = new ArrayList<List<String>>();\\n List<String> ans = new ArrayList<String>();\\n int n;\\n\\n public List<List<String>> partition(String s) {\\n n = s.length();\\n f = new boolean[n][n];\\n for (int i = 0; i < n; ++i) {\\n Arrays.fill(f[i], true);\\n }\\n\\n for (int i = n - 1; i >= 0; --i) {\\n for (int j = i + 1; j < n; ++j) {\\n f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];\\n }\\n }\\n\\n dfs(s, 0);\\n return ret;\\n }\\n\\n public void dfs(String s, int i) {\\n if (i == n) {\\n ret.add(new ArrayList<String>(ans));\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (f[i][j]) {\\n ans.add(s.substring(i, j + 1));\\n dfs(s, j + 1);\\n ans.remove(ans.size() - 1);\\n }\\n }\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def partition(self, s: str) -> List[List[str]]:\\n n = len(s)\\n f = [[True] * n for _ in range(n)]\\n\\n for i in range(n - 1, -1, -1):\\n for j in range(i + 1, n):\\n f[i][j] = (s[i] == s[j]) and f[i + 1][j - 1]\\n\\n ret = list()\\n ans = list()\\n\\n def dfs(i: int):\\n if i == n:\\n ret.append(ans[:])\\n return\\n \\n for j in range(i, n):\\n if f[i][j]:\\n ans.append(s[i:j+1])\\n dfs(j + 1)\\n ans.pop()\\n\\n dfs(0)\\n return ret\\n
###JavaScript
\\n\\nvar partition = function(s) {\\n const dfs = (i) => {\\n if (i === n) {\\n ret.push(ans.slice());\\n return;\\n }\\n for (let j = i; j < n; ++j) {\\n if (f[i][j]) {\\n ans.push(s.slice(i, j + 1));\\n dfs(j + 1);\\n ans.pop();\\n }\\n }\\n }\\n \\n const n = s.length;\\n const f = new Array(n).fill(0).map(() => new Array(n).fill(true));\\n let ret = [], ans = [];\\n \\n for (let i = n - 1; i >= 0; --i) {\\n for (let j = i + 1; j < n; ++j) {\\n f[i][j] = (s[i] === s[j]) && f[i + 1][j - 1];\\n }\\n }\\n dfs(0);\\n return ret;\\n};\\n
###go
\\n\\nfunc partition(s string) (ans [][]string) {\\n n := len(s)\\n f := make([][]bool, n)\\n for i := range f {\\n f[i] = make([]bool, n)\\n for j := range f[i] {\\n f[i][j] = true\\n }\\n }\\n for i := n - 1; i >= 0; i-- {\\n for j := i + 1; j < n; j++ {\\n f[i][j] = s[i] == s[j] && f[i+1][j-1]\\n }\\n }\\n\\n splits := []string{}\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == n {\\n ans = append(ans, append([]string(nil), splits...))\\n return\\n }\\n for j := i; j < n; j++ {\\n if f[i][j] {\\n splits = append(splits, s[i:j+1])\\n dfs(j + 1)\\n splits = splits[:len(splits)-1]\\n }\\n }\\n }\\n dfs(0)\\n return\\n}\\n
###C
\\n\\nvoid dfs(char* s, int n, int i, int** f, char*** ret, int* retSize, int* retColSize, char** ans, int* ansSize) {\\n if (i == n) {\\n char** tmp = malloc(sizeof(char*) * (*ansSize));\\n for (int j = 0; j < (*ansSize); j++) {\\n int ansColSize = strlen(ans[j]);\\n tmp[j] = malloc(sizeof(char) * (ansColSize + 1));\\n strcpy(tmp[j], ans[j]);\\n }\\n ret[*retSize] = tmp;\\n retColSize[(*retSize)++] = *ansSize;\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (f[i][j]) {\\n char* sub = malloc(sizeof(char) * (j - i + 2));\\n for (int k = i; k <= j; k++) {\\n sub[k - i] = s[k];\\n }\\n sub[j - i + 1] = \'\\\\0\';\\n ans[(*ansSize)++] = sub;\\n dfs(s, n, j + 1, f, ret, retSize, retColSize, ans, ansSize);\\n --(*ansSize);\\n }\\n }\\n}\\n\\nchar*** partition(char* s, int* returnSize, int** returnColumnSizes) {\\n int n = strlen(s);\\n int retMaxLen = n * (1 << n);\\n char*** ret = malloc(sizeof(char**) * retMaxLen);\\n *returnSize = 0;\\n *returnColumnSizes = malloc(sizeof(int) * retMaxLen);\\n int* f[n];\\n for (int i = 0; i < n; i++) {\\n f[i] = malloc(sizeof(int) * n);\\n for (int j = 0; j < n; j++) {\\n f[i][j] = 1;\\n }\\n }\\n for (int i = n - 1; i >= 0; --i) {\\n for (int j = i + 1; j < n; ++j) {\\n f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];\\n }\\n }\\n char* ans[n];\\n int ansSize = 0;\\n dfs(s, n, 0, f, ret, returnSize, *returnColumnSizes, ans, &ansSize);\\n return ret;\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n \\\\cdot 2^n)$,其中 $n$ 是字符串 $s$ 的长度。在最坏情况下,$s$ 包含 $n$ 个完全相同的字符,因此它的任意一种划分方法都满足要求。而长度为 $n$ 的字符串的划分方案数为 $2^{n-1}=O(2^n)$,每一种划分方法需要 $O(n)$ 的时间求出对应的划分结果并放入答案,因此总时间复杂度为 $O(n \\\\cdot 2^n)$。尽管动态规划预处理需要 $O(n^2)$ 的时间,但在渐进意义下小于 $O(n \\\\cdot 2^n)$,因此可以忽略。
\\n- \\n
\\n空间复杂度:$O(n^2)$,这里不计算返回答案占用的空间。数组 $f$ 需要使用的空间为 $O(n^2)$,而在回溯的过程中,我们需要使用 $O(n)$ 的栈空间以及 $O(n)$ 的用来存储当前字符串分割方法的空间。由于 $O(n)$ 在渐进意义下小于 $O(n^2)$,因此空间复杂度为 $O(n^2)$。
\\n方法二:回溯 + 记忆化搜索
\\n思路与算法
\\n方法一中的动态规划预处理计算出了任意的 $s[i..j]$ 是否为回文串,我们也可以将这一步改为记忆化搜索。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n vector<vector<int>> f;\\n vector<vector<string>> ret;\\n vector<string> ans;\\n int n;\\n\\npublic:\\n void dfs(const string& s, int i) {\\n if (i == n) {\\n ret.push_back(ans);\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (isPalindrome(s, i, j) == 1) {\\n ans.push_back(s.substr(i, j - i + 1));\\n dfs(s, j + 1);\\n ans.pop_back();\\n }\\n }\\n }\\n\\n // 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串\\n int isPalindrome(const string& s, int i, int j) {\\n if (f[i][j]) {\\n return f[i][j];\\n }\\n if (i >= j) {\\n return f[i][j] = 1;\\n }\\n return f[i][j] = (s[i] == s[j] ? isPalindrome(s, i + 1, j - 1) : -1);\\n }\\n\\n vector<vector<string>> partition(string s) {\\n n = s.size();\\n f.assign(n, vector<int>(n));\\n\\n dfs(s, 0);\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n int[][] f;\\n List<List<String>> ret = new ArrayList<List<String>>();\\n List<String> ans = new ArrayList<String>();\\n int n;\\n\\n public List<List<String>> partition(String s) {\\n n = s.length();\\n f = new int[n][n];\\n\\n dfs(s, 0);\\n return ret;\\n }\\n\\n public void dfs(String s, int i) {\\n if (i == n) {\\n ret.add(new ArrayList<String>(ans));\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (isPalindrome(s, i, j) == 1) {\\n ans.add(s.substring(i, j + 1));\\n dfs(s, j + 1);\\n ans.remove(ans.size() - 1);\\n }\\n }\\n }\\n\\n // 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串\\n public int isPalindrome(String s, int i, int j) {\\n if (f[i][j] != 0) {\\n return f[i][j];\\n }\\n if (i >= j) {\\n f[i][j] = 1;\\n } else if (s.charAt(i) == s.charAt(j)) {\\n f[i][j] = isPalindrome(s, i + 1, j - 1);\\n } else {\\n f[i][j] = -1;\\n }\\n return f[i][j];\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def partition(self, s: str) -> List[List[str]]:\\n n = len(s)\\n\\n ret = list()\\n ans = list()\\n\\n @cache\\n def isPalindrome(i: int, j: int) -> int:\\n if i >= j:\\n return 1\\n return isPalindrome(i + 1, j - 1) if s[i] == s[j] else -1\\n\\n def dfs(i: int):\\n if i == n:\\n ret.append(ans[:])\\n return\\n \\n for j in range(i, n):\\n if isPalindrome(i, j) == 1:\\n ans.append(s[i:j+1])\\n dfs(j + 1)\\n ans.pop()\\n\\n dfs(0)\\n isPalindrome.cache_clear()\\n return ret\\n
###JavaScript
\\n\\nvar partition = function(s) {\\n const dfs = (i) => {\\n if (i === n) {\\n ret.push(ans.slice());\\n return;\\n }\\n for (let j = i; j < n; ++j) {\\n if (isPalindrome(i, j) === 1) {\\n ans.push(s.slice(i, j + 1));\\n dfs(j + 1);\\n ans.pop();\\n }\\n }\\n }\\n\\n // 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串\\n const isPalindrome = (i, j) => {\\n if (f[i][j] !== 0) {\\n return f[i][j];\\n }\\n if (i >= j) {\\n f[i][j] = 1;\\n } else if (s[i] === s[j]) {\\n f[i][j] = isPalindrome(i + 1, j - 1);\\n } else {\\n f[i][j] = -1;\\n }\\n return f[i][j];\\n }\\n\\n const n = s.length;\\n const ret = [], ans = [];\\n const f = new Array(n).fill(0).map(() => new Array(n).fill(0));\\n\\n dfs(0);\\n return ret;\\n};\\n
###go
\\n\\nfunc partition(s string) (ans [][]string) {\\n n := len(s)\\n f := make([][]int8, n)\\n for i := range f {\\n f[i] = make([]int8, n)\\n }\\n\\n // 0 表示尚未搜索,1 表示是回文串,-1 表示不是回文串\\n var isPalindrome func(i, j int) int8\\n isPalindrome = func(i, j int) int8 {\\n if i >= j {\\n return 1\\n }\\n if f[i][j] != 0 {\\n return f[i][j]\\n }\\n f[i][j] = -1\\n if s[i] == s[j] {\\n f[i][j] = isPalindrome(i+1, j-1)\\n }\\n return f[i][j]\\n }\\n\\n splits := []string{}\\n var dfs func(int)\\n dfs = func(i int) {\\n if i == n {\\n ans = append(ans, append([]string(nil), splits...))\\n return\\n }\\n for j := i; j < n; j++ {\\n if isPalindrome(i, j) > 0 {\\n splits = append(splits, s[i:j+1])\\n dfs(j + 1)\\n splits = splits[:len(splits)-1]\\n }\\n }\\n }\\n dfs(0)\\n return\\n}\\n
###C
\\n\\n// 记忆化搜索中,f[i][j] = 0 表示未搜索,1 表示是回文串,-1 表示不是回文串\\nint isPalindrome(char* s, int** f, int i, int j) {\\n if (f[i][j]) {\\n return f[i][j];\\n }\\n if (i >= j) {\\n return f[i][j] = 1;\\n }\\n return f[i][j] = (s[i] == s[j] ? isPalindrome(s, f, i + 1, j - 1) : -1);\\n}\\n\\nvoid dfs(char* s, int n, int i, int** f, char*** ret, int* retSize, int* retColSize, char** ans, int* ansSize) {\\n if (i == n) {\\n char** tmp = malloc(sizeof(char*) * (*ansSize));\\n for (int j = 0; j < (*ansSize); j++) {\\n int ansColSize = strlen(ans[j]);\\n tmp[j] = malloc(sizeof(char) * (ansColSize + 1));\\n strcpy(tmp[j], ans[j]);\\n }\\n ret[*retSize] = tmp;\\n retColSize[(*retSize)++] = *ansSize;\\n return;\\n }\\n for (int j = i; j < n; ++j) {\\n if (isPalindrome(s, f, i, j) == 1) {\\n char* sub = malloc(sizeof(char) * (j - i + 2));\\n for (int k = i; k <= j; k++) {\\n sub[k - i] = s[k];\\n }\\n sub[j - i + 1] = \'\\\\0\';\\n ans[(*ansSize)++] = sub;\\n dfs(s, n, j + 1, f, ret, retSize, retColSize, ans, ansSize);\\n --(*ansSize);\\n }\\n }\\n}\\n\\nchar*** partition(char* s, int* returnSize, int** returnColumnSizes) {\\n int n = strlen(s);\\n int retMaxLen = n * (1 << n);\\n char*** ret = malloc(sizeof(char**) * retMaxLen);\\n *returnSize = 0;\\n *returnColumnSizes = malloc(sizeof(int) * retMaxLen);\\n int* f[n];\\n for (int i = 0; i < n; i++) {\\n f[i] = malloc(sizeof(int) * n);\\n for (int j = 0; j < n; j++) {\\n f[i][j] = 1;\\n }\\n }\\n for (int i = n - 1; i >= 0; --i) {\\n for (int j = i + 1; j < n; ++j) {\\n f[i][j] = (s[i] == s[j]) && f[i + 1][j - 1];\\n }\\n }\\n char* ans[n];\\n int ansSize = 0;\\n dfs(s, n, 0, f, ret, returnSize, *returnColumnSizes, ans, &ansSize);\\n return ret;\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:回溯 + 动态规划预处理 思路与算法\\n\\n由于需要求出字符串 $s$ 的所有分割方案,因此我们考虑使用搜索 + 回溯的方法枚举所有可能的分割方法并进行判断。\\n\\n假设我们当前搜索到字符串的第 $i$ 个字符,且 $s[0..i-1]$ 位置的所有字符已经被分割成若干个回文串,并且分割结果被放入了答案数组 $\\\\textit{ans}$ 中,那么我们就需要枚举下一个回文串的右边界 $j$,使得 $s[i..j]$ 是一个回文串。\\n\\n因此,我们可以从 $i$ 开始,从小到大依次枚举 $j$。对于当前枚举的 $j$ 值,我们使用双指针的方法判断 $s[i..j]$…","guid":"https://leetcode.cn/problems/palindrome-partitioning//solution/fen-ge-hui-wen-chuan-by-leetcode-solutio-6jkv","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-03-06T15:39:12.079Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"线性时间复杂度解法","url":"https://leetcode.cn/problems/break-a-palindrome//solution/xian-xing-shi-jian-fu-za-du-jie-fa-by-xi-9h7h","content":"- \\n
\\n时间复杂度:$O(n \\\\cdot 2^n)$,其中 $n$ 是字符串 $s$ 的长度,与方法一相同。
\\n- \\n
\\n空间复杂度:$O(n^2)$,与方法一相同。
\\n\\n
\\n- 如果只有一个字符,直接返回空字符串;
\\n- 遍历字符串,统计\'a\'的个数,如果字符串长度是n,‘a’的个数为n或者n - 1,只需要把最后一个字符改成‘b’;
\\n- 其他情况,遍历寻找第一个不是‘a’的字符,并把对应字符改成‘a’;
\\n\\nclass Solution {\\npublic:\\n string breakPalindrome(string palindrome) {\\n int n = palindrome.size();\\n if(n == 1) return \\"\\";\\n int cnt = 0;\\n for(char ch: palindrome){\\n if(ch == \'a\') cnt++;\\n }\\n if(cnt >= n - 1){\\n palindrome[n - 1] = \'b\';\\n return palindrome;\\n }\\n for(int i = 0; i < n; ++i){\\n char ch = palindrome[i];\\n if(ch != \'a\'){\\n palindrome[i] = \'a\';\\n return palindrome;\\n }\\n }\\n return palindrome;\\n }\\n};\\n
时间复杂度:O(n)
\\n","description":"如果只有一个字符,直接返回空字符串; 遍历字符串,统计\'a\'的个数,如果字符串长度是n,‘a’的个数为n或者n - 1,只需要把最后一个字符改成‘b’;\\n其他情况,遍历寻找第一个不是‘a’的字符,并把对应字符改成‘a’;\\nclass Solution {\\npublic:\\n string breakPalindrome(string palindrome) {\\n int n = palindrome.size();\\n if(n == 1) return \\"\\";\\n int cnt = 0…","guid":"https://leetcode.cn/problems/break-a-palindrome//solution/xian-xing-shi-jian-fu-za-du-jie-fa-by-xi-9h7h","author":"xiao-ryan","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-02-17T07:51:42.764Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"袋子里最少数目的球","url":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/dai-zi-li-zui-shao-shu-mu-de-qiu-by-zero-upwe","content":"
\\n空间复杂度:O(1)方法一:二分查找
\\n思路与算法
\\n首先转换成判定问题,即:
\\n\\n\\n给定 $\\\\textit{maxOperations}$ 次操作次数,能否可以使得单个袋子里球数目的最大值不超过 $y$。
\\n由于当 $y$ 增加时,操作次数会减少,因此 $y$ 具有单调性,我们可以通过二分查找的方式得到答案。
\\n事实上,如果单个袋子里有 $x$ 个球,那么操作次数即为:
\\n$$
\\n
\\n\\\\lfloor \\\\frac{x-1}{y} \\\\rfloor
\\n$$其中 $\\\\lfloor x \\\\rfloor$ 表示将 $x$ 进行下取整。因此我们需要找到最小的 $y$,使得:
\\n$$
\\n
\\n\\\\sum_{x \\\\in \\\\textit{nums}} \\\\lfloor \\\\frac{x-1}{y} \\\\rfloor \\\\leq \\\\textit{maxOperations}
\\n$$成立。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minimumSize(vector<int>& nums, int maxOperations) {\\n int left = 1, right = *max_element(nums.begin(), nums.end());\\n int ans = 0;\\n while (left <= right) {\\n int y = (left + right) / 2;\\n long long ops = 0;\\n for (int x: nums) {\\n ops += (x - 1) / y;\\n }\\n if (ops <= maxOperations) {\\n ans = y;\\n right = y - 1;\\n }\\n else {\\n left = y + 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二分查找 思路与算法\\n\\n首先转换成判定问题,即:\\n\\n给定 $\\\\textit{maxOperations}$ 次操作次数,能否可以使得单个袋子里球数目的最大值不超过 $y$。\\n\\n由于当 $y$ 增加时,操作次数会减少,因此 $y$ 具有单调性,我们可以通过二分查找的方式得到答案。\\n\\n事实上,如果单个袋子里有 $x$ 个球,那么操作次数即为:\\n\\n$$\\n \\\\lfloor \\\\frac{x-1}{y} \\\\rfloor\\n $$\\n\\n其中 $\\\\lfloor x \\\\rfloor$ 表示将 $x$ 进行下取整。因此我们需要找到最小的 $y$,使得:\\n\\n$$\\n \\\\sum_{x \\\\in…","guid":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/dai-zi-li-zui-shao-shu-mu-de-qiu-by-zero-upwe","author":"zerotrac2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-02-14T04:12:00.940Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++/Java/Python 二分答案,一定给你讲明白~","url":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/c-er-fen-da-an-yi-ding-gei-ni-jiang-ming-vubq","content":"- \\n
\\n时间复杂度:$O(n \\\\log C)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度,$C$ 是数组 $\\\\textit{nums}$ 中的最大值,不超过 $10^9$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n思路:二分答案
\\n题意求最小化最大开销,类似这样的求最大化最小值、最小化最大值等都可以用二分搜索解决。
\\n
\\nLeetCode 1552 两球之间的磁力与本题类似。实现细节:
\\n\\n
\\n- \\n
\\n定义二分搜索的边界
\\n对于一般的二分问题,我们直接定义左右边界如下即可:$left = 1, right = 1e9$
\\n- \\n
\\n二分答案的具体过程
\\n确定好了边界后,每次二分搜索时需要判断当前计算值是否满足条件,这里我们引入 check 函数,对当前计算出的开销最大值进行验证。
\\n验证过程也即判断,在
\\nmaxOperations
的操作次数内,能否使所有袋子中的球类数目最大值控制为当前二分计算出的cost
。我们只需要遍历一次数组,遇到一个装有小球数量大于cost
的袋子,我们便需要将其分成不超过cost
的若干份。遍历完整个数组后,我们判断操作次数是否小于等于最大操作次数maxOperations
即可。- \\n
\\n某一次判断的具体例子
\\n例如,示例 3 中,假设我们当前二分搜索计算出的最大开销为 $7$,那么在验证过程中,我们遍历整个数组,并期望将全部小球分成若干袋,每袋数量均不超过 $7$ 。那么第一个元素 $7$ 自然不需要分解,第二个元素 $17$ 应该被分解到 $7 + 7 + 3$ 的三个袋子中(只需要操作 $2$ 次)。遍历完整个数组后,一共只需要操作 2 次,并未超过最大操作次数。说明当前计算得到的开销可以被满足。由于题目要求我们计算得到
\\n最小化开销
,所以我们需要继续缩小搜索范围,期望找到更小且满足条件的开销。- \\n
\\n关于返回值
\\n一般而言,可以利用一个额外的变量 $ret$ 记录每次满足条件的搜索值,最后返回该值即可。
\\n代码:
\\n###C++
\\n\\nclass Solution {\\n using ll = long long;\\npublic:\\n bool check(vector<int>& nums, int cost, int maxOperations) {\\n ll ans = 0;\\n for(int cur : nums) {\\n if(cur % cost == 0) {\\n ans += cur / cost - 1;\\n } else {\\n ans += cur / cost;\\n }\\n }\\n return ans <= maxOperations;\\n }\\n \\n int minimumSize(vector<int>& nums, int maxOperations) {\\n ll l = 1, r = 1e9;\\n ll ret = 0;\\n while(l <= r) {\\n int mid = (l + r) / 2;\\n if(check(nums, mid, maxOperations)) {\\n r = mid - 1;\\n ret = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return ret;\\n }\\n};\\n
###java
\\n\\nclass Solution {\\n public boolean check(int[] nums, long cost, int maxOperations) {\\n long ans = 0;\\n for(int cur : nums) {\\n if(cur % cost == 0) {\\n ans += cur / cost - 1;\\n } else {\\n ans += cur / cost;\\n }\\n }\\n return ans <= maxOperations;\\n }\\n\\n public int minimumSize(int[] nums, int maxOperations) {\\n long l = 1, r = 1000000000;\\n long ret = 0;\\n while(l <= r) {\\n long mid = (l + r) / 2;\\n if(check(nums, mid, maxOperations)) {\\n r = mid - 1;\\n ret = mid;\\n } else {\\n l = mid + 1;\\n }\\n }\\n return (int)ret;\\n }\\n}\\n
###python3
\\n\\nclass Solution:\\n def check(self, nums, x, maxOperations):\\n ans=0\\n for b in nums:\\n if b%x==0:\\n ans+=int(b/x)-1\\n else:\\n ans+=int(b/x)\\n return ans<=maxOperations\\n\\n def minimumSize(self, nums: List[int], maxOperations: int) -> int:\\n l=1\\n r=1e9\\n while l<=r:\\n mid=int((l+r)/2)\\n if self.check(nums,mid,maxOperations):\\n r=mid-1\\n else:\\n l=mid+1\\n return l\\n
复杂度分析:
\\n\\n
\\n- \\n
\\n时间复杂度 $O(nlgC)$,二分搜索复杂度为 $O(lgC)$,这里 $C = 1e9$ ,每次
\\ncheck
函数需要遍历数组,复杂度 $O(n)$,所以总复杂度为 $O(nlgC)$;- \\n
\\n空间复杂度为 $O(1)$。
\\n
\\n关注GTAlgorithm,专注周赛、面经题解分享,陪大家一起攻克算法难关~
\\n\\n\\n\\n","description":"思路:二分答案 题意求最小化最大开销,类似这样的求最大化最小值、最小化最大值等都可以用二分搜索解决。\\n LeetCode 1552 两球之间的磁力与本题类似。\\n\\n实现细节:\\n\\n定义二分搜索的边界\\n\\n对于一般的二分问题,我们直接定义左右边界如下即可:$left = 1, right = 1e9$\\n\\n二分答案的具体过程\\n\\n确定好了边界后,每次二分搜索时需要判断当前计算值是否满足条件,这里我们引入 check 函数,对当前计算出的开销最大值进行验证。\\n\\n验证过程也即判断,在maxOperations的操作次数内,能否使所有袋子中的球类数目最大值控制为当前二分计算出的…","guid":"https://leetcode.cn/problems/minimum-limit-of-balls-in-a-bag//solution/c-er-fen-da-an-yi-ding-gei-ni-jiang-ming-vubq","author":"已注销","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-02-14T04:06:51.498Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"杨辉三角 II","url":"https://leetcode.cn/problems/pascals-triangle-ii//solution/yang-hui-san-jiao-ii-by-leetcode-solutio-shuk","content":"方法一:递推
\\n杨辉三角,是二项式系数在三角形中的一种几何排列。它是中国古代数学的杰出研究成果之一,它把二项式系数图形化,把组合数内在的一些代数性质直观地从图形中体现出来,是一种离散型的数与形的结合。
\\n杨辉三角具有以下性质:
\\n\\n
\\n- \\n
\\n每行数字左右对称,由 $1$ 开始逐渐变大再变小,并最终回到 $1$。
\\n- \\n
\\n第 $n$ 行(从 $0$ 开始编号)的数字有 $n+1$ 项,前 $n$ 行共有 $\\\\frac{n(n+1)}{2}$ 个数。
\\n- \\n
\\n第 $n$ 行的第 $m$ 个数(从 $0$ 开始编号)可表示为可以被表示为组合数 $\\\\mathcal{C}(n,m)$,记作 $\\\\mathcal{C}_n^m$ 或 $\\\\binom{n}{m}$,即为从 $n$ 个不同元素中取 $m$ 个元素的组合数。我们可以用公式来表示它:$\\\\mathcal{C}_n^m=\\\\dfrac{n!}{m!(n-m)!}$
\\n- \\n
\\n每个数字等于上一行的左右两个数字之和,可用此性质写出整个杨辉三角。即第 $n$ 行的第 $i$ 个数等于第 $n-1$ 行的第 $i-1$ 个数和第 $i$ 个数之和。这也是组合数的性质之一,即 $\\\\mathcal{C}n^i=\\\\mathcal{C}{n-1}^i+\\\\mathcal{C}_{n-1}^{i-1}$。
\\n- \\n
\\n$(a+b)^n$ 的展开式(二项式展开)中的各项系数依次对应杨辉三角的第 $n$ 行中的每一项。
\\n依据性质 $4$,我们可以一行一行地计算杨辉三角。每当我们计算出第 $i$ 行的值,我们就可以在线性时间复杂度内计算出第 $i+1$ 行的值。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> getRow(int rowIndex) {\\n vector<vector<int>> C(rowIndex + 1);\\n for (int i = 0; i <= rowIndex; ++i) {\\n C[i].resize(i + 1);\\n C[i][0] = C[i][i] = 1;\\n for (int j = 1; j < i; ++j) {\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n return C[rowIndex];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public List<Integer> getRow(int rowIndex) {\\n List<List<Integer>> C = new ArrayList<List<Integer>>();\\n for (int i = 0; i <= rowIndex; ++i) {\\n List<Integer> row = new ArrayList<Integer>();\\n for (int j = 0; j <= i; ++j) {\\n if (j == 0 || j == i) {\\n row.add(1);\\n } else {\\n row.add(C.get(i - 1).get(j - 1) + C.get(i - 1).get(j));\\n }\\n }\\n C.add(row);\\n }\\n return C.get(rowIndex);\\n }\\n}\\n
###go
\\n\\nfunc getRow(rowIndex int) []int {\\n C := make([][]int, rowIndex+1)\\n for i := range C {\\n C[i] = make([]int, i+1)\\n C[i][0], C[i][i] = 1, 1\\n for j := 1; j < i; j++ {\\n C[i][j] = C[i-1][j-1] + C[i-1][j]\\n }\\n }\\n return C[rowIndex]\\n}\\n
###JavaScript
\\n\\nvar getRow = function(rowIndex) {\\n const C = new Array(rowIndex + 1).fill(0);\\n for (let i = 0; i <= rowIndex; ++i) {\\n C[i] = new Array(i + 1).fill(0);\\n C[i][0] = C[i][i] = 1;\\n for (let j = 1; j < i; j++) {\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n return C[rowIndex];\\n};\\n
###C
\\n\\nint* getRow(int rowIndex, int* returnSize) {\\n *returnSize = rowIndex + 1;\\n int* C[rowIndex + 1];\\n for (int i = 0; i <= rowIndex; ++i) {\\n C[i] = malloc(sizeof(int) * (i + 1));\\n C[i][0] = C[i][i] = 1;\\n for (int j = 1; j < i; ++j) {\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n return C[rowIndex];\\n}\\n
###Python
\\n\\nclass Solution:\\n def getRow(self, rowIndex: int) -> List[int]:\\n C = [[1] * (i + 1) for i in range(rowIndex + 1)]\\n for i in range(0, rowIndex + 1):\\n for j in range(1, i):\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j]\\n return C[rowIndex]\\n
###TypeScript
\\n\\nfunction getRow(rowIndex: number): number[] {\\n const C: number[][] = Array.from({ length: rowIndex + 1 }, () => []);\\n for (let i = 0; i <= rowIndex; i++) {\\n C[i] = Array(i + 1).fill(1);\\n for (let j = 1; j < i; j++) {\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n return C[rowIndex];\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<int> GetRow(int rowIndex) {\\n List<List<int>> C = new List<List<int>>();\\n for (int i = 0; i <= rowIndex; ++i) {\\n C.Add(new List<int>(new int[i + 1]));\\n C[i][0] = C[i][i] = 1;\\n for (int j = 1; j < i; ++j) {\\n C[i][j] = C[i - 1][j - 1] + C[i - 1][j];\\n }\\n }\\n return C[rowIndex];\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn get_row(row_index: i32) -> Vec<i32> {\\n let mut C: Vec<Vec<i32>> = Vec::with_capacity((row_index + 1) as usize);\\n for i in 0..=row_index {\\n let mut row: Vec<i32> = vec![1; (i + 1) as usize];\\n for j in 1..i {\\n row[j as usize] = C[(i - 1) as usize][(j - 1) as usize] + C[(i - 1) as usize][j as usize];\\n }\\n C.push(row);\\n }\\n C[row_index as usize].clone()\\n }\\n}\\n
优化
\\n注意到对第 $i+1$ 行的计算仅用到了第 $i$ 行的数据,因此可以使用滚动数组的思想优化空间复杂度。
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> getRow(int rowIndex) {\\n vector<int> pre, cur;\\n for (int i = 0; i <= rowIndex; ++i) {\\n cur.resize(i + 1);\\n cur[0] = cur[i] = 1;\\n for (int j = 1; j < i; ++j) {\\n cur[j] = pre[j - 1] + pre[j];\\n }\\n pre = cur;\\n }\\n return pre;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public List<Integer> getRow(int rowIndex) {\\n List<Integer> pre = new ArrayList<Integer>();\\n for (int i = 0; i <= rowIndex; ++i) {\\n List<Integer> cur = new ArrayList<Integer>();\\n for (int j = 0; j <= i; ++j) {\\n if (j == 0 || j == i) {\\n cur.add(1);\\n } else {\\n cur.add(pre.get(j - 1) + pre.get(j));\\n }\\n }\\n pre = cur;\\n }\\n return pre;\\n }\\n}\\n
###go
\\n\\nfunc getRow(rowIndex int) []int {\\n var pre, cur []int\\n for i := 0; i <= rowIndex; i++ {\\n cur = make([]int, i+1)\\n cur[0], cur[i] = 1, 1\\n for j := 1; j < i; j++ {\\n cur[j] = pre[j-1] + pre[j]\\n }\\n pre = cur\\n }\\n return pre\\n}\\n
###JavaScript
\\n\\nvar getRow = function(rowIndex) {\\n let pre = [], cur = [];\\n for (let i = 0; i <= rowIndex; ++i) {\\n cur = new Array(i + 1).fill(0);\\n cur[0] = cur[i] =1;\\n for (let j = 1; j < i; ++j) {\\n cur[j] = pre[j - 1] + pre[j];\\n }\\n pre = cur;\\n }\\n return pre;\\n};\\n
###C
\\n\\nint* getRow(int rowIndex, int* returnSize) {\\n *returnSize = rowIndex + 1;\\n int* pre = malloc(sizeof(int) * (*returnSize));\\n memset(pre, 0, sizeof(int) * (*returnSize));\\n int* cur = malloc(sizeof(int) * (*returnSize));\\n memset(cur, 0, sizeof(int) * (*returnSize));\\n for (int i = 0; i <= rowIndex; ++i) {\\n cur[0] = cur[i] = 1;\\n for (int j = 1; j < i; ++j) {\\n cur[j] = pre[j - 1] + pre[j];\\n }\\n int* tmp = pre;\\n pre = cur;\\n cur = tmp;\\n }\\n return pre;\\n}\\n
###Python
\\n\\nclass Solution:\\n def getRow(self, rowIndex: int) -> List[int]:\\n pre, cur = [], []\\n for i in range(rowIndex + 1):\\n cur = [1] * (i + 1)\\n for j in range(1, i):\\n cur[j] = pre[j - 1] + pre[j]\\n pre = cur\\n return pre\\n
###TypeScript
\\n\\nfunction getRow(rowIndex: number): number[] {\\n let pre: number[] = [];\\n let cur: number[] = [];\\n for (let i = 0; i <= rowIndex; i++) {\\n cur = new Array(i + 1).fill(1);\\n for (let j = 1; j < i; j++) {\\n cur[j] = pre[j - 1] + pre[j];\\n }\\n pre = cur;\\n }\\n return pre;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<int> GetRow(int rowIndex) {\\n List<int> pre = new List<int>();\\n List<int> cur = new List<int>();\\n for (int i = 0; i <= rowIndex; ++i) {\\n cur = new List<int>(new int[i + 1]);\\n cur[0] = cur[i] = 1;\\n for (int j = 1; j < i; ++j) {\\n cur[j] = pre[j - 1] + pre[j];\\n }\\n pre = new List<int>(cur);\\n }\\n return pre;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn get_row(row_index: i32) -> Vec<i32> {\\n let mut pre: Vec<i32> = Vec::new();\\n let mut cur: Vec<i32> = Vec::new();\\n for i in 0..=row_index {\\n cur = vec![1; (i + 1) as usize];\\n for j in 1..i {\\n cur[j as usize] = pre[(j - 1) as usize] + pre[j as usize];\\n }\\n pre = cur.clone();\\n }\\n pre\\n }\\n}\\n
进一步优化
\\n能否只用一个数组呢?
\\n递推式 $\\\\mathcal{C}n^i=\\\\mathcal{C}{n-1}^i+\\\\mathcal{C}_{n-1}^{i-1}$ 表明,当前行第 $i$ 项的计算只与上一行第 $i-1$ 项及第 $i$ 项有关。因此我们可以倒着计算当前行,这样计算到第 $i$ 项时,第 $i-1$ 项仍然是上一行的值。
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> getRow(int rowIndex) {\\n vector<int> row(rowIndex + 1);\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n for (int j = i; j > 0; --j) {\\n row[j] += row[j - 1];\\n }\\n }\\n return row;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public List<Integer> getRow(int rowIndex) {\\n List<Integer> row = new ArrayList<Integer>();\\n row.add(1);\\n for (int i = 1; i <= rowIndex; ++i) {\\n row.add(0);\\n for (int j = i; j > 0; --j) {\\n row.set(j, row.get(j) + row.get(j - 1));\\n }\\n }\\n return row;\\n }\\n}\\n
###go
\\n\\nfunc getRow(rowIndex int) []int {\\n row := make([]int, rowIndex+1)\\n row[0] = 1\\n for i := 1; i <= rowIndex; i++ {\\n for j := i; j > 0; j-- {\\n row[j] += row[j-1]\\n }\\n }\\n return row\\n}\\n
###JavaScript
\\n\\nvar getRow = function(rowIndex) {\\n const row = new Array(rowIndex + 1).fill(0);\\n row[0] = 1;\\n for (let i = 1; i <= rowIndex; ++i) {\\n for (let j = i; j > 0; --j) {\\n row[j] += row[j - 1];\\n }\\n }\\n return row;\\n};\\n
###C
\\n\\nint* getRow(int rowIndex, int* returnSize) {\\n *returnSize = rowIndex + 1;\\n int* row = malloc(sizeof(int) * (*returnSize));\\n memset(row, 0, sizeof(int) * (*returnSize));\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n for (int j = i; j > 0; --j) {\\n row[j] += row[j - 1];\\n }\\n }\\n return row;\\n}\\n
###Python
\\n\\nclass Solution:\\n def getRow(self, rowIndex: int) -> List[int]:\\n row = [0] * (rowIndex + 1)\\n row[0] = 1\\n for i in range(1, rowIndex + 1):\\n for j in range(i, 0, -1):\\n row[j] += row[j - 1]\\n return row\\n
###TypeScript
\\n\\nfunction getRow(rowIndex: number): number[] {\\n const row: number[] = new Array(rowIndex + 1).fill(0);\\n row[0] = 1;\\n for (let i = 1; i <= rowIndex; i++) {\\n for (let j = i; j > 0; j--) {\\n row[j] += row[j - 1];\\n }\\n }\\n return row;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<int> GetRow(int rowIndex) {\\n List<int> row = new List<int>(new int[rowIndex + 1]);\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n for (int j = i; j > 0; --j) {\\n row[j] += row[j - 1];\\n }\\n }\\n return row;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn get_row(row_index: i32) -> Vec<i32> {\\n let mut row = vec![0; (row_index + 1) as usize];\\n row[0] = 1;\\n for i in 1..=row_index {\\n for j in (1..=i).rev() {\\n row[j as usize] += row[(j - 1) as usize];\\n }\\n }\\n row\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(\\\\textit{rowIndex}^2)$。
\\n- \\n
\\n空间复杂度:$O(1)$。不考虑返回值的空间占用。
\\n方法二:线性递推
\\n由组合数公式 $\\\\mathcal{C}_n^m=\\\\dfrac{n!}{m!(n-m)!}$,可以得到同一行的相邻组合数的关系
\\n$$\\\\mathcal{C}_n^m= \\\\mathcal{C}_n^{m-1} \\\\times \\\\dfrac{n-m+1}{m}$$
\\n由于 $\\\\mathcal{C}_n^0=1$,利用上述公式我们可以在线性时间计算出第 $n$ 行的所有组合数。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> getRow(int rowIndex) {\\n vector<int> row(rowIndex + 1);\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n row[i] = 1LL * row[i - 1] * (rowIndex - i + 1) / i;\\n }\\n return row;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public List<Integer> getRow(int rowIndex) {\\n List<Integer> row = new ArrayList<Integer>();\\n row.add(1);\\n for (int i = 1; i <= rowIndex; ++i) {\\n row.add((int) ((long) row.get(i - 1) * (rowIndex - i + 1) / i));\\n }\\n return row;\\n }\\n}\\n
###go
\\n\\nfunc getRow(rowIndex int) []int {\\n row := make([]int, rowIndex+1)\\n row[0] = 1\\n for i := 1; i <= rowIndex; i++ {\\n row[i] = row[i-1] * (rowIndex - i + 1) / i\\n }\\n return row\\n}\\n
###JavaScript
\\n\\nvar getRow = function(rowIndex) {\\n const row = new Array(rowIndex + 1).fill(0);\\n row[0] = 1;\\n for (let i = 1; i <= rowIndex; ++i) {\\n row[i] = row[i - 1] * (rowIndex - i + 1) / i;\\n }\\n return row;\\n};\\n
###C
\\n\\nint* getRow(int rowIndex, int* returnSize) {\\n *returnSize = rowIndex + 1;\\n int* row = malloc(sizeof(int) * (*returnSize));\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n row[i] = 1LL * row[i - 1] * (rowIndex - i + 1) / i;\\n }\\n return row;\\n}\\n
###Python
\\n\\nclass Solution:\\n def getRow(self, rowIndex: int) -> List[int]:\\n row = [1] * (rowIndex + 1)\\n for i in range(1, rowIndex + 1):\\n row[i] = row[i - 1] * (rowIndex - i + 1) // i\\n return row\\n
###TypeScript
\\n\\nfunction getRow(rowIndex: number): number[] {\\n const row: number[] = new Array(rowIndex + 1).fill(1);\\n for (let i = 1; i <= rowIndex; i++) {\\n row[i] = row[i - 1] * (rowIndex - i + 1) / i;\\n }\\n return row;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<int> GetRow(int rowIndex) {\\n List<int> row = new List<int>(new int[rowIndex + 1]);\\n row[0] = 1;\\n for (int i = 1; i <= rowIndex; ++i) {\\n row[i] = (int)((long)row[i - 1] * (rowIndex - i + 1) / i);\\n }\\n return row;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn get_row(row_index: i32) -> Vec<i32> {\\n let mut row = vec![1; (row_index + 1) as usize];\\n for i in 1..=row_index {\\n row[i as usize] = (row[i as usize - 1] as i64 * (row_index - i + 1) as i64 / i as i64) as i32;\\n }\\n row\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:递推 杨辉三角,是二项式系数在三角形中的一种几何排列。它是中国古代数学的杰出研究成果之一,它把二项式系数图形化,把组合数内在的一些代数性质直观地从图形中体现出来,是一种离散型的数与形的结合。\\n\\n杨辉三角具有以下性质:\\n\\n每行数字左右对称,由 $1$ 开始逐渐变大再变小,并最终回到 $1$。\\n\\n第 $n$ 行(从 $0$ 开始编号)的数字有 $n+1$ 项,前 $n$ 行共有 $\\\\frac{n(n+1)}{2}$ 个数。\\n\\n第 $n$ 行的第 $m$ 个数(从 $0$ 开始编号)可表示为可以被表示为组合数 $\\\\mathcal{C}(n,m…","guid":"https://leetcode.cn/problems/pascals-triangle-ii//solution/yang-hui-san-jiao-ii-by-leetcode-solutio-shuk","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-02-11T10:34:44.320Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java手写实现跳表","url":"https://leetcode.cn/problems/design-skiplist//solution/javashou-xie-shi-xian-tiao-biao-by-feng-omdm0","content":"- \\n
\\n时间复杂度:$O(\\\\textit{rowIndex})$。
\\n- \\n
\\n空间复杂度:$O(1)$。不考虑返回值的空间占用。
\\n解题思路
\\n跳表实现的主要难度在于插入(add)算法。只要把add方法搞明白之后,一切都迎刃而解了。
\\n关于跳表的插入,一张图即可描述出来,
\\n
\\n\\n\\n\\n通过这张图,可以先确定跳表中每个节点的数据结构:
\\n###java
\\n\\nclass Node{\\n Integer value; //节点值\\n Node[] next; // 节点在不同层的下一个节点\\n\\n public Node(Integer value,int size) { // 用size表示当前节点在跳表中索引几层\\n this.value = value;\\n this.next = new Node[size];\\n }\\n}\\n
然后就需要考虑:我插入一个节点Node,它到底应该是索引到第几层呢?
\\n一开始我还想着如何准确的维护上一层是下一层的1/2,发现越想越复杂;然后通过相关资料,发现人家早就给出一个解决方案:随机出来一个层数。
\\n这里有一个疑惑:就凭随机出来的一个层数,能保证查询与插入性能吗?
\\n\\n\\n在分析之前,我们还需要着重指出的是,执行插入操作时计算随机数的过程,是一个很关键的过程,它对skiplist的统计特性有着很重要的影响。这并不是一个普通的服从均匀分布的随机数,它的计算过程如下:
\\n
\\n首先,每个节点肯定都有第1层指针(每个节点都在第1层链表里)。
\\n如果一个节点有第i层(i>=1)指针(即节点已经在第1层到第i层链表中),那么它有第(i+1)层指针的概率为p。
\\n节点最大的层数不允许超过一个最大值,记为MaxLevel。
\\n这个计算随机层数的伪码如下所示:###java
\\n\\nint randomLevel()\\n int level = 1;\\n while (Math.random()<p && level<MaxLevel){\\n level ++ ;\\n }\\n return level;\\n
\\n\\nrandomLevel()的伪码中包含两个参数,一个是p,一个是MaxLevel。在Redis的skiplist实现中,这两个参数的取值为:
\\n\\np = 1/4\\nMaxLevel = 32\\n
\\n\\nfrom : http://zhangtielei.com/posts/blog-redis-skiplist.html
\\n知道了层数,这下就好办了。思路如下:
\\n\\n
\\n- \\n
\\n先随机出来一个层数,new要插入的节点,先叫做插入节点newNode;
\\n- \\n
\\n根据跳表实际的总层数从上往下分析,要插入一个节点newNode时,先找到节点在该层的位置:因为是链表,所以需要一个节点node,满足插入插入节点newNode的值刚好不大于node的下一个节点值,当然,如果node的下个节点为空,那么也是满足的。
\\n我们先把找节点在某一层位置的方法封装起来:
\\n###java
\\n\\n/**\\n* 找到level层 value 刚好不小于node 的节点\\n* @param node 从哪个节点开始找\\n* @param levelIndex 所在层\\n* @param value 要插入的节点值\\n* @return\\n*/\\nprivate Node findClosest(Node node,int levelIndex,int value){\\n while ((node.next[levelIndex])!=null && value >node.next[levelIndex].value){\\n node = node.next[levelIndex];\\n }\\n return node;\\n}\\n
\\n
\\n- \\n
\\n确定插入节点newNode在该层的位置后,先判断下newNode的随机层数是否小于当前跳表的总层数,如果是,则用链表的插入方法将newNode插入即可。
\\n- \\n
\\n如此循环,直到最底层插入newNode完毕。
\\n- \\n
\\n循环完毕后,还需要判断下newNode随机出来的层数是否比跳表的实际层数还要大,如果是,直接将超过实际层数的跳表的头节点指向newNode即可,该跳表的实际层数也就变为newNode的随机层数了。
\\n以上就是插入的算法。
\\n理解了插入算法后,查找跟删除就简单多了。
\\n不管是插入、查找还是删除,均是从跳表上层往下层分析,复用上面的findClosest方法,找到要查询的值 在该层closest的节点。比如查询,只需要判断findClosest出来的节点值是否等于该查询值即可,是就返回,不是则继续往下层判断。删除方法思想也是一致的。
\\n代码
\\n###java
\\n\\n","description":"解题思路 跳表实现的主要难度在于插入(add)算法。只要把add方法搞明白之后,一切都迎刃而解了。\\n\\n关于跳表的插入,一张图即可描述出来,\\n\\n\\n图片来自 http://zhangtielei.com/posts/blog-redis-skiplist.html\\n\\n通过这张图,可以先确定跳表中每个节点的数据结构:\\n\\n###java\\n\\nclass Node{\\n Integer value; //节点值\\n Node[] next; // 节点在不同层的下一个节点\\n\\n public Node(Integer value,int size…","guid":"https://leetcode.cn/problems/design-skiplist//solution/javashou-xie-shi-xian-tiao-biao-by-feng-omdm0","author":"feng-xiang-jue-ding-fa-xing-2a","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-02-02T07:09:19.910Z","media":[{"url":"https://pic.leetcode-cn.com/1612247342-QjALhY-skiplist_insertions.png","type":"photo","width":1141,"height":1338,"blurhash":"LHR{lXMxt-_M_NR5R5bxx[tQM{V@"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"python3 详细动态规划","url":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/python3-xiang-xi-dong-tai-gui-hua-by-tin-pqgt","content":"class Skiplist {\\n /**\\n * 最大层数\\n */\\n private static int DEFAULT_MAX_LEVEL = 32;\\n /**\\n * 随机层数概率,也就是随机出的层数,在 第1层以上(不包括第一层)的概率,层数不超过maxLevel,层数的起始号为1\\n */\\n private static double DEFAULT_P_FACTOR = 0.25;\\n\\n Node head = new Node(null,DEFAULT_MAX_LEVEL); //头节点\\n\\n int currentLevel = 1; //表示当前nodes的实际层数,它从1开始\\n\\n\\n public Skiplist() {\\n }\\n\\n public boolean search(int target) {\\n Node searchNode = head;\\n for (int i = currentLevel-1; i >=0; i--) {\\n searchNode = findClosest(searchNode, i, target);\\n if (searchNode.next[i]!=null && searchNode.next[i].value == target){\\n return true;\\n }\\n }\\n return false;\\n }\\n\\n /**\\n *\\n * @param num\\n */\\n public void add(int num) {\\n int level = randomLevel();\\n Node updateNode = head;\\n Node newNode = new Node(num,level);\\n // 计算出当前num 索引的实际层数,从该层开始添加索引\\n for (int i = currentLevel-1; i>=0; i--) {\\n //找到本层最近离num最近的list\\n updateNode = findClosest(updateNode,i,num);\\n if (i<level){\\n if (updateNode.next[i]==null){\\n updateNode.next[i] = newNode;\\n }else{\\n Node temp = updateNode.next[i];\\n updateNode.next[i] = newNode;\\n newNode.next[i] = temp;\\n }\\n }\\n }\\n if (level > currentLevel){ //如果随机出来的层数比当前的层数还大,那么超过currentLevel的head 直接指向newNode\\n for (int i = currentLevel; i < level; i++) {\\n head.next[i] = newNode;\\n }\\n currentLevel = level;\\n }\\n\\n }\\n\\n public boolean erase(int num) {\\n boolean flag = false;\\n Node searchNode = head;\\n for (int i = currentLevel-1; i >=0; i--) {\\n searchNode = findClosest(searchNode, i, num);\\n if (searchNode.next[i]!=null && searchNode.next[i].value == num){\\n //找到该层中该节点\\n searchNode.next[i] = searchNode.next[i].next[i];\\n flag = true;\\n continue;\\n }\\n }\\n return flag;\\n }\\n\\n /**\\n * 找到level层 value 大于node 的节点\\n * @param node\\n * @param levelIndex\\n * @param value\\n * @return\\n */\\n private Node findClosest(Node node,int levelIndex,int value){\\n while ((node.next[levelIndex])!=null && value >node.next[levelIndex].value){\\n node = node.next[levelIndex];\\n }\\n return node;\\n }\\n\\n\\n /**\\n * 随机一个层数\\n */\\n private static int randomLevel(){\\n int level = 1;\\n while (Math.random()<DEFAULT_P_FACTOR && level<DEFAULT_MAX_LEVEL){\\n level ++ ;\\n }\\n return level;\\n }\\n\\n\\n class Node{\\n Integer value;\\n Node[] next;\\n\\n public Node(Integer value,int size) {\\n this.value = value;\\n this.next = new Node[size];\\n }\\n\\n @Override\\n public String toString() {\\n return String.valueOf(value);\\n }\\n }\\n\\n }\\n
解法一:动态规划(一)
\\n思路
\\n\\n
\\n- 用列表
\\ndp
记录每一段s[i:j]
是否是回文字符串,回文字符串判断如下:\\n\\n
\\n- 如果长度为
\\n0
或1
,那么当前字符串是回文字符串;- 如果左右相同,那么就判断去除左和右是否是回文字符串,如果是,则当前字符串是回文字符串,如果不是,则当前字符串不是回文字符串;
\\n- 如果左右不同,那么当前字符串不是回文字符串。
\\n- 把所有情况都判断一遍。
\\n举例
\\n比如,输入
\\n\\"aba\\"
:
\\n情况如下:\\n\\n
\\n\\n \\n\\n\\n开头↓ 结尾→ \\na \\nb \\na \\n\\n \\na \\n\\n True
\\n False
\\n True
\\n \\nb \\n\\n True
\\n True
\\n False
\\n \\na \\n\\n True
\\n True
\\n True
\\n \\n\\n\\n \\n \\n \\n 复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(N^2)$,定义
\\ndp
的时间复杂度为$O(N^2)$,返回结果的时间复杂度为$O(N^2)$,$O(N^2)+O(N^2)=O(N^2)$。- 空间复杂度:$O(N^2)$。
\\n代码
\\n###python3
\\n\\nclass Solution:\\n def checkPartitioning(self, s: str) -> bool:\\n dp = [[True for __ in range(len(s))] for __ in range(len(s))]\\n for i in range(len(s)-1, -1, -1):\\n for j in range(i+1, len(s)):\\n if s[i] == s[j]:\\n dp[i][j] = dp[i+1][j-1]\\n else:\\n dp[i][j] = False\\n return any([dp[0][i-1] and dp[i][j] and dp[j+1][-1] for i in range(1, len(s)) for j in range(i, len(s)-1)])\\n
\\n解法二:动态规划(二)
\\n思路
\\n\\n
\\n- 用列表
\\ndp
记录每一段s[i:j]
是否是回文字符串。- 得到每个
\\ns[:i]
是否可以分为2
个回文字符串,将结果保存在dp2
里,判断方法如下:\\n\\n
\\n- 将每一种情况都试一遍,用
\\ndp
判断是否是回文字符串。- 得到每个
\\ns
是否可以分为3
个回文字符串,判断方法如下:\\n\\n
\\n- 将每一种情况都试一遍,用
\\ndp
和dp2
判断是否是回文字符串。复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(N^2)$,定义dp的时间复杂度为$O(N^2)$,得到
\\ns
分为3
个回文字符串的情况个数的时间复杂度为$O(N^2)$,返回结果的时间复杂度为$N$,$O(N^2)+O(N^2)+O(N)=O(N^2)$。- 空间复杂度:$O(N^2)$,$dp$用的空间为$O(N^2)$,$dp2$用的空间为$O(N)$,$O(N^2)+O(N)=O(N^2)$。
\\n代码
\\n###python3
\\n\\n","description":"思路 用列表dp记录每一段s[i:j]是否是回文字符串,回文字符串判断如下:\\n如果长度为0或1,那么当前字符串是回文字符串;\\n如果左右相同,那么就判断去除左和右是否是回文字符串,如果是,则当前字符串是回文字符串,如果不是,则当前字符串不是回文字符串;\\n如果左右不同,那么当前字符串不是回文字符串。\\n把所有情况都判断一遍。\\n举例\\n\\n比如,输入\\"aba\\":\\n 情况如下:\\n\\n开头↓ 结尾→\\t a\\t b\\t a a\\t True\\t False\\t True \\n b\\t True\\t True\\t False \\n a\\t True\\t True\\t True \\n \\t \\t \\t\\n复杂度分析…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/python3-xiang-xi-dong-tai-gui-hua-by-tin-pqgt","author":"ting-ting-28","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-31T07:08:55.043Z","media":[{"url":"https://pic.leetcode-cn.com/1612072596-znWFTw-5666%E5%9B%9E%E6%96%87%E4%B8%B2%E5%88%86%E5%89%B2IV%E5%9B%BE%E4%BA%8C.png","type":"photo","width":266,"height":141,"blurhash":"LBQvwRxuD%~q~qj]%MM{xuj]xuM{"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划 检查回文子串 O(n^2)","url":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/dong-tai-gui-hua-jian-cha-hui-wen-zi-chu-y6og","content":"class Solution:\\n def checkPartitioning(self, s: str) -> bool:\\n dp = [[True for __ in range(len(s))] for __ in range(len(s))]\\n for i in range(len(s)-1, -1, -1):\\n for j in range(i+1, len(s)):\\n if s[i] == s[j]:\\n dp[i][j] = dp[i+1][j-1]\\n else:\\n dp[i][j] = False\\n dp2 = [False]*(len(s)+1)\\n dp2[0] = True\\n for i in range(1, len(s)):\\n for j in range(1, i+1):\\n if dp[0][j-1] and dp[j][i]:\\n dp2[i+1] = True\\n break\\n dp2[0] = False\\n for j in range(2, len(s)+1):\\n if dp2[j] and dp[j][-1]:\\n return True\\n return False\\n
解题思路
\\n题目要求判断能否分成三个回文子串,也就是判断任意三个非空子串是否全部都为回文子串。只要预处理获得任意子串是否为回文子串,即可实现判定。
\\n
\\n如果不是动态规划,首先映入脑海的是暴力枚举:\\n
\\n- 依据中间段的起始位置和结束位置,将原字符串划分为三个非空子串,再依次判断三个子串是否均为回文子串。
\\n- 这样下来的划分三个子串的复杂度为O(n^2),判断回文子串又为O(n),加起来的时间复杂度为O(n^3),会在最后几个节点处超时。
\\n所以需要用到动态规划的方法。
\\n
\\n我们用bool型dp[i][j]表示字符串以i为开头,j为结尾的字符串是否非回文子串。
\\n判断的边界是dp[i][i]作为单字符全部都是回文子串。
\\n转移方程为$dp[i][j]=\\\\begin{cases}dp[i+1][j-1]&if s[i]=s[j]\\\\0&if s[i]\\\\ne s[j]\\\\end{cases}$
\\n接下来只需要按照字符串长度从2~s.length()进行枚举,更新dp状态转移方程即可。
\\n最后依据dp[i][j],枚举判断是否存在三个非空子串均为回文子串的情况即可。
\\n这样生成动态规划时间为O(n^2),生成三种子串的时间复杂度为O(n^2),总复杂度为二者之和,依然是O(n^2)。
\\n代码
\\n###cpp
\\n\\n","description":"解题思路 题目要求判断能否分成三个回文子串,也就是判断任意三个非空子串是否全部都为回文子串。只要预处理获得任意子串是否为回文子串,即可实现判定。\\n 如果不是动态规划,首先映入脑海的是暴力枚举:\\n\\n依据中间段的起始位置和结束位置,将原字符串划分为三个非空子串,再依次判断三个子串是否均为回文子串。\\n这样下来的划分三个子串的复杂度为O(n^2),判断回文子串又为O(n),加起来的时间复杂度为O(n^3),会在最后几个节点处超时。\\n\\n所以需要用到动态规划的方法。\\n 我们用bool型dp[i][j]表示字符串以i为开头,j为结尾的字符串是否非回文子串。\\n 判断的边界是d…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/dong-tai-gui-hua-jian-cha-hui-wen-zi-chu-y6og","author":"profsnail","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-31T05:02:38.017Z","media":[{"url":"https://pic.leetcode-cn.com/1612069480-oCEtes-image.png","type":"photo","width":739,"height":269,"blurhash":"LTRMrG-q%2-;~DR*WUWVOSofjboL"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数位DP做法","url":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/shu-wei-dpzuo-fa-by-heltion-5wvg","content":"const int maxn = 2010;\\nbool dp[maxn][maxn];\\n\\nclass Solution {\\npublic:\\n bool checkPartitioning(string s) {\\n memset(dp, 0, sizeof(dp));\\n int n = s.length();\\n for(int i = 0; i < n; i++){\\n dp[i][i] = 1;\\n if(i < n - 1){\\n if(s[i]==s[i+1])dp[i][i+1] = 1;\\n }\\n }\\n for(int L = 3; L <= n; L++){\\n for(int i = 0; i + L - 1 < n; i++){\\n int j = i + L - 1;\\n if(s[i]==s[j] && dp[i+1][j-1]){\\n dp[i][j] = 1;\\n }\\n }\\n }\\n for(int s = 1; s <= n - 2; s++)\\n for(int e = s; e <= n - 2; e++)\\n if(dp[0][s-1]&&dp[s][e]&&dp[e+1][n-1])\\n return true;\\n return false;\\n }\\n};\\n
为了方便,记进制为$b$,
\\nhighLimit
为$n$,lowLimit
添加前导零补全到和highLimit
等长
\\n可以用$4$元组表示状态:
\\n$dp($前缀长度,前缀数码和,前缀是否和lowLimit
相等,前缀是否和highLimit
相等$)$.
\\n那么状态数为$O(b\\\\log_b^2n)$,每个状态有$b$种转移,时间复杂度为$O(b^2\\\\log_b^2n)$,使用滚动数组,空间复杂度为$O(b^2\\\\log_bn)$.###C++
\\n\\n","description":"为了方便,记进制为$b$,highLimit为$n$,lowLimit添加前导零补全到和highLimit等长 可以用$4$元组表示状态:\\n $dp($前缀长度,前缀数码和,前缀是否和lowLimit相等,前缀是否和highLimit相等$)$.\\n 那么状态数为$O(b\\\\log_b^2n)$,每个状态有$b$种转移,时间复杂度为$O(b^2\\\\log_b^2n)$,使用滚动数组,空间复杂度为$O(b^2\\\\log_bn)$.\\n\\n###C++\\n\\nstruct State{\\n int v[2][2];\\n};\\nclass Solution {\\npublic:…","guid":"https://leetcode.cn/problems/maximum-number-of-balls-in-a-box//solution/shu-wei-dpzuo-fa-by-heltion-5wvg","author":"Heltion","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-31T04:26:27.732Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ 马拉车拉车拉车","url":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/c-ma-la-che-la-che-la-che-by-bndsbilly-l1xw","content":"struct State{\\n int v[2][2];\\n};\\nclass Solution {\\npublic:\\n int countBalls(int lowLimit, int highLimit) {\\n auto low = to_string(lowLimit), high = to_string(highLimit);\\n int n = high.size(), m = n * 9 + 1;\\n low = string(n - low.size(), \'0\') + low;\\n vector<State> states(m);\\n states[0].v[1][1] = 1;\\n for(int i = 0; i < n; i += 1){\\n vector<State> next_states(m);\\n for(int j = 0; j < m; j += 1)\\n for(int L = 0; L < 2; L += 1)\\n for(int H = 0; H < 2; H += 1)\\n if(states[j].v[L][H])\\n for(int k = 0; k < 10; k += 1){\\n if(L and k < low[i] - \'0\') continue;\\n if(H and k > high[i] - \'0\') continue;\\n int nj = j + k;\\n int nL = L and k == low[i] - \'0\';\\n int nH = H and k == high[i] - \'0\';\\n next_states[nj].v[nL][nH] += states[j].v[L][H];\\n }\\n swap(states, next_states);\\n }\\n int ans = 0;\\n for(int i = 0; i < m; i += 1){\\n int pans = 0;\\n for(int L = 0; L < 2; L += 1)\\n for(int H = 0; H < 2; H += 1)\\n pans += states[i].v[L][H];\\n ans = max(ans, pans);\\n }\\n return ans;\\n }\\n};\\n
题目4:5666. 回文串分割 IV
\\n题目描述:
\\n给你一个字符串
\\ns
,如果可以将它分割成三个 非空 回文子字符串,那么返回true
,否则返回false
。当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。
\\n示例 1:
\\n\\n输入:s = \\"abcbdd\\"\\n输出:true\\n解释:\\"abcbdd\\" = \\"a\\" + \\"bcb\\" + \\"dd\\",三个子字符串都是回文的。\\n
示例 2:
\\n\\n输入:s = \\"bcbddxy\\"\\n输出:false\\n解释:s 没办法被分割成 3 个回文子字符串。\\n
提示:
\\n\\n
\\n- \\n
3 <= s.length <= 2000
- \\n
s
只包含小写英文字母。思路:马拉车算法 + 双指针
\\n\\n
\\n- 马拉车算法预处理 $v[i]$:求出以位置 $i$ 为中心,所能得到的最长单向回文长度(例如对于字符串 $aba$,其中心 $b$ 的单向回文长度为 $2$,对应 $ab$ )
\\n- 双指针定界:分割出三个非空子字符串
\\n- 判断是否为回文串:设找到的子字符串在原字符串中位置为 $[x, y]$,若该子字符串为回文串,则一定以其中心位置 $(x + y) / 2$ 对称。而根据预处理得到的数组,我们可以 $O(1)$ 时间得到以该点为中心点的最长单向回文长度,则其对应的回文串长度为 $v[(x + y) / 2] * 2 - 1$,只要满足该长度大于等于子字符串长度 $y - x + 1$ ,则说明当前子字符串为回文串。
\\n代码:
\\n###c++
\\n\\nclass Solution {\\npublic:\\n bool checkPartitioning(string s) {\\n int n = s.size();\\n string t = \\"$\\";\\n t += \\"#\\";\\n for(int i = 0; i < n; i++) {\\n t += s[i];\\n t += \\"#\\";\\n }\\n t += \\"^\\";\\n n = 2 * n + 3;\\n vector<int> v(n);\\n int id = 0;\\n int mx = 0;\\n for(int i = 1; i < n; i++) {\\n if(i < mx) v[i] = min(v[2 * id - i], mx - i);\\n else v[i] = 1;\\n while(t[i - v[i]] == t[i + v[i]]) v[i]++;\\n if(mx < i + v[i]) {\\n id = i;\\n mx = i + v[i];\\n }\\n }\\n for(int i = 3; i < n; i += 2) {\\n for(int j = i + 2; j < n - 3; j += 2) {\\n if(check(1, i, v) && check(i, j, v) && check(j, n - 2, v)) {\\n return true;\\n }\\n }\\n }\\n return false;\\n }\\n \\n bool check(int x, int y, vector<int>& v) {\\n if(v[(x + y) / 2] * 2 - 1 >= y - x + 1) return true;\\n return false;\\n }\\n};\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度为 $O(n^2)$,双指针定界。
\\n- 空间复杂度为 $O(n)$,预处理了回文长度。
\\n
\\n关注GTAlgorithm,专注周赛、面经题解分享,陪大家一起攻克算法难关~
\\n\\n","description":"题目4:5666. 回文串分割 IV 题目描述:\\n\\n给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。\\n\\n当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。\\n\\n示例 1:\\n\\n输入:s = \\"abcbdd\\"\\n输出:true\\n解释:\\"abcbdd\\" = \\"a\\" + \\"bcb\\" + \\"dd\\",三个子字符串都是回文的。\\n\\n\\n示例 2:\\n\\n输入:s = \\"bcbddxy\\"\\n输出:false\\n解释:s 没办法被分割成 3 个回文子字符串。\\n\\n\\n提示:\\n\\n3 <= s.length <= 2000\\ns 只包…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iv//solution/c-ma-la-che-la-che-la-che-by-bndsbilly-l1xw","author":"已注销","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-31T04:08:21.053Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"等价多米诺骨牌对的数量","url":"https://leetcode.cn/problems/number-of-equivalent-domino-pairs//solution/deng-jie-duo-mi-nuo-gu-pai-dui-de-shu-li-yjlz","content":"方法一:二元组表示 + 计数
\\n思路及解法
\\n本题中我们需要统计所有等价的多米诺骨牌,其中多米诺骨牌使用二元对代表,「等价」的定义是,在允许翻转两个二元对的的情况下,使它们的元素一一对应相等。
\\n于是我们不妨直接让每一个二元对都变为指定的格式,即第一维必须不大于第二维。这样两个二元对「等价」当且仅当两个二元对完全相同。
\\n注意到二元对中的元素均不大于 $9$,因此我们可以将每一个二元对拼接成一个两位的正整数,即 $(x, y) \\\\to 10x + y$。这样就无需使用哈希表统计元素数量,而直接使用长度为 $100$ 的数组即可。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int numEquivDominoPairs(vector<vector<int>>& dominoes) {\\n vector<int> num(100);\\n int ret = 0;\\n for (auto& it : dominoes) {\\n int val = it[0] < it[1] ? it[0] * 10 + it[1] : it[1] * 10 + it[0];\\n ret += num[val];\\n num[val]++;\\n }\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int numEquivDominoPairs(int[][] dominoes) {\\n int[] num = new int[100];\\n int ret = 0;\\n for (int[] domino : dominoes) {\\n int val = domino[0] < domino[1] ? domino[0] * 10 + domino[1] : domino[1] * 10 + domino[0];\\n ret += num[val];\\n num[val]++;\\n }\\n return ret;\\n }\\n}\\n
###JavaScript
\\n\\nvar numEquivDominoPairs = function(dominoes) {\\n const num = new Array(100).fill(0);\\n let ret = 0;\\n for (const domino of dominoes) {\\n const val = domino[0] < domino[1] ? domino[0] * 10 + domino[1] : domino[1] * 10 + domino[0];\\n ret += num[val];\\n num[val]++;\\n }\\n return ret;\\n};\\n
###go
\\n\\nfunc numEquivDominoPairs(dominoes [][]int) (ans int) {\\n cnt := [100]int{}\\n for _, d := range dominoes {\\n if d[0] > d[1] {\\n d[0], d[1] = d[1], d[0]\\n }\\n v := d[0]*10 + d[1]\\n ans += cnt[v]\\n cnt[v]++\\n }\\n return\\n}\\n
###C
\\n\\nint numEquivDominoPairs(int** dominoes, int dominoesSize, int* dominoesColSize) {\\n int num[100];\\n memset(num, 0, sizeof(num));\\n int ret = 0;\\n for (int i = 0; i < dominoesSize; i++) {\\n int val = dominoes[i][0] < dominoes[i][1] ? dominoes[i][0] * 10 + dominoes[i][1] : dominoes[i][1] * 10 + dominoes[i][0];\\n ret += num[val];\\n num[val]++;\\n }\\n return ret;\\n}\\n
###Python
\\n\\nclass Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n num = [0] * 100\\n ret = 0\\n for x, y in dominoes:\\n val = (x * 10 + y if x <= y else y * 10 + x)\\n ret += num[val]\\n num[val] += 1\\n return ret\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二元组表示 + 计数 思路及解法\\n\\n本题中我们需要统计所有等价的多米诺骨牌,其中多米诺骨牌使用二元对代表,「等价」的定义是,在允许翻转两个二元对的的情况下,使它们的元素一一对应相等。\\n\\n于是我们不妨直接让每一个二元对都变为指定的格式,即第一维必须不大于第二维。这样两个二元对「等价」当且仅当两个二元对完全相同。\\n\\n注意到二元对中的元素均不大于 $9$,因此我们可以将每一个二元对拼接成一个两位的正整数,即 $(x, y) \\\\to 10x + y$。这样就无需使用哈希表统计元素数量,而直接使用长度为 $100$ 的数组即可。\\n\\n代码\\n\\n###C++\\n\\nclass…","guid":"https://leetcode.cn/problems/number-of-equivalent-domino-pairs//solution/deng-jie-duo-mi-nuo-gu-pai-dui-de-shu-li-yjlz","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-25T15:06:05.563Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【猫和老鼠 II】【不需要限制步数的拓扑排序方法】","url":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/mao-he-lao-shu-ii-bu-xu-yao-xian-zhi-bu-d2yxn","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是多米诺骨牌的数量。我们至多只需要遍历一次该数组。
\\n- \\n
\\n空间复杂度:$O(1)$,我们只需要常数的空间存储若干变量。
\\n更新
\\n这个方法的正确性存疑,这里 @huanglin 构造出了一个老鼠想要获胜就必须重复经过同一位置的反例,如果读者感兴趣的话可以去 double check 一下。
\\n前言
\\n首先我们需要明确「必胜态」和「必败态」的概念:
\\n\\n
\\n- \\n
\\n一个状态为「必胜态」,当且仅当其相邻状态中至少有一个「必败态」。这里相邻的状态的定义为:在当前状态中进行决策的玩家可以到达的所有状态。也就是说,玩家可以选择移动到一个「必败态」,使得对手必败,因此当前状态是必胜的。
\\n- \\n
\\n一个状态为「必败态」,当且仅当其相邻的所有状态都是「必胜态」。这里的道理是类似的,如果所有相邻状态都是「必胜态」,那么对手必胜,当前玩家必败。
\\n在本题中,我们可以用 $(c_x, c_y, m_x, m_y, \\\\textit{op})$ 表示一个状态,其中 $(c_x, c_y)$ 表示猫的位置,$(m_x, m_y)$ 表示老鼠的位置,$\\\\textit{op}$ 表示当前玩家是猫($\\\\textit{op}=1$)还是老鼠($\\\\textit{op}=0$)。
\\n如果我们把每个状态抽象成图中的一个节点,状态 $\\\\text{A}$ 可以到达状态 $\\\\text{B}$ 就在它们对应的节点之间连接一条有向边,那么我们就可以使用动态规划或者记忆化搜索计算出所有状态是「必胜态」还是「必败态」。然而我们注意到,由于猫和老鼠都是根据规则任意进行移动的,甚至它们可以不进行移动,因此这个图实际上是存在环的。举一个很简单的例子,如果猫和老鼠在它们的轮次中都不进行移动,那么状态 $\\\\text{A}_1$ 可以到达状态 $\\\\text{A}_2$(猫不移动),而状态 $\\\\text{A}_2$ 也可以到达状态 $\\\\text{A}_1$(老鼠不移动),这样 $\\\\text{A}_1$ 是否为「必胜态」依赖于其本身,我们就没法计算这些状态了。
\\n然而题目中给出了一个提示:「如果老鼠不能在 $1000$ 次操作以内到达食物,那么猫获胜」,这使得我们可以在状态中加入一个维度,即 $(c_x, c_y, m_x, m_y, \\\\textit{op}, \\\\textit{step})$ ,其中 $\\\\textit{step}$ 表示老鼠进行的操作次数。这样一来,整个图中就不存在环了,也就是图存在一个拓扑排序,我们就可以计算出所有状态了。实际上,老鼠的操作次数最多也就是 $8\\\\times8=64$ 次,因为老鼠不进行移动或者移动到之前到过的地方,都是没有意义的。
\\n上面的做法已经可以通过本题,例如 【记忆化搜索】思路简单,比较暴力
\\n
\\n 题解中就使用了这种方法。这里我们给出一种时间复杂度更优的做法,可以忽略 $\\\\textit{step}$ 维度,直接在存在环的原图上进行一种特殊的「拓扑排序」并得到答案。方法一:拓扑排序
\\n思路与算法
\\n虽然图中的每个节点都至少在一个环中,我们无法计算出任意一个节点的状态,但我们是预先知道某些状态是「必胜态」还是「必败态」的,也就是游戏结束判定的前三条:
\\n\\n
\\n- \\n
\\n如果猫跟老鼠处在相同的位置,那么猫获胜;
\\n- \\n
\\n如果猫先到达食物,那么猫获胜;
\\n- \\n
\\n如果老鼠先到达食物,那么老鼠获胜。
\\n这些判定情况对应的所有状态都可以预先计算出,那么接下来就很好办了,我们可以反过来考虑「必胜态」和「必败态」的计算条件:
\\n\\n
\\n- \\n
\\n一个状态为「必胜态」,当且仅当其相邻状态中至少有一个「必败态」。因此,如果一个状态是「必败态」,那么其相邻的所有状态都是「必胜态」。因此我们可以从预先计算出的所有「必败态」开始进行广度优先搜索,它们相邻的所有状态都是「必胜态」;
\\n- \\n
\\n一个状态为「必败态」,当且仅当其相邻的所有状态都是「必胜态」。因此,如果一个状态是「必胜态」,那么我们可以将其相邻的所有状态的入度都减少 $1$。如果某个状态的入度减少到了 $0$ 并且它还没有被计算过,那么说明其相邻的所有状态都是「必胜态」,那么它就是「必败态」。
\\n因此我们只需要预处理出所有状态的入度,并且计算出所有与游戏结束相关的状态,随后进行广度优先搜索即可。由于图中存在环,因此并不是每个状态都能够确定其是「必胜态」还是「必败态」,那些状态实际上就是猫和老鼠陷入了循环,那么根据规则判定猫获胜。
\\n流程
\\n算法的流程如下:
\\n\\n
\\n- \\n
\\n预处理出所有状态的入度;
\\n- \\n
\\n预处理出所有已经确定的游戏结束状态是「必胜态」还是「必败态」,并把它们放入队列中;
\\n- \\n
\\n依次从队列中取出状态,如果该状态是「必败态」,那么将所有其相邻且未计算的状态变为「必胜态」,并全部放入队列中;如果该状态是「必胜态」,那么将所有其相邻且未计算的状态的入度减少 $1$,如果某个相邻的状态入度减少为 $0$,那么就将其状态变为「必败态」,并放入队列中。
\\n- \\n
\\n在广度优先搜索结束后,如果初始状态如果为「必败态」或者未计算过,那么猫获胜,否则老鼠获胜。
\\n代码
\\n###C++
\\n\\nclass Solution {\\nprivate:\\n // f[cat_x][cat_y][mouse_x][mouse_y][is_cat_round] = 如果当前玩家必胜那么为 1,否则为 -1\\n int f[8][8][8][8][2];\\n // 统计每个状态的入度,用于拓扑排序\\n int degree[8][8][8][8][2];\\n\\n static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};\\n\\npublic:\\n bool canMouseWin(vector<string>& grid, int catJump, int mouseJump) {\\n int m = grid.size();\\n int n = grid[0].size();\\n\\n auto findPosition = [&](char c) -> pair<int, int> {\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n if (grid[i][j] == c) {\\n return {i, j};\\n }\\n }\\n }\\n return {-1, -1};\\n };\\n\\n auto getNeighbors = [&](int x, int y, int bound) -> vector<pair<int, int>> {\\n vector<pair<int, int>> ret = {{x, y}};\\n for (int d = 0; d < 4; ++d) {\\n int xx = x, yy = y;\\n for (int _ = 1; _ <= bound; ++_) {\\n xx += dirs[d][0];\\n yy += dirs[d][1];\\n if (xx < 0 || xx >= m || yy < 0 || yy >= n || grid[xx][yy] == \'#\') {\\n break;\\n }\\n ret.emplace_back(xx, yy);\\n }\\n }\\n return ret;\\n };\\n\\n auto [cx, cy] = findPosition(\'C\');\\n auto [mx, my] = findPosition(\'M\');\\n auto [fx, fy] = findPosition(\'F\');\\n\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n if (grid[i][j] != \'#\') {\\n for (int k = 0; k < m; ++k) {\\n for (int l = 0; l < n; ++l) {\\n if (grid[k][l] != \'#\') {\\n degree[i][j][k][l][0] = getNeighbors(k, l, mouseJump).size();\\n degree[i][j][k][l][1] = getNeighbors(i, j, catJump).size();\\n }\\n }\\n }\\n }\\n }\\n }\\n\\n memset(f, 0, sizeof(f));\\n queue<tuple<int, int, int, int, int>> q;\\n\\n // 猫和老鼠重合\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n if (grid[i][j] != \'#\' && grid[i][j] != \'F\') {\\n f[i][j][i][j][0] = -1;\\n f[i][j][i][j][1] = 1;\\n q.emplace(i, j, i, j, 0);\\n q.emplace(i, j, i, j, 1);\\n }\\n }\\n }\\n\\n // 猫 or 老鼠到达食物\\n for (int i = 0; i < m; ++i) {\\n for (int j = 0; j < n; ++j) {\\n if (grid[i][j] != \'#\' && grid[i][j] != \'F\') {\\n f[fx][fy][i][j][0] = -1;\\n f[i][j][fx][fy][1] = -1;\\n q.emplace(fx, fy, i, j, 0);\\n q.emplace(i, j, fx, fy, 1);\\n }\\n }\\n }\\n\\n while (!q.empty()) {\\n auto [catx, caty, mousex, mousey, op] = q.front();\\n q.pop();\\n if (op == 1) {\\n vector<pair<int, int>> neighbors = getNeighbors(mousex, mousey, mouseJump);\\n for (auto [x, y]: neighbors) {\\n --degree[catx][caty][x][y][op ^ 1];\\n if (!f[catx][caty][x][y][op ^ 1]) {\\n if (f[catx][caty][mousex][mousey][op] == -1) {\\n f[catx][caty][x][y][op ^ 1] = 1;\\n q.emplace(catx, caty, x, y, op ^ 1);\\n }\\n else if (degree[catx][caty][x][y][op ^ 1] == 0) {\\n f[catx][caty][x][y][op ^ 1] = -1;\\n q.emplace(catx, caty, x, y, op ^ 1);\\n }\\n }\\n }\\n }\\n else {\\n vector<pair<int, int>> neighbors = getNeighbors(catx, caty, catJump);\\n for (auto [x, y]: neighbors) {\\n --degree[x][y][mousex][mousey][op ^ 1];\\n if (!f[x][y][mousex][mousey][op ^ 1]) { \\n if (f[catx][caty][mousex][mousey][op] == -1) {\\n f[x][y][mousex][mousey][op ^ 1] = 1;\\n q.emplace(x, y, mousex, mousey, op ^ 1);\\n }\\n else if (degree[x][y][mousex][mousey][op ^ 1] == 0) {\\n f[x][y][mousex][mousey][op ^ 1] = -1;\\n q.emplace(x, y, mousex, mousey, op ^ 1);\\n }\\n }\\n }\\n }\\n }\\n\\n return f[cx][cy][mx][my][0] == 1;\\n }\\n};\\n
复杂度分析
\\n\\n
\\n","description":"更新 这个方法的正确性存疑,这里 @huanglin 构造出了一个老鼠想要获胜就必须重复经过同一位置的反例,如果读者感兴趣的话可以去 double check 一下。\\n\\n前言\\n\\n首先我们需要明确「必胜态」和「必败态」的概念:\\n\\n一个状态为「必胜态」,当且仅当其相邻状态中至少有一个「必败态」。这里相邻的状态的定义为:在当前状态中进行决策的玩家可以到达的所有状态。也就是说,玩家可以选择移动到一个「必败态」,使得对手必败,因此当前状态是必胜的。\\n\\n一个状态为「必败态」,当且仅当其相邻的所有状态都是「必胜态」。这里的道理是类似的,如果所有相邻状态都是「必胜态…","guid":"https://leetcode.cn/problems/cat-and-mouse-ii//solution/mao-he-lao-shu-ii-bu-xu-yao-xian-zhi-bu-d2yxn","author":"zerotrac2","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-17T05:17:41.543Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"冗余连接","url":"https://leetcode.cn/problems/redundant-connection//solution/rong-yu-lian-jie-by-leetcode-solution-pks2","content":"- \\n
\\n时间复杂度:$O(m^2n^2(m+n))$,其中 $m$ 和 $n$ 分别是方格的行数和列数。
\\n- \\n
\\n空间复杂度:$O(m^2n^2)$。
\\n方法一:并查集
\\n在一棵树中,边的数量比节点的数量少 $1$。如果一棵树有 $n$ 个节点,则这棵树有 $n-1$ 条边。这道题中的图在树的基础上多了一条附加的边,因此边的数量也是 $n$。
\\n树是一个连通且无环的无向图,在树中多了一条附加的边之后就会出现环,因此附加的边即为导致环出现的边。
\\n可以通过并查集寻找附加的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。
\\n\\n
\\n- \\n
\\n如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。
\\n- \\n
\\n如果两个顶点属于相同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间已经连通,因此当前的边导致环出现,为附加的边,将当前的边作为答案返回。
\\n###Java
\\n\\nclass Solution {\\n public int[] findRedundantConnection(int[][] edges) {\\n int n = edges.length;\\n int[] parent = new int[n + 1];\\n for (int i = 1; i <= n; i++) {\\n parent[i] = i;\\n }\\n for (int i = 0; i < n; i++) {\\n int[] edge = edges[i];\\n int node1 = edge[0], node2 = edge[1];\\n if (find(parent, node1) != find(parent, node2)) {\\n union(parent, node1, node2);\\n } else {\\n return edge;\\n }\\n }\\n return new int[0];\\n }\\n\\n public void union(int[] parent, int index1, int index2) {\\n parent[find(parent, index1)] = find(parent, index2);\\n }\\n\\n public int find(int[] parent, int index) {\\n if (parent[index] != index) {\\n parent[index] = find(parent, parent[index]);\\n }\\n return parent[index];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int Find(vector<int>& parent, int index) {\\n if (parent[index] != index) {\\n parent[index] = Find(parent, parent[index]);\\n }\\n return parent[index];\\n }\\n\\n void Union(vector<int>& parent, int index1, int index2) {\\n parent[Find(parent, index1)] = Find(parent, index2);\\n }\\n\\n vector<int> findRedundantConnection(vector<vector<int>>& edges) {\\n int n = edges.size();\\n vector<int> parent(n + 1);\\n for (int i = 1; i <= n; ++i) {\\n parent[i] = i;\\n }\\n for (auto& edge: edges) {\\n int node1 = edge[0], node2 = edge[1];\\n if (Find(parent, node1) != Find(parent, node2)) {\\n Union(parent, node1, node2);\\n } else {\\n return edge;\\n }\\n }\\n return vector<int>{};\\n }\\n};\\n
###JavaScript
\\n\\nvar findRedundantConnection = function(edges) {\\n const n = edges.length;\\n const parent = new Array(n + 1).fill(0).map((value, index) => index);\\n for (let i = 0; i < n; i++) {\\n const edge = edges[i];\\n const node1 = edge[0], node2 = edge[1];\\n if (find(parent, node1) != find(parent, node2)) {\\n union(parent, node1, node2);\\n } else {\\n return edge;\\n }\\n }\\n return [0];\\n};\\n\\nconst union = (parent, index1, index2) => {\\n parent[find(parent, index1)] = find(parent, index2);\\n}\\n\\nconst find = (parent, index) => {\\n if (parent[index] !== index) {\\n parent[index] = find(parent, parent[index]);\\n }\\n return parent[index];\\n}\\n
###Python
\\n\\nclass Solution:\\n def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:\\n n = len(edges)\\n parent = list(range(n + 1))\\n\\n def find(index: int) -> int:\\n if parent[index] != index:\\n parent[index] = find(parent[index])\\n return parent[index]\\n \\n def union(index1: int, index2: int):\\n parent[find(index1)] = find(index2)\\n\\n for node1, node2 in edges:\\n if find(node1) != find(node2):\\n union(node1, node2)\\n else:\\n return [node1, node2]\\n \\n return []\\n
###go
\\n\\nfunc findRedundantConnection(edges [][]int) []int {\\n parent := make([]int, len(edges)+1)\\n for i := range parent {\\n parent[i] = i\\n }\\n var find func(int) int\\n find = func(x int) int {\\n if parent[x] != x {\\n parent[x] = find(parent[x])\\n }\\n return parent[x]\\n }\\n union := func(from, to int) bool {\\n x, y := find(from), find(to)\\n if x == y {\\n return false\\n }\\n parent[x] = y\\n return true\\n }\\n for _, e := range edges {\\n if !union(e[0], e[1]) {\\n return e\\n }\\n }\\n return nil\\n}\\n
###C
\\n\\nint Find(int* parent, int index) {\\n if (parent[index] != index) {\\n parent[index] = Find(parent, parent[index]);\\n }\\n return parent[index];\\n}\\n\\nvoid Union(int* parent, int index1, int index2) {\\n parent[Find(parent, index1)] = Find(parent, index2);\\n}\\n\\nint* findRedundantConnection(int** edges, int edgesSize, int* edgesColSize, int* returnSize) {\\n int n = edgesSize;\\n int parent[n + 1];\\n for (int i = 1; i <= n; ++i) {\\n parent[i] = i;\\n }\\n for (int i = 0; i < edgesSize; ++i) {\\n int node1 = edges[i][0], node2 = edges[i][1];\\n if (Find(parent, node1) != Find(parent, node2)) {\\n Union(parent, node1, node2);\\n } else {\\n *returnSize = 2;\\n return edges[i];\\n }\\n }\\n *returnSize = 0;\\n return NULL;\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:并查集 在一棵树中,边的数量比节点的数量少 $1$。如果一棵树有 $n$ 个节点,则这棵树有 $n-1$ 条边。这道题中的图在树的基础上多了一条附加的边,因此边的数量也是 $n$。\\n\\n树是一个连通且无环的无向图,在树中多了一条附加的边之后就会出现环,因此附加的边即为导致环出现的边。\\n\\n可以通过并查集寻找附加的边。初始时,每个节点都属于不同的连通分量。遍历每一条边,判断这条边连接的两个顶点是否属于相同的连通分量。\\n\\n如果两个顶点属于不同的连通分量,则说明在遍历到当前的边之前,这两个顶点之间不连通,因此当前的边不会导致环出现,合并这两个顶点的连通分量。…","guid":"https://leetcode.cn/problems/redundant-connection//solution/rong-yu-lian-jie-by-leetcode-solution-pks2","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-12T15:51:59.301Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"帮你看懂官解的动态规划(因为我一开始也看了半天没看大懂)","url":"https://leetcode.cn/problems/minimum-number-of-refueling-stops//solution/bang-ni-kan-dong-guan-jie-de-dong-tai-gu-8oyf","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是图中的节点个数。需要遍历图中的 $n$ 条边,对于每条边,需要对两个节点查找祖先,如果两个节点的祖先不同则需要进行合并,需要进行 $2$ 次查找和最多 $1$ 次合并。一共需要进行 $2n$ 次查找和最多 $n$ 次合并,因此总时间复杂度是 $O(2n \\\\log n)=O(n \\\\log n)$。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 $O(n \\\\log n)$,平均情况下的时间复杂度依然是 $O(n \\\\alpha (n))$,其中 $\\\\alpha$ 为阿克曼函数的反函数,$\\\\alpha (n)$ 可以认为是一个很小的常数。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 是图中的节点个数。使用数组 $\\\\textit{parent}$ 记录每个节点的祖先。
\\n解题思路
\\n先说下楼主的解题历程:楼主在尝试此题时没有“优先队列”的知识储备(说白了就是个刷题新手根本就没用过那玩意儿 =.=),所以这里不提及贪心(自己也不擅长贪心)。开始楼主想若对于每个加油站加油与否进行递归,然后进行优化剪枝,但这种“回溯式DFS暴力遍历”的时间仍是指数级的会超时;因为感觉这题肯定是与“动态规划”有关的,所以就想在这里用纯粹的动态规划来解这个题。解题的过程中参考了官解(是的我承认我偷瞄了官解的方法一),然鹅看了几遍并没有大懂,在反复梳理后终于恍然大悟:感觉这题还是很有意义的,能够加深对动态规划问题中一些细节的理解。故在这里整理分享一下本人用纯动态规划方法的解题思路,以及涉及到的一些细节性的问题(就是踩过的一些“坑”)。如果你跟我一样对官解方法一似懂非懂或者在自己的具体实现上遇到了一些问题,希望下文能够对你有所帮助。
\\n动态规划的第一点:在于如何巧妙的设置便于自己理解的dp状态与参数。官解中“dp[i] 为加 i 次油能走的最远距离”,这个设置很巧妙但过于“跳跃”(因为这其实是对dp数组“优化降维”后的结果,后文会详细说明),这也是导致本人在初读官解原码时觉得似懂非懂的主要原因。但是受这个官解设置的启发,结合题干,相对容易被理解的是:不妨设置“dp[i][j]为经过了i个加油站、加了j次油能跑的最远距离(当然0<=j<=i)”。那么,题目的所求就变成了在dp[n][j]中找到\\"满足dp[n][j]>=target的最小的j\\";如果找不到这个j,直观意思就是这n个站无论加多少次油都跑不到target,那就返回-1
\\n动态规划的第二点:如何状态转移?对于dp[i][j],那么我们当前的选择是什么?在这第i个加油站选择“不加油”或“加油”呗。如果在这第i个站不加油,那么就意味着在前面共(i-1)个加油站加了j次油,那么此时dp[i][j]=dp[i-1][j];如果在这第i个站加油,那么意味着在前面(i-1)个站加了(j-1)次油,并在这第i个站加了所有的汽油,故这时候能跑的最远距离为dp[i][j]=dp[i-1][j-1]+第i个加油站的汽油。dp[i][j]为最远距离,故取这两种情况里的最大值。但是别忘了两者情况,都有一个前提,就是上一个状态所给的最远距离,能够让汽车到达第i个加油站。以下是二维dp的具体实现代码,在一些细节上注释便于理解。
\\n代码一:二维dp数组
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {\\n if(startFuel>=target)\\n return 0;\\n int n=stations.size();\\n //vector<vector<int>> dp(n+1,vector<int> (n+1,0));\\n //用二维int dp数组会在提交时发现在一个测试案例中两根整型相加\\n //超过了INT_MAX而溢出报错,故这里换用long\\n //因不支持vector<vector<long>> dp(n+1,vector<int> (n+1,0)); \\n //故换用下面四行来实现\\n long dp[n+1][n+1]; \\n for(int i=0;i<n+1;++i)\\n for(int j=0;j<n+1;++j)\\n dp[i][j]=0; //dp数组初始化\\n \\n //开成n+1的长度是因为把起点视为第0个加油站:起始状态处理\\n for(int i=0;i<n+1;++i)\\n dp[i][0]=startFuel; //甭管经过了几个站,一次油也不加那最多跑的就是startFuel的距离\\n \\n for(int i=1;i<n+1;++i)\\n {\\n for(int j=1;j<=i;++j)\\n {\\n if(dp[i-1][j]>=stations[i-1][0]) //在第i站不加油\\n dp[i][j]=dp[i-1][j];\\n if(dp[i-1][j-1]>=stations[i-1][0]) //在第i站加油\\n dp[i][j]=max(dp[i][j],dp[i-1][j-1]+stations[i-1][1]); //加油与否两种情况取大者\\n }\\n }\\n\\n for(int j=0;j<=n;++j)\\n if(dp[n][j]>=target)\\n return j;\\n return -1;\\n }\\n\\n};\\n\\n
动态规划中常见的空间复杂度优化:上面的代码是能够AC的,但是空间复杂度仍有优化的空间。因为在状态转移方程中,“我们定义的状态dp[i][j]只和有限个相邻状态相关”,这时候可以考虑使用类似“滚动数组”的状态压缩思想来进行dp数组降维(力扣的几个典型的动态规划题如63和64最小路径和、70爬楼梯中均有体现此思想,对二维dp数组降维不熟悉的读者可以去尝试了解)。这里要注意一个细节:降维一维dp数组时,这时候一定要注意状态转移方向!因为在原来二维dp中,dp[i][j]与dp[i-1][j-1]有关。举个例子,原第一层的dp[1][0]和dp[1][1]现在用dp[0]和dp[1]代替,那么对于下一层(第二层),欲用新的dp[1]和dp[2]来代替原来的dp[2][1]和dp[2][2],这时候就需要注意顺序问题。如果第二层先更新的是dp[1],那么再更新dp[2]时用到的是这个刚刚才更新的dp[1],这样结果就不对了,因为第二层所要更新的dp[2](即原来的dp[2][2])是与原dp[1][1](即第一层的dp[1])有关,故这里需要倒序更新(这就解释了为什么官解里的内层循是倒序的、从后往前)。这样,对的二维dp数组版本的代码进行空间复杂度优化后,就得到了与官解方法一相匹配的c++版代码(如下)。这样理解了前文的第一版代码后再对其状态压缩,相信读到这里的各位应该能够充分理解官解的方法一代码了。
\\n代码二:对二维dp数组进行降维优化
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {\\n if(startFuel>=target)\\n return 0;\\n int n=stations.size();\\n long dp[n+1];\\n for(int i=0;i<n+1;++i)\\n dp[i]=0; //dp数组初始化\\n dp[0]=startFuel;\\n\\n for(int i=1;i<n+1;++i)\\n {\\n for(int j=i;j>=1;--j)\\n {\\n if(dp[j]>=stations[i-1][0]) //在第i站不加油\\n dp[j]=dp[j];\\n //上一句没干啥,所以在官解方法一里没有这一if判断语句\\n //留在这是放了方便理解从二维dp降维的过程\\n\\n if(dp[j-1]>=stations[i-1][0]) //在第j站加油\\n dp[j]=max(dp[j],dp[j-1]+stations[i-1][1]);\\n }\\n }\\n\\n for(int j=0;j<=n;++j)\\n if(dp[j]>=target)\\n return j; \\n return -1;\\n }\\n\\n};\\n
本人经验尚浅,故本文也旨在分享自己解题中的一些思考与总结,若有不恰当之处欢迎各位指正,共同进步~
\\n","description":"解题思路 先说下楼主的解题历程:楼主在尝试此题时没有“优先队列”的知识储备(说白了就是个刷题新手根本就没用过那玩意儿 =.=),所以这里不提及贪心(自己也不擅长贪心)。开始楼主想若对于每个加油站加油与否进行递归,然后进行优化剪枝,但这种“回溯式DFS暴力遍历”的时间仍是指数级的会超时;因为感觉这题肯定是与“动态规划”有关的,所以就想在这里用纯粹的动态规划来解这个题。解题的过程中参考了官解(是的我承认我偷瞄了官解的方法一),然鹅看了几遍并没有大懂,在反复梳理后终于恍然大悟:感觉这题还是很有意义的,能够加深对动态规划问题中一些细节的理解…","guid":"https://leetcode.cn/problems/minimum-number-of-refueling-stops//solution/bang-ni-kan-dong-guan-jie-de-dong-tai-gu-8oyf","author":"tten","authorUrl":null,"authorAvatar":null,"publishedAt":"2021-01-09T17:05:04.398Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"java 双百、递归(dfs)、动态规划(bfs)","url":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/java-shuang-bai-di-gui-by-ethan-jx-yvx6","content":"解题思路
\\n\\n
\\n- \\n
\\n考虑什么情况卡住
\\n\\n
\\n- \\n
\\n卡在边缘
\\n\\n
\\n- 卡左边
\\n
\\n- 卡右边
\\n
\\n- \\n
\\n卡在中间
\\n
\\n
\\n- \\n
\\n没有卡住则可以到达下一层
\\n\\n
\\n- \\n
\\n左拐
\\n
\\n- \\n
\\n右拐
\\n
\\n方法一:递归
\\n代码
\\n###java
\\n\\nimport java.util.Arrays;\\n\\nclass Solution {\\n public int[] findBall(int[][] grid) {\\n int row = grid.length;\\n int col = grid[0].length;\\n int[] ans = new int[col];\\n for (int i = 0; i < col; i++) {\\n ans[i] = out(grid, row, col, i, 0);\\n }\\n return ans;\\n }\\n\\n private int out(int[][] grid, int row, int col, int x, int y) {\\n //到达底部\\n if (y == row) {\\n return x;\\n }\\n\\n //卡在边缘\\n if (x == 0 && grid[y][x] == -1) {\\n return -1;\\n }\\n if (x == col - 1 && grid[y][x] == 1) {\\n return -1;\\n }\\n\\n //卡在中间\\n if (grid[y][x] == 1 && grid[y][x + 1] == -1) {\\n return -1;\\n }\\n if (grid[y][x] == -1 && grid[y][x - 1] == 1) {\\n return -1;\\n }\\n\\n //到达下一层\\n return out(grid, row, col, x + grid[y][x], y + 1);\\n }\\n\\n}\\n
时间复杂度:$O(mn)$
\\n
\\n空间复杂度:$O(n)$方法二:动态规划
\\n图解:
\\n<
\\n,
,
>
小球从第一层往下掉时,每进入一层的位置都依赖于上一层掉出的位置,使用一个数组记录小球到达每一层的位置。
\\n\\n
\\n- 一开始每个球在进入二维网格前的位置分别是0、1、2、3……n-1;
\\n- 每经过一层可以得到小球将进入的列,如果小球已经被卡住,则将列置为-1;
\\n- 循环所有的行,便可得到结果。
\\n我们用 $f(x)$ 表示小球到达第x层时每个小球的下标,$f(0)$代表小球进入网格前的小球下标,我们可以列出如下式子:
\\n
\\n$$f(0)=[0,1,2,3,……,n-1]$$
\\n$$f(x) = f(f(x-1))$$###java
\\n\\nclass Solution {\\n public int[] findBall(int[][] grid) {\\n int row = grid.length;\\n int col = grid[0].length;\\n int[] ans = new int[col];\\n\\n // 默认位置\\n for (int i = 0; i < col; i++) {\\n ans[i] = i;\\n }\\n\\n for (int i = 0; i < row; i++) {\\n for (int j = 0; j < col; j++) {\\n if (ans[j] == -1) {//忽略卡住的球\\n continue;\\n }\\n if (grid[i][ans[j]] == 1 && ans[j] + 1 < col && grid[i][ans[j] + 1] == 1) {\\n //右移\\n ans[j]++;\\n } else if (grid[i][ans[j]] == -1 && ans[j] - 1 >= 0 && grid[i][ans[j] - 1] == -1) {\\n //左移\\n ans[j]--;\\n } else {\\n //卡住\\n ans[j] = -1;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
时间复杂度:$O(mn)$
\\n
\\n空间复杂度:$O(n)$最后再求个👍,谢谢各位大佬!
\\n\\n","description":"解题思路 考虑什么情况卡住\\n\\n卡在边缘\\n\\n卡左边\\n\\n卡右边\\n\\n\\n卡在中间\\n \\n\\n\\n没有卡住则可以到达下一层\\n\\n左拐\\n\\n\\n右拐\\n\\n\\n方法一:递归\\n代码\\n\\n###java\\n\\nimport java.util.Arrays;\\n\\nclass Solution {\\n public int[] findBall(int[][] grid) {\\n int row = grid.length;\\n int col = grid[0].length;\\n int[] ans = new int[col];…","guid":"https://leetcode.cn/problems/where-will-the-ball-fall//solution/java-shuang-bai-di-gui-by-ethan-jx-yvx6","author":"Ethan-JX","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-12-27T04:30:04.282Z","media":[{"url":"https://pic.leetcode-cn.com/1609045484-wNcgLz-image.png","type":"photo","width":370,"height":215,"blurhash":"LFS6Pj-:j?~qRhW9j@xu-=t7ofRj"},{"url":"https://pic.leetcode-cn.com/1609045512-nHIPGJ-image.png","type":"photo","width":382,"height":144,"blurhash":"LQRMbx_4oe-:s+o#off4-=Mwj[Ro"},{"url":"https://pic.leetcode-cn.com/1609045596-gNfDVE-image.png","type":"photo","width":464,"height":126,"blurhash":"LJR3TSs~xu?d%FRhRit8-=t9WBRi"},{"url":"https://pic.leetcode-cn.com/1609045580-QKRHYv-image.png","type":"photo","width":490,"height":115,"blurhash":"LQR3TQ?dxut0%LohRjW9xxRgfPoi"},{"url":"https://pic.leetcode-cn.com/1609045850-wMnSBW-image.png","type":"photo","width":467,"height":191,"blurhash":"LBQv%fp0~D^gD#-rxvIS~B-qI:JP"},{"url":"https://pic.leetcode-cn.com/1609045680-XLWaoI-image.png","type":"photo","width":463,"height":186,"blurhash":"LJQmI,~W%3yFD$ITs=t9-SInR.$l"},{"url":"https://pic.leetcode-cn.com/1609123672-jKZTrk-image.png","type":"photo","width":1150,"height":600,"blurhash":"LHR:HI-=^*_3-;kCjskC~pn$IWRj"},{"url":"https://pic.leetcode-cn.com/1609123472-gUETbA-image.png","type":"photo","width":1150,"height":600,"blurhash":"LIRyg7-z?@$~-lR#R#V?tljcVtkC"},{"url":"https://pic.leetcode-cn.com/1609123705-mkVWTO-image.png","type":"photo","width":1150,"height":600,"blurhash":"LDSY]i_2?t?b?cogohj^_NR:D*s;"},{"url":"https://pic.leetcode-cn.com/1609043239-QPVyaO-image.png","type":"photo","width":431,"height":96,"blurhash":"LFR:HF_3?I~q?bfiV[%MbXWBs;jc"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分发糖果","url":"https://leetcode.cn/problems/candy//solution/fen-fa-tang-guo-by-leetcode-solution-f01p","content":"
方法一:两次遍历
\\n思路及解法
\\n我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。
\\n\\n
\\n- \\n
\\n左规则:当 $\\\\textit{ratings}[i - 1] < \\\\textit{ratings}[i]$ 时,$i$ 号学生的糖果数量将比 $i - 1$ 号孩子的糖果数量多。
\\n- \\n
\\n右规则:当 $\\\\textit{ratings}[i] > \\\\textit{ratings}[i + 1]$ 时,$i$ 号学生的糖果数量将比 $i + 1$ 号孩子的糖果数量多。
\\n我们遍历该数组两次,处理出每一个学生分别满足左规则或右规则时,最少需要被分得的糖果数量。每个人最终分得的糖果数量即为这两个数量的最大值。
\\n具体地,以左规则为例:我们从左到右遍历该数组,假设当前遍历到位置 $i$,如果有 $\\\\textit{ratings}[i - 1] < \\\\textit{ratings}[i]$ 那么 $i$ 号学生的糖果数量将比 $i - 1$ 号孩子的糖果数量多,我们令 $\\\\textit{left}[i] = \\\\textit{left}[i - 1] + 1$ 即可,否则我们令 $\\\\textit{left}[i] = 1$。
\\n在实际代码中,我们先计算出左规则 $\\\\textit{left}$ 数组,在计算右规则的时候只需要用单个变量记录当前位置的右规则,同时计算答案即可。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int candy(vector<int>& ratings) {\\n int n = ratings.size();\\n vector<int> left(n);\\n for (int i = 0; i < n; i++) {\\n if (i > 0 && ratings[i] > ratings[i - 1]) {\\n left[i] = left[i - 1] + 1;\\n } else {\\n left[i] = 1;\\n }\\n }\\n int right = 0, ret = 0;\\n for (int i = n - 1; i >= 0; i--) {\\n if (i < n - 1 && ratings[i] > ratings[i + 1]) {\\n right++;\\n } else {\\n right = 1;\\n }\\n ret += max(left[i], right);\\n }\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int candy(int[] ratings) {\\n int n = ratings.length;\\n int[] left = new int[n];\\n for (int i = 0; i < n; i++) {\\n if (i > 0 && ratings[i] > ratings[i - 1]) {\\n left[i] = left[i - 1] + 1;\\n } else {\\n left[i] = 1;\\n }\\n }\\n int right = 0, ret = 0;\\n for (int i = n - 1; i >= 0; i--) {\\n if (i < n - 1 && ratings[i] > ratings[i + 1]) {\\n right++;\\n } else {\\n right = 1;\\n }\\n ret += Math.max(left[i], right);\\n }\\n return ret;\\n }\\n}\\n
###go
\\n\\nfunc candy(ratings []int) (ans int) {\\n n := len(ratings)\\n left := make([]int, n)\\n for i, r := range ratings {\\n if i > 0 && r > ratings[i-1] {\\n left[i] = left[i-1] + 1\\n } else {\\n left[i] = 1\\n }\\n }\\n right := 0\\n for i := n - 1; i >= 0; i-- {\\n if i < n-1 && ratings[i] > ratings[i+1] {\\n right++\\n } else {\\n right = 1\\n }\\n ans += max(left[i], right)\\n }\\n return\\n}\\n\\nfunc max(a, b int) int {\\n if a > b {\\n return a\\n }\\n return b\\n}\\n
###Python
\\n\\nclass Solution:\\n def candy(self, ratings: List[int]) -> int:\\n n = len(ratings)\\n left = [0] * n\\n for i in range(n):\\n if i > 0 and ratings[i] > ratings[i - 1]:\\n left[i] = left[i - 1] + 1\\n else:\\n left[i] = 1\\n \\n right = ret = 0\\n for i in range(n - 1, -1, -1):\\n if i < n - 1 and ratings[i] > ratings[i + 1]:\\n right += 1\\n else:\\n right = 1\\n ret += max(left[i], right)\\n \\n return ret\\n
###JavaScript
\\n\\nvar candy = function(ratings) {\\n const n = ratings.length;\\n const left = new Array(n).fill(0);\\n for (let i = 0; i < n; i++) {\\n if (i > 0 && ratings[i] > ratings[i - 1]) {\\n left[i] = left[i - 1] + 1;\\n } else {\\n left[i] = 1;\\n }\\n }\\n\\n let right = 0, ret = 0;\\n for (let i = n - 1; i > -1; i--) {\\n if (i < n - 1 && ratings[i] > ratings[i + 1]) {\\n right++;\\n } else {\\n right = 1;\\n }\\n ret += Math.max(left[i], right);\\n }\\n return ret;\\n};\\n
###C
\\n\\nint candy(int* ratings, int ratingsSize) {\\n int left[ratingsSize];\\n for (int i = 0; i < ratingsSize; i++) {\\n if (i > 0 && ratings[i] > ratings[i - 1]) {\\n left[i] = left[i - 1] + 1;\\n } else {\\n left[i] = 1;\\n }\\n }\\n int right = 0, ret = 0;\\n for (int i = ratingsSize - 1; i >= 0; i--) {\\n if (i < ratingsSize - 1 && ratings[i] > ratings[i + 1]) {\\n right++;\\n } else {\\n right = 1;\\n }\\n ret += fmax(left[i], right);\\n }\\n return ret;\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是孩子的数量。我们需要遍历两次数组以分别计算满足左规则或右规则的最少糖果数量。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 是孩子的数量。我们需要保存所有的左规则对应的糖果数量。
\\n方法二:常数空间遍历
\\n思路及解法
\\n注意到糖果总是尽量少给,且从 $1$ 开始累计,每次要么比相邻的同学多给一个,要么重新置为 $1$。依据此规则,我们可以画出下图:
\\n\\n
其中相同颜色的柱状图的高度总恰好为 $1,2,3 \\\\dots$。
\\n而高度也不一定一定是升序,也可能是 $\\\\dots 3,2,1$ 的降序:
\\n\\n
注意到在上图中,对于第三个同学,他既可以被认为是属于绿色的升序部分,也可以被认为是属于蓝色的降序部分。因为他同时比两边的同学评分更高。我们对序列稍作修改:
\\n\\n
注意到右边的升序部分变长了,使得第三个同学不得不被分配 $4$ 个糖果。
\\n依据前面总结的规律,我们可以提出本题的解法。我们从左到右枚举每一个同学,记前一个同学分得的糖果数量为 $\\\\textit{pre}$:
\\n\\n
\\n- \\n
\\n如果当前同学比上一个同学评分高,说明我们就在最近的递增序列中,直接分配给该同学 $\\\\textit{pre} + 1$ 个糖果即可。
\\n- \\n
\\n否则我们就在一个递减序列中,我们直接分配给当前同学一个糖果,并把该同学所在的递减序列中所有的同学都再多分配一个糖果,以保证糖果数量还是满足条件。
\\n\\n
\\n- \\n
\\n我们无需显式地额外分配糖果,只需要记录当前的递减序列长度,即可知道需要额外分配的糖果数量。
\\n- \\n
\\n同时注意当当前的递减序列长度和上一个递增序列等长时,需要把最近的递增序列的最后一个同学也并进递减序列中。
\\n这样,我们只要记录当前递减序列的长度 $\\\\textit{dec}$,最近的递增序列的长度 $\\\\textit{inc}$ 和前一个同学分得的糖果数量 $\\\\textit{pre}$ 即可。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int candy(vector<int>& ratings) {\\n int n = ratings.size();\\n int ret = 1;\\n int inc = 1, dec = 0, pre = 1;\\n for (int i = 1; i < n; i++) {\\n if (ratings[i] >= ratings[i - 1]) {\\n dec = 0;\\n pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;\\n ret += pre;\\n inc = pre;\\n } else {\\n dec++;\\n if (dec == inc) {\\n dec++;\\n }\\n ret += dec;\\n pre = 1;\\n }\\n }\\n return ret;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int candy(int[] ratings) {\\n int n = ratings.length;\\n int ret = 1;\\n int inc = 1, dec = 0, pre = 1;\\n for (int i = 1; i < n; i++) {\\n if (ratings[i] >= ratings[i - 1]) {\\n dec = 0;\\n pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;\\n ret += pre;\\n inc = pre;\\n } else {\\n dec++;\\n if (dec == inc) {\\n dec++;\\n }\\n ret += dec;\\n pre = 1;\\n }\\n }\\n return ret;\\n }\\n}\\n
###go
\\n\\nfunc candy(ratings []int) int {\\n n := len(ratings)\\n ans, inc, dec, pre := 1, 1, 0, 1\\n for i := 1; i < n; i++ {\\n if ratings[i] >= ratings[i-1] {\\n dec = 0\\n if ratings[i] == ratings[i-1] {\\n pre = 1\\n } else {\\n pre++\\n }\\n ans += pre\\n inc = pre\\n } else {\\n dec++\\n if dec == inc {\\n dec++\\n }\\n ans += dec\\n pre = 1\\n }\\n }\\n return ans\\n}\\n
###Python
\\n\\nclass Solution:\\n def candy(self, ratings: List[int]) -> int:\\n n = len(ratings)\\n ret = 1\\n inc, dec, pre = 1, 0, 1\\n\\n for i in range(1, n):\\n if ratings[i] >= ratings[i - 1]:\\n dec = 0\\n pre = (1 if ratings[i] == ratings[i - 1] else pre + 1)\\n ret += pre\\n inc = pre\\n else:\\n dec += 1\\n if dec == inc:\\n dec += 1\\n ret += dec\\n pre = 1\\n \\n return ret\\n
###JavaScript
\\n\\nvar candy = function(ratings) {\\n const n = ratings.length;\\n let ret = 1;\\n let inc = 1, dec = 0, pre = 1;\\n\\n for (let i = 1; i < n; i++) {\\n if (ratings[i] >= ratings[i - 1]) {\\n dec = 0;\\n if (ratings[i] === ratings[i - 1]) pre = 1;\\n else pre++;\\n ret += pre;\\n inc = pre;\\n } else {\\n dec++;\\n if (dec === inc) {\\n dec++;\\n }\\n ret += dec;\\n pre = 1;\\n }\\n }\\n return ret;\\n};\\n
###C
\\n\\nint candy(int* ratings, int ratingsSize) {\\n int ret = 1;\\n int inc = 1, dec = 0, pre = 1;\\n for (int i = 1; i < ratingsSize; i++) {\\n if (ratings[i] >= ratings[i - 1]) {\\n dec = 0;\\n pre = ratings[i] == ratings[i - 1] ? 1 : pre + 1;\\n ret += pre;\\n inc = pre;\\n } else {\\n dec++;\\n if (dec == inc) {\\n dec++;\\n }\\n ret += dec;\\n pre = 1;\\n }\\n }\\n return ret;\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:两次遍历 思路及解法\\n\\n我们可以将「相邻的孩子中,评分高的孩子必须获得更多的糖果」这句话拆分为两个规则,分别处理。\\n\\n左规则:当 $\\\\textit{ratings}[i - 1] < \\\\textit{ratings}[i]$ 时,$i$ 号学生的糖果数量将比 $i - 1$ 号孩子的糖果数量多。\\n\\n右规则:当 $\\\\textit{ratings}[i] > \\\\textit{ratings}[i + 1]$ 时,$i$ 号学生的糖果数量将比 $i + 1$ 号孩子的糖果数量多。\\n\\n我们遍历该数组两次,处理出每一个学生分别满足左规则或右规则时…","guid":"https://leetcode.cn/problems/candy//solution/fen-fa-tang-guo-by-leetcode-solution-f01p","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-12-23T15:52:03.670Z","media":[{"url":"https://assets.leetcode.cn/solution-static/135/1.png","type":"photo","width":2000,"height":1125,"blurhash":"LiDT9O.7W9xta{t6ozWD4TMxt8M|"},{"url":"https://assets.leetcode.cn/solution-static/135/2.png","type":"photo","width":2000,"height":1125,"blurhash":"LmDmQ{RiDjoNfifRRjof4Tt8%Lj="},{"url":"https://assets.leetcode.cn/solution-static/135/3.png","type":"photo","width":2000,"height":1125,"blurhash":"LrF$*CRhDhogRQjvM{j[4Tt8x]WA"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两种思路,一种前序遍历,一种后序遍历,速度100%","url":"https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves//solution/liang-chong-si-lu-yi-chong-qian-xu-bian-li-yi-chon","content":"- 时间复杂度:$O(n)$,其中 $n$ 是孩子的数量。
\\n- 空间复杂度:$O(1)$。我们只需要常数的空间保存若干变量。
\\n解题思路
\\n第一种容想到的常规解法
\\n类似于前序遍历,从根节点开始,分别求左右子树的高度left,和right。
\\n\\n
\\n- 情况1:left=right 那么两边子树的最深高度相同,返回本节点
\\n- 情况2:left<right 说明最深节点在右子树,直接返回右子树的递归结果
\\n- 情况2:left>right 说明最深节点在左子树,直接返回右子树的递归结果
\\n其中求子树的高度需要定义一个方法,就是104. 二叉树的最大深度,很简单。
\\n
\\n代码
\\n###java
\\n\\nclass Solution {\\n public TreeNode lcaDeepestLeaves(TreeNode root) {\\n if(root==null) return null;\\n int left=dfs(root.left);\\n int right=dfs(root.right);\\n if(left==right) return root;\\n else if(left<right) return lcaDeepestLeaves(root.right);\\n return lcaDeepestLeaves(root.left);\\n }\\n int dfs(TreeNode node){\\n if(node==null) return 0;\\n return 1+Math.max(dfs(node.right),dfs(node.left));\\n }\\n}\\n
第二种方法,
\\n第二种方法其实就是求后序遍历,代码结构有点类似于求最大深度,只不过要想办法保存最近的节点,和返回深度
\\n首先定义一个点来保存最近公共祖先,定义一个pre来保存上一次得到的最近公共祖先的深度。
\\n
\\n在递归过程中,带一个参数level表示当前遍历到的节点的深度如果node为空,返回当前深度。
\\n
\\n如果不为空,则当前节点的逻辑为:
\\n分别求左子树和右子树的最大深度,left和right\\n
\\n- 1.left=right 如果相同,并且当前深度大于上一次的最大深度,说明当前节点为最新的最近公共祖先,上一次的没有当前这个深,将当前节点保存在结果中,并将深度pre更新。
\\n- 2.left不等于right 则直接返左右子树的最大深度
\\n
\\n\\n","description":"解题思路 类似于前序遍历,从根节点开始,分别求左右子树的高度left,和right。\\n\\n情况1:left=right 那么两边子树的最深高度相同,返回本节点\\n情况2:leftclass Solution {\\n TreeNode res = null;\\n int pre=0;\\n public TreeNode lcaDeepestLeaves(TreeNode root) {\\n dfs(root,1);\\n return res;\\n\\n }\\n int dfs(TreeNode node,int depth){\\n if(node==null) return depth;\\n int left=dfs(node.left,depth+1);\\n int right =dfs(node.right,depth+1);\\n if(left==right&&left>=pre){\\n res=node;\\n pre=left;\\n } \\n return Math.max(left,right);\\n }\\n}\\n
right 说明最深节点在左子树,直接返回右子树的递归结果\\n\\n其中求子树的高度需要定义一个方法,就是104. 二叉树的最大深度,很简单。\\n\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public TreeNode lcaDeepestLeaves(TreeNode root)…","guid":"https://leetcode.cn/problems/lowest-common-ancestor-of-deepest-leaves//solution/liang-chong-si-lu-yi-chong-qian-xu-bian-li-yi-chon","author":"qiujunlin","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-11-17T11:21:41.481Z","media":[{"url":"https://pic.leetcode-cn.com/1605610692-xslaQc-image.png","type":"photo","width":624,"height":231,"blurhash":"LBS6Pl_3t7_3~qt7a#fi-:j[jbof"},{"url":"https://pic.leetcode-cn.com/1605611251-iVnOTl-image.png","type":"photo","width":603,"height":232,"blurhash":"LCR{*|?bWB?b~qj[t7ay%fofayt7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"按奇偶排序数组 II","url":"https://leetcode.cn/problems/sort-array-by-parity-ii//solution/an-qi-ou-pai-xu-shu-zu-ii-by-leetcode-solution","content":" 方法一:两次遍历
\\n思路和算法
\\n遍历一遍数组把所有的偶数放进 $\\\\textit{ans}[0]$,$\\\\textit{ans}[2]$,$\\\\textit{ans}[4]$,以此类推。
\\n再遍历一遍数组把所有的奇数依次放进 $\\\\textit{ans}[1]$,$\\\\textit{ans}[3]$,$\\\\textit{ans}[5]$,以此类推。
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> sortArrayByParityII(vector<int>& nums) {\\n int n = nums.size();\\n vector<int> ans(n);\\n\\n int i = 0;\\n for (int x: nums) {\\n if (x % 2 == 0) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n i = 1;\\n for (int x: nums) {\\n if (x % 2 == 1) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int[] sortArrayByParityII(int[] nums) {\\n int n = nums.length;\\n int[] ans = new int[n];\\n\\n int i = 0;\\n for (int x : nums) {\\n if (x % 2 == 0) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n i = 1;\\n for (int x : nums) {\\n if (x % 2 == 1) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C
\\n\\nint* sortArrayByParityII(int* nums, int numsSize, int* returnSize) {\\n int* ans = malloc(sizeof(int) * numsSize);\\n int add = 0;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] % 2 == 0) {\\n ans[add] = nums[i];\\n add += 2;\\n }\\n }\\n add = 1;\\n for (int i = 0; i < numsSize; i++) {\\n if (nums[i] % 2 == 1) {\\n ans[add] = nums[i];\\n add += 2;\\n }\\n }\\n *returnSize = numsSize;\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar sortArrayByParityII = function(nums) {\\n const n = nums.length;\\n const ans = new Array(n);\\n let i = 0;\\n for (const x of nums) {\\n if (!(x & 1)) {\\n ans[i] = x;\\n i += 2;\\n } \\n }\\n\\n i = 1;\\n for (const x of nums) {\\n if (x & 1) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n\\n return ans;\\n};\\n
###Golang
\\n\\nfunc sortArrayByParityII(nums []int) []int {\\n ans := make([]int, len(nums))\\n i := 0\\n for _, v := range nums {\\n if v%2 == 0 {\\n ans[i] = v\\n i += 2\\n }\\n }\\n i = 1\\n for _, v := range nums {\\n if v%2 == 1 {\\n ans[i] = v\\n i += 2\\n }\\n }\\n return ans\\n}\\n
###Python
\\n\\nclass Solution:\\n def sortArrayByParityII(self, nums: List[int]) -> List[int]:\\n n = len(nums)\\n ans = [0] * n\\n i = 0\\n for x in nums:\\n if x % 2 == 0:\\n ans[i] = x\\n i += 2\\n i = 1\\n for x in nums:\\n if x % 2 == 1:\\n ans[i] = x\\n i += 2\\n return ans\\n
###TypeScript
\\n\\nfunction sortArrayByParityII(nums: number[]): number[] {\\n const n = nums.length;\\n const ans: number[] = new Array(n);\\n let i = 0;\\n for (const x of nums) {\\n if (x % 2 === 0) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n i = 1;\\n for (const x of nums) {\\n if (x % 2 === 1) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n return ans;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] SortArrayByParityII(int[] nums) {\\n int n = nums.Length;\\n int[] ans = new int[n];\\n int i = 0;\\n foreach (int x in nums) {\\n if (x % 2 == 0) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n i = 1;\\n foreach (int x in nums) {\\n if (x % 2 == 1) {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn sort_array_by_parity_ii(nums: Vec<i32>) -> Vec<i32> {\\n let n = nums.len();\\n let mut ans = vec![0; n];\\n let mut i = 0;\\n for &x in &nums {\\n if x % 2 == 0 {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n i = 1;\\n for &x in &nums {\\n if x % 2 == 1 {\\n ans[i] = x;\\n i += 2;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。注意在这里我们不考虑输出数组的空间占用。
\\n方法二:双指针
\\n思路与算法
\\n如果原数组可以修改,则可以使用就地算法求解。
\\n为数组的偶数下标部分和奇数下标部分分别维护指针 $i, j$。随后,在每一步中,如果 $\\\\textit{nums}[i]$ 为奇数,则不断地向前移动 $j$(每次移动两个单位),直到遇见下一个偶数。此时,可以直接将 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[j]$ 交换。我们不断进行这样的过程,最终能够将所有的整数放在正确的位置上。
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> sortArrayByParityII(vector<int>& nums) {\\n int n = nums.size();\\n int j = 1;\\n for (int i = 0; i < n; i += 2) {\\n if (nums[i] % 2 == 1) {\\n while (nums[j] % 2 == 1) {\\n j += 2;\\n }\\n swap(nums[i], nums[j]);\\n }\\n } \\n return nums;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int[] sortArrayByParityII(int[] nums) {\\n int n = nums.length;\\n int j = 1;\\n for (int i = 0; i < n; i += 2) {\\n if (nums[i] % 2 == 1) {\\n while (nums[j] % 2 == 1) {\\n j += 2;\\n }\\n swap(nums, i, j);\\n }\\n } \\n return nums;\\n }\\n\\n public void swap(int[] nums, int i, int j) {\\n int temp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = temp;\\n }\\n}\\n
###C
\\n\\nvoid swap(int* a, int* b) {\\n int t = *a;\\n *a = *b, *b = t;\\n}\\n\\nint* sortArrayByParityII(int* nums, int numsSize, int* returnSize) {\\n int j = 1;\\n for (int i = 0; i < numsSize; i += 2) {\\n if (nums[i] % 2 == 1) {\\n while (nums[j] % 2 == 1) {\\n j += 2;\\n }\\n swap(nums + i, nums + j);\\n }\\n }\\n *returnSize = numsSize;\\n return nums;\\n}\\n
###JavaScript
\\n\\nconst swap = (nums, i, j) => {\\n const temp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = temp;\\n};\\nvar sortArrayByParityII = function(nums) {\\n const n = nums.length;\\n let j = 1;\\n for (let i = 0; i < n; i += 2) {\\n if (nums[i] & 1) {\\n while (nums[j] & 1) {\\n j += 2;\\n }\\n swap(nums, i, j);\\n }\\n } \\n return nums;\\n};\\n
###Golang
\\n\\nfunc sortArrayByParityII(nums []int) []int {\\n for i, j := 0, 1; i < len(nums); i += 2 {\\n if nums[i]%2 == 1 {\\n for nums[j]%2 == 1 {\\n j += 2\\n }\\n nums[i], nums[j] = nums[j], nums[i]\\n }\\n }\\n return nums\\n}\\n
###Python
\\n\\nclass Solution:\\n def sortArrayByParityII(self, nums: List[int]) -> List[int]:\\n n = len(nums)\\n j = 1\\n for i in range(0, n, 2):\\n if nums[i] % 2 == 1:\\n while nums[j] % 2 == 1:\\n j += 2\\n nums[i], nums[j] = nums[j], nums[i]\\n return nums\\n
###TypeScript
\\n\\nfunction sortArrayByParityII(nums: number[]): number[] {\\n const n = nums.length;\\n let j = 1;\\n for (let i = 0; i < n; i += 2) {\\n if (nums[i] % 2 === 1) {\\n while (nums[j] % 2 === 1) {\\n j += 2;\\n }\\n [nums[i], nums[j]] = [nums[j], nums[i]];\\n }\\n }\\n return nums;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] SortArrayByParityII(int[] nums) {\\n int n = nums.Length;\\n int j = 1;\\n for (int i = 0; i < n; i += 2) {\\n if (nums[i] % 2 == 1) {\\n while (nums[j] % 2 == 1) {\\n j += 2;\\n }\\n int temp = nums[i];\\n nums[i] = nums[j];\\n nums[j] = temp;\\n }\\n }\\n return nums;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn sort_array_by_parity_ii(nums: Vec<i32>) -> Vec<i32> {\\n let n = nums.len();\\n let mut nums = nums;\\n let mut j = 1;\\n for i in (0..n).step_by(2) {\\n if nums[i] % 2 == 1 {\\n while nums[j] % 2 == 1 {\\n j += 2;\\n }\\n nums.swap(i, j);\\n }\\n }\\n nums\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:两次遍历 思路和算法\\n\\n遍历一遍数组把所有的偶数放进 $\\\\textit{ans}[0]$,$\\\\textit{ans}[2]$,$\\\\textit{ans}[4]$,以此类推。\\n\\n再遍历一遍数组把所有的奇数依次放进 $\\\\textit{ans}[1]$,$\\\\textit{ans}[3]$,$\\\\textit{ans}[5]$,以此类推。\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n vector- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\nsortArrayByParityII(vector & nums) {\\n int n = nums…","guid":"https://leetcode.cn/problems/sort-array-by-parity-ii//solution/an-qi-ou-pai-xu-shu-zu-ii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-11-11T15:14:32.089Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"N皇后 II","url":"https://leetcode.cn/problems/n-queens-ii//solution/nhuang-hou-ii-by-leetcode-solution","content":" 前言
\\n这道题和「51. N 皇后」非常相似,区别在于,第 $51$ 题需要得到所有可能的解,这道题只需要得到可能的解的数量。因此这道题可以使用第 $51$ 题的做法,只需要将得到所有可能的解改成得到可能的解的数量即可。
\\n皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。
\\n直观的做法是暴力枚举将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。
\\n显然,每个皇后必须位于不同行和不同列,因此将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式得到可能的解的数量。
\\n回溯的具体做法是:依次在每一行放置一个皇后,每次新放置的皇后都不能和已经放置的皇后之间有攻击,即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上。当 $N$ 个皇后都放置完毕,则找到一个可能的解,将可能的解的数量加 $1$。
\\n由于每个皇后必须位于不同列,因此已经放置的皇后所在的列不能放置别的皇后。第一个皇后有 $N$ 列可以选择,第二个皇后最多有 $N-1$ 列可以选择,第三个皇后最多有 $N-2$ 列可以选择(如果考虑到不能在同一条斜线上,可能的选择数量更少),因此所有可能的情况不会超过 $N!$ 种,遍历这些情况的时间复杂度是 $O(N!)$。
\\n为了降低总时间复杂度,每次放置皇后时需要快速判断每个位置是否可以放置皇后,显然,最理想的情况是在 $O(1)$ 的时间内判断该位置所在的列和两条斜线上是否已经有皇后。
\\n以下两种方法分别使用集合和位运算对皇后的放置位置进行判断,都可以在 $O(1)$ 的时间内判断一个位置是否可以放置皇后,算法的总时间复杂度都是 $O(N!)$。
\\n方法一:基于集合的回溯
\\n为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合 $\\\\textit{columns}$、$\\\\textit{diagonals}_1$ 和 $\\\\textit{diagonals}_2$ 分别记录每一列以及两个方向的每条斜线上是否有皇后。
\\n列的表示法很直观,一共有 $N$ 列,每一列的下标范围从 $0$ 到 $N-1$,使用列的下标即可明确表示每一列。
\\n如何表示两个方向的斜线呢?对于每个方向的斜线,需要找到斜线上的每个位置的行下标与列下标之间的关系。
\\n方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等,例如 $(0,0)$ 和 $(3,3)$ 在同一条方向一的斜线上。因此使用行下标与列下标之差即可明确表示每一条方向一的斜线。
\\n\\n
方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等,例如 $(3,0)$ 和 $(1,2)$ 在同一条方向二的斜线上。因此使用行下标与列下标之和即可明确表示每一条方向二的斜线。
\\n\\n
每次放置皇后时,对于每个位置判断其是否在三个集合中,如果三个集合都不包含当前位置,则当前位置是可以放置皇后的位置。
\\n###Java
\\n\\nclass Solution {\\n public int totalNQueens(int n) {\\n Set<Integer> columns = new HashSet<Integer>();\\n Set<Integer> diagonals1 = new HashSet<Integer>();\\n Set<Integer> diagonals2 = new HashSet<Integer>();\\n return backtrack(n, 0, columns, diagonals1, diagonals2);\\n }\\n\\n public int backtrack(int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n for (int i = 0; i < n; i++) {\\n if (columns.contains(i)) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.contains(diagonal1)) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.contains(diagonal2)) {\\n continue;\\n }\\n columns.add(i);\\n diagonals1.add(diagonal1);\\n diagonals2.add(diagonal2);\\n count += backtrack(n, row + 1, columns, diagonals1, diagonals2);\\n columns.remove(i);\\n diagonals1.remove(diagonal1);\\n diagonals2.remove(diagonal2);\\n }\\n return count;\\n }\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int TotalNQueens(int n) {\\n HashSet<int> columns = new HashSet<int>();\\n HashSet<int> diagonals1 = new HashSet<int>();\\n HashSet<int> diagonals2 = new HashSet<int>();\\n return Backtrack(n, 0, columns, diagonals1, diagonals2);\\n }\\n\\n private int Backtrack(int n, int row, HashSet<int> columns, HashSet<int> diagonals1, HashSet<int> diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n for (int i = 0; i < n; i++) {\\n if (columns.Contains(i)) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.Contains(diagonal1)) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.Contains(diagonal2)) {\\n continue;\\n }\\n columns.Add(i);\\n diagonals1.Add(diagonal1);\\n diagonals2.Add(diagonal2);\\n count += Backtrack(n, row + 1, columns, diagonals1, diagonals2);\\n columns.Remove(i);\\n diagonals1.Remove(diagonal1);\\n diagonals2.Remove(diagonal2);\\n }\\n return count;\\n }\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int totalNQueens(int n) {\\n unordered_set<int> columns, diagonals1, diagonals2;\\n return backtrack(n, 0, columns, diagonals1, diagonals2);\\n }\\n\\n int backtrack(int n, int row, unordered_set<int>& columns, unordered_set<int>& diagonals1, unordered_set<int>& diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n for (int i = 0; i < n; i++) {\\n if (columns.find(i) != columns.end()) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.find(diagonal1) != diagonals1.end()) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.find(diagonal2) != diagonals2.end()) {\\n continue;\\n }\\n columns.insert(i);\\n diagonals1.insert(diagonal1);\\n diagonals2.insert(diagonal2);\\n count += backtrack(n, row + 1, columns, diagonals1, diagonals2);\\n columns.erase(i);\\n diagonals1.erase(diagonal1);\\n diagonals2.erase(diagonal2);\\n }\\n return count;\\n }\\n }\\n};\\n
###JavaScript
\\n\\nconst backtrack = (n, row, columns, diagonals1, diagonals2) => {\\n if (row === n) {\\n return 1;\\n } else {\\n let count = 0;\\n for (let i = 0; i < n; i++) {\\n if (columns.has(i)) {\\n continue;\\n }\\n const diagonal1 = row - i;\\n if (diagonals1.has(diagonal1)) {\\n continue;\\n }\\n const diagonal2 = row + i;\\n if (diagonals2.has(diagonal2)) {\\n continue;\\n }\\n columns.add(i);\\n diagonals1.add(diagonal1);\\n diagonals2.add(diagonal2);\\n count += backtrack(n, row + 1, columns, diagonals1, diagonals2);\\n columns.delete(i);\\n diagonals1.delete(diagonal1);\\n diagonals2.delete(diagonal2);\\n }\\n return count;\\n }\\n}\\nvar totalNQueens = function(n) {\\n const columns = new Set();\\n const diagonals1 = new Set();\\n const diagonals2 = new Set();\\n return backtrack(n, 0, columns, diagonals1, diagonals2);\\n};\\n
###Python
\\n\\nclass Solution:\\n def totalNQueens(self, n: int) -> int:\\n def backtrack(row: int) -> int:\\n if row == n:\\n return 1\\n else:\\n count = 0\\n for i in range(n):\\n if i in columns or row - i in diagonal1 or row + i in diagonal2:\\n continue\\n columns.add(i)\\n diagonal1.add(row - i)\\n diagonal2.add(row + i)\\n count += backtrack(row + 1)\\n columns.remove(i)\\n diagonal1.remove(row - i)\\n diagonal2.remove(row + i)\\n return count\\n \\n columns = set()\\n diagonal1 = set()\\n diagonal2 = set()\\n return backtrack(0)\\n
###Go
\\n\\nfunc totalNQueens(n int) (ans int) {\\n columns := make([]bool, n) // 列上是否有皇后\\n diagonals1 := make([]bool, 2*n-1) // 左上到右下是否有皇后\\n diagonals2 := make([]bool, 2*n-1) // 右上到左下是否有皇后\\n var backtrack func(int)\\n backtrack = func(row int) {\\n if row == n {\\n ans++\\n return\\n }\\n for col, hasQueen := range columns {\\n d1, d2 := row+n-1-col, row+col\\n if hasQueen || diagonals1[d1] || diagonals2[d2] {\\n continue\\n }\\n columns[col] = true\\n diagonals1[d1] = true\\n diagonals2[d2] = true\\n backtrack(row + 1)\\n columns[col] = false\\n diagonals1[d1] = false\\n diagonals2[d2] = false\\n }\\n }\\n backtrack(0)\\n return\\n}\\n
###C
\\n\\nstruct hashTable {\\n int key;\\n UT_hash_handle hh;\\n};\\n\\nstruct hashTable* find(struct hashTable** hashtable, int ikey) {\\n struct hashTable* tmp = NULL;\\n HASH_FIND_INT(*hashtable, &ikey, tmp);\\n return tmp;\\n}\\n\\nvoid insert(struct hashTable** hashtable, int ikey) {\\n struct hashTable* tmp = NULL;\\n HASH_FIND_INT(*hashtable, &ikey, tmp);\\n if (tmp == NULL) {\\n tmp = malloc(sizeof(struct hashTable));\\n tmp->key = ikey;\\n HASH_ADD_INT(*hashtable, key, tmp);\\n }\\n}\\n\\nvoid erase(struct hashTable** hashtable, int ikey) {\\n struct hashTable* tmp = NULL;\\n HASH_FIND_INT(*hashtable, &ikey, tmp);\\n if (tmp != NULL) {\\n HASH_DEL(*hashtable, tmp);\\n free(tmp);\\n }\\n}\\n\\nstruct hashTable *columns, *diagonals1, *diagonals2;\\n\\nint backtrack(int n, int row) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n for (int i = 0; i < n; i++) {\\n if (find(&columns, i) != NULL) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (find(&diagonals1, diagonal1) != NULL) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (find(&diagonals2, diagonal2) != NULL) {\\n continue;\\n }\\n insert(&columns, i);\\n insert(&diagonals1, diagonal1);\\n insert(&diagonals2, diagonal2);\\n count += backtrack(n, row + 1);\\n erase(&columns, i);\\n erase(&diagonals1, diagonal1);\\n erase(&diagonals2, diagonal2);\\n }\\n return count;\\n }\\n}\\n\\nint totalNQueens(int n) {\\n columns = diagonals1 = diagonals2 = NULL;\\n return backtrack(n, 0);\\n}\\n
###TypeScript
\\n\\nfunction totalNQueens(n: number): number {\\n const columns: Set<number> = new Set();\\n const diagonal1: Set<number> = new Set();\\n const diagonal2: Set<number> = new Set();\\n\\n function backtrack(row: number): number {\\n if (row === n) {\\n return 1;\\n } else {\\n let count = 0;\\n for (let i = 0; i < n; i++) {\\n if (columns.has(i) || diagonal1.has(row - i) || diagonal2.has(row + i)) {\\n continue;\\n }\\n columns.add(i);\\n diagonal1.add(row - i);\\n diagonal2.add(row + i);\\n count += backtrack(row + 1);\\n columns.delete(i);\\n diagonal1.delete(row - i);\\n diagonal2.delete(row + i);\\n }\\n return count;\\n }\\n }\\n\\n return backtrack(0);\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn total_n_queens(n: i32) -> i32 {\\n let mut columns = std::collections::HashSet::new();\\n let mut diagonal1 = std::collections::HashSet::new();\\n let mut diagonal2 = std::collections::HashSet::new();\\n\\n fn backtrack(\\n row: usize,\\n n: usize,\\n columns: &mut std::collections::HashSet<usize>,\\n diagonal1: &mut std::collections::HashSet<i32>,\\n diagonal2: &mut std::collections::HashSet<i32>,\\n ) -> i32 {\\n if row == n {\\n return 1;\\n } else {\\n let mut count = 0;\\n for i in 0..n {\\n if columns.contains(&i) || diagonal1.contains(&(row as i32 - i as i32)) || diagonal2.contains(&(row as i32 + i as i32)) {\\n continue;\\n }\\n columns.insert(i);\\n diagonal1.insert(row as i32 - i as i32);\\n diagonal2.insert(row as i32 + i as i32);\\n count += backtrack(row + 1, n, columns, diagonal1, diagonal2);\\n columns.remove(&i);\\n diagonal1.remove(&(row as i32 - i as i32));\\n diagonal2.remove(&(row as i32 + i as i32));\\n }\\n return count;\\n }\\n }\\n\\n backtrack(0, n as usize, &mut columns, &mut diagonal1, &mut diagonal2)\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N!)$,其中 $N$ 是皇后数量。
\\n- \\n
\\n空间复杂度:$O(N)$,其中 $N$ 是皇后数量。空间复杂度主要取决于递归调用层数以及三个集合,递归调用层数不会超过 $N$,每个集合的元素个数都不会超过 $N$。
\\n方法二:基于位运算的回溯
\\n方法一使用三个集合记录分别记录每一列以及两个方向的每条斜线上是否有皇后,每个集合最多包含 $N$ 个元素,因此集合的空间复杂度是 $O(N)$。如果利用位运算记录皇后的信息,就可以将记录皇后信息的空间复杂度从 $O(N)$ 降到 $O(1)$。
\\n具体做法是,使用三个整数 $\\\\textit{columns}$、$\\\\textit{diagonals}_1$ 和 $\\\\textit{diagonals}_2$ 分别记录每一列以及两个方向的每条斜线上是否有皇后,每个整数有 $N$ 个二进制位。棋盘的每一列对应每个整数的二进制表示中的一个数位,其中棋盘的最左列对应每个整数的最低二进制位,最右列对应每个整数的最高二进制位。
\\n那么如何根据每次放置的皇后更新三个整数的值呢?在说具体的计算方法之前,首先说一个例子。
\\n棋盘的边长和皇后的数量 $N=8$。如果棋盘的前两行分别在第 $2$ 列和第 $4$ 列放置了皇后(下标从 $0$ 开始),则棋盘的前两行如下图所示。
\\n\\n
如果要在下一行放置皇后,哪些位置不能放置呢?我们用 $0$ 代表可以放置皇后的位置,$1$ 代表不能放置皇后的位置。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一列,因此不能放置在第 $2$ 列和第 $4$ 列,对应 $\\\\textit{columns}=00010100_{(2)}$。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一条方向一(从左上到右下方向)的斜线上,因此不能放置在第 $4$ 列和第 $5$ 列,对应 $\\\\textit{diagonals}1=00110000{(2)}$。其中,第 $4$ 列为其前两行的第 $2$ 列的皇后往右下移动两步的位置,第 $5$ 列为其前一行的第 $4$ 列的皇后往右下移动一步的位置。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一条方向二(从右上到左下方向)的斜线上,因此不能放置在第 $0$ 列和第 $3$ 列,对应 $\\\\textit{diagonals}2=00001001{(2)}$。其中,第 $0$ 列为其前两行的第 $2$ 列的皇后往左下移动两步的位置,第 $3$ 列为其前一行的第 $4$ 列的皇后往左下移动一步的位置。
\\n\\n
由此可以得到三个整数的计算方法:
\\n\\n
\\n- \\n
\\n初始时,三个整数的值都等于 $0$,表示没有放置任何皇后;
\\n- \\n
\\n在当前行放置皇后,如果皇后放置在第 $i$ 列,则将三个整数的第 $i$ 个二进制位(指从低到高的第 $i$ 个二进制位)的值设为 $1$;
\\n- \\n
\\n进入下一行时,$\\\\textit{columns}$ 的值保持不变,$\\\\textit{diagonals}_1$ 左移一位,$\\\\textit{diagonals}_2$ 右移一位,由于棋盘的最左列对应每个整数的最低二进制位,即每个整数的最右二进制位,因此对整数的移位操作方向和对棋盘的移位操作方向相反(对棋盘的移位操作方向是 $\\\\textit{diagonals}_1$ 右移一位,$\\\\textit{diagonals}_2$ 左移一位)。
\\n<
\\n,
,
,
,
,
>
每次放置皇后时,三个整数的按位或运算的结果即为不能放置皇后的位置,其余位置即为可以放置皇后的位置。可以通过 $(2^n-1)~&~(\\\\sim(\\\\textit{columns} | \\\\textit{diagonals}_1 | \\\\textit{diagonals}_2))$ 得到可以放置皇后的位置(该结果的值为 $1$ 的位置表示可以放置皇后的位置),然后遍历这些位置,尝试放置皇后并得到可能的解。
\\n遍历可以放置皇后的位置时,可以利用以下两个按位与运算的性质:
\\n\\n
\\n- \\n
\\n$x~&~(-x)$ 可以获得 $x$ 的二进制表示中的最低位的 $1$ 的位置;
\\n- \\n
\\n$x~&~(x-1)$ 可以将 $x$ 的二进制表示中的最低位的 $1$ 置成 $0$。
\\n具体做法是,每次获得可以放置皇后的位置中的最低位,并将该位的值置成 $0$,尝试在该位置放置皇后。这样即可遍历每个可以放置皇后的位置。
\\n###Java
\\n\\nclass Solution {\\n public int totalNQueens(int n) {\\n return solve(n, 0, 0, 0, 0);\\n }\\n\\n public int solve(int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int TotalNQueens(int n) {\\n return Solve(n, 0, 0, 0, 0);\\n }\\n\\n private int Solve(int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & -availablePositions;\\n availablePositions &= (availablePositions - 1);\\n count += Solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int totalNQueens(int n) {\\n return solve(n, 0, 0, 0, 0);\\n }\\n\\n int solve(int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n }\\n};\\n
###JavaScript
\\n\\nconst __builtin_popcount = (x) => x.toString(2).split(\'\').reduce((prev, v) => prev + (v === \'1\'), 0);\\nconst solve = (n, row, columns, diagonals1, diagonals2) => {\\n if (row === n) {\\n return 1;\\n } else {\\n let count = 0;\\n let availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n const position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n}\\nvar totalNQueens = function(n) {\\n return solve(n, 0, 0, 0, 0);\\n};\\n
###Python
\\n\\nclass Solution:\\n def totalNQueens(self, n: int) -> int:\\n def solve(row: int, columns: int, diagonals1: int, diagonals2: int) -> int:\\n if row == n:\\n return 1\\n else:\\n count = 0\\n availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2))\\n while availablePositions:\\n position = availablePositions & (-availablePositions)\\n availablePositions = availablePositions & (availablePositions - 1)\\n count += solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1)\\n return count\\n\\n return solve(0, 0, 0, 0)\\n
###Go
\\n\\nfunc totalNQueens(n int) (ans int) {\\n var solve func(row, columns, diagonals1, diagonals2 int)\\n solve = func(row, columns, diagonals1, diagonals2 int) {\\n if row == n {\\n ans++\\n return\\n }\\n availablePositions := (1<<n - 1) &^ (columns | diagonals1 | diagonals2)\\n for availablePositions > 0 {\\n position := availablePositions & -availablePositions\\n solve(row+1, columns|position, (diagonals1|position)<<1, (diagonals2|position)>>1)\\n availablePositions &^= position // 移除该比特位\\n }\\n }\\n solve(0, 0, 0, 0)\\n return\\n}\\n
###C
\\n\\nint solve(int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n return 1;\\n } else {\\n int count = 0;\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n count += solve(n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n}\\n\\nint totalNQueens(int n) {\\n return solve(n, 0, 0, 0, 0);\\n}\\n
###TypeScript
\\n\\nfunction totalNQueens(n: number): number {\\n function solve(row: number, columns: number, diagonals1: number, diagonals2: number): number {\\n if (row === n) {\\n return 1;\\n } else {\\n let count = 0;\\n let availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions) {\\n const position = availablePositions & -availablePositions;\\n availablePositions &= availablePositions - 1;\\n count += solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n return count;\\n }\\n }\\n\\n return solve(0, 0, 0, 0);\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn total_n_queens(n: i32) -> i32 {\\n fn solve(row: usize, columns: usize, diagonals1: usize, diagonals2: usize, n: usize) -> i32 {\\n if row == n {\\n return 1;\\n } else {\\n let mut count = 0;\\n let mut available_positions = ((1 << n) - 1) & !(columns | diagonals1 | diagonals2);\\n while available_positions != 0 {\\n let position = available_positions & available_positions.wrapping_neg();\\n available_positions &= available_positions - 1;\\n count += solve(\\n row + 1,\\n columns | position,\\n (diagonals1 | position) << 1,\\n (diagonals2 | position) >> 1,\\n n,\\n );\\n }\\n return count;\\n }\\n }\\n\\n solve(0, 0, 0, 0, n as usize)\\n } \\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N!)$,其中 $N$ 是皇后数量。
\\n- \\n
\\n空间复杂度:$O(N)$,其中 $N$ 是皇后数量。由于使用位运算表示,因此存储皇后信息的空间复杂度是 $O(1)$,空间复杂度主要取决于递归调用层数,递归调用层数不会超过 $N$。
\\n小结
\\n回顾这道题,拿到这道题的时候,其实我们很容易看出需要使用枚举的方法来求解这个问题,当我们不知道用什么办法来枚举是最优的时候,可以从下面三个方向考虑:
\\n\\n
\\n- 子集枚举:可以把问题转化成「从 $n^2$ 个格子中选一个子集,使得子集中恰好有 $n$ 个格子,且任意选出两个都不在同行、同列或者同对角线」,这里枚举的规模是 $2^{n^2}$;
\\n- 组合枚举:可以把问题转化成「从 $n^2$ 个格子中选择 $n$ 个,且任意选出两个都不在同行、同列或者同对角线」,这里的枚举规模是 ${n^2} \\\\choose {n}$;
\\n- 排列枚举:因为这里每行只能放置一个皇后,而所有行中皇后的列号正好构成一个 $1$ 到 $n$ 的排列,所以我们可以把问题转化为一个排列枚举,规模是 $n!$。
\\n带入一些 $n$ 进这三种方法验证,就可以知道哪种方法的枚举规模是最小的,这里我们发现第三种方法的枚举规模最小。这道题给出的两个方法其实和排列枚举的本质是类似的。
\\n","description":"前言 这道题和「51. N 皇后」非常相似,区别在于,第 $51$ 题需要得到所有可能的解,这道题只需要得到可能的解的数量。因此这道题可以使用第 $51$ 题的做法,只需要将得到所有可能的解改成得到可能的解的数量即可。\\n\\n皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。\\n\\n直观的做法是暴力枚举将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的…","guid":"https://leetcode.cn/problems/n-queens-ii//solution/nhuang-hou-ii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-10-16T14:08:56.424Z","media":[{"url":"https://assets.leetcode.cn/solution-static/52/1.png","type":"photo","width":2000,"height":1125,"blurhash":"LeJH5P0L4:R%-VRkWUWE0M%2%1of"},{"url":"https://assets.leetcode.cn/solution-static/52/2.png","type":"photo","width":2000,"height":1125,"blurhash":"LkIg_pE20eoeaiWEoboc0Lxs-oRo"},{"url":"https://assets.leetcode.cn/solution-static/52/3.png","type":"photo","width":2000,"height":1125,"blurhash":"LpKdO@-nIW-nxZj[afj[0NNHt6Rk"},{"url":"https://assets.leetcode.cn/solution-static/52/4.png","type":"photo","width":2000,"height":921,"blurhash":"LnLDk]NHM}NIoef6fRj[0Noeoeoe"},{"url":"https://assets.leetcode.cn/solution-static/52/2_1.png","type":"photo","width":2000,"height":1125,"blurhash":"LKA0joWC9bkB~TWCE2j[t6a|WCj["},{"url":"https://assets.leetcode.cn/solution-static/52/2_2.png","type":"photo","width":2000,"height":1125,"blurhash":"LUE_$=Rk0goeW.fPn+j[E3j[xZj["},{"url":"https://assets.leetcode.cn/solution-static/52/2_3.png","type":"photo","width":2000,"height":1125,"blurhash":"LaF}ltRk0goex@fkRkfk0goL-Ufk"},{"url":"https://assets.leetcode.cn/solution-static/52/2_4.png","type":"photo","width":2000,"height":1125,"blurhash":"LbG8MwRk0gj[xZazR+j[0goL-UbG"},{"url":"https://assets.leetcode.cn/solution-static/52/2_5.png","type":"photo","width":2000,"height":1125,"blurhash":"LcGHu1W;0gep-nayM}kB0gjG-UkV"},{"url":"https://assets.leetcode.cn/solution-static/52/2_6.png","type":"photo","width":2000,"height":1125,"blurhash":"LdGRR|X70ge:%KafM}kB0gjG-nkC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"笨办法,但是好理解","url":"https://leetcode.cn/problems/three-consecutive-odds//solution/ben-ban-fa-dan-shi-hao-li-jie-by-invictus-10","content":"解题思路
\\n此处撰写解题思路
\\n代码
\\n###python3
\\n\\n","description":"解题思路 此处撰写解题思路\\n\\n代码\\n\\n###python3\\n\\nclass Solution:\\n def threeConsecutiveOdds(self, arr: List[int]) -> bool:\\n for i in range(len(arr)-2):\\n if(arr[i]%2!=0 and arr[i+1]%2!=0 and arr[i+2]%2!=0):\\n return True\\n else:\\n continue…","guid":"https://leetcode.cn/problems/three-consecutive-odds//solution/ben-ban-fa-dan-shi-hao-li-jie-by-invictus-10","author":"invictus-10","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-10-11T12:40:24.576Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分割等和子集","url":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/fen-ge-deng-he-zi-ji-by-leetcode-solution","content":"class Solution:\\n def threeConsecutiveOdds(self, arr: List[int]) -> bool:\\n for i in range(len(arr)-2):\\n if(arr[i]%2!=0 and arr[i+1]%2!=0 and arr[i+2]%2!=0):\\n return True\\n else:\\n continue\\n return False\\n
📺 视频题解
\\n\\n
📖 文字题解
\\n前言
\\n作者在这里希望读者认真阅读前言部分。
\\n本题是经典的「NP 完全问题」,也就是说,如果你发现了该问题的一个多项式算法,那么恭喜你证明出了 P=NP,可以期待一下图灵奖了。
\\n正因如此,我们不应期望该问题有多项式时间复杂度的解法。我们能想到的,例如基于贪心算法的「将数组降序排序后,依次将每个元素添加至当前元素和较小的子集中」之类的方法都是错误的,可以轻松地举出反例。因此,我们必须尝试非多项式时间复杂度的算法,例如时间复杂度与元素大小相关的动态规划。
\\n方法一:动态规划
\\n思路与算法
\\n这道题可以换一种表述:给定一个只包含正整数的非空数组 $\\\\textit{nums}[0]$,判断是否可以从数组中选出一些数字,使得这些数字的和等于整个数组的元素和的一半。因此这个问题可以转换成「$0-1$ 背包问题」。这道题与传统的「$0-1$ 背包问题」的区别在于,传统的「$0-1$ 背包问题」要求选取的物品的重量之和不能超过背包的总容量,这道题则要求选取的数字的和恰好等于整个数组的元素和的一半。类似于传统的「$0-1$ 背包问题」,可以使用动态规划求解。
\\n在使用动态规划求解之前,首先需要进行以下判断。
\\n\\n
\\n- \\n
\\n根据数组的长度 $n$ 判断数组是否可以被划分。如果 $n<2$,则不可能将数组分割成元素和相等的两个子集,因此直接返回 $\\\\text{false}$。
\\n- \\n
\\n计算整个数组的元素和 $\\\\textit{sum}$ 以及最大元素 $\\\\textit{maxNum}$。如果 $\\\\textit{sum}$ 是奇数,则不可能将数组分割成元素和相等的两个子集,因此直接返回 $\\\\text{false}$。如果 $\\\\textit{sum}$ 是偶数,则令 $\\\\textit{target}=\\\\frac{\\\\textit{sum}}{2}$,需要判断是否可以从数组中选出一些数字,使得这些数字的和等于 $\\\\textit{target}$。如果 $\\\\textit{maxNum}>\\\\textit{target}$,则除了 $\\\\textit{maxNum}$ 以外的所有元素之和一定小于 $\\\\textit{target}$,因此不可能将数组分割成元素和相等的两个子集,直接返回 $\\\\text{false}$。
\\n创建二维数组 $\\\\textit{dp}$,包含 $n$ 行 $\\\\textit{target}+1$ 列,其中 $\\\\textit{dp}[i][j]$ 表示从数组的 $[0,i]$ 下标范围内选取若干个正整数(可以是 $0$ 个),是否存在一种选取方案使得被选取的正整数的和等于 $j$。初始时,$\\\\textit{dp}$ 中的全部元素都是 $\\\\text{false}$。
\\n在定义状态之后,需要考虑边界情况。以下两种情况都属于边界情况。
\\n\\n
\\n- \\n
\\n如果不选取任何正整数,则被选取的正整数之和等于 $0$。因此对于所有 $0 \\\\le i < n$,都有 $\\\\textit{dp}[i][0]=\\\\text{true}$。
\\n- \\n
\\n当 $i==0$ 时,只有一个正整数 $\\\\textit{nums}[0]$ 可以被选取,因此 $\\\\textit{dp}[0][\\\\textit{nums}[0]]=\\\\text{true}$。
\\n对于 $i>0$ 且 $j>0$ 的情况,如何确定 $\\\\textit{dp}[i][j]$ 的值?需要分别考虑以下两种情况。
\\n\\n
\\n- \\n
\\n如果 $j \\\\ge \\\\textit{nums}[i]$,则对于当前的数字 $\\\\textit{nums}[i]$,可以选取也可以不选取,两种情况只要有一个为 $\\\\text{true}$,就有 $\\\\textit{dp}[i][j]=\\\\text{true}$。
\\n\\n
\\n- 如果不选取 $\\\\textit{nums}[i]$,则 $\\\\textit{dp}[i][j]=\\\\textit{dp}[i-1][j]$;
\\n- 如果选取 $\\\\textit{nums}[i]$,则 $\\\\textit{dp}[i][j]=\\\\textit{dp}[i-1][j-\\\\textit{nums}[i]]$。
\\n- \\n
\\n如果 $j < \\\\textit{nums}[i]$,则在选取的数字的和等于 $j$ 的情况下无法选取当前的数字 $\\\\textit{nums}[i]$,因此有 $\\\\textit{dp}[i][j]=\\\\textit{dp}[i-1][j]$。
\\n状态转移方程如下:
\\n$$
\\n
\\n\\\\textit{dp}[i][j]=\\\\begin{cases}
\\n\\\\textit{dp}[i-1][j]~|~\\\\textit{dp}[i-1][j-\\\\textit{nums}[i]], & j \\\\ge \\\\textit{nums}[i] \\\\
\\n\\\\textit{dp}[i-1][j], & j < \\\\textit{nums}[i]
\\n\\\\end{cases}
\\n$$最终得到 $\\\\textit{dp}[n-1][\\\\textit{target}]$ 即为答案。
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
>
###Java
\\n\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int n = nums.length;\\n if (n < 2) {\\n return false;\\n }\\n int sum = 0, maxNum = 0;\\n for (int num : nums) {\\n sum += num;\\n maxNum = Math.max(maxNum, num);\\n }\\n if (sum % 2 != 0) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n boolean[][] dp = new boolean[n][target + 1];\\n for (int i = 0; i < n; i++) {\\n dp[i][0] = true;\\n }\\n dp[0][nums[0]] = true;\\n for (int i = 1; i < n; i++) {\\n int num = nums[i];\\n for (int j = 1; j <= target; j++) {\\n if (j >= num) {\\n dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];\\n } else {\\n dp[i][j] = dp[i - 1][j];\\n }\\n }\\n }\\n return dp[n - 1][target];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int n = nums.size();\\n if (n < 2) {\\n return false;\\n }\\n int sum = accumulate(nums.begin(), nums.end(), 0);\\n int maxNum = *max_element(nums.begin(), nums.end());\\n if (sum & 1) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n vector<vector<int>> dp(n, vector<int>(target + 1, 0));\\n for (int i = 0; i < n; i++) {\\n dp[i][0] = true;\\n }\\n dp[0][nums[0]] = true;\\n for (int i = 1; i < n; i++) {\\n int num = nums[i];\\n for (int j = 1; j <= target; j++) {\\n if (j >= num) {\\n dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];\\n } else {\\n dp[i][j] = dp[i - 1][j];\\n }\\n }\\n }\\n return dp[n - 1][target];\\n }\\n};\\n
###JavaScript
\\n\\nvar canPartition = function(nums) {\\n const n = nums.length;\\n if (n < 2) {\\n return false;\\n }\\n let sum = 0, maxNum = 0;\\n for (const num of nums) {\\n sum += num;\\n maxNum = maxNum > num ? maxNum : num;\\n }\\n if (sum & 1) {\\n return false;\\n }\\n const target = Math.floor(sum / 2);\\n if (maxNum > target) {\\n return false;\\n }\\n const dp = new Array(n).fill(0).map(() => new Array(target + 1, false));\\n for (let i = 0; i < n; i++) {\\n dp[i][0] = true;\\n }\\n dp[0][nums[0]] = true;\\n for (let i = 1; i < n; i++) {\\n const num = nums[i];\\n for (let j = 1; j <= target; j++) {\\n if (j >= num) {\\n dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];\\n } else {\\n dp[i][j] = dp[i - 1][j];\\n }\\n }\\n }\\n return dp[n - 1][target];\\n};\\n
###Golang
\\n\\nfunc canPartition(nums []int) bool {\\n n := len(nums)\\n if n < 2 {\\n return false\\n }\\n\\n sum, max := 0, 0\\n for _, v := range nums {\\n sum += v\\n if v > max {\\n max = v\\n }\\n }\\n if sum%2 != 0 {\\n return false\\n }\\n\\n target := sum / 2\\n if max > target {\\n return false\\n }\\n\\n dp := make([][]bool, n)\\n for i := range dp {\\n dp[i] = make([]bool, target+1)\\n }\\n for i := 0; i < n; i++ {\\n dp[i][0] = true\\n }\\n dp[0][nums[0]] = true\\n for i := 1; i < n; i++ {\\n v := nums[i]\\n for j := 1; j <= target; j++ {\\n if j >= v {\\n dp[i][j] = dp[i-1][j] || dp[i-1][j-v]\\n } else {\\n dp[i][j] = dp[i-1][j]\\n }\\n }\\n }\\n return dp[n-1][target]\\n}\\n
###C
\\n\\nbool canPartition(int* nums, int numsSize) {\\n if (numsSize < 2) {\\n return false;\\n }\\n int sum = 0, maxNum = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n sum += nums[i];\\n maxNum = fmax(maxNum, nums[i]);\\n }\\n if (sum & 1) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n int dp[numsSize][target + 1];\\n memset(dp, 0, sizeof(dp));\\n for (int i = 0; i < numsSize; i++) {\\n dp[i][0] = true;\\n }\\n dp[0][nums[0]] = true;\\n for (int i = 1; i < numsSize; i++) {\\n int num = nums[i];\\n for (int j = 1; j <= target; j++) {\\n if (j >= num) {\\n dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num];\\n } else {\\n dp[i][j] = dp[i - 1][j];\\n }\\n }\\n }\\n return dp[numsSize - 1][target];\\n}\\n
###Python
\\n\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n n = len(nums)\\n if n < 2:\\n return False\\n \\n total = sum(nums)\\n maxNum = max(nums)\\n if total & 1:\\n return False\\n \\n target = total // 2\\n if maxNum > target:\\n return False\\n \\n dp = [[False] * (target + 1) for _ in range(n)]\\n for i in range(n):\\n dp[i][0] = True\\n \\n dp[0][nums[0]] = True\\n for i in range(1, n):\\n num = nums[i]\\n for j in range(1, target + 1):\\n if j >= num:\\n dp[i][j] = dp[i - 1][j] | dp[i - 1][j - num]\\n else:\\n dp[i][j] = dp[i - 1][j]\\n \\n return dp[n - 1][target]\\n
上述代码的空间复杂度是 $O(n \\\\times \\\\textit{target})$。但是可以发现在计算 $\\\\textit{dp}$ 的过程中,每一行的 $dp$ 值都只与上一行的 $dp$ 值有关,因此只需要一个一维数组即可将空间复杂度降到 $O(\\\\textit{target})$。此时的转移方程为:
\\n
\\n$$
\\n\\\\textit{dp}[j]=\\\\textit{dp}[j]\\\\ |\\\\ dp[j-\\\\textit{nums}[i]]
\\n$$
\\n且需要注意的是第二层的循环我们需要从大到小计算,因为如果我们从小到大更新 $\\\\textit{dp}$ 值,那么在计算 $\\\\textit{dp}[j]$ 值的时候,$\\\\textit{dp}[j-\\\\textit{nums}[i]]$ 已经是被更新过的状态,不再是上一行的 $\\\\textit{dp}$ 值。代码
\\n###Java
\\n\\nclass Solution {\\n public boolean canPartition(int[] nums) {\\n int n = nums.length;\\n if (n < 2) {\\n return false;\\n }\\n int sum = 0, maxNum = 0;\\n for (int num : nums) {\\n sum += num;\\n maxNum = Math.max(maxNum, num);\\n }\\n if (sum % 2 != 0) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n boolean[] dp = new boolean[target + 1];\\n dp[0] = true;\\n for (int i = 0; i < n; i++) {\\n int num = nums[i];\\n for (int j = target; j >= num; --j) {\\n dp[j] |= dp[j - num];\\n }\\n }\\n return dp[target];\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n bool canPartition(vector<int>& nums) {\\n int n = nums.size();\\n if (n < 2) {\\n return false;\\n }\\n int sum = 0, maxNum = 0;\\n for (auto& num : nums) {\\n sum += num;\\n maxNum = max(maxNum, num);\\n }\\n if (sum & 1) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n vector<int> dp(target + 1, 0);\\n dp[0] = true;\\n for (int i = 0; i < n; i++) {\\n int num = nums[i];\\n for (int j = target; j >= num; --j) {\\n dp[j] |= dp[j - num];\\n }\\n }\\n return dp[target];\\n }\\n};\\n
###JavaScript
\\n\\nvar canPartition = function(nums) {\\n const n = nums.length;\\n if (n < 2) {\\n return false;\\n }\\n let sum = 0, maxNum = 0;\\n for (const num of nums) {\\n sum += num;\\n maxNum = maxNum > num ? maxNum : num;\\n }\\n if (sum & 1) {\\n return false;\\n }\\n const target = Math.floor(sum / 2);\\n if (maxNum > target) {\\n return false;\\n }\\n const dp = new Array(target + 1).fill(false);\\n dp[0] = true;\\n for (const num of nums) {\\n for (let j = target; j >= num; --j) {\\n dp[j] |= dp[j - num];\\n }\\n }\\n return dp[target];\\n};\\n
###Golang
\\n\\nfunc canPartition(nums []int) bool {\\n n := len(nums)\\n if n < 2 {\\n return false\\n }\\n\\n sum, max := 0, 0\\n for _, v := range nums {\\n sum += v\\n if v > max {\\n max = v\\n }\\n }\\n if sum%2 != 0 {\\n return false\\n }\\n\\n target := sum / 2\\n if max > target {\\n return false\\n }\\n\\n dp := make([]bool, target+1)\\n dp[0] = true\\n for i := 0; i < n; i++ {\\n v := nums[i]\\n for j := target; j >= v; j-- {\\n dp[j] = dp[j] || dp[j-v]\\n }\\n }\\n return dp[target]\\n}\\n
###C
\\n\\nbool canPartition(int* nums, int numsSize) {\\n if (numsSize < 2) {\\n return false;\\n }\\n int sum = 0, maxNum = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n sum += nums[i];\\n maxNum = fmax(maxNum, nums[i]);\\n }\\n if (sum & 1) {\\n return false;\\n }\\n int target = sum / 2;\\n if (maxNum > target) {\\n return false;\\n }\\n int dp[target + 1];\\n memset(dp, 0, sizeof(dp));\\n dp[0] = true;\\n for (int i = 0; i < numsSize; i++) {\\n int num = nums[i];\\n for (int j = target; j >= num; --j) {\\n dp[j] |= dp[j - num];\\n }\\n }\\n return dp[target];\\n}\\n
###Python
\\n\\nclass Solution:\\n def canPartition(self, nums: List[int]) -> bool:\\n n = len(nums)\\n if n < 2:\\n return False\\n \\n total = sum(nums)\\n if total % 2 != 0:\\n return False\\n \\n target = total // 2\\n dp = [True] + [False] * target\\n for i, num in enumerate(nums):\\n for j in range(target, num - 1, -1):\\n dp[j] |= dp[j - num]\\n \\n return dp[target]\\n
复杂度分析
\\n\\n
\\n","description":"📺 视频题解 📖 文字题解\\n前言\\n\\n作者在这里希望读者认真阅读前言部分。\\n\\n本题是经典的「NP 完全问题」,也就是说,如果你发现了该问题的一个多项式算法,那么恭喜你证明出了 P=NP,可以期待一下图灵奖了。\\n\\n正因如此,我们不应期望该问题有多项式时间复杂度的解法。我们能想到的,例如基于贪心算法的「将数组降序排序后,依次将每个元素添加至当前元素和较小的子集中」之类的方法都是错误的,可以轻松地举出反例。因此,我们必须尝试非多项式时间复杂度的算法,例如时间复杂度与元素大小相关的动态规划。\\n\\n方法一:动态规划\\n\\n思路与算法\\n\\n这道题可以换一种表述…","guid":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/fen-ge-deng-he-zi-ji-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-10-10T15:32:34.746Z","media":[{"url":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/1712d35b-f0ab-48aa-b964-fe22bf0a5931","type":"photo"},{"url":"https://assets.leetcode-cn.com/solution-static/416/1.png","type":"photo","width":2000,"height":1125,"blurhash":"L66@Tds:R*s:oLfQj[a|0yWVoLWB"},{"url":"https://assets.leetcode-cn.com/solution-static/416/2.png","type":"photo","width":2000,"height":1125,"blurhash":"L66tajbtJPbtoffjayay0znkr]e:"},{"url":"https://assets.leetcode-cn.com/solution-static/416/3.png","type":"photo","width":2000,"height":1125,"blurhash":"L56tajXjExkVoybFWVf70ynRwMep"},{"url":"https://assets.leetcode-cn.com/solution-static/416/4.png","type":"photo","width":2000,"height":1125,"blurhash":"L56j|2TGE{oykBW.bHjb0fnSwMaf"},{"url":"https://assets.leetcode-cn.com/solution-static/416/5.png","type":"photo","width":2000,"height":1125,"blurhash":"L56j|2TGJPt6f*W.fkjb0frvr]WC"},{"url":"https://assets.leetcode-cn.com/solution-static/416/6.png","type":"photo","width":2000,"height":1125,"blurhash":"L56aehTGJ6t6baW.j[jb0grvsCWC"},{"url":"https://assets.leetcode-cn.com/solution-static/416/7.png","type":"photo","width":2000,"height":1125,"blurhash":"L56aehTFJ6t6baW.j[jb0grvsCWB"},{"url":"https://assets.leetcode-cn.com/solution-static/416/8.png","type":"photo","width":2000,"height":1125,"blurhash":"L56aehTFJ6t6baW.j[jb0grvsCWB"},{"url":"https://assets.leetcode-cn.com/solution-static/416/9.png","type":"photo","width":2000,"height":1125,"blurhash":"L56aehTFJ6t6baW.j[jb0grvsCWB"},{"url":"https://assets.leetcode-cn.com/solution-static/416/10.png","type":"photo","width":2000,"height":1125,"blurhash":"L35}gQP6EexaofW.bHn+03rcoJR*"},{"url":"https://assets.leetcode-cn.com/solution-static/416/11.png","type":"photo","width":2000,"height":1125,"blurhash":"L45hi8TGNGxawNSxX6ju4XnSoJR*"},{"url":"https://assets.leetcode-cn.com/solution-static/416/12.png","type":"photo","width":2000,"height":1125,"blurhash":"L038@o%LICx[rds:o{V[D+RQxaaf"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"颜色分类","url":"https://leetcode.cn/problems/sort-colors//solution/yan-se-fen-lei-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(n \\\\times \\\\textit{target})$,其中 $n$ 是数组的长度,$\\\\textit{target}$ 是整个数组的元素和的一半。需要计算出所有的状态,每个状态在进行转移时的时间复杂度为 $O(1)$。
\\n- \\n
\\n空间复杂度:$O(\\\\textit{target})$,其中 $\\\\textit{target}$ 是整个数组的元素和的一半。空间复杂度取决于 $\\\\textit{dp}$ 数组,在不进行空间优化的情况下,空间复杂度是 $O(n \\\\times \\\\textit{target})$,在进行空间优化的情况下,空间复杂度可以降到 $O(\\\\textit{target})$。
\\n📺 视频题解
\\n\\n
📖 文字题解
\\n前言
\\n本题是经典的「荷兰国旗问题」,由计算机科学家 Edsger W. Dijkstra 首先提出。
\\n根据题目中的提示,我们可以统计出数组中 $0, 1, 2$ 的个数,再根据它们的数量,重写整个数组。这种方法较为简单,也很容易想到,而本题解中会介绍两种基于指针进行交换的方法。
\\n方法一:单指针
\\n思路与算法
\\n我们可以考虑对数组进行两次遍历。在第一次遍历中,我们将数组中所有的 $0$ 交换到数组的头部。在第二次遍历中,我们将数组中所有的 $1$ 交换到头部的 $0$ 之后。此时,所有的 $2$ 都出现在数组的尾部,这样我们就完成了排序。
\\n具体地,我们使用一个指针 $\\\\textit{ptr}$ 表示「头部」的范围,$\\\\textit{ptr}$ 中存储了一个整数,表示数组 $\\\\textit{nums}$ 从位置 $0$ 到位置 $\\\\textit{ptr}-1$ 都属于「头部」。$\\\\textit{ptr}$ 的初始值为 $0$,表示还没有数处于「头部」。
\\n在第一次遍历中,我们从左向右遍历整个数组,如果找到了 $0$,那么就需要将 $0$ 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 $0$ 都被交换到「头部」的范围,并且「头部」只包含 $0$。
\\n在第二次遍历中,我们从「头部」开始,从左向右遍历整个数组,如果找到了 $1$,那么就需要将 $1$ 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 $1$ 都被交换到「头部」的范围,并且都在 $0$ 之后,此时 $2$ 只出现在「头部」之外的位置,因此排序完成。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n void sortColors(vector<int>& nums) {\\n int n = nums.size();\\n int ptr = 0;\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 0) {\\n swap(nums[i], nums[ptr]);\\n ++ptr;\\n }\\n }\\n for (int i = ptr; i < n; ++i) {\\n if (nums[i] == 1) {\\n swap(nums[i], nums[ptr]);\\n ++ptr;\\n }\\n }\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public void sortColors(int[] nums) {\\n int n = nums.length;\\n int ptr = 0;\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 0) {\\n int temp = nums[i];\\n nums[i] = nums[ptr];\\n nums[ptr] = temp;\\n ++ptr;\\n }\\n }\\n for (int i = ptr; i < n; ++i) {\\n if (nums[i] == 1) {\\n int temp = nums[i];\\n nums[i] = nums[ptr];\\n nums[ptr] = temp;\\n ++ptr;\\n }\\n }\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def sortColors(self, nums: List[int]) -> None:\\n n = len(nums)\\n ptr = 0\\n for i in range(n):\\n if nums[i] == 0:\\n nums[i], nums[ptr] = nums[ptr], nums[i]\\n ptr += 1\\n for i in range(ptr, n):\\n if nums[i] == 1:\\n nums[i], nums[ptr] = nums[ptr], nums[i]\\n ptr += 1\\n
###Golang
\\n\\nfunc swapColors(colors []int, target int) (countTarget int) {\\n for i, c := range colors {\\n if c == target {\\n colors[i], colors[countTarget] = colors[countTarget], colors[i]\\n countTarget++\\n }\\n }\\n return\\n}\\n\\nfunc sortColors(nums []int) {\\n count0 := swapColors(nums, 0) // 把 0 排到前面\\n swapColors(nums[count0:], 1) // nums[:count0] 全部是 0 了,对剩下的 nums[count0:] 把 1 排到前面\\n}\\n
###C
\\n\\nvoid swap(int *a, int *b) {\\n int t = *a;\\n *a = *b, *b = t;\\n}\\n\\nvoid sortColors(int *nums, int numsSize) {\\n int ptr = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n if (nums[i] == 0) {\\n swap(&nums[i], &nums[ptr]);\\n ++ptr;\\n }\\n }\\n for (int i = ptr; i < numsSize; ++i) {\\n if (nums[i] == 1) {\\n swap(&nums[i], &nums[ptr]);\\n ++ptr;\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:双指针
\\n思路与算法
\\n方法一需要进行两次遍历,那么我们是否可以仅使用一次遍历呢?我们可以额外使用一个指针,即使用两个指针分别用来交换 $0$ 和 $1$。
\\n具体地,我们用指针 $p_0$ 来交换 $0$,$p_1$ 来交换 $1$,初始值都为 $0$。当我们从左向右遍历整个数组时:
\\n\\n
\\n- \\n
\\n如果找到了 $1$,那么将其与 $\\\\textit{nums}[p_1]$ 进行交换,并将 $p_1$ 向后移动一个位置,这与方法一是相同的;
\\n- \\n
\\n如果找到了 $0$,那么将其与 $\\\\textit{nums}[p_0]$ 进行交换,并将 $p_0$ 向后移动一个位置。这样做是正确的吗?我们可以注意到,因为连续的 $0$ 之后是连续的 $1$,因此如果我们将 $0$ 与 $\\\\textit{nums}[p_0]$ 进行交换,那么我们可能会把一个 $1$ 交换出去。当 $p_0 < p_1$ 时,我们已经将一些 $1$ 连续地放在头部,此时一定会把一个 $1$ 交换出去,导致答案错误。因此,如果 $p_0 < p_1$,那么我们需要再将 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[p_1]$ 进行交换,其中 $i$ 是当前遍历到的位置,在进行了第一次交换后,$\\\\textit{nums}[i]$ 的值为 $1$,我们需要将这个 $1$ 放到「头部」的末端。在最后,无论是否有 $p_0 < p_1$,我们需要将 $p_0$ 和 $p_1$ 均向后移动一个位置,而不是仅将 $p_0$ 向后移动一个位置。
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
>
代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n void sortColors(vector<int>& nums) {\\n int n = nums.size();\\n int p0 = 0, p1 = 0;\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 1) {\\n swap(nums[i], nums[p1]);\\n ++p1;\\n } else if (nums[i] == 0) {\\n swap(nums[i], nums[p0]);\\n if (p0 < p1) {\\n swap(nums[i], nums[p1]);\\n }\\n ++p0;\\n ++p1;\\n }\\n }\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public void sortColors(int[] nums) {\\n int n = nums.length;\\n int p0 = 0, p1 = 0;\\n for (int i = 0; i < n; ++i) {\\n if (nums[i] == 1) {\\n int temp = nums[i];\\n nums[i] = nums[p1];\\n nums[p1] = temp;\\n ++p1;\\n } else if (nums[i] == 0) {\\n int temp = nums[i];\\n nums[i] = nums[p0];\\n nums[p0] = temp;\\n if (p0 < p1) {\\n temp = nums[i];\\n nums[i] = nums[p1];\\n nums[p1] = temp;\\n }\\n ++p0;\\n ++p1;\\n }\\n }\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def sortColors(self, nums: List[int]) -> None:\\n n = len(nums)\\n p0 = p1 = 0\\n for i in range(n):\\n if nums[i] == 1:\\n nums[i], nums[p1] = nums[p1], nums[i]\\n p1 += 1\\n elif nums[i] == 0:\\n nums[i], nums[p0] = nums[p0], nums[i]\\n if p0 < p1:\\n nums[i], nums[p1] = nums[p1], nums[i]\\n p0 += 1\\n p1 += 1\\n
###Golang
\\n\\nfunc sortColors(nums []int) {\\n p0, p1 := 0, 0\\n for i, c := range nums {\\n if c == 0 {\\n nums[i], nums[p0] = nums[p0], nums[i]\\n if p0 < p1 {\\n nums[i], nums[p1] = nums[p1], nums[i]\\n }\\n p0++\\n p1++\\n } else if c == 1 {\\n nums[i], nums[p1] = nums[p1], nums[i]\\n p1++\\n }\\n }\\n}\\n
###C
\\n\\nvoid swap(int *a, int *b) {\\n int t = *a;\\n *a = *b, *b = t;\\n}\\n\\nvoid sortColors(int *nums, int numsSize) {\\n int p0 = 0, p1 = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n if (nums[i] == 1) {\\n swap(&nums[i], &nums[p1]);\\n ++p1;\\n } else if (nums[i] == 0) {\\n swap(&nums[i], &nums[p0]);\\n if (p0 < p1) {\\n swap(&nums[i], &nums[p1]);\\n }\\n ++p0;\\n ++p1;\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法三:双指针
\\n思路与算法
\\n与方法二类似,我们也可以考虑使用指针 $p_0$ 来交换 $0$,$p_2$ 来交换 $2$。此时,$p_0$ 的初始值仍然为 $0$,而 $p_2$ 的初始值为 $n-1$。在遍历的过程中,我们需要找出所有的 $0$ 交换至数组的头部,并且找出所有的 $2$ 交换至数组的尾部。
\\n由于此时其中一个指针 $p_2$ 是从右向左移动的,因此当我们在从左向右遍历整个数组时,如果遍历到的位置超过了 $p_2$,那么就可以直接停止遍历了。
\\n具体地,我们从左向右遍历整个数组,设当前遍历到的位置为 $i$,对应的元素为 $\\\\textit{nums}[i]$;
\\n\\n
\\n- \\n
\\n如果找到了 $0$,那么与前面两种方法类似,将其与 $\\\\textit{nums}[p_0]$ 进行交换,并将 $p_0$ 向后移动一个位置;
\\n- \\n
\\n如果找到了 $2$,那么将其与 $\\\\textit{nums}[p_2]$ 进行交换,并将 $p_2$ 向前移动一个位置。
\\n这样做是正确的吗?可以发现,对于第二种情况,当我们将 $\\\\textit{nums}[i]$ 与 $\\\\textit{nums}[p_2]$ 进行交换之后,新的 $\\\\textit{nums}[i]$ 可能仍然是 $2$,也可能是 $0$。然而此时我们已经结束了交换,开始遍历下一个元素 $\\\\textit{nums}[i+1]$,不会再考虑 $\\\\textit{nums}[i]$ 了,这样我们就会得到错误的答案。
\\n因此,当我们找到 $2$ 时,我们需要不断地将其与 $\\\\textit{nums}[p_2]$ 进行交换,直到新的 $\\\\textit{nums}[i]$ 不为 $2$。此时,如果 $\\\\textit{nums}[i]$ 为 $0$,那么对应着第一种情况;如果 $\\\\textit{nums}[i]$ 为 $1$,那么就不需要进行任何后续的操作。
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
,
>
代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n void sortColors(vector<int>& nums) {\\n int n = nums.size();\\n int p0 = 0, p2 = n - 1;\\n for (int i = 0; i <= p2; ++i) {\\n while (i <= p2 && nums[i] == 2) {\\n swap(nums[i], nums[p2]);\\n --p2;\\n }\\n if (nums[i] == 0) {\\n swap(nums[i], nums[p0]);\\n ++p0;\\n }\\n }\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public void sortColors(int[] nums) {\\n int n = nums.length;\\n int p0 = 0, p2 = n - 1;\\n for (int i = 0; i <= p2; ++i) {\\n while (i <= p2 && nums[i] == 2) {\\n int temp = nums[i];\\n nums[i] = nums[p2];\\n nums[p2] = temp;\\n --p2;\\n }\\n if (nums[i] == 0) {\\n int temp = nums[i];\\n nums[i] = nums[p0];\\n nums[p0] = temp;\\n ++p0;\\n }\\n }\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def sortColors(self, nums: List[int]) -> None:\\n n = len(nums)\\n p0, p2 = 0, n - 1\\n i = 0\\n while i <= p2:\\n while i <= p2 and nums[i] == 2:\\n nums[i], nums[p2] = nums[p2], nums[i]\\n p2 -= 1\\n if nums[i] == 0:\\n nums[i], nums[p0] = nums[p0], nums[i]\\n p0 += 1\\n i += 1\\n
###Golang
\\n\\nfunc sortColors(nums []int) {\\n p0, p2 := 0, len(nums)-1\\n for i := 0; i <= p2; i++ {\\n for ; i <= p2 && nums[i] == 2; p2-- {\\n nums[i], nums[p2] = nums[p2], nums[i]\\n }\\n if nums[i] == 0 {\\n nums[i], nums[p0] = nums[p0], nums[i]\\n p0++\\n }\\n }\\n}\\n
###C
\\n\\nvoid swap(int *a, int *b) {\\n int t = *a;\\n *a = *b, *b = t;\\n}\\n\\nvoid sortColors(int *nums, int numsSize) {\\n int p0 = 0, p2 = numsSize - 1;\\n for (int i = 0; i <= p2; ++i) {\\n while (i <= p2 && nums[i] == 2) {\\n swap(&nums[i], &nums[p2]);\\n --p2;\\n }\\n if (nums[i] == 0) {\\n swap(&nums[i], &nums[p0]);\\n ++p0;\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"📺 视频题解 📖 文字题解\\n前言\\n\\n本题是经典的「荷兰国旗问题」,由计算机科学家 Edsger W. Dijkstra 首先提出。\\n\\n根据题目中的提示,我们可以统计出数组中 $0, 1, 2$ 的个数,再根据它们的数量,重写整个数组。这种方法较为简单,也很容易想到,而本题解中会介绍两种基于指针进行交换的方法。\\n\\n方法一:单指针\\n\\n思路与算法\\n\\n我们可以考虑对数组进行两次遍历。在第一次遍历中,我们将数组中所有的 $0$ 交换到数组的头部。在第二次遍历中,我们将数组中所有的 $1$ 交换到头部的 $0$ 之后。此时,所有的 $2$ 都出现在数组的尾部…","guid":"https://leetcode.cn/problems/sort-colors//solution/yan-se-fen-lei-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-10-06T13:28:51.515Z","media":[{"url":"https://leetcode.cn/problems/sort-colors//solution/767093ad-0d7e-4f4d-a2d4-2917633c7a80","type":"photo"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_1.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6*H+XnEOxBj[fjjtfQ0Ln3-SNf"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_2.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHXnEOxBfkfjjtfQ0Ln3-SNf"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_3.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHXnEOxBfkfjjtfR0Ln3-SNf"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_4.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHXnEOxBj[fPjtfQ0Ln3-SNf"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_5.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHb_IW+[j[fQfQa}0Liv%KFy"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_6.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHb_IW+[j[fQfQa}0Liv%KFy"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_7.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHb_IW+[j[fQfQa}0Liv%KFy"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_8.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHb_IW+[j[fQfQa}0Liv%KFy"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_9.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHRjZ$K5fkfQfRfP0Lt7k=#+"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_10.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHRjZ$K5j[fQf8fP0Lt7k=#+"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_11.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHRjZ$K5j[fQf8fP0Lt7k=#+"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_12.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHnNMzFzj[fRfQjs0LXUx@+@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_13.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHnNMzFzj[fRfQjs0LXUx@+@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_14.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHnNMzFzj[fRfQjs0LXUx@+@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_15.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHn5M{F{j[fRf7js0LXlxu+["},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_16.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHn5M{F{j[fRf7js0LXlxu+["},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_17.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHvz9}OTj[fRjsfQ0LO@=swN"},{"url":"https://assets.leetcode-cn.com/solution-static/75/2_18.png","type":"photo","width":2000,"height":1181,"blurhash":"LA6twHvz9}OTfkfRjsfP0LO@=swN"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_1.png","type":"photo","width":2000,"height":1125,"blurhash":"LM9@6Toz4:xat8WBRPj[0KV@?GNG"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_2.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7KYjJWRQjpNGoIj[fS0L$etQfn"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_3.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7KYjn5NHnyNGbaoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_4.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7KYjn5NHnyNGbHoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_5.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^Bn5NHnyNGbHoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_6.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^Bn5NHnyNGbHoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_7.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^Bn5NHnyNGbHoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_8.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^Bn5NHnyNGbaoLa~0LXlxZW@"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_9.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^B#7ElOANGX8slo20LO@-5wz"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_10.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^B#7ElOANGX8slo20LO@-5wz"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_11.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^B#7ElOANGX8slo20LO@-5wz"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_12.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^B#7ElOANGX8slo20LO@-5wz"},{"url":"https://assets.leetcode-cn.com/solution-static/75/3_13.png","type":"photo","width":2000,"height":1125,"blurhash":"LC7T^C#7ElOANGX8oIo20LO@-5wz"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"685. 冗余连接 II:【并查集的应用】详解","url":"https://leetcode.cn/problems/redundant-connection-ii//solution/685-rong-yu-lian-jie-iibing-cha-ji-de-ying-yong-xi","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n解题思路
\\n先重点读懂题目中的这句 该图由一个有着 N 个节点 (节点值不重复 1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在 1 到 N 中间,这条附加的边不属于树中已存在的边。
\\n这说明题目中的图原本是是一棵树,只不过在不增加节点的情况下多加了一条边!
\\n还有 若有多个答案,返回最后出现在给定二维数组的答案。这说明在两天边都可以删除的情况下,要删顺序靠后的!
\\n那么有如下三种情况,前两种情况是出现入度为 2 的点,如图:
\\n\\n
且只有一个节点入度为 2,为什么不看出度呢,出度没有意义,一颗树中随便一个父节点就有多个出度。
\\n第三种情况是没有入度为 2 的点,那么图中一定出现了有向环(注意这里强调是有向环!)
\\n如图:
\\n\\n
首先先计算节点的入度,代码如下:
\\n###C++
\\n\\nint inDegree[N] = {0}; // 记录节点入度\\n n = edges.size(); // 边的数量\\n for (int i = 0; i < n; i++) {\\n inDegree[edges[i][1]]++; // 统计入度\\n }\\n
前两种入度为 2 的情况,一定是删除指向入度为 2 的节点的两条边其中的一条,如果删了一条,判断这个图是一个树,那么这条边就是答案,同时注意要从后向前遍历,因为如果两天边删哪一条都可以成为树,就删最后那一条。
\\n代码如下:
\\n###C++
\\n\\nvector<int> vec; // 记录入度为2的边(如果有的话就两条边)\\n // 找入度为2的节点所对应的边,注意要倒叙,因为优先返回最后出现在二维数组中的答案\\n for (int i = n - 1; i >= 0; i--) {\\n if (inDegree[edges[i][1]] == 2) {\\n vec.push_back(i);\\n }\\n }\\n // 处理图中情况1 和 情况2\\n // 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树\\n if (vec.size() > 0) {\\n if (isTreeAfterRemoveEdge(edges, vec[0])) {\\n return edges[vec[0]];\\n } else {\\n return edges[vec[1]];\\n }\\n }\\n
在来看情况三,明确没有入度为 2 的情况,那么一定有有向环,找到构成环的边就是要删除的边。
\\n可以定义一个函数,代码如下:
\\n\\n// 在有向图里找到删除的那条边,使其变成树,返回值就是要删除的边\\nvector<int> getRemoveEdge(const vector<vector<int>>& edges)\\n
此时 大家应该知道了,我们要实现两个最为关键的函数:
\\n\\n
\\n- \\n
isTreeAfterRemoveEdge()
判断删一个边之后是不是树了- \\n
getRemoveEdge
确定图中一定有了有向环,那么要找到需要删除的那条边此时应该是用到 并查集了,并查集为什么可以判断 一个图是不是树呢?
\\n因为如果两个点所在的边在添加图之前如果就可以在并查集里找到了相同的根,那么这条边添加上之后 这个图一定不是树了
\\n时间有限,暂时不对并查集展开过多的讲解了,翻到了自己九年前写过了一篇并查集的文章 并查集学习,哈哈,那时候还太年轻,写不咋地,有空我会重写一篇!
\\n敬请期待!
\\n本题代码如下:(详细注释了)
\\n###C++
\\n\\n","description":"解题思路 先重点读懂题目中的这句 该图由一个有着 N 个节点 (节点值不重复 1, 2, ..., N) 的树及一条附加的边构成。附加的边的两个顶点包含在 1 到 N 中间,这条附加的边不属于树中已存在的边。\\n\\n这说明题目中的图原本是是一棵树,只不过在不增加节点的情况下多加了一条边!\\n\\n还有 若有多个答案,返回最后出现在给定二维数组的答案。这说明在两天边都可以删除的情况下,要删顺序靠后的!\\n\\n那么有如下三种情况,前两种情况是出现入度为 2 的点,如图:\\n\\n且只有一个节点入度为 2,为什么不看出度呢,出度没有意义,一颗树中随便一个父节点就有多个出度。\\n\\n第三种情况…","guid":"https://leetcode.cn/problems/redundant-connection-ii//solution/685-rong-yu-lian-jie-iibing-cha-ji-de-ying-yong-xi","author":"carlsun-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-09-17T04:18:19.927Z","media":[{"url":"https://pic.leetcode-cn.com/1600316277-YhGBBy-685.%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5II1.png","type":"photo","width":1302,"height":656,"blurhash":"LBSY{q~q-;_3_3M{xuWB-;t7D%ay"},{"url":"https://pic.leetcode-cn.com/1600316290-fCDJSg-685.%E5%86%97%E4%BD%99%E8%BF%9E%E6%8E%A5II2.png","type":"photo","width":1064,"height":474,"blurhash":"LAR{#?~q~q_3?bWBM{oft7ayM{t7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"并查集(Java)","url":"https://leetcode.cn/problems/redundant-connection-ii//solution/bing-cha-ji-java-by-liweiwei1419","content":"\\nclass Solution {\\nprivate:\\n static const int N = 1010; // 如题:二维数组大小的在3到1000范围内\\n int father[N];\\n int n; // 边的数量\\n // 并查集初始化\\n void init() {\\n for (int i = 1; i <= n; ++i) {\\n father[i] = i;\\n }\\n }\\n // 并查集里寻根的过程\\n int find(int u) {\\n return u == father[u] ? u : father[u] = find(father[u]);\\n }\\n // 将v->u 这条边加入并查集\\n void join(int u, int v) {\\n u = find(u);\\n v = find(v);\\n if (u == v) return ;\\n father[v] = u;\\n }\\n // 判断 u 和 v是否找到同一个根\\n bool same(int u, int v) {\\n u = find(u);\\n v = find(v);\\n return u == v;\\n }\\n // 在有向图里找到删除的那条边,使其变成树\\n vector<int> getRemoveEdge(const vector<vector<int>>& edges) {\\n init(); // 初始化并查集\\n for (int i = 0; i < n; i++) { // 遍历所有的边\\n if (same(edges[i][0], edges[i][1])) { // 构成有向环了,就是要删除的边\\n return edges[i];\\n }\\n join(edges[i][0], edges[i][1]);\\n }\\n return {};\\n }\\n\\n // 删一条边之后判断是不是树\\n bool isTreeAfterRemoveEdge(const vector<vector<int>>& edges, int deleteEdge) {\\n init(); // 初始化并查集\\n for (int i = 0; i < n; i++) {\\n if (i == deleteEdge) continue;\\n if (same(edges[i][0], edges[i][1])) { // 构成有向环了,一定不是树\\n return false;\\n }\\n join(edges[i][0], edges[i][1]);\\n }\\n return true;\\n }\\npublic:\\n\\n vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {\\n int inDegree[N] = {0}; // 记录节点入度\\n n = edges.size(); // 边的数量\\n for (int i = 0; i < n; i++) {\\n inDegree[edges[i][1]]++; // 统计入度\\n }\\n vector<int> vec; // 记录入度为2的边(如果有的话就两条边)\\n // 找入度为2的节点所对应的边,注意要倒叙,因为优先返回最后出现在二维数组中的答案\\n for (int i = n - 1; i >= 0; i--) {\\n if (inDegree[edges[i][1]] == 2) {\\n vec.push_back(i);\\n }\\n }\\n // 处理图中情况1 和 情况2\\n // 如果有入度为2的节点,那么一定是两条边里删一个,看删哪个可以构成树\\n if (vec.size() > 0) {\\n if (isTreeAfterRemoveEdge(edges, vec[0])) {\\n return edges[vec[0]];\\n } else {\\n return edges[vec[1]];\\n }\\n }\\n // 处理图中情况3\\n // 明确没有入度为2的情况,那么一定有有向环,找到构成环的边返回就可以了\\n return getRemoveEdge(edges);\\n\\n }\\n};\\n
说明:这个问题我的解法接近于暴力解法,不是最优解,大家看看就行。(2020 年 9 月 30 日)
\\n
\\n这个问题与第 684 题的区别是:
\\n\\n
\\n- 第 684 题基于无向图,在无向图中判断是否有环,很容易想到可以使用 并查集;
\\n- 第 685 题基于 有向图,在有向图中判断是是否有环,需要使用拓扑排序(「力扣」第 207 题、第 210 题,思想:贪心算法、BFS,概念:结点的度)。
\\n当前这个问题(第 685 题)需要我们返回多余的一条边。拓扑排序主要回答拓扑序,顺便回答了图中是否有环,对于这个问题来说,使用拓扑排序找到多余的一条边是相对麻烦的。但是拓扑排序中的重要概念 结点的入度 可以帮助我们解决这个问题。
\\n
\\n什么是有根树
\\n有根树的定义非常重要,题目说:
\\n\\n\\n有根树指满足以下条件的 有向图。该树 只有 一个根结点,所有其他结点都是该根结点的后继 。每一个结点 只有 一个父结点,除了根结点没有父结点。
\\n树区别与图的特点是:没有环(不论是对于有向边还是无向边)。
\\n\\n
由此,我们可以归纳出,有根树的特点:
\\n\\n
\\n- 只有唯一的一个入度为 $0$ 的结点,它是根结点;
\\n- 不是根结点的其它所有的结点入度为 $1$;
\\n- 不可能存在入度为 $2$ 的结点。
\\n结合示例分析
\\n\\n
根据示例 1 ,我们知道,不能有入度为 $2$ 的结点;
\\n
\\n根据示例 2 ,我们知道,在不能有入度为 $2$ 的结点的前提下,不能形成回路。为此设计算法如下:
\\n\\n
\\n- 先统计每一个结点的入度,如果有入度为 $2$ 的结点,考虑删除一条边(根据题目意思,删除的是输入的边的列表中最后出现的),剩下的 有向边 是否形成回路(形成环)。如果不能形成环,就应该删除这条边;
\\n- 在没有如果有入度为 $2$ 的结点的前提下,尝试删除形成入度为 $1$ 的 有向边 (不能删除入度为 $0$ 的有向边),判断剩下的 有向边 是否形成环。
\\n说明:在没有入度为 $2$ 的结点的情况下(结合示例 2 来理解),判断有向图是否形成回路,可以把有向图当成无向图来看,因此可以使用并查集。
\\n参考代码:
\\n编码说明:
\\n\\n
\\n- 这个问题里,题目输入的边的条数等于结点的个数,因为就是刚刚好多了 $1$ 条边,题目才让我们删,$3$ 个顶点的有根树只可能有 $2$ 条边,注意代码中 $+1$ 是因为从 $1$ 开始计算;
\\n- 题目要求我们,有多个结果的时候,返回
\\nedges
里最后出现的边,因此 从后向前遍历,删除某条边的意思是:不把它加入并查集。###Java
\\n\\nimport java.util.Arrays;\\n\\npublic class Solution {\\n\\n public int[] findRedundantDirectedConnection(int[][] edges) {\\n // 边的条数(在这个问题里等于结点个数)\\n int len = edges.length;\\n // 步骤 1:预处理入度数组(记录指向某个结点的边的条数)\\n int[] inDegree = new int[len + 1];\\n for (int[] edge : edges) {\\n inDegree[edge[1]]++;\\n }\\n\\n // 步骤 2:先尝试删除构成入度为 2 的边,看看是否形成环\\n for (int i = len - 1; i >= 0; i--) {\\n if (inDegree[edges[i][1]] == 2) {\\n // 如果不构成环,这条边就是要去掉的那条边\\n if (!judgeCircle(edges, len, i)) {\\n return edges[i];\\n }\\n }\\n }\\n\\n // 步骤 3:再尝试删除构成入度为 1 的边,看看是否形成环\\n for (int i = len - 1; i >= 0; i--) {\\n if (inDegree[edges[i][1]] == 1) {\\n // 如果不构成环,这条边就是要去掉的那条边\\n if (!judgeCircle(edges, len, i)) {\\n return edges[i];\\n }\\n }\\n }\\n throw new IllegalArgumentException(\\"输入不符合要求。\\");\\n }\\n\\n /**\\n * 将 removeEdgeIndex 去掉以后,剩下的有向边是否构成环\\n *\\n * @param edges\\n * @param len 结点总数(从 1 开始,因此初始化的时候 + 1)\\n * @param removeEdgeIndex 删除的边的下标\\n * @return 构成环,返回 true\\n */\\n private boolean judgeCircle(int[][] edges, int len, int removeEdgeIndex) {\\n UnionFind unionFind = new UnionFind(len + 1);\\n for (int i = 0; i < len; i++) {\\n if (i == removeEdgeIndex) {\\n continue;\\n }\\n if (!unionFind.union(edges[i][0], edges[i][1])) {\\n // 合并失败,表示 edges[i][0] 和 edges[i][1] 在一个连通分量里,即构成了环\\n return true;\\n }\\n }\\n return false;\\n }\\n\\n private class UnionFind {\\n // 代表元法\\n private int[] parent;\\n\\n public UnionFind(int n) {\\n parent = new int[n];\\n for (int i = 0; i < n; i++) {\\n parent[i] = i;\\n }\\n }\\n\\n public int find(int x) {\\n while (x != parent[x]) {\\n // 路径压缩(隔代压缩)\\n parent[x] = parent[parent[x]];\\n x = parent[x];\\n }\\n return x;\\n }\\n\\n /**\\n * @param x\\n * @param y\\n * @return 如果合并成功返回 true\\n */\\n public boolean union(int x, int y) {\\n int rootX = find(x);\\n int rootY = find(y);\\n\\n if (rootX == rootY) {\\n return false;\\n }\\n parent[rootX] = rootY;\\n return true;\\n }\\n }\\n}\\n
复杂度分析:
\\n(并查集的复杂度分析可能有误,欢迎指正。)
\\n\\n
\\n","description":"说明:这个问题我的解法接近于暴力解法,不是最优解,大家看看就行。(2020 年 9 月 30 日) 这个问题与第 684 题的区别是:\\n\\n第 684 题基于无向图,在无向图中判断是否有环,很容易想到可以使用 并查集;\\n第 685 题基于 有向图,在有向图中判断是是否有环,需要使用拓扑排序(「力扣」第 207 题、第 210 题,思想:贪心算法、BFS,概念:结点的度)。\\n\\n当前这个问题(第 685 题)需要我们返回多余的一条边。拓扑排序主要回答拓扑序,顺便回答了图中是否有环,对于这个问题来说,使用拓扑排序找到多余的一条边是相对麻烦的…","guid":"https://leetcode.cn/problems/redundant-connection-ii//solution/bing-cha-ji-java-by-liweiwei1419","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-09-17T04:13:57.850Z","media":[{"url":"https://pic.leetcode-cn.com/1600314313-OyFTez-image.png","type":"photo","width":2086,"height":948,"blurhash":"LTR:B4.7?Zxv?wjEngWrwGbbR.WC"},{"url":"https://pic.leetcode-cn.com/1600315175-HUIAGn-image.png","type":"photo","width":1834,"height":876,"blurhash":"LKR{#^?b-:-q_Na~R*WBoZRjV]t7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"冗余连接 II","url":"https://leetcode.cn/problems/redundant-connection-ii//solution/rong-yu-lian-jie-ii-by-leetcode-solution","content":"- 时间复杂度:$O(N^2 \\\\log N)$,这里 $N$ 是边的条数(结点的个数),
\\njudgeCircle()
方法里,union
的时间复杂度为 $O(\\\\log N)$,最坏情况下,对于每条边都执行了judgeCircle()
方法(有 2 个for
循环)。因此时间复杂度为 $O(N^2 \\\\log N)$;- 空间复杂度:$O(N)$,入度数组和并查集底层数组的长度。
\\n方法一:并查集
\\n思路与算法
\\n在一棵树中,边的数量比节点的数量少 $1$。如果一棵树有 $n$ 个节点,则这棵树有 $n-1$ 条边。这道题中的图在树的基础上多了一条附加的边,因此边的数量也是 $n$。
\\n树中的每个节点都有一个父节点,除了根节点没有父节点。在多了一条附加的边之后,可能有以下两种情况:
\\n\\n
\\n- \\n
\\n附加的边指向根节点,则包括根节点在内的每个节点都有一个父节点,此时图中一定有环路;
\\n- \\n
\\n附加的边指向非根节点,则恰好有一个节点(即被附加的边指向的节点)有两个父节点,此时图中可能有环路也可能没有环路。
\\n要找到附加的边,需要遍历图中的所有的边构建出一棵树,在构建树的过程中寻找导致冲突(即导致一个节点有两个父节点)的边以及导致环路出现的边。
\\n具体做法是,使用数组 $\\\\textit{parent}$ 记录每个节点的父节点,初始时对于任何 $1 \\\\le i \\\\le n$ 都有 $\\\\textit{parent}[i]=i$,另外创建并查集,初始时并查集中的每个节点都是一个连通分支,该连通分支的根节点就是该节点本身。遍历每条边的过程中,维护导致冲突的边和导致环路出现的边,由于只有一条附加的边,因此最多有一条导致冲突的边和一条导致环路出现的边。
\\n当访问到边 $[u,v]$ 时,进行如下操作:
\\n\\n
\\n- \\n
\\n如果此时已经有 $\\\\textit{parent}[v] \\\\ne v$,说明 $v$ 有两个父节点,将当前的边 $[u,v]$ 记为导致冲突的边;
\\n- \\n
\\n否则,令 $\\\\textit{parent}[v] = u$,然后在并查集中分别找到 $u$ 和 $v$ 的祖先(即各自的连通分支中的根节点),如果祖先相同,说明这条边导致环路出现,将当前的边 $[u,v]$ 记为导致环路出现的边,如果祖先不同,则在并查集中将 $u$ 和 $v$ 进行合并。
\\n根据上述操作,同一条边不可能同时被记为导致冲突的边和导致环路出现的边。如果访问到的边确实同时导致冲突和环路出现,则这条边被记为导致冲突的边。
\\n在遍历图中的所有边之后,根据是否存在导致冲突的边和导致环路出现的边,得到附加的边。
\\n如果没有导致冲突的边,说明附加的边一定导致环路出现,而且是在环路中的最后一条被访问到的边,因此附加的边即为导致环路出现的边。
\\n如果有导致冲突的边,记这条边为 $[u,v]$,则有两条边指向 $v$,另一条边为 $[\\\\textit{parent}[v],v]$,需要通过判断是否有导致环路的边决定哪条边是附加的边。
\\n\\n
\\n- \\n
\\n如果有导致环路的边,则附加的边不可能是 $[u,v]$(因为 $[u,v]$ 已经被记为导致冲突的边,不可能被记为导致环路出现的边),因此附加的边是 $[\\\\textit{parent}[v],v]$。
\\n- \\n
\\n如果没有导致环路的边,则附加的边是后被访问到的指向 $v$ 的边,因此附加的边是 $[u,v]$。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public int[] findRedundantDirectedConnection(int[][] edges) {\\n int n = edges.length;\\n UnionFind uf = new UnionFind(n + 1);\\n int[] parent = new int[n + 1];\\n for (int i = 1; i <= n; ++i) {\\n parent[i] = i;\\n }\\n int conflict = -1;\\n int cycle = -1;\\n for (int i = 0; i < n; ++i) {\\n int[] edge = edges[i];\\n int node1 = edge[0], node2 = edge[1];\\n if (parent[node2] != node2) {\\n conflict = i;\\n } else {\\n parent[node2] = node1;\\n if (uf.find(node1) == uf.find(node2)) {\\n cycle = i;\\n } else {\\n uf.union(node1, node2);\\n }\\n }\\n }\\n if (conflict < 0) {\\n int[] redundant = {edges[cycle][0], edges[cycle][1]};\\n return redundant;\\n } else {\\n int[] conflictEdge = edges[conflict];\\n if (cycle >= 0) {\\n int[] redundant = {parent[conflictEdge[1]], conflictEdge[1]};\\n return redundant;\\n } else {\\n int[] redundant = {conflictEdge[0], conflictEdge[1]};\\n return redundant;\\n }\\n }\\n }\\n}\\n\\nclass UnionFind {\\n int[] ancestor;\\n\\n public UnionFind(int n) {\\n ancestor = new int[n];\\n for (int i = 0; i < n; ++i) {\\n ancestor[i] = i;\\n }\\n }\\n\\n public void union(int index1, int index2) {\\n ancestor[find(index1)] = find(index2);\\n }\\n\\n public int find(int index) {\\n if (ancestor[index] != index) {\\n ancestor[index] = find(ancestor[index]);\\n }\\n return ancestor[index];\\n }\\n}\\n
###Golang
\\n\\nfunc findRedundantDirectedConnection(edges [][]int) (redundantEdge []int) {\\n n := len(edges)\\n uf := newUnionFind(n + 1)\\n parent := make([]int, n+1) // parent[i] 表示 i 的父节点\\n for i := range parent {\\n parent[i] = i\\n }\\n\\n var conflictEdge, cycleEdge []int\\n for _, edge := range edges {\\n from, to := edge[0], edge[1]\\n if parent[to] != to { // to 有两个父节点\\n conflictEdge = edge\\n } else {\\n parent[to] = from\\n if uf.find(from) == uf.find(to) { // from 和 to 已连接\\n cycleEdge = edge\\n } else {\\n uf.union(from, to)\\n }\\n }\\n }\\n\\n // 若不存在一个节点有两个父节点的情况,则附加的边一定导致环路出现\\n if conflictEdge == nil {\\n return cycleEdge\\n }\\n // conflictEdge[1] 有两个父节点,其中之一与其构成附加的边\\n // 由于我们是按照 edges 的顺序连接的,若在访问到 conflictEdge 之前已经形成了环路,则附加的边在环上\\n // 否则附加的边就是 conflictEdge\\n if cycleEdge != nil {\\n return []int{parent[conflictEdge[1]], conflictEdge[1]}\\n }\\n return conflictEdge\\n}\\n\\ntype unionFind struct {\\n ancestor []int\\n}\\n\\nfunc newUnionFind(n int) unionFind {\\n ancestor := make([]int, n)\\n for i := 0; i < n; i++ {\\n ancestor[i] = i\\n }\\n return unionFind{ancestor}\\n}\\n\\nfunc (uf unionFind) find(x int) int {\\n if uf.ancestor[x] != x {\\n uf.ancestor[x] = uf.find(uf.ancestor[x])\\n }\\n return uf.ancestor[x]\\n}\\n\\nfunc (uf unionFind) union(from, to int) {\\n uf.ancestor[uf.find(from)] = uf.find(to)\\n}\\n
###cpp
\\n\\nstruct UnionFind {\\n vector <int> ancestor;\\n\\n UnionFind(int n) {\\n ancestor.resize(n);\\n for (int i = 0; i < n; ++i) {\\n ancestor[i] = i;\\n }\\n }\\n\\n int find(int index) {\\n return index == ancestor[index] ? index : ancestor[index] = find(ancestor[index]);\\n }\\n\\n void merge(int u, int v) {\\n ancestor[find(u)] = find(v);\\n }\\n};\\n\\nclass Solution {\\npublic:\\n vector<int> findRedundantDirectedConnection(vector<vector<int>>& edges) {\\n int n = edges.size();\\n UnionFind uf = UnionFind(n + 1);\\n auto parent = vector<int>(n + 1);\\n for (int i = 1; i <= n; ++i) {\\n parent[i] = i;\\n }\\n int conflict = -1;\\n int cycle = -1;\\n for (int i = 0; i < n; ++i) {\\n auto edge = edges[i];\\n int node1 = edge[0], node2 = edge[1];\\n if (parent[node2] != node2) {\\n conflict = i;\\n } else {\\n parent[node2] = node1;\\n if (uf.find(node1) == uf.find(node2)) {\\n cycle = i;\\n } else {\\n uf.merge(node1, node2);\\n }\\n }\\n }\\n if (conflict < 0) {\\n auto redundant = vector<int> {edges[cycle][0], edges[cycle][1]};\\n return redundant;\\n } else {\\n auto conflictEdge = edges[conflict];\\n if (cycle >= 0) {\\n auto redundant = vector<int> {parent[conflictEdge[1]], conflictEdge[1]};\\n return redundant;\\n } else {\\n auto redundant = vector<int> {conflictEdge[0], conflictEdge[1]};\\n return redundant;\\n }\\n }\\n }\\n};\\n
###Python
\\n\\nclass UnionFind:\\n def __init__(self, n):\\n self.ancestor = list(range(n))\\n \\n def union(self, index1: int, index2: int):\\n self.ancestor[self.find(index1)] = self.find(index2)\\n \\n def find(self, index: int) -> int:\\n if self.ancestor[index] != index:\\n self.ancestor[index] = self.find(self.ancestor[index])\\n return self.ancestor[index]\\n\\nclass Solution:\\n def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:\\n n = len(edges)\\n uf = UnionFind(n + 1)\\n parent = list(range(n + 1))\\n conflict = -1\\n cycle = -1\\n for i, (node1, node2) in enumerate(edges):\\n if parent[node2] != node2:\\n conflict = i\\n else:\\n parent[node2] = node1\\n if uf.find(node1) == uf.find(node2):\\n cycle = i\\n else:\\n uf.union(node1, node2)\\n\\n if conflict < 0:\\n return [edges[cycle][0], edges[cycle][1]]\\n else:\\n conflictEdge = edges[conflict]\\n if cycle >= 0:\\n return [parent[conflictEdge[1]], conflictEdge[1]]\\n else:\\n return [conflictEdge[0], conflictEdge[1]]\\n
###C
\\n\\nint* ancestor;\\n\\nint find(int index) {\\n return index == ancestor[index] ? index : (ancestor[index] = find(ancestor[index]));\\n}\\n\\nvoid merge(int u, int v) {\\n ancestor[find(u)] = find(v);\\n}\\n\\nint* findRedundantDirectedConnection(int** edges, int edgesSize, int* edgesColSize, int* returnSize) {\\n int n = edgesSize;\\n ancestor = malloc(sizeof(int) * (n + 1));\\n for (int i = 1; i <= n; ++i) {\\n ancestor[i] = i;\\n }\\n int parent[n + 1];\\n for (int i = 1; i <= n; ++i) {\\n parent[i] = i;\\n }\\n int conflict = -1;\\n int cycle = -1;\\n for (int i = 0; i < n; ++i) {\\n int node1 = edges[i][0], node2 = edges[i][1];\\n if (parent[node2] != node2) {\\n conflict = i;\\n } else {\\n parent[node2] = node1;\\n if (find(node1) == find(node2)) {\\n cycle = i;\\n } else {\\n merge(node1, node2);\\n }\\n }\\n }\\n int* redundant = malloc(sizeof(int) * 2);\\n *returnSize = 2;\\n if (conflict < 0) {\\n redundant[0] = edges[cycle][0], redundant[1] = edges[cycle][1];\\n return redundant;\\n } else {\\n int* conflictEdge = edges[conflict];\\n if (cycle >= 0) {\\n redundant[0] = parent[conflictEdge[1]], redundant[1] = conflictEdge[1];\\n return redundant;\\n } else {\\n redundant[0] = conflictEdge[0], redundant[1] = conflictEdge[1];\\n return redundant;\\n }\\n }\\n return redundant;\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:并查集 思路与算法\\n\\n在一棵树中,边的数量比节点的数量少 $1$。如果一棵树有 $n$ 个节点,则这棵树有 $n-1$ 条边。这道题中的图在树的基础上多了一条附加的边,因此边的数量也是 $n$。\\n\\n树中的每个节点都有一个父节点,除了根节点没有父节点。在多了一条附加的边之后,可能有以下两种情况:\\n\\n附加的边指向根节点,则包括根节点在内的每个节点都有一个父节点,此时图中一定有环路;\\n\\n附加的边指向非根节点,则恰好有一个节点(即被附加的边指向的节点)有两个父节点,此时图中可能有环路也可能没有环路。\\n\\n要找到附加的边,需要遍历图中的所有的边构建出一棵树…","guid":"https://leetcode.cn/problems/redundant-connection-ii//solution/rong-yu-lian-jie-ii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-09-16T15:17:45.439Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"你可以获得的最大硬币数目","url":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/ni-ke-yi-huo-de-de-zui-da-ying-bi-shu-mu-by-leetco","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是图中的节点个数。需要遍历图中的 $n$ 条边,对于每条边,需要对两个节点查找祖先,如果两个节点的祖先不同则需要进行合并,需要进行 $2$ 次查找和最多 $1$ 次合并。一共需要进行 $2n$ 次查找和最多 $n$ 次合并,因此总时间复杂度是 $O(2n \\\\log n)=O(n \\\\log n)$。这里的并查集使用了路径压缩,但是没有使用按秩合并,最坏情况下的时间复杂度是 $O(n \\\\log n)$,平均情况下的时间复杂度依然是 $O(n \\\\alpha (n))$,其中 $\\\\alpha$ 为阿克曼函数的反函数,$\\\\alpha (n)$ 可以认为是一个很小的常数。
\\n- \\n
\\n空间复杂度:$O(n)$,其中 $n$ 是图中的节点个数。使用数组 $\\\\textit{parent}$ 记录每个节点的父节点,并查集使用数组记录每个节点的祖先。
\\n方法一:贪心
\\n由于一共有 $3n$ 堆硬币,每个人都会取走 $n$ 堆硬币,因此在取走的硬币堆的数量确定的情况下,为了获得最大硬币数目,在每次取走一堆硬币时,应取走数量最多的一堆硬币。
\\n显然,$\\\\text{Alice}$ 一定会取走 $3n$ 堆硬币中数量最多的一堆硬币,在 $\\\\text{Alice}$ 取走数量最多的一堆硬币之后,我们可以取走数量第二多的一堆硬币。当 $n=1$ 时,一共有 $3$ 堆硬币,每个人都只能取走 $1$ 堆硬币,因此我们可以获得的最大硬币数目即为数量第二多的一堆硬币的数目。
\\n当 $n \\\\ge 2$ 时,如何获得最大硬币数目?和 $n = 1$ 的情况相似,第一轮时,$\\\\text{Alice}$ 取走数量最多的,我们取走数量第二多的,但不同的是我们不让 $\\\\text{Bob}$ 取走数量第三多的,我们让 $\\\\text{Bob}$ 取走数量最少的。第二轮时,应该让 $\\\\text{Alice}$ 取走数量第三多的一堆硬币,我们就取走数量第四多的一堆硬币,以此类推。试想如果我们在第一轮让 $\\\\text{Bob}$ 拿走了第三多的,按照这个策略我们只能在第二轮拿到数量第五多的,以此类推,这个策略不如前面的策略好。
\\n基于上述分析,可以看到,每一轮中,$\\\\text{Alice}$ 取数量最多的一堆硬币,我们取数量第二多的一堆硬币,可以让我们获得最大硬币数目。由于每一轮中要选出 $3$ 堆硬币,其中的最后一堆由 $\\\\text{Bob}$ 取走,为了不让 $\\\\text{Bob}$ 影响我们获得的最大硬币数目,只要让 $\\\\text{Bob}$ 每次取的硬币是所有堆中数量最少的即可。
\\n为了方便地知道每一堆硬币的数量之间的关系,首先对数组进行排序。排序后的数组的前 $n$ 个元素是最小的元素,留给 $\\\\text{Bob}$,其余的元素则分别属于我们和 $\\\\text{Alice}$。
\\n每一轮,我们选出 $3$ 堆硬币,包括数量最多的 $2$ 堆硬币和数量最少的 $1$ 堆硬币,我们总能获得这 $3$ 堆硬币中的数量第二多的硬币。
\\n计算可以获得的最大硬币数目时,按照从大到小的顺序遍历数组中的元素,每次遍历 $2$ 个元素,其中较小的元素即为这一轮取走的硬币数量。
\\n###Java
\\n\\nclass Solution {\\n public int maxCoins(int[] piles) {\\n Arrays.sort(piles);\\n int length = piles.length;\\n int rounds = length / 3;\\n int coins = 0;\\n int index = length - 2;\\n for (int i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n }\\n}\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n int maxCoins(vector<int>& piles) {\\n sort(piles.begin(), piles.end());\\n int length = piles.size();\\n int rounds = length / 3;\\n int coins = 0;\\n int index = length - 2;\\n for (int i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def maxCoins(self, piles: List[int]) -> int:\\n n = len(piles)\\n piles.sort()\\n return sum(piles[n // 3 :: 2])\\n
###C
\\n\\nint compare(const void* a, const void* b) {\\n return (*(int*)a - *(int*)b);\\n}\\n\\nint maxCoins(int* piles, int pilesSize) {\\n qsort(piles, pilesSize, sizeof(int), compare);\\n int rounds = pilesSize / 3;\\n int coins = 0;\\n int index = pilesSize - 2;\\n for (int i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n}\\n
###Go
\\n\\nfunc maxCoins(piles []int) int {\\n sort.Ints(piles)\\n length := len(piles)\\n rounds := length / 3\\n coins := 0\\n index := length - 2\\n for i := 0; i < rounds; i++ {\\n coins += piles[index]\\n index -= 2\\n }\\n return coins\\n}\\n
###JavaScript
\\n\\nvar maxCoins = function(piles) {\\n piles.sort((a, b) => a - b);\\n let length = piles.length;\\n let rounds = Math.floor(length / 3);\\n let coins = 0;\\n let index = length - 2;\\n for (let i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n};\\n
###TypeScript
\\n\\nfunction maxCoins(piles: number[]): number {\\n piles.sort((a, b) => a - b);\\n let length = piles.length;\\n let rounds = Math.floor(length / 3);\\n let coins = 0;\\n let index = length - 2;\\n for (let i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxCoins(int[] piles) {\\n Array.Sort(piles);\\n int length = piles.Length;\\n int rounds = length / 3;\\n int coins = 0;\\n int index = length - 2;\\n for (int i = 0; i < rounds; i++) {\\n coins += piles[index];\\n index -= 2;\\n }\\n return coins;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn max_coins(piles: Vec<i32>) -> i32 {\\n let mut piles = piles;\\n piles.sort();\\n let length = piles.len();\\n let rounds = length / 3;\\n let mut coins = 0;\\n let mut index = length - 2;\\n for _ in 0..rounds {\\n coins += piles[index];\\n index -= 2;\\n }\\n coins\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心 由于一共有 $3n$ 堆硬币,每个人都会取走 $n$ 堆硬币,因此在取走的硬币堆的数量确定的情况下,为了获得最大硬币数目,在每次取走一堆硬币时,应取走数量最多的一堆硬币。\\n\\n显然,$\\\\text{Alice}$ 一定会取走 $3n$ 堆硬币中数量最多的一堆硬币,在 $\\\\text{Alice}$ 取走数量最多的一堆硬币之后,我们可以取走数量第二多的一堆硬币。当 $n=1$ 时,一共有 $3$ 堆硬币,每个人都只能取走 $1$ 堆硬币,因此我们可以获得的最大硬币数目即为数量第二多的一堆硬币的数目。\\n\\n当 $n \\\\ge 2$ 时,如何获得最大硬币数目…","guid":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/ni-ke-yi-huo-de-de-zui-da-ying-bi-shu-mu-by-leetco","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-09-10T14:43:09.926Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两球之间的磁力","url":"https://leetcode.cn/problems/magnetic-force-between-two-balls//solution/liang-qiu-zhi-jian-de-ci-li-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(n \\\\log n)$,其中 $n$ 是数组的长度除以 $3$(即数组的长度是 $3n$)。排序的时间复杂度是 $O(3n \\\\log 3n)=O(3n (\\\\log 3 + \\\\log n))=O(n \\\\log n)$,遍历数组计算最大硬币数目的时间复杂度是 $O(n)$,因此总时间复杂度是 $O(n \\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$。C++ 的
\\nsort
整合了快排、堆排和插入排序三种方法,这里递归使用栈空间的大小可以认为是 $O(\\\\log n)$。方法一:二分查找
\\n思路与算法
\\n对于此题我们需要先思考一个子问题:给定 $n$ 个空篮子,$m$ 个球放置的位置已经确定。那么「最小磁力」我们该如何计算?
\\n不难得出「最小磁力」为这 $m$ 个球中相邻两球距离的最小值的结论。对于 $i<j<k$ 三个位置的球,最小磁力一定是 $j-i$ 和 $k-j$ 的较小值,而不是跨越了位置 $j$ 的 $i$ 和 $k$ 的差值 $k-i$。
\\n明确了给定位置最小磁力的计算方法,回到本题,在本题中 $m$ 个球的位置是由我们决定的,只知道空篮子的位置,且题目希望通过排列 $m$ 个球的位置来「最大化最小磁力」。
\\n我们假定最终的答案是 $\\\\textit{ans}$,即这个时候最小磁力为 $\\\\textit{ans}$,那么我们知道小于 $\\\\textit{ans}$ 的答案一定也合法。因为既然我们存在一种放置的方法使得相邻小球间距的最小值大于等于 $\\\\textit{ans}$,那么也一定大于 $[1,\\\\textit{ans} - 1]$ 中的任意一个值,而大于 $\\\\textit{ans}$ 的均不合法,因此我们可以对答案进行二分查找。
\\n假设我们在 $[\\\\textit{left},\\\\textit{right}]$ 的区间查找。每次取 $\\\\textit{mid}$ 为 $\\\\textit{left}$ 和 $\\\\textit{right}$ 的平均值,进行如下操作:
\\n\\n
\\n- 如果当前的 $\\\\textit{mid}$ 合法,则令 $\\\\textit{ans}=\\\\textit{mid}$,并将区间缩小为 $[\\\\textit{mid}+1,\\\\textit{right}]$;
\\n- 如果当前的 $\\\\textit{mid}$ 不合法,则将区间缩小为 $[\\\\textit{left},\\\\textit{mid}-1]$。
\\n最后剩下的问题是如何判断答案是否合法,即给定一个答案 $x$,是否存在一种放置方法使得相邻小球的间距最小值大于等于 $x$。这个问题其实很好解决,相邻小球的间距最小值大于等于 $x$,其实就等价于相邻小球的间距均大于等于 $x$。我们预先对给定的篮子的位置进行排序,那么从贪心的角度考虑,第一个小球放置的篮子一定是 $\\\\textit{position}$ 最小的篮子,即排序后的第一个篮子。那么为了满足上述条件,第二个小球放置的位置一定要大于等于 $\\\\textit{position}[0]+x$,接下来同理。因此我们从前往后扫 $\\\\textit{position}$ 数组,看在当前答案 $x$ 下我们最多能在篮子里放多少个小球,我们记这个数量为 $\\\\textit{cnt}$,如果 $\\\\textit{cnt}$ 大于等于 $m$,那么说明当前答案下我们的贪心策略能放下 $m$ 个小球且它们间距均大于等于 $x$ ,为合法的答案,否则不合法。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n bool check(int x, vector<int>& position, int m) {\\n int pre = position[0], cnt = 1;\\n for (int i = 1; i < position.size(); ++i) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt += 1;\\n }\\n }\\n return cnt >= m;\\n }\\n\\n int maxDistance(vector<int>& position, int m) {\\n sort(position.begin(), position.end());\\n int left = 1, right = position.back() - position[0], ans = -1;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid, position, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int maxDistance(int[] position, int m) {\\n Arrays.sort(position);\\n int left = 1, right = position[position.length - 1] - position[0], ans = -1;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid, position, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n\\n public boolean check(int x, int[] position, int m) {\\n int pre = position[0], cnt = 1;\\n for (int i = 1; i < position.length; ++i) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt += 1;\\n }\\n }\\n return cnt >= m;\\n }\\n}\\n
###JavaScript
\\n\\nconst check = (x, position, m) => {\\n let pre = position[0], cnt = 1;\\n for (let i = 1; i < position.length; ++i) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt += 1;\\n }\\n }\\n return cnt >= m;\\n}\\nvar maxDistance = function(position, m) {\\n position.sort((x, y) => x - y);\\n let left = 1, right = position[position.length - 1] - position[0], ans = -1;\\n while (left <= right) {\\n const mid = Math.floor((left + right) / 2); \\n if (check(mid, position, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n};\\n
###Python
\\n\\nclass Solution:\\n def maxDistance(self, position: List[int], m: int) -> int:\\n def check(x: int) -> bool:\\n pre = position[0]\\n cnt = 1\\n for i in range(1, len(position)):\\n if position[i] - pre >= x:\\n pre = position[i]\\n cnt += 1\\n return cnt >= m\\n\\n position.sort()\\n left, right, ans = 1, position[-1] - position[0], -1\\n while left <= right:\\n mid = (left + right) // 2;\\n if check(mid):\\n ans = mid\\n left = mid + 1\\n else:\\n right = mid - 1\\n \\n return ans\\n
###C#
\\n\\npublic class Solution {\\n public int MaxDistance(int[] position, int m) {\\n Array.Sort(position);\\n int left = 1, right = position[position.Length - 1] - position[0], ans = -1;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (Check(mid, position, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n }\\n\\n public bool Check(int x, int[] position, int m) {\\n int pre = position[0], cnt = 1;\\n for (int i = 1; i < position.Length; i++) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt++;\\n }\\n }\\n return cnt >= m;\\n }\\n}\\n
###Go
\\n\\nfunc maxDistance(position []int, m int) int {\\n sort.Ints(position)\\n left, right := 1, position[len(position)-1] - position[0]\\n ans := -1\\n for left <= right {\\n mid := (left + right) / 2\\n if check(mid, position, m) {\\n ans = mid\\n left = mid + 1\\n } else {\\n right = mid - 1\\n }\\n }\\n return ans\\n}\\n\\nfunc check(x int, position []int, m int) bool {\\n pre, cnt := position[0], 1\\n for i := 1; i < len(position); i++ {\\n if position[i] - pre >= x {\\n pre = position[i]\\n cnt++\\n }\\n }\\n return cnt >= m\\n}\\n
###C
\\n\\nstatic int compare(const void *a, const void *b) {\\n return *(int *)a - *(int *)b;\\n}\\n\\nstatic bool check(int x, const int* position, int positionSize, int m) {\\n int pre = position[0], cnt = 1;\\n for (int i = 1; i < positionSize; ++i) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt += 1;\\n }\\n }\\n return cnt >= m;\\n}\\n\\nint maxDistance(int* position, int positionSize, int m) {\\n qsort(position, positionSize, sizeof(int), compare);\\n int left = 1, right = position[positionSize - 1] - position[0], ans = -1;\\n while (left <= right) {\\n int mid = (left + right) / 2;\\n if (check(mid, position, positionSize, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n}\\n
###TypeScript
\\n\\nfunction maxDistance(position: number[], m: number): number {\\n position.sort((x, y) => x - y);\\n let left = 1, right = position[position.length - 1] - position[0], ans = -1;\\n while (left <= right) {\\n const mid = Math.floor((left + right) / 2); \\n if (check(mid, position, m)) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n return ans;\\n};\\n\\nfunction check(x: number, position: number[], m: number): boolean {\\n let pre = position[0], cnt = 1;\\n for (let i = 1; i < position.length; ++i) {\\n if (position[i] - pre >= x) {\\n pre = position[i];\\n cnt += 1;\\n }\\n }\\n return cnt >= m;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn max_distance(position: Vec<i32>, m: i32) -> i32 {\\n let mut position = position;\\n position.sort();\\n let mut left = 1;\\n let mut right = position[position.len() - 1] - position[0];\\n let mut ans = -1;\\n while left <= right {\\n let mid = (left + right) / 2;\\n if Self::check(mid, &position, m) {\\n ans = mid;\\n left = mid + 1;\\n } else {\\n right = mid - 1;\\n }\\n }\\n ans\\n }\\n\\n fn check(x: i32, position: &Vec<i32>, m: i32) -> bool {\\n let mut pre = position[0];\\n let mut cnt = 1;\\n for &pos in &position[1..] {\\n if pos - pre >= x {\\n pre = pos;\\n cnt += 1;\\n }\\n }\\n cnt >= m\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:二分查找 思路与算法\\n\\n对于此题我们需要先思考一个子问题:给定 $n$ 个空篮子,$m$ 个球放置的位置已经确定。那么「最小磁力」我们该如何计算?\\n\\n不难得出「最小磁力」为这 $m$ 个球中相邻两球距离的最小值的结论。对于 $i- \\n
\\n时间复杂度:$O(n\\\\log (nS))$,其中 $n$ 为篮子的个数,$S$ 为篮子位置的上限。对篮子位置排序需要 $O(n\\\\log n)$ 的时间复杂度,二分查找对篮子位置间隔进行二分,需要 $O(\\\\log S)$ 的时间复杂度。每次统计答案是否符合要求需要 $O(n)$ 的时间复杂度,因此总时间复杂度为 $O(n\\\\log n+n\\\\log S) = O(n\\\\log (nS))$。
\\n- \\n
\\n空间复杂度:$O(\\\\log n)$,即为排序需要的栈空间。
\\n前言\\n 「$N$ 皇后问题」研究的是如何将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上,并且使皇后彼此之间不能相互攻击。
\\n皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。
\\n直观的做法是暴力枚举将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。
\\n显然,每个皇后必须位于不同行和不同列,因此将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上,一定是每一行有且仅有一个皇后,每一列有且仅有一个皇后,且任何两个皇后都不能在同一条斜线上。基于上述发现,可以通过回溯的方式寻找可能的解。
\\n回溯的具体做法是:使用一个数组记录每行放置的皇后的列下标,依次在每一行放置一个皇后。每次新放置的皇后都不能和已经放置的皇后之间有攻击:即新放置的皇后不能和任何一个已经放置的皇后在同一列以及同一条斜线上,并更新数组中的当前行的皇后列下标。当 $N$ 个皇后都放置完毕,则找到一个可能的解。当找到一个可能的解之后,将数组转换成表示棋盘状态的列表,并将该棋盘状态的列表加入返回列表。
\\n由于每个皇后必须位于不同列,因此已经放置的皇后所在的列不能放置别的皇后。第一个皇后有 $N$ 列可以选择,第二个皇后最多有 $N-1$ 列可以选择,第三个皇后最多有 $N-2$ 列可以选择(如果考虑到不能在同一条斜线上,可能的选择数量更少),因此所有可能的情况不会超过 $N!$ 种,遍历这些情况的时间复杂度是 $O(N!)$。
\\n为了降低总时间复杂度,每次放置皇后时需要快速判断每个位置是否可以放置皇后,显然,最理想的情况是在 $O(1)$ 的时间内判断该位置所在的列和两条斜线上是否已经有皇后。
\\n以下两种方法分别使用集合和位运算对皇后的放置位置进行判断,都可以在 $O(1)$ 的时间内判断一个位置是否可以放置皇后,算法的总时间复杂度都是 $O(N!)$。
\\n方法一:基于集合的回溯
\\n为了判断一个位置所在的列和两条斜线上是否已经有皇后,使用三个集合 $\\\\textit{columns}$、$\\\\textit{diagonals}_1$ 和 $\\\\textit{diagonals}_2$ 分别记录每一列以及两个方向的每条斜线上是否有皇后。
\\n列的表示法很直观,一共有 $N$ 列,每一列的下标范围从 $0$ 到 $N-1$,使用列的下标即可明确表示每一列。
\\n如何表示两个方向的斜线呢?对于每个方向的斜线,需要找到斜线上的每个位置的行下标与列下标之间的关系。
\\n方向一的斜线为从左上到右下方向,同一条斜线上的每个位置满足行下标与列下标之差相等,例如 $(0,0)$ 和 $(3,3)$ 在同一条方向一的斜线上。因此使用行下标与列下标之差即可明确表示每一条方向一的斜线。
\\n\\n
方向二的斜线为从右上到左下方向,同一条斜线上的每个位置满足行下标与列下标之和相等,例如 $(3,0)$ 和 $(1,2)$ 在同一条方向二的斜线上。因此使用行下标与列下标之和即可明确表示每一条方向二的斜线。
\\n\\n
每次放置皇后时,对于每个位置判断其是否在三个集合中,如果三个集合都不包含当前位置,则当前位置是可以放置皇后的位置。
\\n###Java
\\n\\nclass Solution {\\n public List<List<String>> solveNQueens(int n) {\\n List<List<String>> solutions = new ArrayList<List<String>>();\\n int[] queens = new int[n];\\n Arrays.fill(queens, -1);\\n Set<Integer> columns = new HashSet<Integer>();\\n Set<Integer> diagonals1 = new HashSet<Integer>();\\n Set<Integer> diagonals2 = new HashSet<Integer>();\\n backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);\\n return solutions;\\n }\\n\\n public void backtrack(List<List<String>> solutions, int[] queens, int n, int row, Set<Integer> columns, Set<Integer> diagonals1, Set<Integer> diagonals2) {\\n if (row == n) {\\n List<String> board = generateBoard(queens, n);\\n solutions.add(board);\\n } else {\\n for (int i = 0; i < n; i++) {\\n if (columns.contains(i)) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.contains(diagonal1)) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.contains(diagonal2)) {\\n continue;\\n }\\n queens[row] = i;\\n columns.add(i);\\n diagonals1.add(diagonal1);\\n diagonals2.add(diagonal2);\\n backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);\\n queens[row] = -1;\\n columns.remove(i);\\n diagonals1.remove(diagonal1);\\n diagonals2.remove(diagonal2);\\n }\\n }\\n }\\n\\n public List<String> generateBoard(int[] queens, int n) {\\n List<String> board = new ArrayList<String>();\\n for (int i = 0; i < n; i++) {\\n char[] row = new char[n];\\n Arrays.fill(row, \'.\');\\n row[queens[i]] = \'Q\';\\n board.add(new String(row));\\n }\\n return board;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<IList<string>> SolveNQueens(int n) {\\n IList<IList<string>> solutions = new List<IList<string>>();\\n int[] queens = new int[n];\\n Array.Fill(queens, -1);\\n HashSet<int> columns = new HashSet<int>();\\n HashSet<int> diagonals1 = new HashSet<int>();\\n HashSet<int> diagonals2 = new HashSet<int>();\\n Backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);\\n return solutions;\\n }\\n\\n private void Backtrack(IList<IList<string>> solutions, int[] queens, int n, int row, HashSet<int> columns, HashSet<int> diagonals1, HashSet<int> diagonals2) {\\n if (row == n) {\\n var board = GenerateBoard(queens, n);\\n solutions.Add(board);\\n } else {\\n for (int i = 0; i < n; i++) {\\n if (columns.Contains(i)) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.Contains(diagonal1)) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.Contains(diagonal2)) {\\n continue;\\n }\\n queens[row] = i;\\n columns.Add(i);\\n diagonals1.Add(diagonal1);\\n diagonals2.Add(diagonal2);\\n Backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);\\n queens[row] = -1;\\n columns.Remove(i);\\n diagonals1.Remove(diagonal1);\\n diagonals2.Remove(diagonal2);\\n }\\n }\\n }\\n\\n private IList<string> GenerateBoard(int[] queens, int n) {\\n IList<string> board = new List<string>();\\n for (int i = 0; i < n; i++) {\\n char[] row = new char[n];\\n Array.Fill(row, \'.\');\\n row[queens[i]] = \'Q\';\\n board.Add(new string(row));\\n }\\n return board;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<string>> solveNQueens(int n) {\\n auto solutions = vector<vector<string>>();\\n auto queens = vector<int>(n, -1);\\n auto columns = unordered_set<int>();\\n auto diagonals1 = unordered_set<int>();\\n auto diagonals2 = unordered_set<int>();\\n backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);\\n return solutions;\\n }\\n\\n void backtrack(vector<vector<string>> &solutions, vector<int> &queens, int n, int row, unordered_set<int> &columns, unordered_set<int> &diagonals1, unordered_set<int> &diagonals2) {\\n if (row == n) {\\n vector<string> board = generateBoard(queens, n);\\n solutions.push_back(board);\\n } else {\\n for (int i = 0; i < n; i++) {\\n if (columns.find(i) != columns.end()) {\\n continue;\\n }\\n int diagonal1 = row - i;\\n if (diagonals1.find(diagonal1) != diagonals1.end()) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2.find(diagonal2) != diagonals2.end()) {\\n continue;\\n }\\n queens[row] = i;\\n columns.insert(i);\\n diagonals1.insert(diagonal1);\\n diagonals2.insert(diagonal2);\\n backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);\\n queens[row] = -1;\\n columns.erase(i);\\n diagonals1.erase(diagonal1);\\n diagonals2.erase(diagonal2);\\n }\\n }\\n }\\n\\n vector<string> generateBoard(vector<int> &queens, int n) {\\n auto board = vector<string>();\\n for (int i = 0; i < n; i++) {\\n string row = string(n, \'.\');\\n row[queens[i]] = \'Q\';\\n board.push_back(row);\\n }\\n return board;\\n }\\n};\\n
###Go
\\n\\nvar solutions [][]string\\n\\nfunc solveNQueens(n int) [][]string {\\n solutions = [][]string{}\\n queens := make([]int, n)\\n for i := 0; i < n; i++ {\\n queens[i] = -1\\n }\\n columns := map[int]bool{}\\n diagonals1, diagonals2 := map[int]bool{}, map[int]bool{}\\n backtrack(queens, n, 0, columns, diagonals1, diagonals2)\\n return solutions\\n}\\n\\nfunc backtrack(queens []int, n, row int, columns, diagonals1, diagonals2 map[int]bool) {\\n if row == n {\\n board := generateBoard(queens, n)\\n solutions = append(solutions, board)\\n return\\n }\\n for i := 0; i < n; i++ {\\n if columns[i] {\\n continue\\n }\\n diagonal1 := row - i\\n if diagonals1[diagonal1] {\\n continue\\n }\\n diagonal2 := row + i\\n if diagonals2[diagonal2] {\\n continue\\n }\\n queens[row] = i\\n columns[i] = true\\n diagonals1[diagonal1], diagonals2[diagonal2] = true, true\\n backtrack(queens, n, row + 1, columns, diagonals1, diagonals2)\\n queens[row] = -1\\n delete(columns, i)\\n delete(diagonals1, diagonal1)\\n delete(diagonals2, diagonal2)\\n }\\n}\\n\\nfunc generateBoard(queens []int, n int) []string {\\n board := []string{}\\n for i := 0; i < n; i++ {\\n row := make([]byte, n)\\n for j := 0; j < n; j++ {\\n row[j] = \'.\'\\n }\\n row[queens[i]] = \'Q\'\\n board = append(board, string(row))\\n }\\n return board\\n}\\n
###Python
\\n\\nclass Solution:\\n def solveNQueens(self, n: int) -> List[List[str]]:\\n def generateBoard():\\n board = list()\\n for i in range(n):\\n row[queens[i]] = \\"Q\\"\\n board.append(\\"\\".join(row))\\n row[queens[i]] = \\".\\"\\n return board\\n\\n def backtrack(row: int):\\n if row == n:\\n board = generateBoard()\\n solutions.append(board)\\n else:\\n for i in range(n):\\n if i in columns or row - i in diagonal1 or row + i in diagonal2:\\n continue\\n queens[row] = i\\n columns.add(i)\\n diagonal1.add(row - i)\\n diagonal2.add(row + i)\\n backtrack(row + 1)\\n columns.remove(i)\\n diagonal1.remove(row - i)\\n diagonal2.remove(row + i)\\n \\n solutions = list()\\n queens = [-1] * n\\n columns = set()\\n diagonal1 = set()\\n diagonal2 = set()\\n row = [\\".\\"] * n\\n backtrack(0)\\n return solutions\\n
###C
\\n\\nint solutionsSize;\\n\\nchar** generateBoard(int* queens, int n) {\\n char** board = (char**)malloc(sizeof(char*) * n);\\n for (int i = 0; i < n; i++) {\\n board[i] = (char*)malloc(sizeof(char) * (n + 1));\\n for (int j = 0; j < n; j++) board[i][j] = \'.\';\\n board[i][queens[i]] = \'Q\', board[i][n] = 0;\\n }\\n return board;\\n}\\n\\nvoid backtrack(char*** solutions, int* queens, int n, int row, int* columns, int* diagonals1, int* diagonals2) {\\n if (row == n) {\\n char** board = generateBoard(queens, n);\\n solutions[solutionsSize++] = board;\\n } else {\\n for (int i = 0; i < n; i++) {\\n if (columns[i]) {\\n continue;\\n }\\n int diagonal1 = row - i + n - 1;\\n if (diagonals1[diagonal1]) {\\n continue;\\n }\\n int diagonal2 = row + i;\\n if (diagonals2[diagonal2]) {\\n continue;\\n }\\n queens[row] = i;\\n columns[i] = true;\\n diagonals1[diagonal1] = true;\\n diagonals2[diagonal2] = true;\\n backtrack(solutions, queens, n, row + 1, columns, diagonals1, diagonals2);\\n queens[row] = -1;\\n columns[i] = false;\\n diagonals1[diagonal1] = false;\\n diagonals2[diagonal2] = false;\\n }\\n }\\n}\\n\\nchar*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {\\n char*** solutions = malloc(sizeof(char**) * 501);\\n solutionsSize = 0;\\n int queens[n];\\n int columns[n];\\n int diagonals1[n + n];\\n int diagonals2[n + n];\\n memset(queens, -1, sizeof(queens));\\n memset(columns, 0, sizeof(columns));\\n memset(diagonals1, 0, sizeof(diagonals1));\\n memset(diagonals2, 0, sizeof(diagonals2));\\n backtrack(solutions, queens, n, 0, columns, diagonals1, diagonals2);\\n *returnSize = solutionsSize;\\n *returnColumnSizes = malloc(sizeof(int*) * solutionsSize);\\n for (int i = 0; i < solutionsSize; i++) {\\n (*returnColumnSizes)[i] = n;\\n }\\n return solutions;\\n}\\n
###JavaScript
\\n\\nvar solveNQueens = function(n) {\\n const solutions = [];\\n const queens = new Array(n).fill(-1);\\n const columns = new Set();\\n const diagonal1 = new Set();\\n const diagonal2 = new Set();\\n const row = new Array(n).fill(\\".\\");\\n\\n function generateBoard() {\\n const board = [];\\n for (let i = 0; i < n; i++) {\\n row[queens[i]] = \\"Q\\";\\n board.push(row.join(\\"\\"));\\n row[queens[i]] = \\".\\";\\n }\\n return board;\\n }\\n\\n function backtrack(row) {\\n if (row === n) {\\n const board = generateBoard();\\n solutions.push(board);\\n } else {\\n for (let i = 0; i < n; i++) {\\n if (columns.has(i) || diagonal1.has(row - i) || diagonal2.has(row + i)) {\\n continue;\\n }\\n queens[row] = i;\\n columns.add(i);\\n diagonal1.add(row - i);\\n diagonal2.add(row + i);\\n backtrack(row + 1);\\n columns.delete(i);\\n diagonal1.delete(row - i);\\n diagonal2.delete(row + i);\\n }\\n }\\n }\\n\\n backtrack(0);\\n return solutions;\\n};\\n
###TypeScript
\\n\\nfunction solveNQueens(n: number): string[][] {\\n const solutions: string[][] = [];\\n const queens: number[] = new Array(n).fill(-1);\\n const columns: Set<number> = new Set();\\n const diagonal1: Set<number> = new Set();\\n const diagonal2: Set<number> = new Set();\\n const row: string[] = new Array(n).fill(\\".\\");\\n\\n function generateBoard(): string[] {\\n const board: string[] = [];\\n for (let i = 0; i < n; i++) {\\n row[queens[i]] = \\"Q\\";\\n board.push(row.join(\\"\\"));\\n row[queens[i]] = \\".\\";\\n }\\n return board;\\n }\\n\\n function backtrack(row: number): void {\\n if (row === n) {\\n const board = generateBoard();\\n solutions.push(board);\\n } else {\\n for (let i = 0; i < n; i++) {\\n if (columns.has(i) || diagonal1.has(row - i) || diagonal2.has(row + i)) {\\n continue;\\n }\\n queens[row] = i;\\n columns.add(i);\\n diagonal1.add(row - i);\\n diagonal2.add(row + i);\\n backtrack(row + 1);\\n columns.delete(i);\\n diagonal1.delete(row - i);\\n diagonal2.delete(row + i);\\n }\\n }\\n }\\n\\n backtrack(0);\\n return solutions;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn solve_n_queens(n: i32) -> Vec<Vec<String>> {\\n let mut solutions = Vec::new();\\n let mut queens = vec![-1; n as usize];\\n let mut columns = std::collections::HashSet::new();\\n let mut diagonal1 = std::collections::HashSet::new();\\n let mut diagonal2 = std::collections::HashSet::new();\\n let row = vec![\\".\\".to_string(); n as usize];\\n\\n fn generate_board(queens: &Vec<i32>, n: usize, row: &Vec<String>) -> Vec<String> {\\n let mut board = Vec::new();\\n for &q in queens.iter() {\\n let mut r = row.clone();\\n r[q as usize] = \\"Q\\".to_string();\\n board.push(r.join(\\"\\"));\\n }\\n board\\n }\\n\\n fn backtrack(\\n row: usize,\\n n: usize,\\n queens: &mut Vec<i32>,\\n columns: &mut std::collections::HashSet<usize>,\\n diagonal1: &mut std::collections::HashSet<i32>,\\n diagonal2: &mut std::collections::HashSet<i32>,\\n solutions: &mut Vec<Vec<String>>,\\n row_pattern: &Vec<String>,\\n ) {\\n if row == n {\\n let board = generate_board(queens, n, row_pattern);\\n solutions.push(board);\\n } else {\\n for i in 0..n {\\n if columns.contains(&i) || diagonal1.contains(&(row as i32 - i as i32)) || diagonal2.contains(&(row as i32 + i as i32)) {\\n continue;\\n }\\n queens[row] = i as i32;\\n columns.insert(i);\\n diagonal1.insert(row as i32 - i as i32);\\n diagonal2.insert(row as i32 + i as i32);\\n backtrack(row + 1, n, queens, columns, diagonal1, diagonal2, solutions, row_pattern);\\n columns.remove(&i);\\n diagonal1.remove(&(row as i32 - i as i32));\\n diagonal2.remove(&(row as i32 + i as i32));\\n }\\n }\\n }\\n\\n backtrack(0, n as usize, &mut queens, &mut columns, &mut diagonal1, &mut diagonal2, &mut solutions, &row);\\n solutions\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N!)$,其中 $N$ 是皇后数量。
\\n- \\n
\\n空间复杂度:$O(N)$,其中 $N$ 是皇后数量。空间复杂度主要取决于递归调用层数、记录每行放置的皇后的列下标的数组以及三个集合,递归调用层数不会超过 $N$,数组的长度为 $N$,每个集合的元素个数都不会超过 $N$。
\\n方法二:基于位运算的回溯
\\n方法一使用三个集合记录分别记录每一列以及两个方向的每条斜线上是否有皇后,每个集合最多包含 $N$ 个元素,因此集合的空间复杂度是 $O(N)$。如果利用位运算记录皇后的信息,就可以将记录皇后信息的空间复杂度从 $O(N)$ 降到 $O(1)$。
\\n具体做法是,使用三个整数 $\\\\textit{columns}$、$\\\\textit{diagonals}_1$ 和 $\\\\textit{diagonals}_2$ 分别记录每一列以及两个方向的每条斜线上是否有皇后,每个整数有 $N$ 个二进制位。棋盘的每一列对应每个整数的二进制表示中的一个数位,其中棋盘的最左列对应每个整数的最低二进制位,最右列对应每个整数的最高二进制位。
\\n那么如何根据每次放置的皇后更新三个整数的值呢?在说具体的计算方法之前,首先说一个例子。
\\n棋盘的边长和皇后的数量 $N=8$。如果棋盘的前两行分别在第 $2$ 列和第 $4$ 列放置了皇后(下标从 $0$ 开始),则棋盘的前两行如下图所示。
\\n\\n
如果要在下一行放置皇后,哪些位置不能放置呢?我们用 $0$ 代表可以放置皇后的位置,$1$ 代表不能放置皇后的位置。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一列,因此不能放置在第 $2$ 列和第 $4$ 列,对应 $\\\\textit{columns}=00010100_{(2)}$。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一条方向一(从左上到右下方向)的斜线上,因此不能放置在第 $4$ 列和第 $5$ 列,对应 $\\\\textit{diagonals}1=00110000{(2)}$。其中,第 $4$ 列为其前两行的第 $2$ 列的皇后往右下移动两步的位置,第 $5$ 列为其前一行的第 $4$ 列的皇后往右下移动一步的位置。
\\n新放置的皇后不能和任何一个已经放置的皇后在同一条方向二(从右上到左下方向)的斜线上,因此不能放置在第 $0$ 列和第 $3$ 列,对应 $\\\\textit{diagonals}2=00001001{(2)}$。其中,第 $0$ 列为其前两行的第 $2$ 列的皇后往左下移动两步的位置,第 $3$ 列为其前一行的第 $4$ 列的皇后往左下移动一步的位置。
\\n\\n
由此可以得到三个整数的计算方法:
\\n\\n
\\n- \\n
\\n初始时,三个整数的值都等于 $0$,表示没有放置任何皇后;
\\n- \\n
\\n在当前行放置皇后,如果皇后放置在第 $i$ 列,则将三个整数的第 $i$ 个二进制位(指从低到高的第 $i$ 个二进制位)的值设为 $1$;
\\n- \\n
\\n进入下一行时,$\\\\textit{columns}$ 的值保持不变,$\\\\textit{diagonals}_1$ 左移一位,$\\\\textit{diagonals}_2$ 右移一位,由于棋盘的最左列对应每个整数的最低二进制位,即每个整数的最右二进制位,因此对整数的移位操作方向和对棋盘的移位操作方向相反(对棋盘的移位操作方向是 $\\\\textit{diagonals}_1$ 右移一位,$\\\\textit{diagonals}_2$ 左移一位)。
\\n<
\\n,
,
,
,
,
>
每次放置皇后时,三个整数的按位或运算的结果即为不能放置皇后的位置,其余位置即为可以放置皇后的位置。可以通过 $(2^n-1)~&~(\\\\sim(\\\\textit{columns} | \\\\textit{diagonals}_1 | \\\\textit{diagonals}_2))$ 得到可以放置皇后的位置(该结果的值为 $1$ 的位置表示可以放置皇后的位置),然后遍历这些位置,尝试放置皇后并得到可能的解。
\\n遍历可以放置皇后的位置时,可以利用以下两个按位与运算的性质:
\\n\\n
\\n- \\n
\\n$x~&~(-x)$ 可以获得 $x$ 的二进制表示中的最低位的 $1$ 的位置;
\\n- \\n
\\n$x~&~(x-1)$ 可以将 $x$ 的二进制表示中的最低位的 $1$ 置成 $0$。
\\n具体做法是,每次获得可以放置皇后的位置中的最低位,并将该位的值置成 $0$,尝试在该位置放置皇后。这样即可遍历每个可以放置皇后的位置。
\\n###Java
\\n\\nclass Solution {\\n public List<List<String>> solveNQueens(int n) {\\n int[] queens = new int[n];\\n Arrays.fill(queens, -1);\\n List<List<String>> solutions = new ArrayList<List<String>>();\\n solve(solutions, queens, n, 0, 0, 0, 0);\\n return solutions;\\n }\\n\\n public void solve(List<List<String>> solutions, int[] queens, int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n List<String> board = generateBoard(queens, n);\\n solutions.add(board);\\n } else {\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n int column = Integer.bitCount(position - 1);\\n queens[row] = column;\\n solve(solutions, queens, n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n queens[row] = -1;\\n }\\n }\\n }\\n\\n public List<String> generateBoard(int[] queens, int n) {\\n List<String> board = new ArrayList<String>();\\n for (int i = 0; i < n; i++) {\\n char[] row = new char[n];\\n Arrays.fill(row, \'.\');\\n row[queens[i]] = \'Q\';\\n board.add(new String(row));\\n }\\n return board;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public IList<IList<string>> SolveNQueens(int n) {\\n int[] queens = new int[n];\\n Array.Fill(queens, -1);\\n IList<IList<string>> solutions = new List<IList<string>>();\\n Solve(solutions, queens, n, 0, 0, 0, 0);\\n return solutions;\\n }\\n\\n private void Solve(IList<IList<string>> solutions, int[] queens, int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n var board = GenerateBoard(queens, n);\\n solutions.Add(board);\\n } else {\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & -availablePositions;\\n availablePositions &= availablePositions - 1;\\n int column = BitCount(position - 1);\\n queens[row] = column;\\n Solve(solutions, queens, n, row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n queens[row] = -1;\\n }\\n }\\n }\\n\\n private IList<string> GenerateBoard(int[] queens, int n) {\\n IList<string> board = new List<string>();\\n for (int i = 0; i < n; i++) {\\n char[] row = new char[n];\\n Array.Fill(row, \'.\');\\n row[queens[i]] = \'Q\';\\n board.Add(new string(row));\\n }\\n return board;\\n }\\n\\n private int BitCount(int x) {\\n int count = 0;\\n while (x != 0) {\\n count += x & 1;\\n x >>= 1;\\n }\\n return count;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<vector<string>> solveNQueens(int n) {\\n auto solutions = vector<vector<string>>();\\n auto queens = vector<int>(n, -1);\\n solve(solutions, queens, n, 0, 0, 0, 0);\\n return solutions;\\n }\\n\\n void solve(vector<vector<string>> &solutions, vector<int> &queens, int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n auto board = generateBoard(queens, n);\\n solutions.push_back(board);\\n } else {\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n int column = __builtin_ctz(position);\\n queens[row] = column;\\n solve(solutions, queens, n, row + 1, columns | position, (diagonals1 | position) >> 1, (diagonals2 | position) << 1);\\n queens[row] = -1;\\n }\\n }\\n }\\n\\n vector<string> generateBoard(vector<int> &queens, int n) {\\n auto board = vector<string>();\\n for (int i = 0; i < n; i++) {\\n string row = string(n, \'.\');\\n row[queens[i]] = \'Q\';\\n board.push_back(row);\\n }\\n return board;\\n }\\n};\\n
###Go
\\n\\nvar solutions [][]string\\n\\nfunc solveNQueens(n int) [][]string {\\n solutions = [][]string{}\\n queens := make([]int, n)\\n for i := 0; i < n; i++ {\\n queens[i] = -1\\n }\\n solve(queens, n, 0, 0, 0, 0)\\n return solutions\\n}\\n\\nfunc solve(queens []int, n, row, columns, diagonals1, diagonals2 int) {\\n if row == n {\\n board := generateBoard(queens, n)\\n solutions = append(solutions, board)\\n return\\n }\\n availablePositions := ((1 << n) - 1) & (^(columns | diagonals1 | diagonals2))\\n for availablePositions != 0 {\\n position := availablePositions & (-availablePositions)\\n availablePositions = availablePositions & (availablePositions - 1)\\n column := bits.OnesCount(uint(position - 1))\\n queens[row] = column\\n solve(queens, n, row + 1, columns | position, (diagonals1 | position) >> 1, (diagonals2 | position) << 1)\\n queens[row] = -1\\n }\\n}\\n\\nfunc generateBoard(queens []int, n int) []string {\\n board := []string{}\\n for i := 0; i < n; i++ {\\n row := make([]byte, n)\\n for j := 0; j < n; j++ {\\n row[j] = \'.\'\\n }\\n row[queens[i]] = \'Q\'\\n board = append(board, string(row))\\n }\\n return board\\n}\\n
###Python
\\n\\nclass Solution:\\n def solveNQueens(self, n: int) -> List[List[str]]:\\n def generateBoard():\\n board = list()\\n for i in range(n):\\n row[queens[i]] = \\"Q\\"\\n board.append(\\"\\".join(row))\\n row[queens[i]] = \\".\\"\\n return board\\n\\n def solve(row: int, columns: int, diagonals1: int, diagonals2: int):\\n if row == n:\\n board = generateBoard()\\n solutions.append(board)\\n else:\\n availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2))\\n while availablePositions:\\n position = availablePositions & (-availablePositions)\\n availablePositions = availablePositions & (availablePositions - 1)\\n column = bin(position - 1).count(\\"1\\")\\n queens[row] = column\\n solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1)\\n\\n solutions = list()\\n queens = [-1] * n\\n row = [\\".\\"] * n\\n solve(0, 0, 0, 0)\\n return solutions\\n
###C
\\n\\nint solutionsSize;\\n\\nchar** generateBoard(int* queens, int n) {\\n char** board = (char**)malloc(sizeof(char*) * n);\\n for (int i = 0; i < n; i++) {\\n board[i] = (char*)malloc(sizeof(char) * (n + 1));\\n for (int j = 0; j < n; j++) board[i][j] = \'.\';\\n board[i][queens[i]] = \'Q\', board[i][n] = 0;\\n }\\n return board;\\n}\\n\\nvoid solve(char*** solutions, int* queens, int n, int row, int columns, int diagonals1, int diagonals2) {\\n if (row == n) {\\n char** board = generateBoard(queens, n);\\n solutions[solutionsSize++] = board;\\n } else {\\n int availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions != 0) {\\n int position = availablePositions & (-availablePositions);\\n availablePositions = availablePositions & (availablePositions - 1);\\n int column = __builtin_ctz(position);\\n queens[row] = column;\\n solve(solutions, queens, n, row + 1, columns | position, (diagonals1 | position) >> 1, (diagonals2 | position) << 1);\\n queens[row] = -1;\\n }\\n }\\n}\\n\\nchar*** solveNQueens(int n, int* returnSize, int** returnColumnSizes) {\\n char*** solutions = malloc(sizeof(char**) * 501);\\n solutionsSize = 0;\\n int queens[n];\\n memset(queens, -1, sizeof(queens));\\n solve(solutions, queens, n, 0, 0, 0, 0);\\n *returnSize = solutionsSize;\\n *returnColumnSizes = malloc(sizeof(int*) * solutionsSize);\\n for (int i = 0; i < solutionsSize; i++) {\\n (*returnColumnSizes)[i] = n;\\n }\\n return solutions;\\n}\\n
###JavaScript
\\n\\nvar solveNQueens = function(n) {\\n const solutions = [];\\n const queens = new Array(n).fill(-1);\\n const row = new Array(n).fill(\\".\\");\\n\\n function generateBoard() {\\n const board = [];\\n for (let i = 0; i < n; i++) {\\n row[queens[i]] = \\"Q\\";\\n board.push(row.join(\\"\\"));\\n row[queens[i]] = \\".\\";\\n }\\n return board;\\n }\\n\\n function solve(row, columns, diagonals1, diagonals2) {\\n if (row === n) {\\n const board = generateBoard();\\n solutions.push(board);\\n } else {\\n let availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions) {\\n const position = availablePositions & -availablePositions;\\n availablePositions &= availablePositions - 1;\\n const column = Math.log2(position);\\n queens[row] = column;\\n solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n }\\n }\\n\\n solve(0, 0, 0, 0);\\n return solutions;\\n};\\n
###TypeScript
\\n\\nfunction solveNQueens(n: number): string[][] {\\n const solutions: string[][] = [];\\n const queens: number[] = new Array(n).fill(-1);\\n const row: string[] = new Array(n).fill(\\".\\");\\n\\n function generateBoard(): string[] {\\n const board: string[] = [];\\n for (let i = 0; i < n; i++) {\\n row[queens[i]] = \\"Q\\";\\n board.push(row.join(\\"\\"));\\n row[queens[i]] = \\".\\";\\n }\\n return board;\\n }\\n\\n function solve(row: number, columns: number, diagonals1: number, diagonals2: number): void {\\n if (row === n) {\\n const board = generateBoard();\\n solutions.push(board);\\n } else {\\n let availablePositions = ((1 << n) - 1) & (~(columns | diagonals1 | diagonals2));\\n while (availablePositions) {\\n const position = availablePositions & -availablePositions;\\n availablePositions &= availablePositions - 1;\\n const column = Math.log2(position);\\n queens[row] = column;\\n solve(row + 1, columns | position, (diagonals1 | position) << 1, (diagonals2 | position) >> 1);\\n }\\n }\\n }\\n\\n solve(0, 0, 0, 0);\\n return solutions;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn solve_n_queens(n: i32) -> Vec<Vec<String>> {\\n let mut solutions = Vec::new();\\n let mut queens = vec![-1; n as usize];\\n let row = vec![\\".\\".to_string(); n as usize];\\n\\n fn generate_board(queens: &Vec<i32>, n: usize, row: &Vec<String>) -> Vec<String> {\\n let mut board = Vec::new();\\n for &q in queens.iter() {\\n let mut r = row.clone();\\n r[q as usize] = \\"Q\\".to_string();\\n board.push(r.join(\\"\\"));\\n }\\n board\\n }\\n\\n fn solve(\\n row: usize,\\n columns: usize,\\n diagonals1: usize,\\n diagonals2: usize,\\n n: usize,\\n queens: &mut Vec<i32>,\\n solutions: &mut Vec<Vec<String>>,\\n row_pattern: &Vec<String>,\\n ) {\\n if row == n {\\n let board = generate_board(queens, n, row_pattern);\\n solutions.push(board);\\n } else {\\n let mut available_positions = ((1 << n) - 1) & !(columns | diagonals1 | diagonals2);\\n while available_positions != 0 {\\n let position = available_positions & available_positions.wrapping_neg();\\n available_positions &= available_positions - 1;\\n let column = position.trailing_zeros() as usize;\\n queens[row] = column as i32;\\n solve(\\n row + 1,\\n columns | position,\\n (diagonals1 | position) << 1,\\n (diagonals2 | position) >> 1,\\n n,\\n queens,\\n solutions,\\n row_pattern,\\n );\\n }\\n }\\n }\\n\\n solve(0, 0, 0, 0, n as usize, &mut queens, &mut solutions, &row);\\n solutions\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N!)$,其中 $N$ 是皇后数量。
\\n- \\n
\\n空间复杂度:$O(N)$,其中 $N$ 是皇后数量。由于使用位运算表示,因此存储皇后信息的空间复杂度是 $O(1)$,空间复杂度主要取决于递归调用层数和记录每行放置的皇后的列下标的数组,递归调用层数不会超过 $N$,数组的长度为 $N$。
\\n小结
\\n回顾这道题,拿到这道题的时候,其实我们很容易看出需要使用枚举的方法来求解这个问题,当我们不知道用什么办法来枚举是最优的时候,可以从下面三个方向考虑:
\\n\\n
\\n- 子集枚举:可以把问题转化成「从 $n^2$ 个格子中选一个子集,使得子集中恰好有 $n$ 个格子,且任意选出两个都不在同行、同列或者同对角线」,这里枚举的规模是 $2^{n^2}$;
\\n- 组合枚举:可以把问题转化成「从 $n^2$ 个格子中选择 $n$ 个,且任意选出两个都不在同行、同列或者同对角线」,这里的枚举规模是 ${n^2} \\\\choose {n}$;
\\n- 排列枚举:因为这里每行只能放置一个皇后,而所有行中皇后的列号正好构成一个 $1$ 到 $n$ 的排列,所以我们可以把问题转化为一个排列枚举,规模是 $n!$。
\\n带入一些 $n$ 进这三种方法验证,就可以知道哪种方法的枚举规模是最小的,这里我们发现第三种方法的枚举规模最小。这道题给出的两个方法其实和排列枚举的本质是类似的。
\\n","description":"前言 「$N$ 皇后问题」研究的是如何将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上,并且使皇后彼此之间不能相互攻击。\\n\\n皇后的走法是:可以横直斜走,格数不限。因此要求皇后彼此之间不能相互攻击,等价于要求任何两个皇后都不能在同一行、同一列以及同一条斜线上。\\n\\n直观的做法是暴力枚举将 $N$ 个皇后放置在 $N \\\\times N$ 的棋盘上的所有可能的情况,并对每一种情况判断是否满足皇后彼此之间不相互攻击。暴力枚举的时间复杂度是非常高的,因此必须利用限制条件加以优化。\\n\\n显然,每个皇后必须位于不同行和不同列,因此将 $N$ 个皇后放置在 $N…","guid":"https://leetcode.cn/problems/n-queens//solution/nhuang-hou-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-09-02T15:41:25.146Z","media":[{"url":"https://assets.leetcode.cn/solution-static/51/1.png","type":"photo","width":2000,"height":1125,"blurhash":"LeJH5P0L4:R%-VRkWUWE0M%2%1of"},{"url":"https://assets.leetcode.cn/solution-static/51/2.png","type":"photo","width":2000,"height":1125,"blurhash":"LkIg_pE20eoeaiWEoboc0Lxs-oRo"},{"url":"https://assets.leetcode.cn/solution-static/51/3.png","type":"photo","width":2000,"height":1125,"blurhash":"LpKdO@-nIW-nxZj[afj[0NNHt6Rk"},{"url":"https://assets.leetcode.cn/solution-static/51/4.png","type":"photo","width":2000,"height":921,"blurhash":"LnLDk]NHM}NIoef6fRj[0Noeoeoe"},{"url":"https://assets.leetcode.cn/solution-static/51/2_1.png","type":"photo","width":2000,"height":1125,"blurhash":"LKA0joWC9bkB~TWCE2j[t6a|WCj["},{"url":"https://assets.leetcode.cn/solution-static/51/2_2.png","type":"photo","width":2000,"height":1125,"blurhash":"LUE_$=Rk0goeW.fPn+j[E3j[xZj["},{"url":"https://assets.leetcode.cn/solution-static/51/2_3.png","type":"photo","width":2000,"height":1125,"blurhash":"LaF}ltRk0goex@fkRkfk0goL-Ufk"},{"url":"https://assets.leetcode.cn/solution-static/51/2_4.png","type":"photo","width":2000,"height":1125,"blurhash":"LbG8MwRk0gj[xZazR+j[0goL-UbG"},{"url":"https://assets.leetcode.cn/solution-static/51/2_5.png","type":"photo","width":2000,"height":1125,"blurhash":"LcGHu1W;0gep-nayM}kB0gjG-UkV"},{"url":"https://assets.leetcode.cn/solution-static/51/2_6.png","type":"photo","width":2000,"height":1125,"blurhash":"LSC$lfWD4=jt~TWVE3j[E3jZxZj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"贪心","url":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/tan-xin-by-time-limit","content":"因为每轮选择中,Bob 总是选择最少的一堆。所以 Bob 得到最少的 n 堆是对其他两个人最有利的局面。
\\n剩下的 2n 堆该如何分配呢?
\\n
\\n每次选择最大的两堆,Alice 获得其中较大的,我获得其中较小的。
\\n虽然这样选择,Alice 还是能获得剩余的里面最大的一堆,但是可以让剩余堆中的最大值最小。\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n int maxCoins(vector<int>& piles) {\\n sort(piles.begin(), piles.end(), [](int l, int r) -> bool {\\n return l > r;\\n });\\n \\n int anw = 0;\\n \\n for(int i = 0; i < piles.size()/3; i++) {\\n anw += piles[i*2+1];\\n }\\n return anw;\\n }\\n};\\n
\\n如果感觉有点意思,那就关注一下【我的公众号】吧~
\\n","description":"因为每轮选择中,Bob 总是选择最少的一堆。所以 Bob 得到最少的 n 堆是对其他两个人最有利的局面。 剩下的 2n 堆该如何分配呢?\\n 每次选择最大的两堆,Alice 获得其中较大的,我获得其中较小的。\\n 虽然这样选择,Alice 还是能获得剩余的里面最大的一堆,但是可以让剩余堆中的最大值最小。\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int maxCoins(vector& piles) {\\n sort(piles.begin(), piles.end(), [](int l, int r) -…","guid":"https://leetcode.cn/problems/maximum-number-of-coins-you-can-get//solution/tan-xin-by-time-limit","author":"Time-Limit","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-23T08:54:34.672Z","media":[{"url":"https://pic.leetcode-cn.com/1598173896-gaFTry-image.png","type":"photo","width":1154,"height":312,"blurhash":"LgRx[lrro}sAL#VsozVstlkCaxkC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"存在连续三个奇数的数组","url":"https://leetcode.cn/problems/three-consecutive-odds//solution/cun-zai-lian-xu-san-ge-qi-shu-de-shu-zu-by-leetcod","content":" 方法一:枚举
\\n思路与算法
\\n枚举所有的连续的三个元素,判断这三个元素是否都是奇数,如果是,则返回
\\ntrue
。如果所有的连续的三个元素中,没有一个满足条件,返回false
。代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n bool threeConsecutiveOdds(vector<int>& arr) {\\n int n = arr.size();\\n for (int i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) & (arr[i + 1] & 1) & (arr[i + 2] & 1)) {\\n return true;\\n }\\n }\\n return false;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public boolean threeConsecutiveOdds(int[] arr) {\\n int n = arr.length;\\n for (int i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) != 0 && (arr[i + 1] & 1) != 0 && (arr[i + 2] & 1) != 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public bool ThreeConsecutiveOdds(int[] arr) {\\n int n = arr.Length;\\n for (int i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) != 0 && (arr[i + 1] & 1) != 0 && (arr[i + 2] & 1) != 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
###Go
\\n\\nfunc threeConsecutiveOdds(arr []int) bool {\\n n := len(arr)\\n for i := 0; i <= n - 3; i++ {\\n if (arr[i] & 1) != 0 && (arr[i + 1] & 1) != 0 && (arr[i + 2] & 1) != 0 {\\n return true;\\n }\\n }\\n return false;\\n}\\n
###C
\\n\\nbool threeConsecutiveOdds(int* arr, int arrSize) {\\n for (int i = 0; i <= arrSize - 3; ++i) {\\n if ((arr[i] & 1) & (arr[i + 1] & 1) & (arr[i + 2] & 1)) {\\n return true;\\n }\\n }\\n return false;\\n}\\n
###JavaScript
\\n\\nvar threeConsecutiveOdds = function(arr) {\\n const n = arr.length;\\n for (let i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) & (arr[i + 1] & 1) & (arr[i + 2] & 1)) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
###Python
\\n\\nclass Solution:\\n def threeConsecutiveOdds(self, arr: List[int]) -> bool:\\n n = len(arr)\\n return n >= 3 and \\\\\\n any(arr[i] & 1 and arr[i + 1] & 1 and arr[i + 2] & 1 \\\\\\n for i in range(n - 2))\\n
###TypeScript
\\n\\nfunction threeConsecutiveOdds(arr: number[]): boolean {\\n const n = arr.length;\\n for (let i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) & (arr[i + 1] & 1) & (arr[i + 2] & 1)) {\\n return true;\\n }\\n }\\n return false;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn three_consecutive_odds(arr: Vec<i32>) -> bool {\\n let n = arr.len();\\n if n < 3 {\\n return false;\\n }\\n for i in 0..= n - 3 {\\n if ((arr[i] & 1) != 0 && (arr[i + 1] & 1) != 0 && (arr[i + 2] & 1) != 0) {\\n return true;\\n }\\n }\\n return false;\\n }\\n}\\n
复杂度分析
\\n记原序列的长度为 $n$。
\\n\\n
\\n","description":"方法一:枚举 思路与算法\\n\\n枚举所有的连续的三个元素,判断这三个元素是否都是奇数,如果是,则返回 true。如果所有的连续的三个元素中,没有一个满足条件,返回 false。\\n\\n代码\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n bool threeConsecutiveOdds(vector- 时间复杂度:$O(n)$。
\\n- 空间复杂度:$O(1)$。
\\n& arr) {\\n int n = arr.size();\\n for (int i = 0; i <= n - 3; ++i) {\\n if ((arr[i] & 1) & (arr[i…","guid":"https://leetcode.cn/problems/three-consecutive-odds//solution/cun-zai-lian-xu-san-ge-qi-shu-de-shu-zu-by-leetcod","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-20T13:02:27.168Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"官方题解的理解","url":"https://leetcode.cn/problems/count-good-triplets//solution/guan-fang-ti-jie-de-li-jie-by-nian-you-de-ke-er-bo","content":" \\n
\\n- 阿巴阿巴的我终于看懂了
\\n一、暴力解法
\\n没啥说的,直接仨循环嵌套就是。52 ms
\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vector<int>& arr, int a, int b, int c) {\\n int res = 0;\\n int s = arr.size();\\n for(int i=s-1;i>-1;i--){\\n for(int j=s-1;j>i;j--){\\n for(int k=s-1;k>j;k--){\\n if(abs(arr[i]-arr[j])>a || abs(arr[j]-arr[k])>b || abs(arr[i]-arr[k])>c)\\n continue;\\n res++;\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
不过可以在第二个循环的时候提前结束。24 ms
\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vector<int>& arr, int a, int b, int c) {\\n int res = 0;\\n int s = arr.size();\\n for(int i=s-1;i>-1;i--){\\n for(int j=s-1;j>i;j--){\\n if (abs(arr[i] - arr[j]) <= a)\\n for(int k=s-1;k>j;k--){\\n if(abs(arr[j]-arr[k])>b || abs(arr[i]-arr[k])>c)\\n continue;\\n res++;\\n }\\n }\\n }\\n return res;\\n }\\n};\\n
二、官方的枚举优化
\\n这个有点打脑壳。不过有图的话就容易理解一些。
\\n考虑在 |arr[j] - arr[k]| ≤ b 情况下,我们需要找到一个arr[i]满足:
\\n\\n
\\n- i < j
\\n- ∣arr[i]−arr[j]∣≤a ==> arr[j] - a <= arr[i] <= arr[j] + a
\\n- ∣arr[i]−arr[k]∣≤c ==> arr[k] - c <= arr[i] <= arr[k] + c
\\n- 0 <= arr[i] <= 1000
\\n\\n
\\n根据图可得:\\n
\\n- l = max(0, lj, lk)
\\n- r = min(rj, rk, 1000)
\\n注意:
\\n
\\n这里的l, r不是索引,而是数组里的值抛开这个,我们先看另一段代码:
\\n\\nfor (int j = 0; j < n; ++j) {\\n .......\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n}\\n
这里是把在数组[0, 1, 2,..., 1000]中所有索引为arr[j]后面的数据全部加一。如下图所示:
\\n
\\n
\\n因为j是从头开始遍历,i必定存在j之前,所以计算l, r时可以正常得出。
\\n好,结合之前得出的l, r:
\\n
\\na[i]个数是不是刚好等于sum[r] - sum[l] ?
\\n
\\n这种呢?莫慌,取sum[l-1]就行了,因为sum[arr[i] - 1]必定小于sum[l]
\\n所以:\\nans += sum[r] - sum[l - 1];\\n
官方代码:12ms
\\n\\n","description":"阿巴阿巴的我终于看懂了 没啥说的,直接仨循环嵌套就是。52 ms\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vectorclass Solution {\\npublic:\\n int countGoodTriplets(vector<int>& arr, int a, int b, int c) {\\n int ans = 0, n = arr.size();\\n vector<int> sum(1001, 0);\\n for (int j = 0; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (abs(arr[j] - arr[k]) <= b) {\\n int lj = arr[j] - a, rj = arr[j] + a;\\n int lk = arr[k] - c, rk = arr[k] + c;\\n int l = max(0, max(lj, lk)), r = min(1000, min(rj, rk));\\n if (l <= r) {\\n if (l == 0) {\\n ans += sum[r];\\n }\\n else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n }\\n return ans;\\n }\\n};\\n
& arr, int a, int b, int c) {\\n int res = 0;\\n int s = arr.size();\\n for(int i=s-1;i>-1;i--){\\n for(int j=s-1;j>i;j--){\\n for(int k=s-1;k>j…","guid":"https://leetcode.cn/problems/count-good-triplets//solution/guan-fang-ti-jie-de-li-jie-by-nian-you-de-ke-er-bo","author":"nian-you-de-ke-er-bo-luo-si-b","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-18T05:15:21.076Z","media":[{"url":"https://pic.leetcode-cn.com/29a260c285ef3835d9cd07fb1201a581a69f2d5926125f3d161920d932d6d5d5-image.png","type":"photo","width":633,"height":243,"blurhash":"LDS$ov~qWB_3?bt7jua{?boeWBay"},{"url":"https://pic.leetcode-cn.com/2c5d617768c9ac0d8cdcf6514bfc1c1df533ce2027a2e3df4468bc8d807f185d-image.png","type":"photo","width":567,"height":223,"blurhash":"LFSF@S-;of-;~Xj[j[fQ9Yofofof"},{"url":"https://pic.leetcode-cn.com/f223505f47a0624ec8c87619b58f37ef4aeb3d486223f9049e46dfcfe9934439-image.png","type":"photo","width":592,"height":277,"blurhash":"LBSY~x~qt7~q~Xt7oft7xbofayof"},{"url":"https://pic.leetcode-cn.com/0e4bdc035969ebd2aa2614514b7189326cb01adaa85292c2229d33e9df883002-image.png","type":"photo","width":578,"height":251,"blurhash":"LDSY~x~qt7_3~qxuoft7t8t7ofof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"切棍子的最小成本","url":"https://leetcode.cn/problems/minimum-cost-to-cut-a-stick//solution/qie-gun-zi-de-zui-xiao-cheng-ben-by-leetcode-solut","content":" 前言
\\n本题和 312. 戳气球 较为相似,都是经典的区间动态规划题。
\\n方法一:动态规划
\\n思路与算法
\\n在我们任意一次切割时,待切割木棍的左端点要么是原始木棍的左端点 $0$,要么是之前某一次切割的位置;同理,待切割木棍的右端点要么是原始木棍的右端点 $n$,要么是之前某一次切割的位置。
\\n因此,如果我们将切割位置数组 $\\\\textit{cuts}$ 进行排序,并在左侧添加 $0$,右侧添加 $n$,那么待切割的木棍就对应着数组中一段连续的区间。这样一来,我们就可以用动态规划来解决本题。
\\n我们用数组 $\\\\textit{cuts}[1..m]$ 表示题目中给定的数组 $\\\\textit{cuts}$ 按照升序排序后的结果,其中 $m$ 是数组 $\\\\textit{cuts}$ 的长度,并令 $cuts[0] = 0$,$cuts[m+1] = n$。同时,我们用 $f[i][j]$ 表示在当前待切割的木棍的左端点为 $\\\\textit{cuts}[i-1]$,右端点为 $\\\\textit{cuts}[j+1]$ 时,将木棍全部切开的最小总成本。
\\n\\n\\n这里全部切开的意思是,木棍中有 $j-i+1$ 个切割位置 $\\\\textit{cuts}[i], \\\\cdots, \\\\textit{cuts}[j]$,我们需要将木棍根据这些位置,切割成 $j-i+2$ 段。
\\n为了得到最小总成本,我们可以枚举第一刀的位置。如果第一刀的位置为 $\\\\textit{cuts}[k]$,其中 $k \\\\in [i, j]$,那么我们会将待切割的木棍分成两部分,左侧部分的木棍为 $\\\\textit{cuts}[i-1..k]$,对应的可以继续切割的位置为 $\\\\textit{cuts}[i..k-1]$;右侧部分的木棍为 $\\\\textit{cuts}[k..j+1]$,对应的可以继续切割的位置为 $\\\\textit{cuts}[k+1..j]$。由于左右两侧均为规模较小的子问题,因此我们可以得到状态转移方程:
\\n$$
\\n
\\nf[i][j] = \\\\min_{k \\\\in [i,j]} { f[i][k-1] + f[k+1][j] } + (\\\\textit{cuts}[j+1] - \\\\textit{cuts}[i-1])
\\n$$即我们无论在哪里切第一刀,这一刀的成本都是木棍的长度 $\\\\textit{cuts}[j+1] - \\\\textit{cuts}[i-1]$。
\\n状态转移方程的边界条件为:
\\n$$
\\n
\\nf[i][j] = 0, ~其中~ i > j
\\n$$也就是说,如果没有可以切割的位置,那么它要么是一根无法再切割的木棍(此时 $i=j+1$),要么根本就不是一根木棍(此时 $i>j+1$)。无论是哪一种情况,对应的最小总成本都是 $0$。
\\n最后的答案即为 $f[1][m]$。
\\n细节
\\n在区间动态规划中,我们要注意状态计算的顺序,即在计算 $f[i][j]$ 时,所有满足 $k \\\\in [i, j]$ 的 $f[i][k]$ 和 $f[k][j]$ 都需要已经被计算过。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minCost(int n, vector<int>& cuts) {\\n int m = cuts.size();\\n sort(cuts.begin(), cuts.end());\\n cuts.insert(cuts.begin(), 0);\\n cuts.push_back(n);\\n vector<vector<int>> f(m + 2, vector<int>(m + 2));\\n for (int i = m; i >= 1; --i) {\\n for (int j = i; j <= m; ++j) {\\n f[i][j] = (i == j ? 0 : INT_MAX);\\n for (int k = i; k <= j; ++k) {\\n f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j]);\\n }\\n f[i][j] += cuts[j + 1] - cuts[i - 1];\\n }\\n }\\n return f[1][m];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int minCost(int n, int[] cuts) {\\n int m = cuts.length;\\n Arrays.sort(cuts);\\n int[] newCuts = new int[m + 2];\\n newCuts[0] = 0;\\n for (int i = 1; i <= m; ++i) {\\n newCuts[i] = cuts[i - 1];\\n }\\n newCuts[m + 1] = n;\\n int[][] f = new int[m + 2][m + 2];\\n for (int i = m; i >= 1; --i) {\\n for (int j = i; j <= m; ++j) {\\n f[i][j] = i == j ? 0 : Integer.MAX_VALUE;\\n for (int k = i; k <= j; ++k) {\\n f[i][j] = Math.min(f[i][j], f[i][k - 1] + f[k + 1][j]);\\n }\\n f[i][j] += newCuts[j + 1] - newCuts[i - 1];\\n }\\n }\\n return f[1][m];\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def minCost(self, n: int, cuts: List[int]) -> int:\\n m = len(cuts)\\n cuts = [0] + sorted(cuts) + [n]\\n f = [[0] * (m + 2) for _ in range(m + 2)]\\n\\n for i in range(m, 0, -1):\\n for j in range(i, m + 1):\\n f[i][j] = 0 if i == j else \\\\\\n min(f[i][k - 1] + f[k + 1][j] for k in range(i, j + 1))\\n f[i][j] += cuts[j + 1] - cuts[i - 1]\\n \\n return f[1][m]\\n
###C
\\n\\nint comp(const void* a, const void* b) {\\n return *(int*)a - *(int*)b;\\n}\\n\\nint minCost(int n, int* cuts, int cutsSize) {\\n qsort(cuts, cutsSize, sizeof(int), comp);\\n int* tmp = malloc(sizeof(int) * (cutsSize + 2));\\n for (int i = 0; i < cutsSize; i++) {\\n tmp[i + 1] = cuts[i];\\n }\\n tmp[0] = 0, tmp[cutsSize + 1] = n;\\n int f[cutsSize + 2][cutsSize + 2];\\n memset(f, 0, sizeof(f));\\n for (int i = cutsSize; i >= 1; --i) {\\n for (int j = i; j <= cutsSize; ++j) {\\n f[i][j] = (i == j ? 0 : INT_MAX);\\n for (int k = i; k <= j; ++k) {\\n f[i][j] = fmin(f[i][j], f[i][k - 1] + f[k + 1][j]);\\n }\\n f[i][j] += tmp[j + 1] - tmp[i - 1];\\n }\\n }\\n free(tmp);\\n return f[1][cutsSize];\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinCost(int n, int[] cuts) {\\n int m = cuts.Length;\\n Array.Sort(cuts);\\n int[] newCuts = new int[m + 2];\\n newCuts[0] = 0;\\n for (int i = 1; i <= m; ++i) {\\n newCuts[i] = cuts[i - 1];\\n }\\n newCuts[m + 1] = n;\\n int[,] f = new int[m + 2, m + 2];\\n for (int i = m; i >= 1; --i) {\\n for (int j = i; j <= m; ++j) {\\n f[i, j] = (i == j) ? 0 : int.MaxValue;\\n for (int k = i; k <= j; ++k) {\\n f[i, j] = Math.Min(f[i, j], f[i, k - 1] + f[k + 1, j]);\\n }\\n f[i, j] += newCuts[j + 1] - newCuts[i - 1];\\n }\\n }\\n \\n return f[1, m];\\n }\\n};\\n
###Go
\\n\\nfunc minCost(n int, cuts []int) int {\\n m := len(cuts)\\n cuts = append([]int{0}, cuts...)\\n cuts = append(cuts, n)\\n sort.Ints(cuts)\\n \\n f := make([][]int, m + 2)\\n for i := range f {\\n f[i] = make([]int, m + 2)\\n }\\n\\n for i := m; i >= 1; i-- {\\n for j := i; j <= m; j++ {\\n if i == j {\\n f[i][j] = 0\\n } else {\\n f[i][j] = int(^uint(0) >> 1)\\n }\\n for k := i; k <= j; k++ {\\n f[i][j] = min(f[i][j], f[i][k - 1] + f[k + 1][j])\\n }\\n f[i][j] += cuts[j + 1] - cuts[i - 1]\\n }\\n }\\n\\n return f[1][m]\\n}\\n
###JavaScript
\\n\\nvar minCost = function(n, cuts) {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort((a, b) => a - b);\\n const m = cuts.length;\\n const f = Array.from({ length: m }, () => Array(m).fill(0));\\n\\n for (let i = m - 3; i >= 0; i--) {\\n for (let j = i + 2; j < m; j++) {\\n let minCost = Infinity;\\n for (let k = i + 1; k < j; k++) {\\n minCost = Math.min(minCost, f[i][k] + f[k][j]);\\n }\\n f[i][j] = minCost + (cuts[j] - cuts[i]);\\n }\\n }\\n\\n return f[0][m - 1];\\n};\\n
###TypeScript
\\n\\nfunction minCost(n: number, cuts: number[]): number {\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort((a, b) => a - b);\\n const m = cuts.length;\\n const f: number[][] = Array.from({ length: m }, () => Array(m).fill(0));\\n\\n for (let i = m - 3; i >= 0; i--) {\\n for (let j = i + 2; j < m; j++) {\\n let minCost = Infinity;\\n for (let k = i + 1; k < j; k++) {\\n minCost = Math.min(minCost, f[i][k] + f[k][j]);\\n }\\n f[i][j] = minCost + (cuts[j] - cuts[i]);\\n }\\n }\\n\\n return f[0][m - 1];\\n};\\n
###Rust
\\n\\nuse std::cmp::min;\\n\\nimpl Solution {\\n pub fn min_cost(n: i32, cuts: Vec<i32>) -> i32 {\\n let mut cuts = cuts;\\n cuts.push(0);\\n cuts.push(n);\\n cuts.sort();\\n let m = cuts.len();\\n let mut f = vec![vec![0; m]; m];\\n\\n for i in (0..m).rev() {\\n for j in i + 2..m {\\n f[i][j] = i32::MAX;\\n for k in i + 1..j {\\n f[i][j] = min(f[i][j], f[i][k] + f[k][j]);\\n }\\n f[i][j] += cuts[j] - cuts[i];\\n }\\n }\\n\\n f[0][m - 1]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"前言 本题和 312. 戳气球 较为相似,都是经典的区间动态规划题。\\n\\n方法一:动态规划\\n\\n思路与算法\\n\\n在我们任意一次切割时,待切割木棍的左端点要么是原始木棍的左端点 $0$,要么是之前某一次切割的位置;同理,待切割木棍的右端点要么是原始木棍的右端点 $n$,要么是之前某一次切割的位置。\\n\\n因此,如果我们将切割位置数组 $\\\\textit{cuts}$ 进行排序,并在左侧添加 $0$,右侧添加 $n$,那么待切割的木棍就对应着数组中一段连续的区间。这样一来,我们就可以用动态规划来解决本题。\\n\\n我们用数组 $\\\\textit{cuts}[1..m…","guid":"https://leetcode.cn/problems/minimum-cost-to-cut-a-stick//solution/qie-gun-zi-de-zui-xiao-cheng-ben-by-leetcode-solut","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-18T03:32:19.388Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ 二分搜索 应该能给你讲明白","url":"https://leetcode.cn/problems/magnetic-force-between-two-balls//solution/c-er-fen-sou-suo-ying-gai-neng-gei-ni-jiang-ming-b","content":"- \\n
\\n时间复杂度:$O(m^3)$,其中 $m$ 是数组 $\\\\textit{cuts}$ 的长度。状态的数量为 $O(m^2)$,转移的时间复杂度为 $O(m)$,相乘即可得到总时间复杂度。此外,将数组 $\\\\textit{cuts}$ 进行排序的时间复杂度以及插入 $0$ 和 $n$ 的时间复杂度在渐进意义下小于 $O(m^3)$,因此可以忽略不计。
\\n- \\n
\\n空间复杂度:$O(m^2)$,即为存储所有状态需要的空间。
\\n思路:二分搜索
\\n题意求最大化最小,类似这样的求最大化最小值、最小化最大值等都可以用二分搜索解决。
\\n实现细节:
\\n首先要找到二分搜索的边界,根据题意,要返回的是最小磁力,所以第一步要找到最小磁力的最小可能取值和最大可能取值。
\\n对于最小可能取值,当然就是给定数组中距离最近的两个位置之间的磁力,所以对数组进行排序,并遍历数组找到相邻两个位置的最小距离。
\\n对于最大可能取值,一共有m个球,所以有
\\nm - 1
个间隔,最大的可能取值便是最平均的取值,所以根据给定数组最大值与最小值之差与间隔数的比值计算出平均距离,就是给定的最大可能取值。这里给定简单证明,假设有 k 个间隔,给定数组规定的篮子间最大距离为 x,那么最小磁力的最大可能取值是
\\nx / k
,假设有某一可能取值 y 大于最大可能取值,那么所有距离都一定大于等于 y,此时假设 k 个间隔距离均为 y,总距离ky > k * x / k = x
,也大于给定的最大距离,所以不成立。确定好了边界后,每次二分搜索时需要判断当前计算值是否满足条件,这里我们引入 check 函数,对当前计算出的最小磁力进行验证。验证过程使用贪心算法,遍历数组,若找到两位置之间距离大于等于最小磁力,则计数值加 1,最后只需要判断总计数值是否大于等于给定间隔数
\\nm - 1
即可。例如,示例 1 中,假设我们当前二分搜索计算出的距离为 2,那么我们遍历数组,假设第一个位置为 1,那么下一个找到的位置应该是 3,因为
\\n3 - 1 >= 2
,计数值加 1;再下面找到的是 7,因为7 - 3 >= 2
,计数值加 1。此时数组遍历完成,总计数值为 2,而给定间隔数m - 1 = 2
,满足条件,说明最小磁力为 2 是可以做到的。但如果我们当前计算出的距离为 4,那么第一个位置为 1,找到的第二个位置就只能是 7,数组遍历完成总计数值为 1,小于给定间隔数,说明最小磁力为 4 是不成立的。在判断计算值满足条件与否之后,我们要对二分搜索边界进行转化,由于题目要求的是最大化的最小磁力,所以若当前计算出的最小磁力满足条件,我们要将左边界右移,去判断稍大一点的数值是否满足条件;若当前计算出的最小磁力不满足条件,我们要将右边界左移,判断稍小的数值是否满足条件。
\\n由于每次满足条件后左边界右移,所以左边界的左边一个数值是一定满足条件的,所以最后返回值为
\\nl - 1
,具体返回值根据边界移动的判定规则进行判断。代码:
\\n\\nclass Solution {\\npublic:\\n bool check(int x, vector<int>& a, int m) {\\n int cnt = 0;\\n int target = a[0] + x;\\n for(int i = 0; i < a.size() - 1; i++) {\\n if(a[i] < target && a[i + 1] >= target) {\\n cnt++;\\n target = a[i + 1] + x;\\n }\\n }\\n return cnt >= m - 1;\\n }\\n \\n int maxDistance(vector<int>& a, int m) {\\n sort(a.begin(), a.end());\\n int len = a.size();\\n int diff = a[len - 1] - a[0]; // 最大间隔\\n int mn = INT_MAX;// 记录最小间隔\\n for(int i = 0; i < len - 1; i++) {\\n if(mn > a[i + 1] - a[i]) {\\n mn = a[i + 1] - a[i];\\n }\\n }\\n if(m == 2) {// 这里特判了m = 2的情况,也可以归到底下的代码中。\\n return diff;\\n } else {\\n int l = mn, r = diff / (m - 1);// 确定左右边界\\n while(l <= r) {// 二分搜索\\n int mid = (l + r) / 2;\\n // printf(\\"l = %d, r = %d, mid = %d\\\\n\\", l, r, mid);\\n if(check(mid, a, m)) {\\n l = mid + 1;\\n } else {\\n r = mid - 1;\\n }\\n }\\n return l - 1;\\n }\\n }\\n};\\n
复杂度分析:
\\n数组排序时间复杂度 $O(NlgN)$,二分搜索复杂度为 $O(lgN)$,每次进行 check 需要遍历数组,复杂度 $O(N)$,所以二分整体复杂度也为 $O(NlgN)$,故时间复杂度为 $O(NlgN)$;
\\n维护了几个变量,空间复杂度为 $O(1)$。
\\n
\\n关注GTAlgorithm,专注周赛、面经题解分享,陪大家一起攻克算法难关~
\\n\\n\\n\\n","description":"思路:二分搜索 题意求最大化最小,类似这样的求最大化最小值、最小化最大值等都可以用二分搜索解决。\\n\\n实现细节:\\n\\n首先要找到二分搜索的边界,根据题意,要返回的是最小磁力,所以第一步要找到最小磁力的最小可能取值和最大可能取值。\\n\\n对于最小可能取值,当然就是给定数组中距离最近的两个位置之间的磁力,所以对数组进行排序,并遍历数组找到相邻两个位置的最小距离。\\n\\n对于最大可能取值,一共有m个球,所以有 m - 1 个间隔,最大的可能取值便是最平均的取值,所以根据给定数组最大值与最小值之差与间隔数的比值计算出平均距离,就是给定的最大可能取值。\\n\\n这里给定简单证明,假设有 k…","guid":"https://leetcode.cn/problems/magnetic-force-between-two-balls//solution/c-er-fen-sou-suo-ying-gai-neng-gei-ni-jiang-ming-b","author":"已注销","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-16T04:19:41.316Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"一文掌握并查集算法","url":"https://leetcode.cn/problems/redundant-connection//solution/yi-wen-zhang-wo-bing-cha-ji-suan-fa-by-a-fei-8","content":"\\n\\n欢迎阅读、点赞、转发、订阅,你的举手之间,我的动力源泉。
\\n\\n
{:width=\\"400px\\"}
定义
\\n\\n\\n并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果,只能用并查集来描述。
\\n并查集是一种树型的数据结构,用于处理一些不相交集合($Disjoint$ $Sets$)的合并及查询问题。常常在使用中以森林来表示。
\\n概念
\\n\\n
\\n- 合并($Union$):把两个不相交的集合合并为一个集合
\\n- 查询($Find$):查询两个元素是否在同一个集合中
\\n伪代码
\\n###python
\\n\\nclass UnionFindSet:\\ndef UnionFindSet(n):\\nparents = [0,1...n] # 记录每个元素的parent即根节点 先将它们的父节点设为自己\\nranks =[0,0...0] # 记录节点的rank值\\n\\n # 如下图 递归版本 路径压缩(Path Compression)\\n # 如果当前的x不是其父节点,就找到当前x的父节点的根节点(find(parents[x])) 并将这个值赋值给x的父节点\\ndef find(x):\\nif ( x !=parents[x]): # 注意这里的if\\nparents[x] = find(parents[x])\\nreturn parents[x]\\n\\n# 如下图 根据Rank来合并(Union by Rank)\\ndef union(x,y):\\nrootX = find(x) # 找到x的根节点rootX\\nrootY = find(y) # 找到y的根节点rootY\\n #取rank值小的那个挂到大的那个节点下面,此时两个根节点的rank值并没有发生变化,还是原来的值\\nif(ranks[rootX]>ranks[rootY]): parents[rootY] = rootX \\nif(ranks[rootX]<ranks[rootY]): parents[rootX] = rootY\\n # 当两个rank值相等时,随便选择一个根节点挂到另外一个跟节点上,但是被挂的那个根节点的rank值需要+1 \\nif(ranks[rootX] == ranks[rootY] ):\\nparents[rootY] = rootX\\nranks[rootX]++\\n
解释
\\n\\n
\\n- $parents[x]$表示的是$x$的父节点,初始化时,有一些初始化写法是$parents[x]$=$x$,表示将$x$的父节点指向自己
\\n\\n\\n非递归版本find(x),如下图
\\n###python
\\n\\ndef find(x):\\nrootX = x # 找到x的根节点\\nwhile (rootX!=parents[rootX]):\\nrootX = parents[rootX]\\ncurr = x # 准备一个curr变量\\nwhile (curr!=rootX):\\nnext = parents[curr] # 暂存curr的父节点\\nparents[curr] = rootX # 将curr节点的父节点设置为rootX\\ncurr = next # curr节点调到下个节点\\n return rootX \\n
\\n
{:width=\\"400px\\"}
根据Rank来合并($Union$ $by$ $Rank$)
\\n\\n
{:width=\\"400px\\"}
路径压缩($Path$ $Compression$)
\\n\\n
{:width=\\"400px\\"}
应用
\\n1.被围绕的区域
\\n\\n
{:width=\\"400px\\"}
思路
\\n\\n
\\n- 准备一个并查集$UnionFindSet$,初始化时,多一个节点设置为哑结点$dummy$
\\n- 因为是二维矩阵的缘故,可以将其坐标转化为一维矩阵,$i * 列数 + j$
\\n- 边缘处的$O$直接与$dummy$ 进行合并
\\n- 非边缘的$O$则需要上下左右四个方向探测,进行合并
\\n- 遍历,当发现当前节点与$dummy$节点的根节点相同,即联通的话,这个点维持不变
\\n并查集
\\n###java
\\n\\nstatic class UnionFindSet {\\n int[] parents;\\n int[] ranks;\\n\\n public UnionFindSet(int n) {\\n parents = new int[n];\\n ranks = new int[n];\\n for (int i = 0; i < n; i++) {\\n parents[i] = i;\\n }\\n }\\n\\n\\n public int find(int x) {\\n if (x != parents[x]) {\\n parents[x] = find(parents[x]);\\n }\\n System.out.println(x + \\":\\" + parents[x]);\\n return parents[x];\\n }\\n\\n public void union(int x, int y) {\\n int rootX = find(x);\\n int rootY = find(y);\\n if (rootX == rootY) return;\\n if (ranks[rootX] > ranks[rootY]) parents[rootY] = rootX;\\n if (ranks[rootX] < ranks[rootY]) parents[rootX] = rootY;\\n if (ranks[rootX] == ranks[rootY]) {\\n parents[rootY] = rootX;\\n ranks[rootX]++;\\n }\\n }\\n }\\n\\n
主体代码
\\n###java
\\n\\nint m, n;\\nint[][] directions = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};\\n\\npublic void solve(char[][] board) {\\n if (board == null || board.length == 0) return;\\n m = board.length;\\n n = board[0].length;\\n int initValue = m * n + 1;\\n UnionFindSet unionFindSet = new UnionFindSet(initValue);\\n int dummy = m * n;\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n if (board[i][j] == \'O\') {\\n if (i == 0 || i == m - 1 || j == 0 || j == n - 1) {\\n unionFindSet.union(node(i, j), dummy);\\n } else {\\n for (int k = 0; k < directions.length; k++) {\\n int nextI = i + directions[k][0];\\n int nextJ = j + directions[k][1];\\n if ((nextI > 0 || nextI < m || nextJ > 0 || nextJ < n) && board[nextI][nextJ] == \'O\') {\\n unionFindSet.union(node(i, j), node(nextI, nextJ));\\n }\\n }\\n }\\n }\\n }\\n }\\n for (int i = 0; i < m; i++) {\\n for (int j = 0; j < n; j++) {\\n if (unionFindSet.find(node(i, j)) == unionFindSet.find(dummy)) {\\n board[i][j] = \'O\';\\n } else {\\n board[i][j] = \'X\';\\n }\\n }\\n }\\n}\\n\\npublic int node(int i, int j) {\\n return i * n + j;\\n}\\n
2.冗余连接
\\n\\n\\n图传上来就没了,有毒,684题:冗余连接
\\n思路
\\n\\n
\\n- 判断节点第一次出现环的边$edge$进行返回,如下图,当$1$的根节点是$4$的时候,从$1->2->3->4$出现一条路径,大概$[1,4]$这个$edge$进来后,发现$1$可以直接指向$4$,这时候出现了环,这条边是冗余边
\\n\\n
{:width=\\"400px\\"}
###java
\\n\\nint[] parents;\\n\\n public int[] findRedundantConnection(int[][] edges) {\\n if (edges == null || edges.length == 0) return new int[]{0, 0};\\n int n = edges.length + 1; //注意此处下标多放一个\\n init(n);\\n for (int[] edge : edges) {\\n int x = edge[0], y = edge[1];\\n if ((!union(x, y))) {//第二次出现了联通的边时,表示已经找到了\\n return edge;\\n }\\n }\\n return new int[]{0, 0};\\n }\\n //初始化parents\\n public void init(int n) {\\n parents = new int[n];\\n for (int i = 0; i < n; i++) {\\n parents[i] = i;\\n }\\n }\\n\\n //递归版路径压缩,找到x的根节点\\n public int find(int x) {\\n if (x != parents[x]) {\\n parents[x] = find(parents[x]);\\n }\\n return parents[x];\\n }\\n\\n //改写union方法,第一次当x与y没有联通时,将其设置联通关系,返回ture\\n //第二次x和y的跟节点发现一致时,他们已经联通了,返回false\\n public boolean union(int x, int y) {\\n int rootX = find(x), rootY = find(y);\\n if (rootX == rootY) return false;\\n parents[rootX] = rootY;\\n return true;\\n }\\n
番外
\\n###java
\\n\\n//非递归版路径压缩\\npublic int find(int x) {\\n int rootX = x;\\n while (rootX != parents[rootX]) {\\n rootX = parents[rootX];\\n }\\n int curr = x;\\n while (curr != rootX) {\\n int next = parents[curr];\\n parents[curr] = rootX;\\n curr = next;\\n }\\n return rootX;\\n}\\n
推荐阅读
\\n推荐阅读
\\n\\n\\n
\\n\\n \\n\\n\\n题号 \\n链接 \\n\\n \\n130/684 \\n一文掌握并查集算法 \\n\\n \\n\\n 一文掌握Morris遍历算法 \\n\\n \\n200/- \\n岛屿问题之岛屿的数量[Eighty-eight Butterfly] \\n\\n \\n493/695 \\n岛屿问题之岛屿的周长面积[Morpho Cypris Aphrodite] \\n\\n \\n130 \\n岛屿问题之被围绕的区域[Cicada] \\n\\n \\n\\n-/- \\n岛屿问题之不同岛屿的数量[Monarch Butterfly] \\nReference
\\n\\n
\\n- 参考资料
\\n- 视频资料
\\n
\\n番外:
\\n最近阿飞把链接做成了脑图(下图),整理起来,会做成PDF,感兴趣的同学关注下,不迷路,个人主页【阿飞算法】 ,关注公众号会弹出资料下载地址,每个分支都可以点击链接跳转,欢迎入群交流学习。
\\n\\n","description":"欢迎阅读、点赞、转发、订阅,你的举手之间,我的动力源泉。 {:width=\\"400px\\"}\\n\\n定义\\n\\n并查集,在一些有N个元素的集合应用问题中,我们通常是在开始时让每个元素构成一个单元素的集合,然后按一定顺序将属于同一组的元素所在的集合合并,其间要反复查找一个元素在哪个集合中。这一类问题近几年来反复出现在信息学的国际国内赛题中,其特点是看似并不复杂,但数据量极大,若用正常的数据结构来描述的话,往往在空间上过大,计算机无法承受;即使在空间上勉强通过,运行的时间复杂度也极高,根本就不可能在比赛规定的运行时间(1~3秒)内计算出试题需要的结果…","guid":"https://leetcode.cn/problems/redundant-connection//solution/yi-wen-zhang-wo-bing-cha-ji-suan-fa-by-a-fei-8","author":"a-fei-8","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-12T11:57:21.078Z","media":[{"url":"https://pic.leetcode-cn.com/0e3a0a427798bf9fb7a23aa3247b225d8c85e94e6b56eeecaa724092ed84ee48-onepiece-5087720_640.jpg","type":"photo","width":640,"height":360,"blurhash":"LPPWXS-A{|$*t8jur?j[|@bHKOjY"},{"url":"https://pic.leetcode-cn.com/d46b363bc0ae3720c4ec3b8a9f3e662e38cad056d328d0e355fb3b99d1e92abf-image-20200812094204053.png","type":"photo","width":686,"height":271,"blurhash":"LCSPOs-pcs_3~C%M%#t7W;xuozNG"},{"url":"https://pic.leetcode-cn.com/7e92ac550b58af7d5c5f1bb602db0a5b61a9f1a8465e899b050b5293d8a509d3-image-20200812090131209.png","type":"photo","width":880,"height":562,"blurhash":"LFSPU._MtS^+-qogX7WAbHoLtRtR"},{"url":"https://pic.leetcode-cn.com/3558d619eef6a83d84f5002c3b9b0a4e82ea2d6a2e55d9e0c2699c54dec54bac-image-20200812090146844.png","type":"photo","width":591,"height":325,"blurhash":"LBSiaA.8%g~W~C%gyDQ-R5t7tlR5"},{"url":"https://pic.leetcode-cn.com/886d3eec96eab2ef7554d737353f027624b0aa9fd591a92f7aadcb4bc1378cbd-image-20200812200206957.png","type":"photo","width":654,"height":435,"blurhash":"L8SF;L?bRj~q~qaeofayoff6j[f6"},{"url":"https://pic.leetcode-cn.com/0f7f4da738dedbce5848b883f9a603b9920ab74ef1fd3cf26b484d77dd7a4843-image-20200812192751553.png","type":"photo","width":609,"height":248,"blurhash":"L7O:;O~qS4~W_No}oMoLtRVts:Sd"},{"url":"https://pic.leetcode-cn.com/1630892220-MyzuIE-%E9%98%BF%E9%A3%9E%E7%AE%97%E6%B3%95.png","type":"photo","width":2992,"height":14770,"blurhash":"LFS$ie_3jZ_4_3V@ogt7R+Rjt7f8"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计好三元组","url":"https://leetcode.cn/problems/count-good-triplets//solution/tong-ji-hao-san-yuan-zu-by-leetcode-solution","content":"
方法一:枚举
\\n思路与算法
\\n用 $O(n^3)$ 的循环依次枚举所有的 $(i, j, k)$,这里 $0 \\\\leq i < j < k < {\\\\rm arr.length}$,对于每组 $(i, j, k)$,判断 ${\\\\rm arr}[i]$、${\\\\rm arr}[j]$、${\\\\rm arr}[k]$ 是否满足条件。
\\n最终统计出所有满足条件的三元组的数量。
\\n代码
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vector<int>& arr, int a, int b, int c) {\\n int n = arr.size(), cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n for (int j = i + 1; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (abs(arr[i] - arr[j]) <= a && abs(arr[j] - arr[k]) <= b && abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int countGoodTriplets(int[] arr, int a, int b, int c) {\\n int n = arr.length, cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n for (int j = i + 1; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (Math.abs(arr[i] - arr[j]) <= a && Math.abs(arr[j] - arr[k]) <= b && Math.abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n }\\n}\\n
###JavaScript
\\n\\nvar countGoodTriplets = function(arr, a, b, c) {\\n const n = arr.length;\\n let cnt = 0;\\n for (let i = 0; i < n; ++i) {\\n for (let j = i + 1; j < n; ++j) {\\n for (let k = j + 1; k < n; ++k) {\\n if (Math.abs(arr[i] - arr[j]) <= a && Math.abs(arr[j] - arr[k]) <= b && Math.abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n};\\n
###Python
\\n\\nclass Solution:\\n def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int:\\n n = len(arr)\\n cnt = 0\\n for i in range(n):\\n for j in range(i + 1, n):\\n for k in range(j + 1, n):\\n if abs(arr[i] - arr[j]) <= a and abs(arr[j] - arr[k]) <= b and abs(arr[i] - arr[k]) <= c:\\n cnt += 1\\n return cnt\\n
###C#
\\n\\npublic class Solution {\\n public int CountGoodTriplets(int[] arr, int a, int b, int c) {\\n int n = arr.Length, cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n for (int j = i + 1; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (Math.Abs(arr[i] - arr[j]) <= a && Math.Abs(arr[j] - arr[k]) <= b && Math.Abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n }\\n}\\n
###Go
\\n\\nfunc countGoodTriplets(arr []int, a int, b int, c int) int {\\n n := len(arr)\\ncnt := 0\\nfor i := 0; i < n; i++ {\\nfor j := i + 1; j < n; j++ {\\nfor k := j + 1; k < n; k++ {\\nif abs(arr[i] - arr[j]) <= a && abs(arr[j] - arr[k]) <= b && abs(arr[i]-arr[k]) <= c {\\ncnt++\\n}\\n}\\n}\\n}\\nreturn cnt\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
###C
\\n\\nint countGoodTriplets(int* arr, int arrSize, int a, int b, int c) {\\n int cnt = 0;\\n for (int i = 0; i < arrSize; ++i) {\\n for (int j = i + 1; j < arrSize; ++j) {\\n for (int k = j + 1; k < arrSize; ++k) {\\n if (abs(arr[i] - arr[j]) <= a && abs(arr[j] - arr[k]) <= b && abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n}\\n
###TypeScript
\\n\\nfunction countGoodTriplets(arr: number[], a: number, b: number, c: number): number {\\n let n = arr.length, cnt = 0;\\n for (let i = 0; i < n; ++i) {\\n for (let j = i + 1; j < n; ++j) {\\n for (let k = j + 1; k < n; ++k) {\\n if (Math.abs(arr[i] - arr[j]) <= a && Math.abs(arr[j] - arr[k]) <= b && Math.abs(arr[i] - arr[k]) <= c) {\\n ++cnt;\\n }\\n }\\n }\\n }\\n return cnt;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn count_good_triplets(arr: Vec<i32>, a: i32, b: i32, c: i32) -> i32 {\\n let mut cnt = 0;\\n let n = arr.len();\\n for i in 0..n {\\n for j in i + 1..n {\\n for k in j + 1..n {\\n if (arr[i] - arr[j]).abs() <= a && (arr[j] - arr[k]).abs() <= b && (arr[i] - arr[k]).abs() <= c {\\n cnt += 1;\\n }\\n }\\n }\\n }\\n cnt\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\\\\textit{arr}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:枚举优化
\\n思路与算法
\\n我们考虑 $O(n^2)$ 枚举满足 $|\\\\rm arr[j]-\\\\rm arr[k]|\\\\le b$ 的二元组 $(j,k)$,统计这个二元组下有多少 $i$ 满足条件。由题目已知 $i$ 的限制条件为 $|\\\\rm arr[i]-\\\\rm arr[j]|\\\\le a \\\\ &&\\\\ |\\\\rm arr[i]-\\\\rm arr[k]|\\\\le c$,我们可以拆开绝对值,得到符合条件的值一定是 $[\\\\rm arr[j]-a,\\\\rm arr[j]+a]$ 和 $[\\\\rm arr[k]-c,\\\\rm arr[k]+c]$ 两个区间的交集,我们记为 $[l,r]$。因此,在枚举 $(j,k)$ 这个二元组的时候,我们只需要快速统计出满足 $i<j$ 且 $\\\\rm arr[i]$ 的值域范围在 $[l,r]$ 的 $i$ 的个数即可。
\\n很容易想到维护一个 $\\\\rm arr[i]$ 频次数组的前缀和 $\\\\rm sum$,对于一个二元组 $(j,k)$,我们可以 $O(1)$ 得到答案为 $\\\\rm sum[r]-\\\\rm sum[l-1]$。考虑怎么维护保证当前频次数组存的数的下标符合 $i<j$ 的限制,我们只要从小到大枚举 $j$,每次 $j$ 移动指针加一的时候,将 $\\\\rm arr[j]$ 的值更新到 $\\\\rm sum$ 数组中即可,这样能保证枚举到 $j$ 的时候 $\\\\rm sum$ 数组里存的值的下标满足限制。
\\n「将 $\\\\rm arr[j]$ 的值更新到 $\\\\rm sum$ 数组中」这个操作在本方法中是暴力更新,因为数组的值域上限很小,有能力的读者可以考虑怎么在进一步优化这一部分的复杂度,可以从离散化或者树状数组的角度考虑,这里不再赘述。
\\n代码
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vector<int>& arr, int a, int b, int c) {\\n int ans = 0, n = arr.size();\\n vector<int> sum(1001, 0);\\n for (int j = 0; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (abs(arr[j] - arr[k]) <= b) {\\n int lj = arr[j] - a, rj = arr[j] + a;\\n int lk = arr[k] - c, rk = arr[k] + c;\\n int l = max(0, max(lj, lk)), r = min(1000, min(rj, rk));\\n if (l <= r) {\\n if (l == 0) {\\n ans += sum[r];\\n }\\n else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int countGoodTriplets(int[] arr, int a, int b, int c) {\\n int ans = 0, n = arr.length;\\n int[] sum = new int[1001];\\n for (int j = 0; j < n; ++j) {\\n for (int k = j + 1 ; k < n; ++k) {\\n if (Math.abs(arr[j] - arr[k]) <= b) {\\n int lj = arr[j] - a, rj = arr[j] + a;\\n int lk = arr[k] - c, rk = arr[k] + c;\\n int l = Math.max(0, Math.max(lj, lk)), r = Math.min(1000, Math.min(rj, rk));\\n if (l <= r) {\\n if (l == 0) {\\n ans += sum[r];\\n }\\n else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n }\\n return ans;\\n }\\n}\\n
###JavaScript
\\n\\nvar countGoodTriplets = function(arr, a, b, c) {\\n const n = arr.length, sum = new Array(1001).fill(0);\\n let ans = 0;\\n for (let j = 0; j < n; ++j) {\\n for (let k = j + 1; k < n; ++k) {\\n if (Math.abs(arr[j] - arr[k]) <= b) {\\n const lj = arr[j] - a, rj = arr[j] + a;\\n const lk = arr[k] - c, rk = arr[k] + c;\\n const l = Math.max(0, Math.max(lj, lk)), r = Math.min(1000, Math.min(rj, rk));\\n if (l <= r) {\\n if (l === 0) {\\n ans += sum[r];\\n }\\n else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (let k = arr[j]; k <= 1000; ++k) {\\n sum[k] += 1;\\n }\\n }\\n return ans;\\n};\\n
###Python
\\n\\nclass Solution:\\n def countGoodTriplets(self, arr: List[int], a: int, b: int, c: int) -> int:\\n ans = 0\\n n = len(arr)\\n total = [0] * 1001\\n for j in range(n):\\n for k in range(j + 1, n):\\n if abs(arr[j] - arr[k]) <= b:\\n lj, rj = arr[j] - a, arr[j] + a\\n lk, rk = arr[k] - c, arr[k] + c\\n l = max(0, lj, lk)\\n r = min(1000, rj, rk)\\n if l <= r:\\n ans += total[r] if l == 0 else total[r] - total[l - 1]\\n for k in range(arr[j], 1001):\\n total[k] += 1\\n \\n return ans\\n
###C#
\\n\\npublic class Solution {\\n public int CountGoodTriplets(int[] arr, int a, int b, int c) {\\n int ans = 0, n = arr.Length;\\n int[] sum = new int[1001];\\n for (int j = 0; j < n; ++j) {\\n for (int k = j + 1; k < n; ++k) {\\n if (Math.Abs(arr[j] - arr[k]) <= b) {\\n int lj = arr[j] - a, rj = arr[j] + a;\\n int lk = arr[k] - c, rk = arr[k] + c;\\n int l = Math.Max(0, Math.Max(lj, lk)), r = Math.Min(1000, Math.Min(rj, rk));\\n if (l <= r) {\\n if (l == 0) {\\n ans += sum[r];\\n } else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc countGoodTriplets(arr []int, a int, b int, c int) int {\\n ans := 0\\nn := len(arr)\\nsum := make([]int, 1001)\\nfor j := 0; j < n; j++ {\\nfor k := j + 1; k < n; k++ {\\nif abs(arr[j] - arr[k]) <= b {\\nlj, rj := arr[j] - a, arr[j] + a\\nlk, rk := arr[k] - c, arr[k] + c\\nl := max(0, max(lj, lk))\\nr := min(1000, min(rj, rk))\\nif l <= r {\\nif l == 0 {\\nans += sum[r]\\n} else {\\nans += sum[r] - sum[l-1]\\n}\\n}\\n}\\n}\\nfor k := arr[j]; k <= 1000; k++ {\\nsum[k]++\\n}\\n}\\nreturn ans\\n}\\n\\nfunc abs(x int) int {\\n if x < 0 {\\n return -x\\n }\\n return x\\n}\\n
###C
\\n\\nint countGoodTriplets(int* arr, int arrSize, int a, int b, int c) {\\n int ans = 0;\\n int sum[1001] = {0};\\n for (int j = 0; j < arrSize; ++j) {\\n for (int k = j + 1; k < arrSize; ++k) {\\n if (abs(arr[j] - arr[k]) <= b) {\\n int lj = arr[j] - a, rj = arr[j] + a;\\n int lk = arr[k] - c, rk = arr[k] + c;\\n int l = fmax(0, fmax(lj, lk)), r = fmin(1000, fmin(rj, rk));\\n if (l <= r) {\\n if (l == 0) {\\n ans += sum[r];\\n } else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (int k = arr[j]; k <= 1000; ++k) {\\n ++sum[k];\\n }\\n }\\n return ans;\\n}\\n
###TypeScript
\\n\\nfunction countGoodTriplets(arr: number[], a: number, b: number, c: number): number {\\n let ans = 0;\\n const n = arr.length;\\n const sum = new Array(1001).fill(0);\\n for (let j = 0; j < n; ++j) {\\n for (let k = j + 1; k < n; ++k) {\\n if (Math.abs(arr[j] - arr[k]) <= b) {\\n const lj = arr[j] - a, rj = arr[j] + a;\\n const lk = arr[k] - c, rk = arr[k] + c;\\n const l = Math.max(0, Math.max(lj, lk)), r = Math.min(1000, Math.min(rj, rk));\\n if (l <= r) {\\n if (l === 0) {\\n ans += sum[r];\\n } else {\\n ans += sum[r] - sum[l - 1];\\n }\\n }\\n }\\n }\\n for (let k = arr[j]; k <= 1000; ++k) {\\n sum[k]++;\\n }\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::cmp::{max, min};\\n\\nimpl Solution {\\n pub fn count_good_triplets(arr: Vec<i32>, a: i32, b: i32, c: i32) -> i32 {\\n let mut ans = 0;\\n let n = arr.len();\\n let mut sum = vec![0; 1001];\\n for j in 0..n {\\n for k in j + 1..n {\\n if (arr[j] - arr[k]).abs() <= b {\\n let lj = arr[j] - a;\\n let rj = arr[j] + a;\\n let lk = arr[k] - c;\\n let rk = arr[k] + c;\\n let l = max(0, max(lj, lk));\\n let r = min(1000, min(rj, rk));\\n if l <= r {\\n if l == 0 {\\n ans += sum[r as usize];\\n } else {\\n ans += sum[r as usize] - sum[(l - 1) as usize];\\n }\\n }\\n }\\n }\\n for k in arr[j]..=1000 {\\n sum[k as usize] += 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举 思路与算法\\n\\n用 $O(n^3)$ 的循环依次枚举所有的 $(i, j, k)$,这里 $0 \\\\leq i < j < k < {\\\\rm arr.length}$,对于每组 $(i, j, k)$,判断 ${\\\\rm arr}[i]$、${\\\\rm arr}[j]$、${\\\\rm arr}[k]$ 是否满足条件。\\n\\n最终统计出所有满足条件的三元组的数量。\\n\\n代码\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int countGoodTriplets(vector- \\n
\\n时间复杂度:$O(n^2+nS)$,其中 $n$ 是数组 $\\\\textit{arr}$ 的长度,$S$ 为数组的值域上限,这里为 $1000$。
\\n- \\n
\\n空间复杂度:$O(S)$。我们需要 $O(S)$ 的空间维护 $\\\\rm arr[i]$ 频次数组的前缀和。
\\n& arr, int a, int b, int c)…","guid":"https://leetcode.cn/problems/count-good-triplets//solution/tong-ji-hao-san-yuan-zu-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-08-12T03:04:31.018Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最小区间","url":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/zui-xiao-qu-jian-by-leetcode-solution","content":" 方法一:贪心 + 最小堆
\\n给定 $k$ 个列表,需要找到最小区间,使得每个列表都至少有一个数在该区间中。该问题可以转化为,从 $k$ 个列表中各取一个数,使得这 $k$ 个数中的最大值与最小值的差最小。
\\n假设这 $k$ 个数中的最小值是第 $i$ 个列表中的 $x$,对于任意 $j \\\\ne i$,设第 $j$ 个列表中被选为 $k$ 个数之一的数是 $y$,则为了找到最小区间,$y$ 应该取第 $j$ 个列表中大于等于 $x$ 的最小的数,这是一个贪心的策略。贪心策略的正确性简单证明如下:假设 $z$ 也是第 $j$ 个列表中的数,且 $z>y$,则有 $z-x>y-x$,同时包含 $x$ 和 $z$ 的区间一定不会小于同时包含 $x$ 和 $y$ 的区间。因此,其余 $k-1$ 个列表中应该取大于等于 $x$ 的最小的数。
\\n由于 $k$ 个列表都是升序排列的,因此对每个列表维护一个指针,通过指针得到列表中的元素,指针右移之后指向的元素一定大于或等于之前的元素。
\\n使用最小堆维护 $k$ 个指针指向的元素中的最小值,同时维护堆中元素的最大值。初始时,$k$ 个指针都指向下标 $0$,最大元素即为所有列表的下标 $0$ 位置的元素中的最大值。每次从堆中取出最小值,根据最大值和最小值计算当前区间,如果当前区间小于最小区间则用当前区间更新最小区间,然后将对应列表的指针右移,将新元素加入堆中,并更新堆中元素的最大值。
\\n如果一个列表的指针超出该列表的下标范围,则说明该列表中的所有元素都被遍历过,堆中不会再有该列表中的元素,因此退出循环。
\\n###Java
\\n\\nclass Solution {\\n public int[] smallestRange(List<List<Integer>> nums) {\\n int rangeLeft = 0, rangeRight = Integer.MAX_VALUE;\\n int minRange = rangeRight - rangeLeft;\\n int max = Integer.MIN_VALUE;\\n int size = nums.size();\\n int[] next = new int[size];\\n PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(new Comparator<Integer>() {\\n public int compare(Integer index1, Integer index2) {\\n return nums.get(index1).get(next[index1]) - nums.get(index2).get(next[index2]);\\n }\\n });\\n for (int i = 0; i < size; i++) {\\n priorityQueue.offer(i);\\n max = Math.max(max, nums.get(i).get(0));\\n }\\n while (true) {\\n int minIndex = priorityQueue.poll();\\n int curRange = max - nums.get(minIndex).get(next[minIndex]);\\n if (curRange < minRange) {\\n minRange = curRange;\\n rangeLeft = nums.get(minIndex).get(next[minIndex]);\\n rangeRight = max;\\n }\\n next[minIndex]++;\\n if (next[minIndex] == nums.get(minIndex).size()) {\\n break;\\n }\\n priorityQueue.offer(minIndex);\\n max = Math.max(max, nums.get(minIndex).get(next[minIndex]));\\n }\\n return new int[]{rangeLeft, rangeRight};\\n }\\n}\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n vector<int> smallestRange(vector<vector<int>>& nums) {\\n int rangeLeft = 0, rangeRight = INT_MAX;\\n int size = nums.size();\\n vector<int> next(size);\\n \\n auto cmp = [&](const int& u, const int& v) {\\n return nums[u][next[u]] > nums[v][next[v]];\\n };\\n priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);\\n int minValue = 0, maxValue = INT_MIN;\\n for (int i = 0; i < size; ++i) {\\n pq.emplace(i);\\n maxValue = max(maxValue, nums[i][0]);\\n }\\n\\n while (true) {\\n int row = pq.top();\\n pq.pop();\\n minValue = nums[row][next[row]];\\n if (maxValue - minValue < rangeRight - rangeLeft) {\\n rangeLeft = minValue;\\n rangeRight = maxValue;\\n }\\n if (next[row] == nums[row].size() - 1) {\\n break;\\n }\\n ++next[row];\\n maxValue = max(maxValue, nums[row][next[row]]);\\n pq.emplace(row);\\n }\\n\\n return {rangeLeft, rangeRight};\\n }\\n};\\n
###golang
\\n\\nvar (\\n next []int\\n numsC [][]int\\n)\\n\\nfunc smallestRange(nums [][]int) []int {\\n numsC = nums\\n rangeLeft, rangeRight := 0, math.MaxInt32\\n minRange := rangeRight - rangeLeft\\n max := math.MinInt32\\n size := len(nums)\\n next = make([]int, size)\\n h := &IHeap{}\\n heap.Init(h)\\n\\n for i := 0; i < size; i++ {\\n heap.Push(h, i)\\n max = Max(max, nums[i][0])\\n }\\n\\n for {\\n minIndex := heap.Pop(h).(int)\\n curRange := max - nums[minIndex][next[minIndex]]\\n if curRange < minRange {\\n minRange = curRange\\n rangeLeft, rangeRight = nums[minIndex][next[minIndex]], max\\n }\\n next[minIndex]++\\n if next[minIndex] == len(nums[minIndex]) {\\n break\\n }\\n heap.Push(h, minIndex)\\n max = Max(max, nums[minIndex][next[minIndex]])\\n }\\n return []int{rangeLeft, rangeRight}\\n}\\n\\ntype IHeap []int\\n\\nfunc (h IHeap) Len() int { return len(h) }\\nfunc (h IHeap) Less(i, j int) bool { return numsC[h[i]][next[h[i]]] < numsC[h[j]][next[h[j]]] }\\nfunc (h IHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }\\n\\nfunc (h *IHeap) Push(x interface{}) {\\n *h = append(*h, x.(int))\\n}\\n\\nfunc (h *IHeap) Pop() interface{} {\\n old := *h\\n n := len(old)\\n x := old[n-1]\\n *h = old[0 : n-1]\\n return x\\n}\\n\\nfunc Max(x, y int) int {\\n if x > y {\\n return x\\n }\\n return y\\n}\\n
###Python
\\n\\nclass Solution:\\n def smallestRange(self, nums: List[List[int]]) -> List[int]:\\n rangeLeft, rangeRight = -10**9, 10**9\\n maxValue = max(vec[0] for vec in nums)\\n priorityQueue = [(vec[0], i, 0) for i, vec in enumerate(nums)]\\n heapq.heapify(priorityQueue)\\n\\n while True:\\n minValue, row, idx = heapq.heappop(priorityQueue)\\n if maxValue - minValue < rangeRight - rangeLeft:\\n rangeLeft, rangeRight = minValue, maxValue\\n if idx == len(nums[row]) - 1:\\n break\\n maxValue = max(maxValue, nums[row][idx + 1])\\n heapq.heappush(priorityQueue, (nums[row][idx + 1], row, idx + 1))\\n \\n return [rangeLeft, rangeRight]\\n
###C
\\n\\n#define maxn 100005\\n\\nint heap[maxn];\\nint heap_count;\\nint **rec, *nx;\\n\\nbool heap_comp(int *first, int *second) {\\n return rec[*first][nx[*first]] < rec[*second][nx[*second]];\\n}\\n\\nvoid swap(int *first, int *second) {\\n int temp = *second;\\n *second = *first;\\n *first = temp;\\n return;\\n}\\n\\nvoid push(int num) {\\n int pos = ++heap_count;\\n heap[pos] = num;\\n while (pos > 1) {\\n if (heap_comp(&heap[pos], &heap[pos >> 1])) {\\n swap(&heap[pos], &heap[pos >> 1]);\\n pos >>= 1;\\n } else\\n break;\\n }\\n return;\\n}\\n\\nvoid pop() {\\n int top_num = 1;\\n int now;\\n swap(&heap[top_num], &heap[heap_count--]);\\n while ((now = (top_num << 1)) <= heap_count) {\\n if (heap_comp(&heap[now + 1], &heap[now]) && now < heap_count) now++;\\n if (heap_comp(&heap[now], &heap[top_num])) {\\n swap(&heap[top_num], &heap[now]);\\n top_num = now;\\n } else\\n break;\\n }\\n}\\n\\nint top() { return heap[1]; }\\n\\nint *smallestRange(int **nums, int numsSize, int *numsColSize,\\n int *returnSize) {\\n heap_count = 0;\\n nx = (int *)malloc(sizeof(int) * numsSize);\\n memset(nx, 0, sizeof(int) * numsSize);\\n rec = nums;\\n\\n int rangeLeft = 0, rangeRight = 2147483647;\\n int minValue = 0, maxValue = -2147483648;\\n for (int i = 0; i < numsSize; ++i) {\\n push(i);\\n maxValue = fmax(maxValue, nums[i][0]);\\n }\\n\\n while (true) {\\n int row = top();\\n pop();\\n minValue = nums[row][nx[row]];\\n if (maxValue - minValue < rangeRight - rangeLeft) {\\n rangeLeft = minValue;\\n rangeRight = maxValue;\\n }\\n if (nx[row] == numsColSize[row] - 1) {\\n break;\\n }\\n ++nx[row];\\n maxValue = fmax(maxValue, nums[row][nx[row]]);\\n push(row);\\n }\\n int *ret = malloc(sizeof(int) * 2);\\n ret[0] = rangeLeft, ret[1] = rangeRight;\\n *returnSize = 2;\\n return ret;\\n}\\n
###JavaScript
\\n\\nvar smallestRange = function(nums) {\\n let rangeLeft = 0, rangeRight = Number.MAX_SAFE_INTEGER;\\n const size = nums.length;\\n const next = new Array(size).fill(0);\\n const pq = new MinPriorityQueue();\\n let minValue = 0, maxValue = Number.MIN_SAFE_INTEGER;\\n\\n for (let i = 0; i < size; ++i) {\\n pq.enqueue(i, nums[i][next[i]]);\\n maxValue = Math.max(maxValue, nums[i][0]);\\n }\\n\\n while (true) {\\n const row = pq.dequeue().element;\\n minValue = nums[row][next[row]];\\n if (maxValue - minValue < rangeRight - rangeLeft) {\\n rangeLeft = minValue;\\n rangeRight = maxValue;\\n }\\n if (next[row] === nums[row].length - 1) {\\n break;\\n }\\n ++next[row];\\n maxValue = Math.max(maxValue, nums[row][next[row]]);\\n pq.enqueue(row, nums[row][next[row]]);\\n }\\n\\n return [rangeLeft, rangeRight];\\n};\\n
###TypeScript
\\n\\nfunction smallestRange(nums: number[][]): number[] {\\n let rangeLeft = 0, rangeRight = Number.MAX_SAFE_INTEGER;\\n const size = nums.length;\\n const next: number[] = new Array(size).fill(0);\\n\\n const pq = new MinPriorityQueue();\\n let minValue = 0, maxValue = Number.MIN_SAFE_INTEGER;\\n\\n for (let i = 0; i < size; ++i) {\\n pq.enqueue(i, nums[i][next[i]]);\\n maxValue = Math.max(maxValue, nums[i][0]);\\n }\\n\\n while (true) {\\n const row = pq.dequeue().element;\\n minValue = nums[row][next[row]];\\n if (maxValue - minValue < rangeRight - rangeLeft) {\\n rangeLeft = minValue;\\n rangeRight = maxValue;\\n }\\n if (next[row] === nums[row].length - 1) {\\n break;\\n }\\n ++next[row];\\n maxValue = Math.max(maxValue, nums[row][next[row]]);\\n pq.enqueue(row, nums[row][next[row]]);\\n }\\n\\n return [rangeLeft, rangeRight];\\n};\\n
###Rust
\\n\\nuse std::cmp::Ordering;\\nuse std::collections::BinaryHeap;\\n\\nimpl Solution {\\n pub fn smallest_range(nums: Vec<Vec<i32>>) -> Vec<i32> {\\n let mut range_left = 0;\\n let mut range_right = i32::MAX;\\n let size = nums.len();\\n let mut next = vec![0; size];\\n let mut max_value = i32::MIN;\\n let mut pq = BinaryHeap::new();\\n\\n for i in 0..size {\\n max_value = max_value.max(nums[i][0]);\\n pq.push(std::cmp::Reverse((nums[i][0], i)));\\n }\\n\\n while let Some(std::cmp::Reverse((min_value, row))) = pq.pop() {\\n if max_value - min_value < range_right - range_left {\\n range_left = min_value;\\n range_right = max_value;\\n }\\n if next[row] == nums[row].len() - 1 {\\n break;\\n }\\n next[row] += 1;\\n max_value = max_value.max(nums[row][next[row]]);\\n pq.push(std::cmp::Reverse((nums[row][next[row]], row)));\\n }\\n\\n vec![range_left, range_right]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(nk \\\\log k)$,其中 $n$ 是所有列表的平均长度,$k$ 是列表数量。所有的指针移动的总次数最多是 $nk$ 次,每次从堆中取出元素和添加元素都需要更新堆,时间复杂度是 $O(\\\\log k)$,因此总时间复杂度是 $O(nk \\\\log k)$。
\\n- \\n
\\n空间复杂度:$O(k)$,其中 $k$ 是列表数量。空间复杂度取决于堆的大小,堆中维护 $k$ 个元素。
\\n方法二:哈希表 + 滑动窗口
\\n思路
\\n在讲这个方法之前我们先思考这样一个问题:有一个序列 $A = { a_1, a_2, \\\\cdots, a_n }$ 和一个序列 $B = {b_1, b_2, \\\\cdots, b_m}$,请找出一个 $B$ 中的一个最小的区间,使得在这个区间中 $A$ 序列的每个数字至少出现一次,请注意 $A$ 中的元素可能重复,也就是说如果 $A$ 中有 $p$ 个 $u$,那么你选择的这个区间中 $u$ 的个数一定不少于 $p$。没错,这就是我们五月份的一道打卡题:「76. 最小覆盖子串」。官方题解使用了一种滑动窗口的方法,遍历整个 $B$ 序列并用一个哈希表表示当前窗口中的元素:
\\n\\n
\\n- 右边界在每次遍历到新元素的时候右移,同时将拓展到的新元素加入哈希表;
\\n- 左边界右移当且仅当当前区间为一个合法的答案区间,即当前窗口内的元素包含 $A$ 中所有的元素,同时将原来左边界指向的元素从哈希表中移除;
\\n- 答案更新当且仅当当前窗口内的元素包含 $A$ 中所有的元素。
\\n如果这个地方不理解,可以参考「76. 最小覆盖子串的官方题解」。
\\n回到这道题,我们发现这两道题的相似之处在于都要求我们找到某个符合条件的最小区间,我们可以借鉴「76. 最小覆盖子串」的做法:这里序列 ${ 0, 1, \\\\cdots , k - 1 }$ 就是上面描述的 $A$ 序列,即 $k$ 个列表,我们需要在一个 $B$ 序列当中找到一个区间,可以覆盖 $A$ 序列。这里的 $B$ 序列是什么?我们可以用一个哈希映射来表示 $B$ 序列—— $B[i]$ 表示 $i$ 在哪些列表当中出现过,这里哈希映射的键是一个整数,表示列表中的某个数值,哈希映射的值是一个数组,这个数组里的元素代表当前的键出现在哪些列表里。也许文字表述比较抽象,大家可以结合下面这个例子来理解。
\\n\\n
\\n- 如果列表集合为:
\\n\\n0: [-1, 2, 3]\\n1: [1]\\n2: [1, 2]\\n3: [1, 1, 3]\\n
- 那么可以得到这样一个哈希映射
\\n\\n-1: [0]\\n 1: [1, 2, 3, 3]\\n 2: [0, 2]\\n 3: [0, 3]\\n
我们得到的这个哈希映射就是这里的 $B$ 序列。我们要做的就是在 $B$ 序列上使用两个指针维护一个滑动窗口,并用一个哈希表维护当前窗口中已经包含了哪些列表中的元素,记录它们的索引。遍历 $B$ 序列的每一个元素:
\\n\\n
\\n- 指向窗口右边界的指针右移当且仅当每次遍历到新的元素,并将这个新的元素对应的值数组中的每一个数加入到哈希表中;
\\n- 指向窗口左边界的指针右移当且仅当当前区间内的元素包含 $A$ 中所有的元素,同时将原来左边界对应的值数组的元素们从哈希表中移除;
\\n- 答案更新当且仅当当前窗口内的元素包含 $A$ 中所有的元素。
\\n大家可以参考代码理解这个过程。
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public int[] smallestRange(List<List<Integer>> nums) {\\n int size = nums.size();\\n Map<Integer, List<Integer>> indices = new HashMap<Integer, List<Integer>>();\\n int xMin = Integer.MAX_VALUE, xMax = Integer.MIN_VALUE;\\n for (int i = 0; i < size; i++) {\\n for (int x : nums.get(i)) {\\n List<Integer> list = indices.getOrDefault(x, new ArrayList<Integer>());\\n list.add(i);\\n indices.put(x, list);\\n xMin = Math.min(xMin, x);\\n xMax = Math.max(xMax, x);\\n }\\n }\\n\\n int[] freq = new int[size];\\n int inside = 0;\\n int left = xMin, right = xMin - 1;\\n int bestLeft = xMin, bestRight = xMax;\\n\\n while (right < xMax) {\\n right++;\\n if (indices.containsKey(right)) {\\n for (int x : indices.get(right)) {\\n freq[x]++;\\n if (freq[x] == 1) {\\n inside++;\\n }\\n }\\n while (inside == size) {\\n if (right - left < bestRight - bestLeft) {\\n bestLeft = left;\\n bestRight = right;\\n }\\n if (indices.containsKey(left)) {\\n for (int x: indices.get(left)) {\\n freq[x]--;\\n if (freq[x] == 0) {\\n inside--;\\n }\\n }\\n }\\n left++;\\n }\\n }\\n }\\n\\n return new int[]{bestLeft, bestRight};\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> smallestRange(vector<vector<int>>& nums) {\\n int n = nums.size();\\n unordered_map<int, vector<int>> indices;\\n int xMin = INT_MAX, xMax = INT_MIN;\\n for (int i = 0; i < n; ++i) {\\n for (const int& x: nums[i]) {\\n indices[x].push_back(i);\\n xMin = min(xMin, x);\\n xMax = max(xMax, x);\\n }\\n }\\n\\n vector<int> freq(n);\\n int inside = 0;\\n int left = xMin, right = xMin - 1;\\n int bestLeft = xMin, bestRight = xMax;\\n\\n while (right < xMax) {\\n ++right;\\n if (indices.count(right)) {\\n for (const int& x: indices[right]) {\\n ++freq[x];\\n if (freq[x] == 1) {\\n ++inside;\\n }\\n }\\n while (inside == n) {\\n if (right - left < bestRight - bestLeft) {\\n bestLeft = left;\\n bestRight = right;\\n }\\n if (indices.count(left)) {\\n for (const int& x: indices[left]) {\\n --freq[x];\\n if (freq[x] == 0) {\\n --inside;\\n }\\n }\\n }\\n ++left;\\n }\\n }\\n }\\n\\n return {bestLeft, bestRight};\\n }\\n};\\n
###Go
\\n\\nfunc smallestRange(nums [][]int) []int {\\n size := len(nums)\\n indices := map[int][]int{}\\n xMin, xMax := math.MaxInt32, math.MinInt32\\n for i := 0; i < size; i++ {\\n for _, x := range nums[i] {\\n indices[x] = append(indices[x], i)\\n xMin = min(xMin, x)\\n xMax = max(xMax, x)\\n }\\n }\\n freq := make([]int, size)\\n inside := 0\\n left, right := xMin, xMin - 1\\n bestLeft, bestRight := xMin, xMax\\n for right < xMax {\\n right++\\n if len(indices[right]) > 0 {\\n for _, x := range indices[right] {\\n freq[x]++\\n if freq[x] == 1 {\\n inside++\\n }\\n }\\n for inside == size {\\n if right - left < bestRight - bestLeft {\\n bestLeft, bestRight = left, right\\n }\\n for _, x := range indices[left] {\\n freq[x]--\\n if freq[x] == 0 {\\n inside--\\n }\\n }\\n left++\\n }\\n }\\n }\\n return []int{bestLeft, bestRight}\\n}\\n\\nfunc min(x, y int) int {\\n if x < y {\\n return x\\n }\\n return y\\n}\\n\\nfunc max(x, y int) int {\\n if x > y {\\n return x\\n }\\n return y\\n}\\n
###Python
\\n\\nclass Solution:\\n def smallestRange(self, nums: List[List[int]]) -> List[int]:\\n n = len(nums)\\n indices = collections.defaultdict(list)\\n xMin, xMax = 10**9, -10**9\\n for i, vec in enumerate(nums):\\n for x in vec:\\n indices[x].append(i)\\n xMin = min(xMin, *vec)\\n xMax = max(xMax, *vec)\\n \\n freq = [0] * n\\n inside = 0\\n left, right = xMin, xMin - 1\\n bestLeft, bestRight = xMin, xMax\\n\\n while right < xMax:\\n right += 1\\n if right in indices:\\n for x in indices[right]:\\n freq[x] += 1\\n if freq[x] == 1:\\n inside += 1\\n while inside == n:\\n if right - left < bestRight - bestLeft:\\n bestLeft, bestRight = left, right\\n if left in indices:\\n for x in indices[left]:\\n freq[x] -= 1\\n if freq[x] == 0:\\n inside -= 1\\n left += 1\\n\\n return [bestLeft, bestRight]\\n
###C
\\n\\ntypedef struct {\\n int key;\\n struct ListNode *val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nstruct ListNode *createListNode(int val) {\\n struct ListNode *p = (struct ListNode*)malloc(sizeof(struct ListNode));\\n p->val = val;\\n p->next = NULL;\\n return p;\\n}\\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n struct ListNode *p = createListNode(val);\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (pEntry) {\\n p->next = pEntry->val;\\n pEntry->val = p;\\n return true;\\n }\\n pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = p;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nstruct ListNode* hashGetItem(HashItem **obj, int key) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return NULL;\\n }\\n return pEntry->val;\\n}\\n\\nvoid freeList(struct ListNode *list) {\\n while (list) {\\n struct ListNode *p = list;\\n list = list->next;\\n free(p);\\n }\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nint* smallestRange(int** nums, int numsSize, int* numsColSize, int* returnSize) {\\n int n = numsSize;\\n HashItem *indices = NULL;\\n int xMin = INT_MAX, xMax = INT_MIN;\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < numsColSize[i]; j++) {\\n int x = nums[i][j];\\n hashAddItem(&indices, x, i);\\n xMin = fmin(xMin, x);\\n xMax = fmax(xMax, x);\\n }\\n }\\n\\n int freq[n];\\n memset(freq, 0, sizeof(freq));\\n int inside = 0;\\n int left = xMin, right = xMin - 1;\\n int bestLeft = xMin, bestRight = xMax;\\n\\n while (right < xMax) {\\n ++right;\\n if (hashFindItem(&indices, right)) {\\n for (struct ListNode *p = hashGetItem(&indices, right); p; p = p->next) {\\n int x = p->val;\\n ++freq[x];\\n if (freq[x] == 1) {\\n ++inside;\\n }\\n }\\n while (inside == n) {\\n if (right - left < bestRight - bestLeft) {\\n bestLeft = left;\\n bestRight = right;\\n }\\n if (hashFindItem(&indices, left)) {\\n for (struct ListNode *p = hashGetItem(&indices, left); p; p = p->next) {\\n int x = p->val;\\n --freq[x];\\n if (freq[x] == 0) {\\n --inside;\\n }\\n }\\n }\\n ++left;\\n }\\n }\\n }\\n int *res = (int *)malloc(sizeof(int) * 2);\\n res[0] = bestLeft;\\n res[1] = bestRight;\\n *returnSize = 2;\\n hashFree(&indices);\\n return res;\\n}\\n
###JavaScript
\\n\\nvar smallestRange = function(nums) {\\n const size = nums.length;\\n const indices = new Map();\\n let xMin = Number.MAX_SAFE_INTEGER, xMax = Number.MIN_SAFE_INTEGER;\\n\\n for (let i = 0; i < size; i++) {\\n for (const x of nums[i]) {\\n if (!indices.has(x)) {\\n indices.set(x, []);\\n }\\n indices.get(x).push(i);\\n xMin = Math.min(xMin, x);\\n xMax = Math.max(xMax, x);\\n }\\n }\\n\\n const freq = new Array(size).fill(0);\\n let inside = 0;\\n let left = xMin, right = xMin - 1;\\n let bestLeft = xMin, bestRight = xMax;\\n\\n while (right < xMax) {\\n right++;\\n if (indices.has(right)) {\\n for (const x of indices.get(right)) {\\n freq[x]++;\\n if (freq[x] === 1) {\\n inside++;\\n }\\n }\\n while (inside === size) {\\n if (right - left < bestRight - bestLeft) {\\n bestLeft = left;\\n bestRight = right;\\n }\\n if (indices.has(left)) {\\n for (const x of indices.get(left)) {\\n freq[x]--;\\n if (freq[x] === 0) {\\n inside--;\\n }\\n }\\n }\\n left++;\\n }\\n }\\n }\\n\\n return [bestLeft, bestRight];\\n};\\n
###TypeScript
\\n\\nfunction smallestRange(nums: number[][]): number[] {\\n const size = nums.length;\\n const indices = new Map<number, number[]>();\\n let xMin = Number.MAX_SAFE_INTEGER, xMax = Number.MIN_SAFE_INTEGER;\\n\\n for (let i = 0; i < size; i++) {\\n for (const x of nums[i]) {\\n if (!indices.has(x)) {\\n indices.set(x, []);\\n }\\n indices.get(x)!.push(i);\\n xMin = Math.min(xMin, x);\\n xMax = Math.max(xMax, x);\\n }\\n }\\n\\n const freq = new Array(size).fill(0);\\n let inside = 0;\\n let left = xMin, right = xMin - 1;\\n let bestLeft = xMin, bestRight = xMax;\\n\\n while (right < xMax) {\\n right++;\\n if (indices.has(right)) {\\n for (const x of indices.get(right)!) {\\n freq[x]++;\\n if (freq[x] === 1) {\\n inside++;\\n }\\n }\\n while (inside === size) {\\n if (right - left < bestRight - bestLeft) {\\n bestLeft = left;\\n bestRight = right;\\n }\\n if (indices.has(left)) {\\n for (const x of indices.get(left)!) {\\n freq[x]--;\\n if (freq[x] === 0) {\\n inside--;\\n }\\n }\\n }\\n left++;\\n }\\n }\\n }\\n\\n return [bestLeft, bestRight];\\n};\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn smallest_range(nums: Vec<Vec<i32>>) -> Vec<i32> {\\n let size = nums.len();\\n let mut indices: HashMap<i32, Vec<usize>> = HashMap::new();\\n let mut x_min = i32::MAX;\\n let mut x_max = i32::MIN;\\n\\n for i in 0..size {\\n for &x in &nums[i] {\\n indices.entry(x).or_insert_with(Vec::new).push(i);\\n x_min = x_min.min(x);\\n x_max = x_max.max(x);\\n }\\n }\\n\\n let mut freq = vec![0; size];\\n let mut inside = 0;\\n let mut left = x_min;\\n let mut right = x_min - 1;\\n let mut best_left = x_min;\\n let mut best_right = x_max;\\n\\n while right < x_max {\\n right += 1;\\n if let Some(vec) = indices.get(&right) {\\n for &x in vec {\\n freq[x] += 1;\\n if freq[x] == 1 {\\n inside += 1;\\n }\\n }\\n while inside == size {\\n if right - left < best_right - best_left {\\n best_left = left;\\n best_right = right;\\n }\\n if let Some(vec) = indices.get(&left) {\\n for &x in vec {\\n freq[x] -= 1;\\n if freq[x] == 0 {\\n inside -= 1;\\n }\\n }\\n }\\n left += 1;\\n }\\n }\\n }\\n\\n vec![best_left, best_right]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心 + 最小堆 给定 $k$ 个列表,需要找到最小区间,使得每个列表都至少有一个数在该区间中。该问题可以转化为,从 $k$ 个列表中各取一个数,使得这 $k$ 个数中的最大值与最小值的差最小。\\n\\n假设这 $k$ 个数中的最小值是第 $i$ 个列表中的 $x$,对于任意 $j \\\\ne i$,设第 $j$ 个列表中被选为 $k$ 个数之一的数是 $y$,则为了找到最小区间,$y$ 应该取第 $j$ 个列表中大于等于 $x$ 的最小的数,这是一个贪心的策略。贪心策略的正确性简单证明如下:假设 $z$ 也是第 $j$ 个列表中的数,且 $z>y$,则有…","guid":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/zui-xiao-qu-jian-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-07-31T14:43:15.669Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"两个数组的交集 II","url":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(nk + |V|)$,其中 $n$ 是所有列表的平均长度,$k$ 是列表数量,$|V|$ 是列表中元素的值域,在本题中 $|V| \\\\leq 2*10^5$。构造哈希映射的时间复杂度为 $O(nk)$,双指针的移动范围为 $|V|$,在此过程中会对哈希映射再进行一次遍历,时间复杂度为 $O(nk)$,因此总时间复杂度为 $O(nk + |V|)$。
\\n- \\n
\\n空间复杂度:$O(nk)$,即为哈希映射使用的空间。哈希映射的「键」的数量由列表中的元素个数 $nk$ 以及值域 $|V|$ 中的较小值决定,「值」为长度不固定的数组,但是它们的长度之和为 $nk$,因此哈希映射使用的空间为 $O(nk)$。在使用双指针时,还需要一个长度为 $n$ 的数组,其对应的空间在渐进意义下小于 $O(nk)$,因此可以忽略。
\\n📺 视频题解
\\n\\n
📖 文字题解
\\n方法一:哈希表
\\n由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。
\\n首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
\\n为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
\\n\\n
###Java
\\n\\nclass Solution {\\n public int[] intersect(int[] nums1, int[] nums2) {\\n if (nums1.length > nums2.length) {\\n return intersect(nums2, nums1);\\n }\\n Map<Integer, Integer> map = new HashMap<Integer, Integer>();\\n for (int num : nums1) {\\n int count = map.getOrDefault(num, 0) + 1;\\n map.put(num, count);\\n }\\n int[] intersection = new int[nums1.length];\\n int index = 0;\\n for (int num : nums2) {\\n int count = map.getOrDefault(num, 0);\\n if (count > 0) {\\n intersection[index++] = num;\\n count--;\\n if (count > 0) {\\n map.put(num, count);\\n } else {\\n map.remove(num);\\n }\\n }\\n }\\n return Arrays.copyOfRange(intersection, 0, index);\\n }\\n}\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n if (nums1.size() > nums2.size()) {\\n return intersect(nums2, nums1);\\n }\\n unordered_map <int, int> m;\\n for (int num : nums1) {\\n ++m[num];\\n }\\n vector<int> intersection;\\n for (int num : nums2) {\\n if (m.count(num)) {\\n intersection.push_back(num);\\n --m[num];\\n if (m[num] == 0) {\\n m.erase(num);\\n }\\n }\\n }\\n return intersection;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:\\n if len(nums1) > len(nums2):\\n return self.intersect(nums2, nums1)\\n \\n m = collections.Counter()\\n for num in nums1:\\n m[num] += 1\\n \\n intersection = list()\\n for num in nums2:\\n if (count := m.get(num, 0)) > 0:\\n intersection.append(num)\\n m[num] -= 1\\n if m[num] == 0:\\n m.pop(num)\\n \\n return intersection\\n
###golang
\\n\\nfunc intersect(nums1 []int, nums2 []int) []int {\\n if len(nums1) > len(nums2) {\\n return intersect(nums2, nums1)\\n }\\n m := map[int]int{}\\n for _, num := range nums1 {\\n m[num]++\\n }\\n\\n intersection := []int{}\\n for _, num := range nums2 {\\n if m[num] > 0 {\\n intersection = append(intersection, num)\\n m[num]--\\n }\\n }\\n return intersection\\n}\\n
###C
\\n\\nint* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize) {\\n if (nums1Size > nums2Size) {\\n return intersect(nums2, nums2Size, nums1, nums1Size, returnSize);\\n }\\n\\n int* m = (int*)calloc(1001, sizeof(int));\\n for (int i = 0; i < nums1Size; ++i) {\\n ++m[nums1[i]];\\n }\\n\\n int* intersection = (int*)calloc(nums1Size, sizeof(int));\\n int index = 0;\\n for (int i = 0; i < nums2Size; ++i) {\\n if (m[nums2[i]] > 0) {\\n intersection[index++] = nums2[i];\\n --m[nums2[i]];\\n }\\n }\\n \\n free(m);\\n *returnSize = index;\\n return intersection;\\n}\\n
###JavaScript
\\n\\nvar intersect = function(nums1, nums2) {\\n if (nums1.length > nums2.length) {\\n return intersect(nums2, nums1);\\n }\\n\\n let map = new Map();\\n for (let num of nums1) {\\n map.set(num, (map.get(num) || 0) + 1);\\n }\\n\\n let intersection = [];\\n for (let num of nums2) {\\n if (map.has(num) && map.get(num) > 0) {\\n intersection.push(num);\\n map.set(num, map.get(num) - 1);\\n }\\n }\\n \\n return intersection;\\n};\\n
###TypeScript
\\n\\nfunction intersect(nums1: number[], nums2: number[]): number[] {\\n if (nums1.length > nums2.length) {\\n return intersect(nums2, nums1);\\n }\\n \\n const map = new Map<number, number>();\\n for (const num of nums1) {\\n map.set(num, (map.get(num) || 0) + 1);\\n }\\n \\n const intersection: number[] = [];\\n for (const num of nums2) {\\n if (map.has(num) && map.get(num)! > 0) {\\n intersection.push(num);\\n map.set(num, map.get(num)! - 1);\\n }\\n }\\n \\n return intersection;\\n}\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn intersect(nums1: Vec<i32>, nums2: Vec<i32>) -> Vec<i32> {\\n let mut map = HashMap::new();\\n for &num in &nums1 {\\n *map.entry(num).or_insert(0) += 1;\\n }\\n\\n let mut intersection = Vec::new();\\n for &num in &nums2 {\\n if let Some(count) = map.get_mut(&num) {\\n if *count > 0 {\\n intersection.push(num);\\n *count -= 1;\\n }\\n }\\n }\\n\\n intersection\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] Intersect(int[] nums1, int[] nums2) {\\n if (nums1.Length > nums2.Length) {\\n return Intersect(nums2, nums1);\\n }\\n\\n Dictionary<int, int> map = new Dictionary<int, int>();\\n foreach (int num in nums1) {\\n if (map.ContainsKey(num)) {\\n map[num]++;\\n } else {\\n map[num] = 1;\\n }\\n }\\n\\n List<int> intersection = new List<int>();\\n foreach (int num in nums2) {\\n if (map.ContainsKey(num) && map[num] > 0) {\\n intersection.Add(num);\\n map[num]--;\\n if (map[num] == 0) {\\n map.Remove(num);\\n }\\n }\\n }\\n\\n return intersection.ToArray();\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(m+n)$,其中 $m$ 和 $n$ 分别是两个数组的长度。需要遍历两个数组并对哈希表进行操作,哈希表操作的时间复杂度是 $O(1)$,因此总时间复杂度与两个数组的长度和呈线性关系。
\\n- \\n
\\n空间复杂度:$O(\\\\min(m,n))$,其中 $m$ 和 $n$ 分别是两个数组的长度。对较短的数组进行哈希表的操作,哈希表的大小不会超过较短的数组的长度。为返回值创建一个数组
\\nintersection
,其长度为较短的数组的长度。方法二:排序 + 双指针
\\n如果两个数组是有序的,则可以使用双指针的方法得到两个数组的交集。
\\n首先对两个数组进行排序,然后使用两个指针遍历两个数组。
\\n初始时,两个指针分别指向两个数组的头部。每次比较两个指针指向的两个数组中的数字,如果两个数字不相等,则将指向较小数字的指针右移一位,如果两个数字相等,将该数字添加到答案,并将两个指针都右移一位。当至少有一个指针超出数组范围时,遍历结束。
\\n###Java
\\n\\nclass Solution {\\n public int[] intersect(int[] nums1, int[] nums2) {\\n Arrays.sort(nums1);\\n Arrays.sort(nums2);\\n int length1 = nums1.length, length2 = nums2.length;\\n int[] intersection = new int[Math.min(length1, length2)];\\n int index1 = 0, index2 = 0, index = 0;\\n while (index1 < length1 && index2 < length2) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection[index] = nums1[index1];\\n index1++;\\n index2++;\\n index++;\\n }\\n }\\n return Arrays.copyOfRange(intersection, 0, index);\\n }\\n}\\n
###cpp
\\n\\nclass Solution {\\npublic:\\n vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {\\n sort(nums1.begin(), nums1.end());\\n sort(nums2.begin(), nums2.end());\\n int length1 = nums1.size(), length2 = nums2.size();\\n vector<int> intersection;\\n int index1 = 0, index2 = 0;\\n while (index1 < length1 && index2 < length2) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection.push_back(nums1[index1]);\\n index1++;\\n index2++;\\n }\\n }\\n return intersection;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def intersect(self, nums1: List[int], nums2: List[int]) -> List[int]:\\n nums1.sort()\\n nums2.sort()\\n\\n length1, length2 = len(nums1), len(nums2)\\n intersection = list()\\n index1 = index2 = 0\\n while index1 < length1 and index2 < length2:\\n if nums1[index1] < nums2[index2]:\\n index1 += 1\\n elif nums1[index1] > nums2[index2]:\\n index2 += 1\\n else:\\n intersection.append(nums1[index1])\\n index1 += 1\\n index2 += 1\\n \\n return intersection\\n
###golang
\\n\\nfunc intersect(nums1 []int, nums2 []int) []int {\\n sort.Ints(nums1)\\n sort.Ints(nums2)\\n length1, length2 := len(nums1), len(nums2)\\n index1, index2 := 0, 0\\n\\n intersection := []int{}\\n for index1 < length1 && index2 < length2 {\\n if nums1[index1] < nums2[index2] {\\n index1++\\n } else if nums1[index1] > nums2[index2] {\\n index2++\\n } else {\\n intersection = append(intersection, nums1[index1])\\n index1++\\n index2++\\n }\\n }\\n return intersection\\n}\\n
###C
\\n\\nint cmp(const void* _a, const void* _b) {\\n int *a = _a, *b = (int*)_b;\\n return *a == *b ? 0 : *a > *b ? 1 : -1;\\n}\\n\\nint* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size,\\n int* returnSize) {\\n qsort(nums1, nums1Size, sizeof(int), cmp);\\n qsort(nums2, nums2Size, sizeof(int), cmp);\\n *returnSize = 0;\\n int* intersection = (int*)malloc(sizeof(int) * fmin(nums1Size, nums2Size));\\n int index1 = 0, index2 = 0;\\n while (index1 < nums1Size && index2 < nums2Size) {\\n if (nums1[index1] < nums2[index2]) {\\n index1++;\\n } else if (nums1[index1] > nums2[index2]) {\\n index2++;\\n } else {\\n intersection[(*returnSize)++] = nums1[index1];\\n index1++;\\n index2++;\\n }\\n }\\n return intersection;\\n}\\n
###JavaScript
\\n\\nvar intersect = function(nums1, nums2) {\\n nums1.sort((a, b) => a - b);\\n nums2.sort((a, b) => a - b);\\n let intersection = [];\\n let i = 0, j = 0;\\n while (i < nums1.length && j < nums2.length) {\\n if (nums1[i] < nums2[j]) {\\n i++;\\n } else if (nums1[i] > nums2[j]) {\\n j++;\\n } else {\\n intersection.push(nums1[i]);\\n i++;\\n j++;\\n }\\n }\\n return intersection;\\n};\\n
###TypeScript
\\n\\nfunction intersect(nums1: number[], nums2: number[]): number[] {\\n nums1.sort((a, b) => a - b);\\n nums2.sort((a, b) => a - b);\\n const intersection: number[] = [];\\n let i = 0, j = 0;\\n while (i < nums1.length && j < nums2.length) {\\n if (nums1[i] < nums2[j]) {\\n i++;\\n } else if (nums1[i] > nums2[j]) {\\n j++;\\n } else {\\n intersection.push(nums1[i]);\\n i++;\\n j++;\\n }\\n }\\n return intersection;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn intersect(nums1: Vec<i32>, nums2: Vec<i32>) -> Vec<i32> {\\n let mut nums1 = nums1;\\n let mut nums2 = nums2;\\n nums1.sort();\\n nums2.sort();\\n let mut intersection = Vec::new();\\n let (mut i, mut j) = (0, 0);\\n while i < nums1.len() && j < nums2.len() {\\n if nums1[i] < nums2[j] {\\n i += 1;\\n } else if nums1[i] > nums2[j] {\\n j += 1;\\n } else {\\n intersection.push(nums1[i]);\\n i += 1;\\n j += 1;\\n }\\n }\\n intersection\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] Intersect(int[] nums1, int[] nums2) {\\n Array.Sort(nums1);\\n Array.Sort(nums2);\\n List<int> intersection = new List<int>();\\n int i = 0, j = 0;\\n while (i < nums1.Length && j < nums2.Length) {\\n if (nums1[i] < nums2[j]) {\\n i++;\\n } else if (nums1[i] > nums2[j]) {\\n j++;\\n } else {\\n intersection.Add(nums1[i]);\\n i++;\\n j++;\\n }\\n }\\n return intersection.ToArray();\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(m \\\\log m+n \\\\log n)$,其中 $m$ 和 $n$ 分别是两个数组的长度。对两个数组进行排序的时间复杂度是 $O(m \\\\log m+n \\\\log n)$,遍历两个数组的时间复杂度是 $O(m+n)$,因此总时间复杂度是 $O(m \\\\log m+n \\\\log n)$。
\\n- \\n
\\n空间复杂度:$O(\\\\min(m,n))$,其中 $m$ 和 $n$ 分别是两个数组的长度。为返回值创建一个数组
\\nintersection
,其长度为较短的数组的长度。不过在 C++ 中,我们可以直接创建一个vector
,不需要把答案临时存放在一个额外的数组中,所以这种实现的空间复杂度为 $O(1)$。结语
\\n如果 $\\\\textit{nums}_2$ 的元素存储在磁盘上,磁盘内存是有限的,并且你不能一次加载所有的元素到内存中。那么就无法高效地对 $\\\\textit{nums}_2$ 进行排序,因此推荐使用方法一而不是方法二。在方法一中,$\\\\textit{nums}_2$ 只关系到查询操作,因此每次读取 $\\\\textit{nums}_2$ 中的一部分数据,并进行处理即可。
\\n","description":"📺 视频题解 📖 文字题解\\n方法一:哈希表\\n\\n由于同一个数字在两个数组中都可能出现多次,因此需要用哈希表存储每个数字出现的次数。对于一个数字,其在交集中出现的次数等于该数字在两个数组中出现次数的最小值。\\n\\n首先遍历第一个数组,并在哈希表中记录第一个数组中的每个数字以及对应出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。\\n\\n为了降低空间复杂度,首先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。\\n\\n###Java\\n\\nclass…","guid":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/liang-ge-shu-zu-de-jiao-ji-ii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-07-12T11:14:53.112Z","media":[{"url":"https://leetcode.cn/problems/intersection-of-two-arrays-ii//solution/59b3957f-2bb4-413f-b3f9-3e35c4b9b3d1","type":"photo"},{"url":"https://assets.leetcode.cn/solution-static/350/350_fig1.gif","type":"photo","width":1013,"height":480,"blurhash":"LJB3]9MyD%Rj~qM{M{RjsqS1spWn"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简单DP,🤷♀️必须秒懂!","url":"https://leetcode.cn/problems/unique-paths-ii//solution/jian-dan-dpbi-xu-miao-dong-by-sweetiee","content":"🙋 今日打卡!
\\n一、题目分析
\\n递归思路:
\\n假设我们定义到达右下角的走法数为 $f(m, n)$, 因为右下角只能由它上方或者左方的格子走过去,因此可以很容易的写出递归求解式,即 $f(m, n) = f(m - 1, n) + f(m, n - 1)$,最后加上递归终止条件,SO EASY 看起来大功告成啦!
\\n然而事情并木有结束~ 因为这样自底向上的递归会存在大量的重复计算,所以我们将其改写为在二维数组中自顶向下的递推即可,即 $dp[i, j] = dp[i - 1, j] + dp[i, j - 1]$。
\\n1、状态定义:
\\n$dp[i][j]$ 表示走到格子 $(i, j)$ 的方法数。
\\n2、状态转移:
\\n如果网格 $(i, j)$ 上有障碍物,则 $dp[i][j]$ 值为 $0$,表示走到该格子的方法数为 $0$;
\\n
\\n否则网格 $(i, j)$ 可以从网格 $(i - 1, j)$ 或者 网格 $(i, j - 1)$ 走过来,因此走到该格子的方法数为走到网格 $(i - 1, j)$ 和网格 $(i, j - 1)$ 的方法数之和,即 $dp[i, j] = dp[i - 1, j] + dp[i, j - 1]$。状态转移方程如下:
\\n$$ dp[i][j] = \\\\begin{cases}
\\n
\\ndp[i - 1, j] + dp[i, j - 1] & & {(i, j) 上无障碍物} \\\\
\\n0 & & {(i, j) 上有障碍物}
\\n\\\\end{cases} $$3、初始条件
\\n第 1 列的格子只有从其上边格子走过去这一种走法,因此初始化 dp[i][0] 值为 1,存在障碍物时为 0;
\\n第 1 行的格子只有从其左边格子走过去这一种走法,因此初始化 dp[0][j] 值为 1,存在障碍物时为 0。
\\n\\nint m = obstacleGrid.length, n = obstacleGrid[0].length;\\nint[][] dp = new int[m][n];\\nfor (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {\\n dp[i][0] = 1;\\n}\\nfor (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {\\n dp[0][j] = 1;\\n}\\n
二、具体实现
\\n\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n if (obstacleGrid == null || obstacleGrid.length == 0) {\\n return 0;\\n }\\n \\n // 定义 dp 数组并初始化第 1 行和第 1 列。\\n int m = obstacleGrid.length, n = obstacleGrid[0].length;\\n int[][] dp = new int[m][n];\\n for (int i = 0; i < m && obstacleGrid[i][0] == 0; i++) {\\n dp[i][0] = 1;\\n }\\n for (int j = 0; j < n && obstacleGrid[0][j] == 0; j++) {\\n dp[0][j] = 1;\\n }\\n\\n // 根据状态转移方程 dp[i][j] = dp[i - 1][j] + dp[i][j - 1] 进行递推。\\n for (int i = 1; i < m; i++) {\\n for (int j = 1; j < n; j++) {\\n if (obstacleGrid[i][j] == 0) {\\n dp[i][j] = dp[i - 1][j] + dp[i][j - 1];\\n }\\n }\\n }\\n return dp[m - 1][n - 1];\\n }\\n}\\n
三、题目拓展
\\n\\n
\\n","description":"🙋 今日打卡! 一、题目分析\\n递归思路:\\n\\n假设我们定义到达右下角的走法数为 $f(m, n)$, 因为右下角只能由它上方或者左方的格子走过去,因此可以很容易的写出递归求解式,即 $f(m, n) = f(m - 1, n) + f(m, n - 1)$,最后加上递归终止条件,SO EASY 看起来大功告成啦!\\n\\n然而事情并木有结束~ 因为这样自底向上的递归会存在大量的重复计算,所以我们将其改写为在二维数组中自顶向下的递推即可,即 $dp[i, j] = dp[i - 1, j] + dp[i, j - 1]$。\\n\\n1、状态定义:\\n\\n$dp[i][j…","guid":"https://leetcode.cn/problems/unique-paths-ii//solution/jian-dan-dpbi-xu-miao-dong-by-sweetiee","author":"sweetiee","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-07-06T05:37:30.340Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"不同路径 II","url":"https://leetcode.cn/problems/unique-paths-ii//solution/bu-tong-lu-jing-ii-by-leetcode-solution-2","content":"- 62. 不同路径 是本题的阉割版,少了障碍物的设置,做法都是一样的。
\\n📺 视频题解
\\n\\n
📖 文字题解
\\n方法一:动态规划
\\n思路和算法
\\n我们用 $f(i, j)$ 来表示从坐标 $(0, 0)$ 到坐标 $(i, j)$ 的路径总数,$u(i, j)$ 表示坐标 $(i, j)$ 是否可行,如果坐标 $(i, j)$ 有障碍物,$u(i, j) = 0$,否则 $u(i, j) = 1$。
\\n因为「机器人每次只能向下或者向右移动一步」,所以从坐标 $(0, 0)$ 到坐标 $(i, j)$ 的路径总数的值只取决于从坐标 $(0, 0)$ 到坐标 $(i - 1, j)$ 的路径总数和从坐标 $(0, 0)$ 到坐标 $(i, j - 1)$ 的路径总数,即 $f(i, j)$ 只能通过 $f(i - 1, j)$ 和 $f(i, j - 1)$ 转移得到。当坐标 $(i, j)$ 本身有障碍的时候,任何路径都到到不了 $f(i, j)$,此时 $f(i, j) = 0$;下面我们来讨论坐标 $(i, j)$ 没有障碍的情况:如果坐标 $(i - 1, j)$ 没有障碍,那么就意味着从坐标 $(i - 1, j)$ 可以走到 $(i, j)$,即 $(i - 1, j)$ 位置对 $f(i, j)$ 的贡献为 $f(i - 1, j)$,同理,当坐标 $(i, j - 1)$ 没有障碍的时候,$(i, j - 1)$ 位置对 $f(i, j)$ 的贡献为 $f(i, j - 1)$。综上所述,我们可以得到这样的动态规划转移方程:
\\n$$
\\n
\\nf(i, j) = \\\\left { \\\\begin{aligned}
\\n0 & , & u(i, j) = 0 \\\\
\\nf(i - 1, j) + f(i, j - 1) & , & u(i, j) \\\\neq 0 \\\\end{aligned} \\\\right.
\\n$$很显然我们可以给出一个时间复杂度 $O(nm)$ 并且空间复杂度也是 $O(nm)$ 的实现,由于这里 $f(i, j)$ 只与 $f(i - 1, j)$ 和 $f(i, j - 1)$ 相关,我们可以运用「滚动数组思想」把空间复杂度优化称 $O(m)$。「滚动数组思想」是一种常见的动态规划优化方法,在我们的题目中已经多次使用到,例如「剑指 Offer 46. 把数字翻译成字符串」、「70. 爬楼梯」等,当我们定义的状态在动态规划的转移方程中只和某几个状态相关的时候,就可以考虑这种优化方法,目的是给空间复杂度「降维」。如果你还不知道什么是「滚动数组思想」,一定要查阅相关资料进行学习哦。
\\n代码中给出了使用「滚动数组思想」优化后的实现。
\\n回顾这道题,其实这类动态规划的题目在题库中也出现过多次,例如「221. 最大正方形」、「1162. 地图分析」等。他们都以二维坐标作为状态,大多数都可以使用滚动数组进行优化。如果我们熟悉这类问题,可以一眼看出这是一个动态规划问题。当我们不熟悉的时候,怎么想到用动态规划来解决这个问题呢?我们需要从问题本身出发,寻找一些有用的信息,例如本题中:
\\n\\n
\\n- $(i, j)$ 位置只能从 $(i - 1, j)$ 和 $(i, j - 1)$ 走到,这样的条件就是在告诉我们这里转移是 「无后效性」 的,$f(i, j)$ 和任何的 $f(i\', j\')(i\' > i, j\' > j)$ 无关。
\\n- 动态规划的题目分为两大类,一种是求最优解类,典型问题是背包问题,另一种就是计数类,比如这里的统计方案数的问题,它们都存在一定的递推性质。前者的递推性质还有一个名字,叫做 「最优子结构」 ——即当前问题的最优解取决于子问题的最优解,后者类似,当前问题的方案数取决于子问题的方案数。所以在遇到求方案数的问题时,我们可以往动态规划的方向考虑。
\\n通常如果我们察觉到了这两点要素,这个问题八成可以用动态规划来解决。读者可以多多练习,熟能生巧。
\\n代码
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {\\n int n = obstacleGrid.size(), m = obstacleGrid.at(0).size();\\n vector <int> f(m);\\n\\n f[0] = (obstacleGrid[0][0] == 0);\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < m; ++j) {\\n if (obstacleGrid[i][j] == 1) {\\n f[j] = 0;\\n continue;\\n }\\n if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n\\n return f.back();\\n }\\n};\\n
###java
\\n\\nclass Solution {\\n public int uniquePathsWithObstacles(int[][] obstacleGrid) {\\n int n = obstacleGrid.length, m = obstacleGrid[0].length;\\n int[] f = new int[m];\\n\\n f[0] = obstacleGrid[0][0] == 0 ? 1 : 0;\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < m; ++j) {\\n if (obstacleGrid[i][j] == 1) {\\n f[j] = 0;\\n continue;\\n }\\n if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n \\n return f[m - 1];\\n }\\n}\\n
###golang
\\n\\nfunc uniquePathsWithObstacles(obstacleGrid [][]int) int {\\n n, m := len(obstacleGrid), len(obstacleGrid[0])\\n f := make([]int, m)\\n if obstacleGrid[0][0] == 0 {\\n f[0] = 1\\n }\\n for i := 0; i < n; i++ {\\n for j := 0; j < m; j++ {\\n if obstacleGrid[i][j] == 1 {\\n f[j] = 0\\n continue\\n }\\n if j - 1 >= 0 && obstacleGrid[i][j-1] == 0 {\\n f[j] += f[j-1]\\n }\\n }\\n }\\n return f[len(f)-1]\\n}\\n
###C
\\n\\nint uniquePathsWithObstacles(int** obstacleGrid, int obstacleGridSize,\\n int* obstacleGridColSize) {\\n int n = obstacleGridSize, m = obstacleGridColSize[0];\\n int f[m];\\n memset(f, 0, sizeof(f));\\n f[0] = (obstacleGrid[0][0] == 0);\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < m; ++j) {\\n if (obstacleGrid[i][j] == 1) {\\n f[j] = 0;\\n continue;\\n }\\n if (j - 1 >= 0 && obstacleGrid[i][j - 1] == 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n\\n return f[m - 1];\\n}\\n
###Python
\\n\\nclass Solution:\\n def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:\\n n, m = len(obstacleGrid), len(obstacleGrid[0])\\n f = [0] * m\\n f[0] = 1 if obstacleGrid[0][0] == 0 else 0\\n for i in range(n):\\n for j in range(m):\\n if obstacleGrid[i][j] == 1:\\n f[j] = 0\\n elif j > 0 and obstacleGrid[i][j - 1] == 0:\\n f[j] += f[j - 1]\\n return f[-1]\\n
###JavaScript
\\n\\nvar uniquePathsWithObstacles = function(obstacleGrid) {\\n const n = obstacleGrid.length, m = obstacleGrid[0].length;\\n const f = new Array(m).fill(0);\\n f[0] = obstacleGrid[0][0] === 0 ? 1 : 0;\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j < m; j++) {\\n if (obstacleGrid[i][j] === 1) {\\n f[j] = 0;\\n } else if (j > 0 && obstacleGrid[i][j - 1] === 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n return f[m - 1];\\n};\\n
###TypeScript
\\n\\nfunction uniquePathsWithObstacles(obstacleGrid: number[][]): number {\\n const n = obstacleGrid.length, m = obstacleGrid[0].length;\\n const f: number[] = new Array(m).fill(0);\\n f[0] = obstacleGrid[0][0] === 0 ? 1 : 0;\\n for (let i = 0; i < n; i++) {\\n for (let j = 0; j < m; j++) {\\n if (obstacleGrid[i][j] === 1) {\\n f[j] = 0;\\n } else if (j > 0 && obstacleGrid[i][j - 1] === 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n return f[m - 1];\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int UniquePathsWithObstacles(int[][] obstacleGrid) {\\n int n = obstacleGrid.Length, m = obstacleGrid[0].Length;\\n int[] f = new int[m];\\n f[0] = obstacleGrid[0][0] == 0 ? 1 : 0;\\n for (int i = 0; i < n; ++i) {\\n for (int j = 0; j < m; ++j) {\\n if (obstacleGrid[i][j] == 1) {\\n f[j] = 0;\\n } else if (j > 0 && obstacleGrid[i][j - 1] == 0) {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n return f[m - 1];\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn unique_paths_with_obstacles(obstacle_grid: Vec<Vec<i32>>) -> i32 {\\n let n = obstacle_grid.len();\\n let m = obstacle_grid[0].len();\\n let mut f = vec![0; m];\\n f[0] = if obstacle_grid[0][0] == 0 { 1 } else { 0 };\\n for i in 0..n {\\n for j in 0..m {\\n if obstacle_grid[i][j] == 1 {\\n f[j] = 0;\\n } else if j > 0 && obstacle_grid[i][j - 1] == 0 {\\n f[j] += f[j - 1];\\n }\\n }\\n }\\n f[m - 1]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"📺 视频题解 📖 文字题解\\n方法一:动态规划\\n\\n思路和算法\\n\\n我们用 $f(i, j)$ 来表示从坐标 $(0, 0)$ 到坐标 $(i, j)$ 的路径总数,$u(i, j)$ 表示坐标 $(i, j)$ 是否可行,如果坐标 $(i, j)$ 有障碍物,$u(i, j) = 0$,否则 $u(i, j) = 1$。\\n\\n因为「机器人每次只能向下或者向右移动一步」,所以从坐标 $(0, 0)$ 到坐标 $(i, j)$ 的路径总数的值只取决于从坐标 $(0, 0)$ 到坐标 $(i - 1, j)$ 的路径总数和从坐标 $(0, 0)$ 到坐标 $(i…","guid":"https://leetcode.cn/problems/unique-paths-ii//solution/bu-tong-lu-jing-ii-by-leetcode-solution-2","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-07-05T12:49:04.722Z","media":[{"url":"https://leetcode.cn/problems/unique-paths-ii//solution/ab6377a8-24bf-4c03-bd31-d29810272468","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最佳观光组合","url":"https://leetcode.cn/problems/best-sightseeing-pair//solution/zui-jia-guan-guang-zu-he-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(nm)$,其中 $n$ 为网格的行数,$m$ 为网格的列数。我们只需要遍历所有网格一次即可。
\\n- \\n
\\n空间复杂度:$O(m)$。利用滚动数组优化,我们可以只用 $O(m)$ 大小的空间来记录当前行的 $f$ 值。
\\n方法一:遍历
\\n思路和算法
\\n我们考虑从前往后遍历 $j$ 来统计答案,对于每个观光景点 $j$ 而言,我们需要遍历 $[0,j-1]$ 的观光景点 $i$ 来计算组成观光组合 $(i,j)$ 得分的最大值 $\\\\textit{cnt}j$ 来作为第 $j$ 个观光景点的值,那么最后的答案无疑就是所有观光景点值的最大值,即 $\\\\max{j=0..n-1}{cnt_j}$。但是遍历 $j$ 需要 $O(n)$ 的时间复杂度,遍历 $[0,j-1]$ 的观光景点 $i$ 也需要 $O(n)$ 的时间复杂度,因此该方法总复杂度为 $O(n^2)$,不能通过所有测试用例,我们需要进一步优化时间复杂度。
\\n我们回过头来看得分公式,我们可以将其拆分成 $\\\\textit{values}[i]+i$ 和 $\\\\textit{values}[j]-j$ 两部分,这样对于统计景点 $j$ 答案的时候,由于 $\\\\textit{values}[j]-j$ 是固定不变的,因此最大化 $\\\\textit{values}[i]+i+\\\\textit{values}[j]-j$ 的值其实就等价于求 $[0,j-1]$ 中 $\\\\textit{values}[i]+i$ 的最大值 $\\\\textit{mx}$,景点 $j$ 的答案即为 $\\\\textit{mx}+\\\\textit{values}[j]-j$ 。而 $\\\\textit{mx}$ 的值我们只要从前往后遍历 $j$ 的时候同时维护即可,这样每次遍历到景点 $j$ 的时候,寻找使得得分最大的 $i$ 就能从 $O(n)$ 降至 $O(1)$ 的时间复杂度,总时间复杂度就能从 $O(n^2)$ 降至 $O(n)$。
\\n<
\\n,
,
,
,
,
,
,
,
>
###C++
\\n\\nclass Solution {\\npublic:\\n int maxScoreSightseeingPair(vector<int>& values) {\\n int ans = 0, mx = values[0] + 0;\\n for (int j = 1; j < values.size(); ++j) {\\n ans = max(ans, mx + values[j] - j);\\n // 边遍历边维护\\n mx = max(mx, values[j] + j);\\n }\\n return ans;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int maxScoreSightseeingPair(int[] values) {\\n int ans = 0, mx = values[0] + 0;\\n for (int j = 1; j < values.length; ++j) {\\n ans = Math.max(ans, mx + values[j] - j);\\n // 边遍历边维护\\n mx = Math.max(mx, values[j] + j);\\n }\\n return ans;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def maxScoreSightseeingPair(self, values: List[int]) -> int:\\n ans = 0\\n mx = values[0] + 0\\n for j in range(1, len(values)):\\n ans = max(ans, mx + values[j] - j)\\n # 边遍历边维护\\n mx = max(mx, values[j] + j)\\n return ans\\n
###JavaScript
\\n\\nvar maxScoreSightseeingPair = function(values) {\\n let ans = 0;\\n let mx = values[0] + 0;\\n for (let j = 1; j < values.length; ++j) {\\n ans = Math.max(ans, mx + values[j] - j);\\n // 边遍历边维护\\n mx = Math.max(mx, values[j] + j);\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction maxScoreSightseeingPair(values: number[]): number {\\n let ans = 0;\\n let mx = values[0] + 0;\\n for (let j = 1; j < values.length; ++j) {\\n ans = Math.max(ans, mx + values[j] - j);\\n // 边遍历边维护\\n mx = Math.max(mx, values[j] + j);\\n }\\n return ans;\\n};\\n
###Go
\\n\\nfunc maxScoreSightseeingPair(values []int) int {\\n ans, mx := 0, values[0] + 0\\n for j := 1; j < len(values); j++ {\\n ans = max(ans, mx + values[j] - j)\\n // 边遍历边维护\\n mx = max(mx, values[j] + j)\\n }\\n return ans\\n}\\n\\nfunc max(x, y int) int {\\n if x > y {\\n return x\\n }\\n return y\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxScoreSightseeingPair(int[] values) {\\n int ans = 0, mx = values[0] + 0;\\n for (int j = 1; j < values.Length; ++j) {\\n ans = Math.Max(ans, mx + values[j] - j);\\n // 边遍历边维护\\n mx = Math.Max(mx, values[j] + j);\\n }\\n return ans;\\n }\\n}\\n
###C
\\n\\nint maxScoreSightseeingPair(int* values, int valuesSize) {\\n int ans = 0, mx = values[0];\\n for (int j = 1; j < valuesSize; ++j) {\\n ans = (ans > mx + values[j] - j) ? ans : mx + values[j] - j;\\n // 边遍历边维护\\n mx = (mx > values[j] + j) ? mx : values[j] + j;\\n }\\n return ans;\\n}\\n\\n
###Rust
\\n\\nimpl Solution {\\n pub fn max_score_sightseeing_pair(values: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n let mut mx = values[0];\\n for j in 1..values.len() {\\n ans = ans.max(mx + values[j] - j as i32);\\n // 边遍历边维护\\n mx = mx.max(values[j] + j as i32);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:遍历 思路和算法\\n\\n我们考虑从前往后遍历 $j$ 来统计答案,对于每个观光景点 $j$ 而言,我们需要遍历 $[0,j-1]$ 的观光景点 $i$ 来计算组成观光组合 $(i,j)$ 得分的最大值 $\\\\textit{cnt}j$ 来作为第 $j$ 个观光景点的值,那么最后的答案无疑就是所有观光景点值的最大值,即 $\\\\max{j=0..n-1}{cnt_j}$。但是遍历 $j$ 需要 $O(n)$ 的时间复杂度,遍历 $[0,j-1]$ 的观光景点 $i$ 也需要 $O(n)$ 的时间复杂度,因此该方法总复杂度为 $O(n^2…","guid":"https://leetcode.cn/problems/best-sightseeing-pair//solution/zui-jia-guan-guang-zu-he-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-06-16T14:21:05.661Z","media":[{"url":"https://assets.leetcode.cn/solution-static/1014/1.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/2.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/3.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/4.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/5.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/6.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/7.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/8.PNG","type":"photo","width":1280,"height":644},{"url":"https://assets.leetcode.cn/solution-static/1014/9.PNG","type":"photo","width":1280,"height":644}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"啥代码不代码的,先上图","url":"https://leetcode.cn/problems/design-browser-history//solution/sha-dai-ma-bu-dai-ma-de-xian-shang-tu-by-time-limi","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 为数组 $\\\\textit{values}$ 的大小。我们只需要遍历一遍数组即可。
\\n- \\n
\\n空间复杂度:$O(1)$。我们只需要常数空间来存放若干变量。
\\n\\n
\\n- 知识点:栈
\\n- 时间复杂度:O(n),n 为操作次数
\\n使用一个栈记录浏览历史,使用一个 pos 记录当前网页在栈中的位置。每次 back 和 forward 操作都只更新 pos 。因为visit操作会把浏览历史前进的记录全部删除,所以每次 visit 先根据 pos 更新下栈顶指针,然后再将 url 入栈。
\\n
\\n可以先看图在看代码~
\\n
\\n
\\n###cpp
\\n\\nclass BrowserHistory {\\npublic:\\n int pos;\\n int top;\\n string history[5001];\\n BrowserHistory(string homepage) : pos(-1), top(0) {\\n visit(homepage);\\n }\\n \\n void visit(string url) {\\n pos ++;\\n top = pos;\\n history[top++] = url;\\n }\\n \\n string back(int steps) {\\n if(steps > pos) {\\n steps = pos;\\n }\\n pos -= steps;\\n return history[pos];\\n }\\n \\n string forward(int steps) {\\n steps = min(steps, top - pos - 1);\\n pos += steps;\\n return history[pos];\\n }\\n};\\n
如果感觉有点意思,可以关注👏HelloNebula👏
\\n\\n
\\n","description":"知识点:栈 时间复杂度:O(n),n 为操作次数\\n\\n使用一个栈记录浏览历史,使用一个 pos 记录当前网页在栈中的位置。每次 back 和 forward 操作都只更新 pos 。因为visit操作会把浏览历史前进的记录全部删除,所以每次 visit 先根据 pos 更新下栈顶指针,然后再将 url 入栈。\\n 可以先看图在看代码~\\n \\n \\n\\n\\n###cpp\\n\\nclass BrowserHistory {\\npublic:\\n int pos;\\n int top;\\n string history[5001];\\n BrowserHistory…","guid":"https://leetcode.cn/problems/design-browser-history//solution/sha-dai-ma-bu-dai-ma-de-xian-shang-tu-by-time-limi","author":"Time-Limit","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-06-07T13:45:36.617Z","media":[{"url":"https://pic.leetcode-cn.com/d957dfd85ee5e21d897e89dcbc7b644fa5d9a869100f349c256b97b23947d620.gif","type":"photo","width":470,"height":441,"blurhash":"LhPt45?a~U9ct8WVj@a}?FNHIW%1"},{"url":"https://pic.leetcode-cn.com/7902dbbdc9b9b8efc34bdc33f7f4743391ca97d798bc8b31d66017a89676d384.gif","type":"photo","width":470,"height":441,"blurhash":"LhPjid^*~U9ct8WVj?a}?FNHIW%0"},{"url":"https://pic.leetcode-cn.com/1f47c4a65d13cb81776bdddac4df0ae200e049916e2da175703d96f8c861c48e-visit.gif","type":"photo","width":470,"height":441,"blurhash":"LhPZ}*~V?G9ctRWVj[oe4;xZxsM}"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"从两侧向中间找到不等的字符,删除后判断是否回文","url":"https://leetcode.cn/problems/valid-palindrome-ii//solution/cong-liang-ce-xiang-zhong-jian-zhao-dao-bu-deng-de","content":"- 分享周赛题解
\\n- 分享计算机专业课知识
\\n- 分享C++相关岗位面试题
\\n- 分享专业书籍PDF
\\n思路来源
\\n题目问我们最多删除一个字符的情况下是否可以构成回文字符串,第一反应是逐个删除各个字符看剩下的字符串是否为回文串,但是这个时间复杂度是
\\nO(N ^ 2)
,题目给出的字符串的长度最大为 50000 ,此做法会超时。回文串的特点是左右对称。假如有两个指针从字符串的两端同时向中间走:如果遇到的元素相等,则该相等的元素是最终回文字符串的一部分;如果遇到的元素不等,则认为此时遇到了构建回文字符串的「障碍」,应当进行处理,处理方式见下文。
\\n初版方案
\\n当左右两个指针遇到不等的元素时,按照题目最原本的意思,我们处理的方式是删除 左指针指向的字符 或者 右指针指向的字符,判断 剩余的所有字符 是否可以构成回文串。
\\n我们观察一下题目给出的示例 2:
\\n\\n输入: \\"abca\\"\\n输出: True\\n解释: 你可以删除c字符。\\n
如果左右指针从两端同时向中间走,那么:
\\n\\n第一步:\\na b c a\\n| |\\nleft right\\n\\n第二步:\\na b c a\\n | |\\n left right\\n
第一步,左右指针遇到的元素相等,继续向中间走;
\\n
\\n第二步,左右指针遇到的元素不等,则必须进行处理:我们必须删除其中的一个字符,然后再判断 剩余的所有字符 是否是回文串。\\n删除 b:\\na c a\\n\\n或者, 删除 c:\\na b a\\n
即判断
\\naca
或者aba
是否为回文字符串。如果删除一个字符后,剩余的全部字符构成字符串 是回文字符串,那么就满足题意。
\\n本方案的时间复杂度是:
\\nO(N)
;由于我判断是否回文使用了[::-1]
翻转形成了新字符串,所以空间复杂度是O(N)
。如果不通过翻转的方式来判断,空间复杂度可以降到O(1)
。###Python
\\n\\nclass Solution(object):\\n def validPalindrome(self, s):\\n \\"\\"\\"\\n :type s: str\\n :rtype: bool\\n \\"\\"\\"\\n isPalindrome = lambda s: s == s[::-1]\\n strPart = lambda s, x: s[:x] + s[x + 1:]\\n left = 0\\n right = len(s) - 1\\n while left < right:\\n if s[left] != s[right]:\\n return isPalindrome(strPart(s, left)) or isPalindrome(strPart(s, right))\\n left += 1\\n right -= 1\\n return True\\n\\n
进阶方案
\\n我们注意到「初版方案」中,在找到第一个不相等的元素后,删除了不相等的一个元素,判断 剩余的所有字符 是不是回文字符串。这个做法和题目最原本的意思完全一致。是否可以简化呢?
\\n分析发现,在找到不相等的元素时,
\\n[0, left)
和(right, len(s) - 1]
这两部分已经判断过是回文的,因此不用再次判断。只用判断[left, right]
区间中的字符串,即删除left
或者right
指向的元素,剩余的区间(left, right]
或者[left, right)
是否为回文串。若
\\n(left, right]
或者[left, right)
为回文串,则说明删除了一个字符可以构成回文串。如题目的示例 2 ,当左右指针遇到了不等元素时,删除
\\nleft
或者right
指向元素后, 我们只用判断c
或者b
是否为回文串。由于这两者是回文串,所以总体的字符串s
删除left
或者right
指向元素也可以构成回文串。本方案的时间复杂度是:
\\nO(N)
;由于我判断是否回文使用了[::-1]
翻转形成了新字符串,所以空间复杂度是O(N)
。如果不通过翻转的方式来判断,空间复杂度可以降到O(1)
。这个方案在找到左右指针不等的字符后,所要检查的字符串更少。
\\n###Python
\\n\\n","description":"思路来源 题目问我们最多删除一个字符的情况下是否可以构成回文字符串,第一反应是逐个删除各个字符看剩下的字符串是否为回文串,但是这个时间复杂度是 O(N ^ 2),题目给出的字符串的长度最大为 50000 ,此做法会超时。\\n\\n回文串的特点是左右对称。假如有两个指针从字符串的两端同时向中间走:如果遇到的元素相等,则该相等的元素是最终回文字符串的一部分;如果遇到的元素不等,则认为此时遇到了构建回文字符串的「障碍」,应当进行处理,处理方式见下文。\\n\\n初版方案\\n\\n当左右两个指针遇到不等的元素时,按照题目最原本的意思,我们处理的方式是删除 左指针指向的字符 或者…","guid":"https://leetcode.cn/problems/valid-palindrome-ii//solution/cong-liang-ce-xiang-zhong-jian-zhao-dao-bu-deng-de","author":"fuxuemingzhu","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-18T17:25:34.299Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"验证回文字符串 Ⅱ","url":"https://leetcode.cn/problems/valid-palindrome-ii//solution/yan-zheng-hui-wen-zi-fu-chuan-ii-by-leetcode-solut","content":"class Solution(object):\\n def validPalindrome(self, s):\\n \\"\\"\\"\\n :type s: str\\n :rtype: bool\\n \\"\\"\\"\\n isPalindrome = lambda x : x == x[::-1]\\n left, right = 0, len(s) - 1\\n while left <= right:\\n if s[left] == s[right]:\\n left += 1\\n right -= 1\\n else:\\n return isPalindrome(s[left + 1 : right + 1]) or isPalindrome(s[left: right])\\n return True\\n
📺视频题解
\\n\\n
📖文字题解
\\n方法一:贪心
\\n考虑最朴素的方法:首先判断原串是否是回文串,如果是,就返回 $\\\\text{true}$;如果不是,则枚举每一个位置作为被删除的位置,再判断剩下的字符串是否是回文串。这种做法的渐进时间复杂度是 $O(n^2)$ 的,会超出时间限制。
\\n我们换一种想法。首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同,如果不相同,则不是回文串;如果相同,则将左右指针都往中间移动一位,直到左右指针相遇,则字符串是回文串。
\\n在允许最多删除一个字符的情况下,同样可以使用双指针,通过贪心实现。初始化两个指针 $\\\\textit{low}$ 和 $\\\\textit{high}$ 分别指向字符串的第一个字符和最后一个字符。每次判断两个指针指向的字符是否相同,如果相同,则更新指针,将 $\\\\textit{low}$ 加 $1$,$\\\\textit{high}$ 减 $1$,然后判断更新后的指针范围内的子串是否是回文字符串。如果两个指针指向的字符不同,则两个字符中必须有一个被删除,此时我们就分成两种情况:即删除左指针对应的字符,留下子串 $s[\\\\textit{low} + 1 : \\\\textit{high}]$,或者删除右指针对应的字符,留下子串 $s[\\\\textit{low} : \\\\textit{high} - 1]$。当这两个子串中至少有一个是回文串时,就说明原始字符串删除一个字符之后就以成为回文串。
\\n\\n
###Java
\\n\\nclass Solution {\\n public boolean validPalindrome(String s) {\\n int low = 0, high = s.length() - 1;\\n while (low < high) {\\n char c1 = s.charAt(low), c2 = s.charAt(high);\\n if (c1 == c2) {\\n ++low;\\n --high;\\n } else {\\n return validPalindrome(s, low, high - 1) || validPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n }\\n\\n public boolean validPalindrome(String s, int low, int high) {\\n for (int i = low, j = high; i < j; ++i, --j) {\\n char c1 = s.charAt(i), c2 = s.charAt(j);\\n if (c1 != c2) {\\n return false;\\n }\\n }\\n return true;\\n }\\n}\\n
###python
\\n\\nclass Solution:\\n def validPalindrome(self, s: str) -> bool:\\n def checkPalindrome(low, high):\\n i, j = low, high\\n while i < j:\\n if s[i] != s[j]:\\n return False\\n i += 1\\n j -= 1\\n return True\\n\\n low, high = 0, len(s) - 1\\n while low < high:\\n if s[low] == s[high]: \\n low += 1\\n high -= 1\\n else:\\n return checkPalindrome(low + 1, high) or checkPalindrome(low, high - 1)\\n return True\\n
###C++
\\n\\nclass Solution {\\npublic:\\n bool checkPalindrome(const string& s, int low, int high) {\\n for (int i = low, j = high; i < j; ++i, --j) {\\n if (s[i] != s[j]) {\\n return false;\\n }\\n }\\n return true;\\n }\\n\\n bool validPalindrome(string s) {\\n int low = 0, high = s.size() - 1;\\n while (low < high) {\\n char c1 = s[low], c2 = s[high];\\n if (c1 == c2) {\\n ++low;\\n --high;\\n } else {\\n return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n }\\n};\\n
###golang
\\n\\nfunc validPalindrome(s string) bool {\\n low, high := 0, len(s) - 1\\n for low < high {\\n if s[low] == s[high] {\\n low++\\n high--\\n } else {\\n flag1, flag2 := true, true\\n for i, j := low, high - 1; i < j; i, j = i + 1, j - 1 {\\n if s[i] != s[j] {\\n flag1 = false\\n break\\n }\\n }\\n for i, j := low + 1, high; i < j; i, j = i + 1, j - 1 {\\n if s[i] != s[j] {\\n flag2 = false\\n break\\n }\\n }\\n return flag1 || flag2\\n }\\n }\\n return true\\n}\\n
###C
\\n\\nbool checkPalindrome(const char* s, int low, int high) {\\n for (int i = low, j = high; i < j; ++i, --j) {\\n if (s[i] != s[j]) {\\n return false;\\n }\\n }\\n return true;\\n}\\n\\nbool validPalindrome(char* s) {\\n int low = 0, high = strlen(s) - 1;\\n while (low < high) {\\n if (s[low] == s[high]) {\\n low++;\\n high--;\\n } else {\\n return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n}\\n
###JavaScript
\\n\\nfunction checkPalindrome(s, low, high) {\\n while (low < high) {\\n if (s[low] !== s[high]) {\\n return false;\\n }\\n low++;\\n high--;\\n }\\n return true;\\n}\\n\\nfunction validPalindrome(s) {\\n let low = 0, high = s.length - 1;\\n while (low < high) {\\n if (s[low] === s[high]) {\\n low++;\\n high--;\\n } else {\\n return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n}\\n
###TypeScript
\\n\\nfunction checkPalindrome(s: string, low: number, high: number): boolean {\\n while (low < high) {\\n if (s[low] !== s[high]) {\\n return false;\\n }\\n low++;\\n high--;\\n }\\n return true;\\n}\\n\\nfunction validPalindrome(s: string): boolean {\\n let low = 0, high = s.length - 1;\\n while (low < high) {\\n if (s[low] === s[high]) {\\n low++;\\n high--;\\n } else {\\n return checkPalindrome(s, low, high - 1) || checkPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public bool CheckPalindrome(string s, int low, int high) {\\n while (low < high) {\\n if (s[low] != s[high]) {\\n return false;\\n }\\n low++;\\n high--;\\n }\\n return true;\\n }\\n\\n public bool ValidPalindrome(string s) {\\n int low = 0, high = s.Length - 1;\\n while (low < high) {\\n if (s[low] == s[high]) {\\n low++;\\n high--;\\n } else {\\n return CheckPalindrome(s, low, high - 1) || CheckPalindrome(s, low + 1, high);\\n }\\n }\\n return true;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn check_palindrome(s: &str, low: usize, high: usize) -> bool {\\n let s = s.as_bytes();\\n let (mut low, mut high) = (low, high);\\n while low < high {\\n if s[low] != s[high] {\\n return false;\\n }\\n low += 1;\\n high -= 1;\\n }\\n true\\n }\\n\\n pub fn valid_palindrome(s: String) -> bool {\\n let (mut low, mut high) = (0, s.len() - 1);\\n while low < high {\\n if s.as_bytes()[low] == s.as_bytes()[high] {\\n low += 1;\\n high -= 1;\\n } else {\\n return Solution::check_palindrome(&s, low, high - 1) || Solution::check_palindrome(&s, low + 1, high);\\n }\\n }\\n true\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"📺视频题解 📖文字题解\\n方法一:贪心\\n\\n考虑最朴素的方法:首先判断原串是否是回文串,如果是,就返回 $\\\\text{true}$;如果不是,则枚举每一个位置作为被删除的位置,再判断剩下的字符串是否是回文串。这种做法的渐进时间复杂度是 $O(n^2)$ 的,会超出时间限制。\\n\\n我们换一种想法。首先考虑如果不允许删除字符,如何判断一个字符串是否是回文串。常见的做法是使用双指针。定义左右指针,初始时分别指向字符串的第一个字符和最后一个字符,每次判断左右指针指向的字符是否相同,如果不相同,则不是回文串;如果相同,则将左右指针都往中间移动一位,直到左右指针相遇…","guid":"https://leetcode.cn/problems/valid-palindrome-ii//solution/yan-zheng-hui-wen-zi-fu-chuan-ii-by-leetcode-solut","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-18T05:02:03.141Z","media":[{"url":"https://leetcode.cn/problems/valid-palindrome-ii//solution/93497e92-4bb9-494f-8550-f4a59326a9e8","type":"photo"},{"url":"https://assets.leetcode.cn/solution-static/680/680_fig1.png","type":"photo","width":1280,"height":720,"blurhash":"LGS6Mb~XFb-W?bn+axaex]jGoJRj"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DP,记忆化递归","url":"https://leetcode.cn/problems/knight-dialer//solution/dpji-yi-hua-di-gui-by-8bun","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是字符串的长度。判断整个字符串是否是回文字符串的时间复杂度是 $O(n)$,遇到不同字符时,判断两个子串是否是回文字符串的时间复杂度也都是 $O(n)$。
\\n- \\n
\\n空间复杂度:$O(1)$。只需要维护有限的常量空间。
\\n二维普通DP
\\n###dart
\\n\\n/**\\n * 每个数字及可达数字为:\\n * 0 -> 4, 6\\n * 1 -> 6, 8\\n * 2 -> 7, 9\\n * 3 -> 4, 8\\n * 4 -> 3, 9, 0\\n * 5 ->\\n * 6 -> 1, 7, 0\\n * 7 -> 2, 6\\n * 8 -> 1, 3\\n * 9 -> 4, 2\\n * 设dp[time][num] 表示骑士第time次跳到数字num时组成的不同号码的个数\\n * 那么要实现第time次跳到数字num,那么就要保证第time-1次跳到num的可达数字\\n * 也就是说dp[time][num]是第time-1跳到num的所有可达数字的dp的总和\\n * 最后返回要求dp[N-1][]的值\\n */\\npublic class Solution {\\n //行下标为起点num,每一行的所有数据为可达数字\\n private int[][] path = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {3, 9, 0}, {}, {1, 7, 0}, {2, 6}, {1, 3}, {4, 2}};\\n private static final int MOD = 1000000007;\\n\\n public int knightDialer(int N) {\\n int[][] dp = new int[N][10];\\n for (int num = 0; num < 10; num++)\\n dp[0][num] = 1; //第0次(跳到)num的不同号码个数为1\\n\\n for (int time = 1; time < N; time++) {\\n for (int num = 0; num < 10; num++){\\n for (int arrive : path[num])\\n dp[time][num] = (dp[time][num] + dp[time - 1][arrive]) % MOD;\\n }\\n }\\n int res = 0;\\n for (int num = 0; num < 10; num++)\\n res = (res + dp[N - 1][num]) % MOD;\\n return res;\\n }\\n}\\n\\n
记忆化递归
\\n###dart
\\n\\n/**\\n * 记忆化回溯\\n * 7 -> 2, 6\\n * 9 -> 4, 2\\n * 由上面两个例子可知,2可以到7和9,所以在动态规划中求如dp[N-1][7]和dp[N-1][9]时\\n * 都需要计算dp[N-2][2]的值,如果在递归里面不提前记忆dp[N-2][2]的值\\n * 就有可能造成了重复计算(在动规里面不会,但是这里使用递归,所以还是需要用一个数据结构来记忆)\\n */\\npublic class Solution {\\n\\n private int[][] path = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {3, 9, 0}, {}, {1, 7, 0}, {2, 6}, {1, 3}, {4, 2}};\\n private static final int MOD = 1000000007;\\n\\n public int knightDialer(int N) {\\n int[][] memo = new int[N][10];\\n int res = 0;\\n for (int num = 0; num < 10; num++)\\n res = (res + helper(N - 1, num, memo, path)) % MOD;\\n return res;\\n }\\n\\n /**\\n * 求第n次跳到num时组成的不同号码的个数\\n * @param n\\n * @param num\\n * @param memo\\n * @param path\\n * @return\\n */\\n private int helper(int n, int num, int[][] memo, int[][] path) {\\n if (n == 0) return 1;\\n if (memo[n][num] != 0) //如果已经算过\\n return memo[n][num];\\n int res = 0;\\n for (int neighbor : path[num])\\n res = (res + helper(n - 1, neighbor, memo, path)) % MOD;\\n return memo[n][num] = res;\\n }\\n}\\n\\n
滚动数组
\\n###java
\\n\\n","description":"二维普通DP ###dart\\n\\n/**\\n * 每个数字及可达数字为:\\n * 0 -> 4, 6\\n * 1 -> 6, 8\\n * 2 -> 7, 9\\n * 3 -> 4, 8\\n * 4 -> 3, 9, 0\\n * 5 ->\\n * 6 -> 1, 7, 0\\n * 7 -> 2, 6\\n * 8 -> 1, 3\\n * 9 -> 4, 2\\n * 设dp[time][num] 表示骑士第time次跳到数字num时组成的不同号码的个数\\n * 那么要实现第time次跳到数字num,那么就要保证第time-1次跳到num的可达数字\\n * 也就是说dp[time…","guid":"https://leetcode.cn/problems/knight-dialer//solution/dpji-yi-hua-di-gui-by-8bun","author":"8bun","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-16T09:10:59.875Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"排序滑窗","url":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/pai-xu-hua-chuang-by-netcan","content":"public class Solution {\\n //行下标为起点num,每一行的所有数据为可达数字\\n private int[][] path = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {3, 9, 0}, {}, {1, 7, 0}, {2, 6}, {1, 3}, {4, 2}};\\n private static final int MOD = 1_100_000_007; //这是Java7引入的新特性。分割数字增强可读性。当然也有限制:https://docs.oracle.com/javase/7/docs/technotes/guides/language/underscores-literals.html\\n\\n public int knightDialer(int N) {\\n int[][] dp = new int[2][10];\\n Arrays.fill(dp[0], 1);\\n //第0步跳到不同num的不同号码个数先存储在第0行,第一行接下来存跳第一步之后的结果(time=1),\\n //然后第0行根据第一行的结果存跳第二步之后的结果....,所以,步数 -> 结果存的行有如下规律:\\n /**\\n * 0 0\\n * 1 1\\n * 2 0\\n * 3 1\\n * ..\\n * 所以第N-1步要看N-1是奇数还是偶数,奇数就存在第1行,偶数存在第0行\\n */\\n for (int time = 1; time < N; ++time) { //当time从1开始递增时,time&1为1 0 1 0..., ~time&1 为 0 1 0 1....\\n Arrays.fill(dp[time & 1], 0); //轮流更新dp[1][]和dp[0][]\\n\\n for (int num = 0; num < 10; num++) {\\n for (int arrive : path[num])\\n dp[time & 1][arrive] = (dp[time & 1][arrive] + dp[~time & 1][arrive]) % MOD;\\n }\\n }\\n int res = 0;\\n /**\\n * 第N-1步要看N-1是奇数还是偶数,奇数就存在第1行,偶数存在第0行\\n * 即看N是是奇数还是偶数,奇数就存在第0行,偶数存在第1行\\n * 由于当N从1(N>=1)开始递增时,也就是N分别为1,2,3,4时,~N&1分别为0 1 0 1(N为奇数则是0)\\n * 那么我们使用~N&1的结果就可以求出N-1结果的所在的行\\n */\\n for (int x : dp[~N & 1])\\n res = (res + x) % MOD;\\n return (int)res;\\n }\\n}\\n\\n
解题思路:
\\n首先将 $k$ 组数据升序合并成一组,并记录每个数字所属的组,例如:
\\n$[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]$
\\n合并升序后得到:
\\n
\\n$[(0, 1), (4, 0), (5, 2), (9, 1), (10, 0), (12, 1), (15, 0), (18, 2), (20, 1), (22, 2), (24, 0), (26, 0), (30, 2)]$然后只看所属组的话,那么
\\n
\\n$[1, 0, 2, 1, 0, 1, 0, 2, 1, 2, 0, 0, 2]$按组进行滑窗,保证一个窗口的组满足$k$组后在记录窗口的最小区间值。
\\n\\n[1 0 2] 2 1 0 1 0 2 1 2 0 0 2 [0, 5]\\n1 [0 2 1] 1 0 1 0 2 1 2 0 0 2 [0, 5]\\n1 0 [2 1 0] 0 1 0 2 1 2 0 0 2 [0, 5]\\n1 0 [2 1 0 1] 1 0 2 1 2 0 0 2 [0, 5]\\n1 0 [2 1 0 1 0] 0 2 1 2 0 0 2 [0, 5]\\n1 0 2 1 0 [1 0 2] 2 1 2 0 0 2 [0, 5]\\n1 0 2 1 0 1 [0 2 1] 1 2 0 0 2 [0, 5]\\n1 0 2 1 0 1 [0 2 1 2] 2 0 0 2 [0, 5]\\n1 0 2 1 0 1 0 2 [1 2 0] 0 0 2 [20, 24]\\n1 0 2 1 0 1 0 2 [1 2 0 0] 0 2 [20, 24]\\n1 0 2 1 0 1 0 2 [1 2 0 0 2] 2 [20, 24]\\n
###C++
\\n\\n","description":"解题思路: 首先将 $k$ 组数据升序合并成一组,并记录每个数字所属的组,例如:\\n\\n$[[4,10,15,24,26], [0,9,12,20], [5,18,22,30]]$\\n\\n合并升序后得到:\\n $[(0, 1), (4, 0), (5, 2), (9, 1), (10, 0), (12, 1), (15, 0), (18, 2), (20, 1), (22, 2), (24, 0), (26, 0), (30, 2)]$\\n\\n然后只看所属组的话,那么\\n $[1, 0, 2, 1, 0, 1, 0, 2, 1, 2, 0, 0, 2]$\\n\\n按组进行滑窗…","guid":"https://leetcode.cn/problems/smallest-range-covering-elements-from-k-lists//solution/pai-xu-hua-chuang-by-netcan","author":"netcan","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-10T07:07:07.042Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【🐼熊猫刷题Python3】一维动态规划,易懂(附视频题解)","url":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/xiong-mao-shua-ti-python3-dong-tai-gui-hua-yi-do-2","content":"class Solution {\\npublic:\\n vector<int> smallestRange(vector<vector<int>>& nums) {\\n vector<pair<int, int>> ordered; // (number, group)\\n for (size_t k = 0; k < nums.size(); ++k)\\n for (auto n: nums[k]) ordered.push_back({n, k});\\n sort(ordered.begin(), ordered.end());\\n\\n int i = 0, k = 0;\\n vector<int> ans;\\n unordered_map<int, int> count;\\n for (size_t j = 0; j < ordered.size(); ++j) {\\n if (! count[ordered[j].second]++) ++k;\\n if (k == nums.size()) { \\n while (count[ordered[i].second] > 1) --count[ordered[i++].second]; // minialize range\\n if (ans.empty() || ans[1] - ans[0] > ordered[j].first - ordered[i].first) {\\n ans = vector<int>{ordered[i].first, ordered[j].first};\\n }\\n }\\n }\\n\\n return ans;\\n }\\n};\\n
\\n
\\n⏲阅读大约需要 3min🔑解题思路
\\n动态规划问题最重要是3步:
\\n\\n
\\n- 明确创建怎样的dp数组
\\n- 初始化dp数组
\\n- 明确动态转移方程
\\n对于本题不难想到应该用一个数组存储到当前某一天需要花费的最少费用,这里为了下标和天数对应,dp数组的长度选择 days 中最后一个天数多加 1 个长度,因为开始没有费用,所以初始化为 0 ,之后开始对 dp 数组进行更新,那么每到达一个位置首先考虑当前天数是否在days 中,如果不在那花费的费用肯定和它前一天花费的最少费用相同(这里用一个 idx 指标指示应该处理哪一个天数,这样就不必用
\\nif i in days
这样的语句判断天数是否需要处理了,可以让程序快一些),如果在的话,我们就要从三种购买方式中选择一种花费费用最少的,即你想到达第 i 天,你需要从 i 的前1或7或30天的后一位置花费对应cost[0]、cost[1]、cost[2]的钱才能到第 i 天。
\\n具体细节见代码。视频讲解:
\\n
\\n🐼代码部分
\\n###python3
\\n\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n dp = [0 for _ in range(days[-1] + 1)] # dp数组,每个元素代表到当前天数最少钱数,为下标方便对应,多加一个 0 位置\\n days_idx = 0 # 设定一个days指标,标记应该处理 days 数组中哪一个元素\\n for i in range(1, len(dp)):\\n if i != days[days_idx]: # 若当前天数不是待处理天数,则其花费费用和前一天相同\\n dp[i] = dp[i - 1]\\n else:\\n # 若 i 走到了待处理天数,则从三种方式中选一个最小的\\n dp[i] = min(dp[max(0, i - 1)] + costs[0],\\n dp[max(0, i - 7)] + costs[1],\\n dp[max(0, i - 30)] + costs[2])\\n days_idx += 1\\n return dp[-1] # 返回最后一天对应的费用即可\\n
如果你喜欢这条题解的话,欢迎左下角点个赞👍👍👍
\\n🎈在我的力扣主页@LotusPanda可以找到之前的文字题解和视频题解(主页左侧有b站和油管链接),欢迎关注!
\\n","description":"⏲阅读大约需要 3min\\n\\n🔑解题思路\\n\\n动态规划问题最重要是3步:\\n\\n明确创建怎样的dp数组\\n初始化dp数组\\n明确动态转移方程\\n\\n对于本题不难想到应该用一个数组存储到当前某一天需要花费的最少费用,这里为了下标和天数对应,dp数组的长度选择 days 中最后一个天数多加 1 个长度,因为开始没有费用,所以初始化为 0 ,之后开始对 dp 数组进行更新,那么每到达一个位置首先考虑当前天数是否在days 中,如果不在那花费的费用肯定和它前一天花费的最少费用相同(这里用一个 idx 指标指示应该处理哪一个天数,这样就不必用 if i in days 这样的语句…","guid":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/xiong-mao-shua-ti-python3-dong-tai-gui-hua-yi-do-2","author":"LotusPanda","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-06T01:16:31.046Z","media":[{"url":"https://pic.leetcode-cn.com/cdc32fc07d5e209c1d6235f8ae0a9e926f3d2a1bb07c6d81092df4cf9bf7b192-leetcode.png","type":"photo","width":1440,"height":900,"blurhash":"L07UI{j[fQj[j[fQfQfQfQfQfQfQ"},{"url":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/50a49ea3-6e23-49b0-aa6f-8d8bd56c308c","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最低票价","url":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/zui-di-piao-jie-by-leetcode-solution","content":"📺 视频题解
\\n\\n
📖 文字题解
\\n方法一:记忆化搜索(日期变量型)
\\n思路和算法
\\n我们用 $\\\\textit{dp}(i)$ 来表示从第 $i$ 天开始到一年的结束,我们需要花的钱。考虑到一张通行证可以让我们在「接下来」的若干天进行旅行,所以我们「从后往前」倒着进行动态规划。
\\n对于一年中的任意一天:
\\n\\n
\\n- \\n
\\n如果这一天不是必须出行的日期,那我们可以贪心地选择不买。这是因为如果今天不用出行,那么也不必购买通行证,并且通行证越晚买越好。所以有 $\\\\textit{dp}(i) = \\\\textit{dp}(i + 1)$;
\\n- \\n
\\n如果这一天是必须出行的日期,我们可以选择买 $1$,$7$ 或 $30$ 天的通行证。若我们购买了 $j$ 天的通行证,那么接下来的 $j - 1$ 天,我们都不再需要购买通行证,只需要考虑第 $i + j$ 天及以后即可。因此,我们有
\\n$$
\\n
\\n\\\\textit{dp}(i) = \\\\min{\\\\textit{cost}(j) + \\\\textit{dp}(i + j)}, \\\\quad j \\\\in {1, 7, 30}
\\n$$其中 $\\\\textit{cost}(j)$ 表示 $j$ 天通行证的价格。为什么我们只需要考虑第 $i+j$ 天及以后呢?这里和第一条的贪心思路是一样的,如果我们需要购买通行证,那么一定越晚买越好,在握着一张有效的通行证的时候购买其它的通行证显然是不划算的。
\\n由于我们是倒着进行动态规划的,因此我们可以使用记忆化搜索,减少代码的编写难度。我们使用一个长度为 $366$ 的数组(因为天数是 $[1, 365]$,而数组的下标是从 $0$ 开始的)存储所有的动态规划结果,这样所有的 $\\\\textit{dp}(i)$ 只会被计算一次(和普通的动态规划相同),时间复杂度不会增大。
\\n最终的答案记为 $\\\\textit{dp}(1)$。
\\n###Java
\\n\\nclass Solution {\\n int[] costs;\\n Integer[] memo;\\n Set<Integer> dayset;\\n\\n public int mincostTickets(int[] days, int[] costs) {\\n this.costs = costs;\\n memo = new Integer[366];\\n dayset = new HashSet();\\n for (int d: days) {\\n dayset.add(d);\\n }\\n return dp(1);\\n }\\n\\n public int dp(int i) {\\n if (i > 365) {\\n return 0;\\n }\\n if (memo[i] != null) {\\n return memo[i];\\n }\\n if (dayset.contains(i)) {\\n memo[i] = Math.min(Math.min(dp(i + 1) + costs[0], dp(i + 7) + costs[1]), dp(i + 30) + costs[2]);\\n } else {\\n memo[i] = dp(i + 1);\\n }\\n return memo[i];\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n dayset = set(days)\\n durations = [1, 7, 30]\\n\\n @lru_cache(None)\\n def dp(i):\\n if i > 365:\\n return 0\\n elif i in dayset:\\n return min(dp(i + d) + c for c, d in zip(costs, durations))\\n else:\\n return dp(i + 1)\\n\\n return dp(1)\\n
###C++
\\n\\nclass Solution {\\n unordered_set<int> dayset;\\n vector<int> costs;\\n int memo[366] = {0};\\n\\npublic:\\n int mincostTickets(vector<int>& days, vector<int>& costs) {\\n this->costs = costs;\\n for (int d: days) {\\n dayset.insert(d);\\n }\\n memset(memo, -1, sizeof(memo));\\n return dp(1);\\n }\\n\\n int dp(int i) {\\n if (i > 365) {\\n return 0;\\n }\\n if (memo[i] != -1) {\\n return memo[i];\\n }\\n if (dayset.count(i)) {\\n memo[i] = min(min(dp(i + 1) + costs[0], dp(i + 7) + costs[1]), dp(i + 30) + costs[2]);\\n } else {\\n memo[i] = dp(i + 1);\\n }\\n return memo[i];\\n }\\n};\\n
###golang
\\n\\nfunc mincostTickets(days []int, costs []int) int {\\n memo := [366]int{}\\n dayM := map[int]bool{}\\n for _, d := range days {\\n dayM[d] = true\\n }\\n\\n var dp func(day int) int \\n dp = func(day int) int {\\n if day > 365 {\\n return 0\\n }\\n if memo[day] > 0 {\\n return memo[day]\\n }\\n if dayM[day] {\\n memo[day] = min(min(dp(day + 1) + costs[0], dp(day + 7) + costs[1]), dp(day + 30) + costs[2])\\n } else {\\n memo[day] = dp(day + 1)\\n }\\n return memo[day]\\n }\\n return dp(1)\\n}\\n\\nfunc min(x, y int) int {\\n if x < y {\\n return x\\n }\\n return y\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(W)$,其中 $W = 365$ 是旅行计划中日期的最大值,我们需要计算 $W$ 个解,而每个解最多需要查询 $3$ 个其他的解,因此计算量为 $O(3 * W)=O(W)$。
\\n- \\n
\\n空间复杂度:$O(W)$,我们需要长度为 $O(W)$ 的数组来存储所有的解。
\\n方法二:记忆化搜索(窗口变量型)
\\n思路
\\n方法一需要遍历一年中所有的天数,无论 $\\\\textit{days}$ 的长度是多少。
\\n但是观察方法一的递推式,我们可以看到,如果我们查询 $\\\\textit{dp}(i)$,而第 $i$ 天我们又不需要出行的话,那么 $\\\\textit{dp}$ 函数会一直向后计算 $\\\\textit{dp}(i + 1) = \\\\textit{dp}(i + 2) = \\\\textit{dp}(i + 3)$ 一直到一年结束或者有一天我们需要出行为止。那么我们其实可以直接跳过这些不需要出行的日期,直接找到下一个需要出行的日期。
\\n算法
\\n现在,我们令 $\\\\textit{dp}(i)$ 表示能够完成从第 $\\\\textit{days}[i]$ 天到最后的旅行计划的最小花费(注意,不再是第 $i$ 天到最后的最小花费)。令 $j_1$ 是满足 $\\\\textit{days}[j_1] >= \\\\textit{days}[i] + 1$ 的最小下标,$j_7$ 是满足 $\\\\textit{days}[j_7] >= \\\\textit{days}[i] + 7$ 的最小下标, $j_{30}$ 是满足 $\\\\textit{days}[j_{30}] >= \\\\textit{days}[i] + 30$ 的最小下标,那么就有:
\\n$$
\\n
\\n\\\\textit{dp}(i) = \\\\min(\\\\textit{dp}(j_1) + \\\\textit{costs}[0], \\\\textit{dp}(j_7) + \\\\textit{costs}[1], \\\\textit{dp}(j_{30}) + \\\\textit{costs}[2])
\\n$$###Java
\\n\\nclass Solution {\\n int[] days, costs;\\n Integer[] memo;\\n int[] durations = new int[]{1, 7, 30};\\n\\n public int mincostTickets(int[] days, int[] costs) {\\n this.days = days;\\n this.costs = costs;\\n memo = new Integer[days.length];\\n return dp(0);\\n }\\n\\n public int dp(int i) {\\n if (i >= days.length) {\\n return 0;\\n }\\n if (memo[i] != null) {\\n return memo[i];\\n }\\n memo[i] = Integer.MAX_VALUE;\\n int j = i;\\n for (int k = 0; k < 3; ++k) {\\n while (j < days.length && days[j] < days[i] + durations[k]) {\\n j++;\\n }\\n memo[i] = Math.min(memo[i], dp(j) + costs[k]);\\n }\\n return memo[i];\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def mincostTickets(self, days: List[int], costs: List[int]) -> int:\\n N = len(days)\\n durations = [1, 7, 30]\\n\\n @lru_cache(None)\\n def dp(i):\\n if i >= N:\\n return 0\\n ans = 10**9\\n j = i\\n for c, d in zip(costs, durations):\\n while j < N and days[j] < days[i] + d:\\n j += 1\\n ans = min(ans, dp(j) + c)\\n return ans\\n\\n return dp(0)\\n
###C++
\\n\\nclass Solution {\\nprivate:\\n vector<int> days, costs;\\n vector<int> memo;\\n int durations[3] = {1, 7, 30};\\n \\npublic:\\n int mincostTickets(vector<int>& days, vector<int>& costs) {\\n this->days = days;\\n this->costs = costs;\\n memo.assign(days.size(), -1);\\n return dp(0);\\n }\\n\\n int dp(int i) {\\n if (i >= days.size()) {\\n return 0;\\n }\\n if (memo[i] != -1) {\\n return memo[i];\\n }\\n memo[i] = INT_MAX;\\n int j = i;\\n for (int k = 0; k < 3; ++k) {\\n while (j < days.size() && days[j] < days[i] + durations[k]) {\\n ++j;\\n }\\n memo[i] = min(memo[i], dp(j) + costs[k]);\\n }\\n return memo[i];\\n }\\n};\\n
###golang
\\n\\nfunc mincostTickets(days []int, costs []int) int {\\n memo := [366]int{}\\n durations := []int{1, 7, 30}\\n\\n var dp func(idx int) int \\n dp = func(idx int) int {\\n if idx >= len(days) {\\n return 0\\n }\\n if memo[idx] > 0 {\\n return memo[idx]\\n }\\n memo[idx] = math.MaxInt32\\n j := idx\\n for i := 0; i < 3; i++ {\\n for ; j < len(days) && days[j] < days[idx] + durations[i]; j++ { }\\n memo[idx] = min(memo[idx], dp(j) + costs[i])\\n }\\n return memo[idx]\\n }\\n return dp(0)\\n}\\n\\nfunc min(x, y int) int {\\n if x < y {\\n return x\\n }\\n return y\\n}\\n
###Cangjie
\\n\\nclass Solution {\\n var costs = Array<Int>()\\n let memo = Array<Int>(366, item: -1)\\n let dayset = HashSet<Int>()\\n\\n func mincostTickets(days: Array<Int64>, costs: Array<Int64>): Int64 {\\n this.costs = costs\\n for (d in days) {\\n dayset.put(d)\\n }\\n return dp(1)\\n }\\n\\n func dp(i: Int): Int {\\n if (i > 365) {\\n return 0\\n }\\n if (memo[i] != -1) {\\n return memo[i]\\n }\\n if (dayset.contains(i)) {\\n memo[i] = min(min(dp(i + 1) + costs[0], dp(i + 7) + costs[1]), dp(i + 30) + costs[2])\\n } else {\\n memo[i] = dp(i + 1)\\n }\\n return memo[i]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"📺 视频题解 📖 文字题解\\n方法一:记忆化搜索(日期变量型)\\n\\n思路和算法\\n\\n我们用 $\\\\textit{dp}(i)$ 来表示从第 $i$ 天开始到一年的结束,我们需要花的钱。考虑到一张通行证可以让我们在「接下来」的若干天进行旅行,所以我们「从后往前」倒着进行动态规划。\\n\\n对于一年中的任意一天:\\n\\n如果这一天不是必须出行的日期,那我们可以贪心地选择不买。这是因为如果今天不用出行,那么也不必购买通行证,并且通行证越晚买越好。所以有 $\\\\textit{dp}(i) = \\\\textit{dp}(i + 1)$;\\n\\n如果这一天是必须出行的日期,我们可以选择买…","guid":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/zui-di-piao-jie-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-05T10:53:11.394Z","media":[{"url":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/571c1009-3b14-47e2-b768-c04f2aa99f2d","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"[java] 动态规划思路步骤 (从后向前迭代)","url":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/java-dong-tai-gui-hua-si-lu-bu-zou-cong-hou-xiang-","content":"- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是出行日期的数量,我们需要计算 $N$ 个解,而计算每个解的过程中最多将指针挪动 $30$ 步,计算量为 $O(30 * N)=O(N)$。
\\n- \\n
\\n空间复杂度:$O(N)$,我们需要长度为 $O(N)$ 的数组来存储所有的解。
\\n动态规划
\\n关键图
\\n\\n\\n
思路
\\n\\n
\\n- 今天不需要出门,不用买票
\\n- 今天如果要出门,需要买几天?\\n
\\n\\n
\\n- 看往后几天(最多 30 天内)要不要出门\\n
\\n\\n
\\n- 30 天内都没有要出行的,那只买今天就好
\\n- 有要出门的(不同决策)\\n
\\n\\n
\\n- 这次 和 后面几次 分开买更省
\\n- 这次 和 后面几次 一起买更省
\\n细化思路
\\n上述思路显而易见,最关键在于:「今天买多少,得看后几天怎么安排」,即「前面依赖后面」——从后向前来买。
\\n如图所示,例
\\ndays = [1,4,6,7,8,20]
\\n
\\n- 第
\\n21
及以后的日子都不需要出门,不用买票- 第
\\n20
需要出门,需要买几天?\\n\\n
\\n- 不考虑
\\n20
之前要不要出门,否则与思路相违背- 第
\\n20
之后没有出门日,故买「一天」的costs[0]
最省钱\\n
\\n
\\n- 第
\\n9
-19
不需要出门,则不用买\\n
\\n
\\n- 第
\\n8
需要出门,需要买几天?\\n\\n
\\n- 往后(只需看往后 30 天)有出门的需求\\n
\\n\\n
\\n- 决策 1:买一天期,后面的不包
\\n- 决策 2:买七天期,包到第
\\n8 + 7 - 1
天,第8 + 7
天往后的不包- 决策 3:买三十天期,包到第
\\n8 + 30 - 1
天,第8 + 30
天往后的不包- 下图展示了三种决策所包含的日期跨度(黄色区域画多了一天...)、所花费用\\n
\\n\\n
\\n- 可见,决策 3 包三十天期的话,第
\\n20
可不用花钱\\n
假设题目只有
\\n[8, 20]
两天需要出行,则\\nint result8 = min(c[0] + c[0], c[1] + c[0], c[2] + 0);\\n// 第 8 天 = min(决策1, 决策2, 决策3);\\n
\\n
\\n- 抽象,定义状态,确定从后向前的递推公式\\n
\\n\\n
\\n- 将上述结果换个说法:「
\\nresult
为第8
天开始,所需最小费用 累计」- 抽象,定义状态: 「
\\ndp[i]
为第i
天开始,所需最小费用 累计」- 则
\\n\\ndp[i] = min(决策1, 决策2, 决策3);\\n = min(c[0] + 1天后不包, c[1] + 7天后不包, c[2] + 30天不包);\\n = min(c[0] + dp[i + 1], c[1] + dp[i + 7], c[2] + dp[i + 30]);\\n
\\n
参考代码
\\n\\nclass Solution {\\n public int mincostTickets(int[] days, int[] costs) {\\n int len = days.length, maxDay = days[len - 1], minDay = days[0];\\n int[] dp = new int[maxDay + 31]; // 多扩几天,省得判断 365 的限制\\n // 只需看 maxDay -> minDay,此区间外都不需要出门,不会增加费用\\n for (int d = maxDay, i = len - 1; d >= minDay; d--) {\\n // i 表示 days 的索引\\n // 也可提前将所有 days 放入 Set,再通过 set.contains() 判断\\n if (d == days[i]) {\\n dp[d] = Math.min(dp[d + 1] + costs[0], dp[d + 7] + costs[1]);\\n dp[d] = Math.min(dp[d], dp[d + 30] + costs[2]);\\n i--; // 别忘了递减一天\\n } else dp[d] = dp[d + 1]; // 不需要出门\\n }\\n return dp[minDay]; // 从后向前遍历,返回最前的 minDay\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:$O(maxDay - minDay)$
\\n- 空间复杂度:$O(maxDay)$
\\n再优化
\\n\\n
\\n","description":"动态规划 关键图\\n\\n!跳到代码\\n\\n思路\\n今天不需要出门,不用买票\\n今天如果要出门,需要买几天?\\n看往后几天(最多 30 天内)要不要出门\\n30 天内都没有要出行的,那只买今天就好\\n有要出门的(不同决策)\\n这次 和 后面几次 分开买更省\\n这次 和 后面几次 一起买更省\\n细化思路\\n\\n上述思路显而易见,最关键在于:「今天买多少,得看后几天怎么安排」,即「前面依赖后面」——从后向前来买。\\n\\n如图所示,例 days = [1,4,6,7,8,20]\\n\\n第 21 及以后的日子都不需要出门,不用买票\\n第 20 需要出门,需要买几天?\\n不考虑 20 之…","guid":"https://leetcode.cn/problems/minimum-cost-for-tickets//solution/java-dong-tai-gui-hua-si-lu-bu-zou-cong-hou-xiang-","author":"lzhlyle","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-05T06:28:47.928Z","media":[{"url":"https://pic.leetcode-cn.com/6b85bdc77ff483209c4d66e1c3ba1261e34a8a62350334144006d5a5803b20c4-image.png","type":"photo","width":705,"height":199,"blurhash":"LSS$oeoYkHojoma%fKfNtAj_awau"},{"url":"https://pic.leetcode-cn.com/284d7ef7ed80b3065fbd18a085bc1fe0fdae55afc014db36fda1bac68471f4fb-image.png","type":"photo","width":944,"height":129,"blurhash":"L07UI{j[fQj[t7fQfQfQfQfQfQfQ"},{"url":"https://pic.leetcode-cn.com/f46be536fb31210704abd4918aa55dbf17040a2b1fabc20c30b35a9b2f41c7ac-image.png","type":"photo","width":943,"height":133,"blurhash":"L07UI{j[fQj[t7fQfQfQfQfQfQfQ"},{"url":"https://pic.leetcode-cn.com/6eb931f53b200745f3a52a93fecf0effed056f07912565d8a48736c1a8334f7c-image.png","type":"photo","width":976,"height":221,"blurhash":"L07UI{j[fQj[offQfQfQfQfQfQfQ"},{"url":"https://pic.leetcode-cn.com/6b85bdc77ff483209c4d66e1c3ba1261e34a8a62350334144006d5a5803b20c4-image.png","type":"photo","width":705,"height":199,"blurhash":"L07UI{j[fQj[t7fQfQfQfQfQfQfQ"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"跳跃游戏 II","url":"https://leetcode.cn/problems/jump-game-ii//solution/tiao-yue-you-xi-ii-by-leetcode-solution","content":"- 当
\\nd != days[i]
时,可直接将d = days[i]
以快速跳到前一个需要出发的d
- 若如此,则需将中间跳过的
\\ndp[d]
都补上总费用累计解题思路
\\n这道题是典型的贪心算法,通过局部最优解得到全局最优解。以下两种方法都是使用贪心算法实现,只是贪心的策略不同。
\\n方法一:反向查找出发位置
\\n我们的目标是到达数组的最后一个位置,因此我们可以考虑最后一步跳跃前所在的位置,该位置通过跳跃能够到达最后一个位置。
\\n如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上来看,我们可以「贪心」地选择距离最后一个位置最远的那个位置,也就是对应下标最小的那个位置。因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。
\\n找到最后一步跳跃前所在的位置之后,我们继续贪心地寻找倒数第二步跳跃前所在的位置,以此类推,直到找到数组的开始位置。
\\n###Java
\\n\\nclass Solution {\\n public int jump(int[] nums) {\\n int position = nums.length - 1;\\n int steps = 0;\\n while (position > 0) {\\n for (int i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n }\\n}\\n
###golang
\\n\\nfunc jump(nums []int) int {\\n position := len(nums) - 1\\n steps := 0\\n for position > 0 {\\n for i := 0; i < position; i++ {\\n if i + nums[i] >= position {\\n position = i\\n steps++\\n break\\n }\\n }\\n }\\n return steps\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int jump(vector<int>& nums) {\\n int position = nums.size() - 1;\\n int steps = 0;\\n while (position > 0) {\\n for (int i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def jump(self, nums: List[int]) -> int:\\n position = len(nums) - 1\\n steps = 0\\n while position > 0:\\n for i in range(position):\\n if i + nums[i] >= position:\\n position = i\\n steps += 1\\n break\\n return steps\\n
###C
\\n\\nint jump(int* nums, int numsSize) {\\n int position = numsSize - 1;\\n int steps = 0;\\n while (position > 0) {\\n for (int i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n}\\n
###JavaScript
\\n\\nvar jump = function(nums) {\\n let position = nums.length - 1;\\n let steps = 0;\\n while (position > 0) {\\n for (let i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n};\\n
###TypeScript
\\n\\nfunction jump(nums: number[]): number {\\n let position = nums.length - 1;\\n let steps = 0;\\n while (position > 0) {\\n for (let i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int Jump(int[] nums) {\\n int position = nums.Length - 1;\\n int steps = 0;\\n while (position > 0) {\\n for (int i = 0; i < position; i++) {\\n if (i + nums[i] >= position) {\\n position = i;\\n steps++;\\n break;\\n }\\n }\\n }\\n return steps;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn jump(nums: Vec<i32>) -> i32 {\\n let mut position = nums.len() - 1;\\n let mut steps = 0;\\n while position > 0 {\\n for i in 0..position {\\n if i + nums[i] as usize >= position {\\n position = i;\\n steps += 1;\\n break;\\n }\\n }\\n }\\n steps\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(n^2)$,其中 $n$ 是数组长度。有两层嵌套循环,在最坏的情况下,例如数组中的所有元素都是 $1$,
\\nposition
需要遍历数组中的每个位置,对于position
的每个值都有一次循环。- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:正向查找可到达的最大位置
\\n方法一虽然直观,但是时间复杂度比较高,有没有办法降低时间复杂度呢?
\\n如果我们「贪心」地进行正向查找,每次找到可到达的最远位置,就可以在线性时间内得到最少的跳跃次数。
\\n例如,对于数组
\\n[2,3,1,2,4,2,3]
,初始位置是下标 0,从下标 0 出发,最远可到达下标 2。下标 0 可到达的位置中,下标 1 的值是 3,从下标 1 出发可以达到更远的位置,因此第一步到达下标 1。从下标 1 出发,最远可到达下标 4。下标 1 可到达的位置中,下标 4 的值是 4 ,从下标 4 出发可以达到更远的位置,因此第二步到达下标 4。
\\n\\n
在具体的实现中,我们维护当前能够到达的最大下标位置,记为边界。我们从左到右遍历数组,到达边界时,更新边界并将跳跃次数增加 1。
\\n在遍历数组时,我们不访问最后一个元素,这是因为在访问最后一个元素之前,我们的边界一定大于等于最后一个位置,否则就无法跳到最后一个位置了。如果访问最后一个元素,在边界正好为最后一个位置的情况下,我们会增加一次「不必要的跳跃次数」,因此我们不必访问最后一个元素。
\\n###Java
\\n\\nclass Solution {\\n public int jump(int[] nums) {\\n int length = nums.length;\\n int end = 0;\\n int maxPosition = 0; \\n int steps = 0;\\n for (int i = 0; i < length - 1; i++) {\\n maxPosition = Math.max(maxPosition, i + nums[i]); \\n if (i == end) {\\n end = maxPosition;\\n steps++;\\n }\\n }\\n return steps;\\n }\\n}\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int jump(vector<int>& nums) {\\n int maxPos = 0, n = nums.size(), end = 0, step = 0;\\n for (int i = 0; i < n - 1; ++i) {\\n if (maxPos >= i) {\\n maxPos = max(maxPos, i + nums[i]);\\n if (i == end) {\\n end = maxPos;\\n ++step;\\n }\\n }\\n }\\n return step;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def jump(self, nums: List[int]) -> int:\\n n = len(nums)\\n maxPos, end, step = 0, 0, 0\\n for i in range(n - 1):\\n if maxPos >= i:\\n maxPos = max(maxPos, i + nums[i])\\n if i == end:\\n end = maxPos\\n step += 1\\n return step\\n
###golang
\\n\\nfunc jump(nums []int) int {\\n length := len(nums)\\n end := 0\\n maxPosition := 0\\n steps := 0\\n for i := 0; i < length - 1; i++ {\\n maxPosition = max(maxPosition, i + nums[i])\\n if i == end {\\n end = maxPosition\\n steps++\\n }\\n }\\n return steps\\n}\\n\\nfunc max(x, y int) int {\\n if x > y {\\n return x\\n }\\n return y\\n}\\n
###C
\\n\\nint jump(int* nums, int numsSize) {\\n int maxPos = 0, end = 0, steps = 0;\\n for (int i = 0; i < numsSize - 1; ++i) {\\n if (maxPos >= i) {\\n maxPos = (maxPos > i + nums[i]) ? maxPos : i + nums[i];\\n if (i == end) {\\n end = maxPos;\\n ++steps;\\n }\\n }\\n }\\n return steps;\\n}\\n
###JavaScript
\\n\\nvar jump = function(nums) {\\n let maxPos = 0, end = 0, steps = 0;\\n for (let i = 0; i < nums.length - 1; ++i) {\\n if (maxPos >= i) {\\n maxPos = Math.max(maxPos, i + nums[i]);\\n if (i == end) {\\n end = maxPos;\\n ++steps;\\n }\\n }\\n }\\n return steps;\\n};\\n
###TypeScript
\\n\\nfunction jump(nums: number[]): number {\\n let maxPos = 0, end = 0, steps = 0;\\n for (let i = 0; i < nums.length - 1; ++i) {\\n if (maxPos >= i) {\\n maxPos = Math.max(maxPos, i + nums[i]);\\n if (i === end) {\\n end = maxPos;\\n ++steps;\\n }\\n }\\n }\\n return steps;\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int Jump(int[] nums) {\\n int maxPos = 0, end = 0, steps = 0;\\n for (int i = 0; i < nums.Length - 1; ++i) {\\n if (maxPos >= i) {\\n maxPos = Math.Max(maxPos, i + nums[i]);\\n if (i == end) {\\n end = maxPos;\\n ++steps;\\n }\\n }\\n }\\n return steps;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn jump(nums: Vec<i32>) -> i32 {\\n let mut max_pos = 0;\\n let mut end = 0;\\n let mut steps = 0;\\n for i in 0..nums.len() - 1 {\\n if max_pos >= i {\\n max_pos = max_pos.max(i + nums[i] as usize);\\n if i == end {\\n end = max_pos;\\n steps += 1;\\n }\\n }\\n }\\n steps\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"解题思路 这道题是典型的贪心算法,通过局部最优解得到全局最优解。以下两种方法都是使用贪心算法实现,只是贪心的策略不同。\\n\\n方法一:反向查找出发位置\\n\\n我们的目标是到达数组的最后一个位置,因此我们可以考虑最后一步跳跃前所在的位置,该位置通过跳跃能够到达最后一个位置。\\n\\n如果有多个位置通过跳跃都能够到达最后一个位置,那么我们应该如何进行选择呢?直观上来看,我们可以「贪心」地选择距离最后一个位置最远的那个位置,也就是对应下标最小的那个位置。因此,我们可以从左到右遍历数组,选择第一个满足要求的位置。\\n\\n找到最后一步跳跃前所在的位置之后…","guid":"https://leetcode.cn/problems/jump-game-ii//solution/tiao-yue-you-xi-ii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-05-03T04:13:43.839Z","media":[{"url":"https://assets.leetcode.cn/solution-static/45/45_fig1.png","type":"photo","width":1352,"height":842,"blurhash":"L597Ox~U54?]NaWBnOV?IUWCofWB"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"别点开这一篇,击败99.32%","url":"https://leetcode.cn/problems/count-largest-group//solution/bie-dian-kai-zhe-yi-pian-ji-bai-9932-by-hengzai","content":"- \\n
\\n时间复杂度:$O(n)$,其中 $n$ 是数组长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n解题思路
\\n1 先思考如何将十进制的各个数位相加,因为求的是从1-n的数位和,所以可以找出规律:
\\n###java
\\n\\nsum[i] = sum[i / 10] + i % 10 //按顺序添加sum[i]\\n
2 然后统计各数位和的个数(有多少个相同个数并列的组)
\\n3 求出并列数目最多的组,也就是统计和中最大的那个数
\\n4 最后计算与max相等的数有多少个就可以了。
\\n一开始代码写的比较长,按照基本的思路写,例如计数那块用了HashMap,求数位和用了i % 10 和 i / 10的计算方法
\\n后来慢慢优化,答案还可以优化成一个for循环求出答案,但是这样需要做的判断却增加了,不如分开两个for循环计算
\\n代码
\\n###java
\\n\\n","description":"解题思路 1 先思考如何将十进制的各个数位相加,因为求的是从1-n的数位和,所以可以找出规律:\\n\\n###java\\n\\nsum[i] = sum[i / 10] + i % 10 //按顺序添加sum[i]\\n\\n\\n2 然后统计各数位和的个数(有多少个相同个数并列的组)\\n\\n3 求出并列数目最多的组,也就是统计和中最大的那个数\\n\\n4 最后计算与max相等的数有多少个就可以了。\\n\\n一开始代码写的比较长,按照基本的思路写,例如计数那块用了HashMap,求数位和用了i % 10 和 i / 10的计算方法\\n\\n后来慢慢优化,答案还可以优化成一个for循环求出答案…","guid":"https://leetcode.cn/problems/count-largest-group//solution/bie-dian-kai-zhe-yi-pian-ji-bai-9932-by-hengzai","author":"hengzai","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-04-19T17:50:30.920Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计最大组的数目","url":"https://leetcode.cn/problems/count-largest-group//solution/tong-ji-zui-da-zu-de-shu-mu-by-leetcode-solution","content":"class Solution {\\n public int countLargestGroup(int n) {\\n\\n int ans = 0, max = 1;\\n int[] count = new int[n + 1];// 统计数位和有多少\\n int[] sum = new int[n + 1]; //计算1-n各个元素的数位和,例如数字i的数位和是sum[i / 10] + i % 10\\n \\n for(int i = 1; i <= n; i++){\\n sum[i] = sum[i / 10] + i % 10;\\n count[sum[i]]++;\\n if(count[sum[i]] > max) \\n max = count[sum[i]];\\n }\\n\\n for(int num : count) ans += num == max ? 1 : 0;\\n \\n return ans;\\n }\\n}\\n
方法一:哈希表
\\n思路
\\n对于 $[1, n]$ 中的每一个整数 $i$,我们可以计算出它的数位和 $s_i$。建立一个从数位和到原数字的哈希映射,对每一个数字 $i$,使键 $s_i$ 对应的值自增一。然后我们在值的集合中找到最大的值 $m$,再遍历哈希表,统计值为 $m$ 的个数即可。
\\n代码
\\n###Python
\\n\\nclass Solution:\\n def countLargestGroup(self, n: int) -> int:\\n hashMap = collections.Counter()\\n for i in range(1, n + 1): \\n key = sum([int(x) for x in str(i)])\\n hashMap[key] += 1\\n maxValue = max(hashMap.values())\\n count = sum(1 for v in hashMap.values() if v == maxValue)\\n return count\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int countLargestGroup(int n) {\\n unordered_map<int, int> hashMap;\\n int maxValue = 0;\\n for (int i = 1; i <= n; ++i) {\\n int key = 0, i0 = i;\\n while (i0) {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n ++hashMap[key];\\n maxValue = max(maxValue, hashMap[key]);\\n }\\n int count = 0;\\n for (auto& kvpair: hashMap) {\\n if (kvpair.second == maxValue) {\\n ++count;\\n }\\n }\\n return count;\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int countLargestGroup(int n) {\\n unordered_map<int, int> hashMap;\\n int maxValue = 0;\\n for (int i = 1; i <= n; ++i) {\\n int key = 0, i0 = i;\\n while (i0) {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n ++hashMap[key];\\n maxValue = max(maxValue, hashMap[key]);\\n }\\n int count = 0;\\n for (auto& [_, value]: hashMap) {\\n if (value == maxValue) {\\n ++count;\\n }\\n }\\n return count;\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int countLargestGroup(int n) {\\n Map<Integer, Integer> hashMap = new HashMap<Integer, Integer>();\\n int maxValue = 0;\\n for (int i = 1; i <= n; ++i) {\\n int key = 0, i0 = i;\\n while (i0 != 0) {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n hashMap.put(key, hashMap.getOrDefault(key, 0) + 1);\\n maxValue = Math.max(maxValue, hashMap.get(key));\\n }\\n int count = 0;\\n for (Map.Entry<Integer, Integer> kvpair : hashMap.entrySet()) {\\n if (kvpair.getValue() == maxValue) {\\n ++count;\\n }\\n }\\n return count;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int CountLargestGroup(int n) {\\n var hashMap = new Dictionary<int, int>();\\n int maxValue = 0;\\n for (int i = 1; i <= n; ++i) {\\n int key = 0, i0 = i;\\n while (i0 > 0) {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n if (hashMap.ContainsKey(key)) {\\n hashMap[key]++;\\n } else {\\n hashMap[key] = 1;\\n }\\n maxValue = Math.Max(maxValue, hashMap[key]);\\n }\\n\\n int count = 0;\\n foreach (var value in hashMap.Values) {\\n if (value == maxValue) {\\n count++;\\n }\\n }\\n return count;\\n }\\n}\\n
###Go
\\n\\nfunc countLargestGroup(n int) int {\\n hashMap := make(map[int]int)\\nmaxValue := 0\\nfor i := 1; i <= n; i++ {\\nkey := 0\\ni0 := i\\nfor i0 > 0 {\\nkey += i0 % 10\\ni0 /= 10\\n}\\nhashMap[key]++\\nmaxValue = max(maxValue, hashMap[key])\\n}\\n\\ncount := 0\\nfor _, value := range hashMap {\\nif value == maxValue {\\ncount++\\n}\\n}\\nreturn count\\n}\\n
###C
\\n\\nint countLargestGroup(int n) {\\n int hashMap[100] = {0};\\n int maxValue = 0;\\n for (int i = 1; i <= n; ++i) {\\n int key = 0, i0 = i;\\n while (i0) {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n hashMap[key]++;\\n if (hashMap[key] > maxValue) {\\n maxValue = hashMap[key];\\n }\\n }\\n\\n int count = 0;\\n for (int i = 0; i < 100; ++i) {\\n if (hashMap[i] == maxValue) {\\n count++;\\n }\\n }\\n return count;\\n}\\n
###JavaScript
\\n\\nvar countLargestGroup = function(n) {\\n let hashMap = {};\\n let maxValue = 0;\\n for (let i = 1; i <= n; ++i) {\\n let key = 0, i0 = i;\\n while (i0) {\\n key += i0 % 10;\\n i0 = Math.floor(i0 / 10);\\n }\\n hashMap[key] = (hashMap[key] || 0) + 1;\\n maxValue = Math.max(maxValue, hashMap[key]);\\n }\\n\\n let count = 0;\\n for (let value of Object.values(hashMap)) {\\n if (value === maxValue) {\\n count++;\\n }\\n }\\n return count;\\n};\\n
###TypeScript
\\n\\nfunction countLargestGroup(n: number): number {\\n let hashMap: { [key: number]: number } = {};\\n let maxValue = 0;\\n for (let i = 1; i <= n; ++i) {\\n let key = 0, i0 = i;\\n while (i0) {\\n key += i0 % 10;\\n i0 = Math.floor(i0 / 10);\\n }\\n hashMap[key] = (hashMap[key] || 0) + 1;\\n maxValue = Math.max(maxValue, hashMap[key]);\\n }\\n\\n let count = 0;\\n for (let value of Object.values(hashMap)) {\\n if (value === maxValue) {\\n count++;\\n }\\n }\\n return count;\\n};\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn count_largest_group(n: i32) -> i32 {\\n let mut hash_map = HashMap::new();\\n let mut max_value = 0;\\n for i in 1..=n {\\n let mut key = 0;\\n let mut i0 = i;\\n while i0 > 0 {\\n key += i0 % 10;\\n i0 /= 10;\\n }\\n *hash_map.entry(key).or_insert(0) += 1;\\n max_value = max_value.max(*hash_map.get(&key).unwrap());\\n }\\n\\n let mut count = 0;\\n for &value in hash_map.values() {\\n if value == max_value {\\n count += 1;\\n }\\n }\\n count\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:哈希表 思路\\n\\n对于 $[1, n]$ 中的每一个整数 $i$,我们可以计算出它的数位和 $s_i$。建立一个从数位和到原数字的哈希映射,对每一个数字 $i$,使键 $s_i$ 对应的值自增一。然后我们在值的集合中找到最大的值 $m$,再遍历哈希表,统计值为 $m$ 的个数即可。\\n\\n代码\\n\\n###Python\\n\\nclass Solution:\\n def countLargestGroup(self, n: int) -> int:\\n hashMap = collections.Counter()\\n for i in…","guid":"https://leetcode.cn/problems/count-largest-group//solution/tong-ji-zui-da-zu-de-shu-mu-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-04-11T07:43:21.018Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划(只解释官方题解方法一)(Java)","url":"https://leetcode.cn/problems/super-egg-drop//solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang","content":"- \\n
\\n时间复杂度:对数 $x$ 求数位和的时间为 $O(\\\\log_{10} x) = O(\\\\log x)$,因此总时间代价为 $O(n \\\\log n)$,选出最大元素和遍历哈希表的时间代价均为 $O(n)$,故渐渐时间复杂度 $O(n \\\\log n) + O(n) = O(n \\\\log n)$。
\\n- \\n
\\n空间复杂度:使用哈希表作为辅助空间,$n$ 的数位个数为 $O(\\\\log_{10} n) = O(\\\\log n)$,每一个数位都在 $[0, 9]$ 之间,故哈希表最多包含的键的个数为 $O(10 \\\\log n) = O(\\\\log n)$,渐进空间复杂度为 $O(\\\\log n)$。
\\n本题解只解释了官方题解方法一,题解方法二和方法三不在我能解释的范围内,官方题解已经写得足够清楚,欢迎大家围观。我这里也是写点学习体会,欢迎大家指正。
\\n补充:方法二和方法三,视情况掌握和理解,方法二官方写得非常具体了,所以我没有可以补充的地方。方法三官方说不具有一般性,我个人觉得没有必要掌握,看一下就好。
\\n题目意思没有看懂的朋友,建议先看李永乐老师的视频:《复工复产找工作?先来看看这道面试题:双蛋问题》(建议关掉弹幕,要不然得笑晕过去),我下面也会解释,我也是从这个视频里理解到题目的意思的。
\\n解释题意
\\n\\n
\\n- 题目中「移动」的意思是:做一次实验,把一个鸡蛋从某个楼层扔下去,看它是否破碎。没有破碎的鸡蛋可以重复使用;
\\n- 这
\\nK
个鸡蛋,F
值满足的特点是:\\n\\n
\\n- 在所有小于等于
\\nF
的楼层扔下它不破碎;- 在所有大于
\\nF
的楼层扔下它一定会破碎;- 所有鸡蛋的
\\nF
值都一样,且确定的,并且0 <= F <= N
,即F
值一定不会超过楼层高度。- \\n
F
值是确定的,但它不是题目要我们求的。题目要我们求的是找到这个F
值的最小实验次数。这其实是时间复杂度的概念,时间复杂度是在最坏情况下(即运气最差的情况下),程序执行完毕最少执行的次数,例如:\\n\\n
\\n- 在一个数组(长度为 $N$)里查找一个数,找到某个数可以用线性查找,最好情况下,下标为 0 的位置就是要找的元素,但是在计算复杂度的时候,需要考虑到最差情况,即看到最后一个位置的时候,才找到这个元素,因此至少执行数组长度这么多次的查找,才能找到;
\\n- 在一个有序数组(长度为 $N$)里查找,可以使用二分查找算法,最好情况下依然是 1 次就找到(中点位置),但是最坏情况下,例如中点的两个邻居,就得找 $\\\\log N$(这个数值需要上取整,这里不深究)次;
\\n- 题目中的「最小」字眼很让人迷惑,我的理解是:把求解
\\nF
的过程认为是用最好的算法,即使是在最坏的运气下,为了准确得到结果,找到F
这个值的实验的次数最少是多少。正是因为用「最好的算法」,谈「最少次数」才有意义,这一点是比较绕的。而最好的算法隐含在「动态规划」的推导过程中,是通过比较得出来的。显然这种最优化的问题,只问结果,不问过程,就是用「动态规划」去求解的。
\\n分析题意
\\n题目中只有一个限制:鸡蛋的个数。
\\n\\n
\\n- 如果鸡蛋无穷多,相当于没有限制,由于
\\nF
是一个确定的整数值,可以使用二分查找去做,二分查找是最好的算法;- 如果只有 1 个鸡蛋:由于一定要测出
\\nF
值,只能从底层到高层,一层一层尝试去仍,直到这个鸡蛋破碎为止,破碎位置的楼层高度 $- 1$,就是F
值。那么,如果鸡蛋的个数有若干个,该怎么利用好这些鸡蛋,还能保证运气最坏的情况下实验次数最少,这就相当复杂了。李永乐老师的视频里面有说一些方案,这些方案人脑直接想是想不完全的,需要通过计算机编程的方法去计算。
\\n方法:动态规划
\\n动态规划,可以认为是一种打表格的方法(定义来自《算法导论》)。规划(Programming)本来的意思是表格,在学习的基础算法领域使用这个语义是非常贴切的,因此大家理解这个规划意思的时候,可以暂时不用把它和数学里的线性规划联系起来,我们做基础算法题的动态规划问题还远没有到用数学方法解「约束条件下线性目标函数的极值问题」的高度,即使 Programming 在线性规划里确实是这个意思,但是基础算法领域里的规划强调的是:在求解的过程中,记住结果,以后要用到的时候,直接使用而不必重复计算,即「空间换时间」。「背包问题」、「最长回文子串」、「最长公共子序列」、「编辑距离」这些问题的求解我们都会看到程序其实就是在填写一张表格。
\\n如果没有学习「动态规划」,我们还有「递归」,然后发现重复子问题,使用「缓存」记住结果是非常自然的,这叫「记忆化递归」(或者「记忆化搜索」)。而「动态规划」还告诉了我们一种思路,可以从一个问题最初的样子去考虑它是如何一步一步得到最终的规模。
\\n「动态规划」的两个思考方向:
\\n\\n
\\n- 自顶向下求解,称之为「记忆化递归」:初学的时候,建议先写「记忆化递归」的代码,然后把代码改成「自底向上」的「递推」求解;
\\n- 自底向上求解,称之为「递推」或者就叫「动态规划」:在基础的「动态规划」问题里,绝大多数都可以从这个角度入手,做多了以后建议先从这个角度先思考,实在难以解决再考虑「记忆化递归」。
\\n说明:《算法导论》上把「记忆化递归」也归为「动态规划」的概念里。不管是「记忆化递归」还是「动态规划」,在这中间很关键的点是:如何拆分问题。这就涉及到「状态」的定义,「状态」我个人的理解是:求解一个问题所处的阶段,这个定义是非常关键的,在解题的时候一定要定义清楚,不能是模糊不清的。
\\n这里的约束只有鸡蛋的个数,因此,为了消除鸡蛋的个数对递推的过程中造成的影响,我们在设置状态的时候要在后面加上一个维度,这种做法叫消除「后效性」,是常见的套路。在「打家劫舍」问题一、问题三还有「股票」的 6 道问题用的就是这个技巧。一般而言,一个约束对应一个维度的状态。约束越多,状态的维数就越多(这里限于我的水平和经验,没有严格论证)。
\\n第 1 步:定义状态
\\n\\n
dp[i][j]
:一共有i
层楼梯(注意:这里i
不表示高度)的情况下,使用j
个鸡蛋的最少实验的次数。说明:
\\n\\n
\\n- \\n
i
表示的是楼层的大小,不是高度(第几层)的意思,例如楼层区间[8, 9, 10]
的大小为 $3$。- \\n
j
表示可以使用的鸡蛋的个数,它是约束条件。第一个维度最先容易想到的是表示楼层的高度,这个定义的调整是在状态转移的过程中完成的。因为如果通过实验知道了鸡蛋的
\\nF
值在高度区间[8, 9, 10]
里,这个时候只有 1 枚鸡蛋,显然需要做 3 次实验,和区间的大小是相关的。注意:这里我定义的维度顺序和官方解答的定义是反着的,我个人习惯将约束的那个条件,放置在后面的维度,表示消除后效性的意思。
\\n第 2 步:推导状态转移方程
\\n推导状态转移方程经常做的事情是「分类讨论」,这里「分类讨论」的依据就是,在指定的层数里扔下鸡蛋,根据这个鸡蛋是否破碎,就把问题拆分成了两个子问题。
\\n设指定的楼层为
\\nk
,k >= 1
且k <= i
:\\n
\\n- 如果鸡蛋破碎,测试
\\nF
值的实验就得在k
层以下做(不包括 k 层),这里已经使用了一个鸡蛋,因此测出F
值的最少实验次数是:dp[k - 1][j - 1]
;- 如果鸡蛋完好,测试
\\nF
值的实验就得在k
层以上做(不包括 k 层),这里这个鸡蛋还能使用,因此测出F
值的最少实验次数是:dp[i - k][j]
,例如总共 8 层,在第5
层扔下去没有破碎,则需要在[6, 7, 8]
层继续做实验,因此区间的大小就是8 - 5 = 3
。最坏情况下,是这两个子问题的较大者,由于在第
\\nk
层扔下鸡蛋算作一次实验,k
的值在 $1 \\\\le k \\\\le i$,对于每一个k
都对应了一组值的最大值,取这些k
下的最小值(最优子结构),因此:$$
\\n
\\ndp[i][j] = \\\\min_{1 \\\\le k \\\\le i} \\\\left(\\\\max(dp[k - 1][j - 1], dp[i - k][j]) + 1 \\\\right)
\\n$$解释:
\\n\\n
\\n- 由于仍那一个鸡蛋需要记录一次操作,所以末尾要加上 $1$;
\\n- 每一个新值的计算,都参考了比它行数少,列数少的值,这些值一定是之前已经计算出来的,这样的过程就叫做「状态转移」。
\\n这个问题只是状态转移方程稍显复杂,但空间换时间,逐层递推填表的思想依然是常见的动态规划的思路。
\\n第 3 步:考虑初始化
\\n一般而言,需要
\\n0
这个状态的值,这里0
层楼和0
个鸡蛋是需要考虑进去的,它们的值会被后来的值所参考,并且也比较容易得到。因此表格需要
\\nN + 1
行,K + 1
列。由于
\\nF
值不会超过最大楼层的高度,要求的是最小值,因此初始化的时候,可以叫表格的单元格值设置成一个很大的数,但是这个数肯定也不会超过当前考虑的楼层的高度。\\n
\\n- 第 0 行:楼层为 $0$ 的时候,不管鸡蛋个数多少,都测试不出鸡蛋的
\\nF
值,故全为 $0$;- 第 1 行:楼层为 $1$ 的时候,$0$ 个鸡蛋的时候,扔 $0$ 次,$1$ 个以及 $1$ 个鸡蛋以上只需要扔 $1$ 次;
\\n- 第 0 列:鸡蛋个数为 $0$ 的时候,不管楼层为多少,也测试不出鸡蛋的
\\nF
值,故全为 $0$,虽然不符合题意,但是这个值有效,它在后面的计算中会被用到;- 第 1 列:鸡蛋个数为 $1$ 的时候,这是一种极端情况,要试出
\\nF
值,最少次数就等于楼层高度;第 4 步:考虑输出
\\n输出就是表格的最后一个单元格的值
\\ndp[N][K]
。第 5 步:思考空间优化
\\n看状态转移方程,当前单元格的值只依赖之前的行,当前列和它左边一列的值。可以状态压缩,让「列」滚动起来。但是「空间优化」的代码增加了理解的难度,我们这里不做。
\\n参考代码 1:(超时,但超时的数据是规模大的数据,说明算法没有问题。)
\\n###Java
\\n\\nimport java.util.Arrays;\\n\\npublic class Solution {\\n\\n public int superEggDrop(int K, int N) {\\n\\n // dp[i][j]:一共有 i 层楼梯的情况下,使用 j 个鸡蛋的最少实验的次数\\n // 注意:\\n // 1、i 表示的是楼层的大小,不是第几层的意思,例如楼层区间 [8, 9, 10] 的大小为 3,这一点是在状态转移的过程中调整的定义\\n // 2、j 表示可以使用的鸡蛋的个数,它是约束条件,我个人习惯放在后面的维度,表示消除后效性的意思\\n\\n // 0 个楼层和 0 个鸡蛋的情况都需要算上去,虽然没有实际的意义,但是作为递推的起点,被其它状态值所参考\\n int[][] dp = new int[N + 1][K + 1];\\n\\n // 由于求的是最小值,因此初始化的时候赋值为一个较大的数,9999 或者 i 都可以\\n for (int i = 0; i <= N; i++) {\\n Arrays.fill(dp[i], i);\\n }\\n\\n // 初始化:填写下标为 0、1 的行和下标为 0、1 的列\\n // 第 0 行:楼层为 0 的时候,不管鸡蛋个数多少,都测试不出鸡蛋的 F 值,故全为 0\\n for (int j = 0; j <= K; j++) {\\n dp[0][j] = 0;\\n }\\n\\n // 第 1 行:楼层为 1 的时候,0 个鸡蛋的时候,扔 0 次,1 个以及 1 个鸡蛋以上只需要扔 1 次\\n dp[1][0] = 0;\\n for (int j = 1; j <= K; j++) {\\n dp[1][j] = 1;\\n }\\n\\n // 第 0 列:鸡蛋个数为 0 的时候,不管楼层为多少,也测试不出鸡蛋的 F 值,故全为 0\\n // 第 1 列:鸡蛋个数为 1 的时候,这是一种极端情况,要试出 F 值,最少次数就等于楼层高度(想想复杂度的定义)\\n for (int i = 0; i <= N; i++) {\\n dp[i][0] = 0;\\n dp[i][1] = i;\\n }\\n\\n // 从第 2 行,第 2 列开始填表\\n for (int i = 2; i <= N; i++) {\\n for (int j = 2; j <= K; j++) {\\n for (int k = 1; k <= i; k++) {\\n // 碎了,就需要往低层继续扔:层数少 1 ,鸡蛋也少 1\\n // 不碎,就需要往高层继续扔:层数是当前层到最高层的距离差,鸡蛋数量不少\\n // 两种情况都做了一次尝试,所以加 1\\n dp[i][j] = Math.min(dp[i][j], Math.max(dp[k - 1][j - 1], dp[i - k][j]) + 1);\\n }\\n }\\n }\\n return dp[N][K];\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(N^2K)$,三层
\\nfor
循环,每层循环都是线性的;- 空间复杂度:$O(NK)$,表格的大小。
\\n这里需要盯着「状态转移方程」使劲看:
\\n$$
\\n
\\ndp[i][j] = \\\\min_{1 \\\\le k \\\\le i} \\\\left(\\\\max(dp[k - 1][j - 1], dp[i - k][j]) + 1 \\\\right)
\\n$$「状态转移方程」里最外层的变量是
\\nk
,它枚举了扔下鸡蛋的楼层的高度,这里它是自变量,将其余的i
和j
视为常数:\\n
\\n- \\n
dp[k - 1][j - 1]
:根据语义,k
增大的时候,楼层大小越大,它的值就越大;- \\n
dp[i - k][j]
:根据语义,k
增大的时候,楼层大小越小,它的值就越小。可以得出一个是单调不减的(
\\ndp[k - 1][j - 1]
,下图红点),一个是单调不增的(dp[i - k][j]
,下图绿星),并且它们的值都是整数。我使用了一组数据,制作成图表(每次取数据都取最后一行最后一列的那个单元格计算的数据)。
\\n情况 1:最低点只有 1 个点
\\n\\n
情况 2:最低点是若干个重合的点
\\n\\n
情况 3:最低点不重合,但是两边的值一样
\\n\\n
(生成图表的代码在本题解末尾。)
\\n从图上可以看出:二者的较大值的最小点在它们交汇的地方。那么有没有可能不交汇,当然有可能(上面第 3 张图),二者较大值的最小者一定出现在画成曲线段交点的两侧,并且二者的差值不会超过 $1$,也就是如果没有重合的点,两边的最大值是一样的(从图上看出来的,没有严格证明),因此取左侧和右侧两点中的一点都可以,不失一般性,可以取左边的那个点的
\\nk
。也就是找到使得
\\ndp[i - k][j] <= dp[k - i][j - 1]
最大的那个k
值即可。这里使用二分查找算法。关键在于dp[i - k][j] > dp[k - i][j - 1]
的时候,k
一定不是我们要找的,根据这一点写出二分的代码。写对二分查找法的方法在第 35 题题解,希望能对大家有帮助。
\\n参考代码 2:
\\n###Java
\\n\\nimport java.util.Arrays;\\n\\npublic class Solution {\\n\\n public int superEggDrop(int K, int N) {\\n // dp[i][j]:一共有 i 层楼梯的情况下,使用 j 个鸡蛋的最少仍的次数\\n int[][] dp = new int[N + 1][K + 1];\\n \\n // 初始化\\n for (int i = 0; i <= N; i++) {\\n Arrays.fill(dp[i], i);\\n }\\n for (int j = 0; j <= K; j++) {\\n dp[0][j] = 0;\\n }\\n\\n dp[1][0] = 0;\\n for (int j = 1; j <= K; j++) {\\n dp[1][j] = 1;\\n }\\n for (int i = 0; i <= N; i++) {\\n dp[i][0] = 0;\\n dp[i][1] = i;\\n }\\n\\n // 开始递推\\n for (int i = 2; i <= N; i++) {\\n for (int j = 2; j <= K; j++) {\\n // 在区间 [1, i] 里确定一个最优值\\n int left = 1;\\n int right = i;\\n while (left < right) {\\n // 找 dp[k - 1][j - 1] <= dp[i - mid][j] 的最大值 k\\n int mid = left + (right - left + 1) / 2;\\n \\n int breakCount = dp[mid - 1][j - 1];\\n int notBreakCount = dp[i - mid][j];\\n if (breakCount > notBreakCount) {\\n // 排除法(减治思想)写对二分见第 35 题,先想什么时候不是解\\n // 严格大于的时候一定不是解,此时 mid 一定不是解\\n // 下一轮搜索区间是 [left, mid - 1]\\n right = mid - 1;\\n } else {\\n // 这个区间一定是上一个区间的反面,即 [mid, right]\\n // 注意这个时候取中间数要上取整,int mid = left + (right - left + 1) / 2;\\n left = mid;\\n }\\n }\\n // left 这个下标就是最优的 k 值,把它代入转移方程 Math.max(dp[k - 1][j - 1], dp[i - k][j]) + 1) 即可\\n dp[i][j] = Math.max(dp[left - 1][j - 1], dp[i - left][j]) + 1;\\n }\\n }\\n return dp[N][K];\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(NK \\\\log N)$,其中一层循环变成二分查找,复杂度成为对数;
\\n- 空间复杂度:$O(NK)$,表格的大小。
\\n把求解问题使用的表格打印出来,就是李老师视频里最后展现的那个表格。
\\n附录:
\\nPython 代码 1:
\\n原始代码,增加了收集数据的代码。
\\n###python
\\n\\nclass Solution:\\n def superEggDrop(self, K: int, N: int) -> int:\\n\\n dp = [[i for j in range(K + 1)] for i in range(N + 1)]\\n\\n for j in range(K + 1):\\n dp[0][j] = 0\\n\\n dp[1][0] = 0\\n for j in range(K + 1):\\n dp[1][j] = 1\\n\\n for i in range(N + 1):\\n dp[i][0] = 0\\n dp[i][1] = i\\n \\n data = []\\n for i in range(2, N + 1):\\n for j in range(2, K + 1):\\n for k in range(1, i + 1):\\n # 把最后一行,最后一列的求解过程制作成图表\\n if i == N and j == K:\\n data.append([k, dp[k - 1][j - 1], dp[i - k][j]])\\n dp[i][j] = min(dp[i][j], max(dp[k - 1][j - 1], dp[i - k][j]) + 1)\\n return dp[N][K], data\\n
Python 代码 2:
\\n使用测试用例得到展现散点图的数据。
\\n###python
\\n\\nsolution = Solution()\\nK = 3\\nN = 14\\nres, data = solution.superEggDrop(K, N)\\nres\\n
Python 代码 3:
\\n封装到
\\nDataFrame
里方便调用。###python
\\n\\nimport pandas as pd\\n\\nscatters = pd.DataFrame(data=data, columns=[\'k\',\'dp[k - 1][j - 1]\',\'dp[i - k][j]\'])\\n
Python 代码 4:
\\n绘图。
\\n###python
\\n\\nimport matplotlib.pyplot as plt\\n\\n\\nplt.figure(figsize=(10, 8))\\nplt.scatter(\\n scatters[\'k\'], scatters[\'dp[k - 1][j - 1]\'], c=\'r\', marker=\'o\', s=200)\\nplt.scatter(scatters[\'k\'], scatters[\'dp[i - k][j]\'], c=\'g\', marker=\'*\', s=300)\\nplt.legend(loc=\'upper left\', prop={\'size\': 18})\\n\\n \\nplt.xticks([i for i in range(len(data) + 1)]) \\nplt.tick_params(labelsize=14) \\nplt.title(\'$N = 14, K = 3$\',size = 20)\\nplt.ylabel(\'dp value\', size = 14)\\nplt.xlabel(\'$k$\', size = 14)\\nplt.grid() \\nplt.savefig(\\"image.png\\")\\n
在
\\nK = 10
、N = 1000
时的表格:###shell
\\n\\n","description":"本题解只解释了官方题解方法一,题解方法二和方法三不在我能解释的范围内,官方题解已经写得足够清楚,欢迎大家围观。我这里也是写点学习体会,欢迎大家指正。 补充:方法二和方法三,视情况掌握和理解,方法二官方写得非常具体了,所以我没有可以补充的地方。方法三官方说不具有一般性,我个人觉得没有必要掌握,看一下就好。\\n\\n题目意思没有看懂的朋友,建议先看李永乐老师的视频:《复工复产找工作?先来看看这道面试题:双蛋问题》(建议关掉弹幕,要不然得笑晕过去),我下面也会解释,我也是从这个视频里理解到题目的意思的。\\n\\n解释题意\\n题目中「移动」的意思是:做一次实验…","guid":"https://leetcode.cn/problems/super-egg-drop//solution/dong-tai-gui-hua-zhi-jie-shi-guan-fang-ti-jie-fang","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-04-11T00:26:00.091Z","media":[{"url":"https://pic.leetcode-cn.com/eaefcee366542bb41757dd9b98ec9e61b3290da24ec11d617df358fe3f8bb0ac-image.png","type":"photo","width":720,"height":576,"blurhash":"LAS$lm_3oz~q^+Szs;ofa~bbsAsp"},{"url":"https://pic.leetcode-cn.com/5336c2dbf3717c3e455e0180ef487fbffc4e1ec2315e476161908ef343ebe507-image.png","type":"photo","width":720,"height":576,"blurhash":"L8Sr},~Xo2~q~WOBt7t7bwXRn$sC"},{"url":"https://pic.leetcode-cn.com/08e0971dcb71e5a91485ff71d4d9e01af6f643bd5289d103737d14a7e8aac108-image.png","type":"photo","width":720,"height":576,"blurhash":"LASs1[_3oz~q^+Szt7ofbIbur?so"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"C++ :: 思路剖析(3) dp解决 博弈策略(最佳状态) + 状态转移","url":"https://leetcode.cn/problems/cat-and-mouse//solution/c-si-lu-pou-xi-3-dpjie-jue-bo-yi-ce-lue-zui-jia-zh","content":"[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]\\n[0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]\\n[0, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2]\\n[0, 3, 2, 2, 2, 2, 2, 2, 2, 2, 2]\\n[0, 4, 3, 3, 3, 3, 3, 3, 3, 3, 3]\\n[0, 5, 3, 3, 3, 3, 3, 3, 3, 3, 3]\\n[0, 6, 3, 3, 3, 3, 3, 3, 3, 3, 3]\\n[0, 7, 4, 3, 3, 3, 3, 3, 3, 3, 3]\\n[0, 8, 4, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 9, 4, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 10, 4, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 11, 5, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 12, 5, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 13, 5, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 14, 5, 4, 4, 4, 4, 4, 4, 4, 4]\\n[0, 15, 5, 5, 4, 4, 4, 4, 4, 4, 4]\\n[0, 16, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 17, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 18, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 19, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 20, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 21, 6, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 22, 7, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 23, 7, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 24, 7, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 25, 7, 5, 5, 5, 5, 5, 5, 5, 5]\\n[0, 26, 7, 6, 5, 5, 5, 5, 5, 5, 5]\\n[0, 27, 7, 6, 5, 5, 5, 5, 5, 5, 5]\\n[0, 28, 7, 6, 5, 5, 5, 5, 5, 5, 5]\\n[0, 29, 8, 6, 5, 5, 5, 5, 5, 5, 5]\\n[0, 30, 8, 6, 5, 5, 5, 5, 5, 5, 5]\\n[0, 31, 8, 6, 6, 5, 5, 5, 5, 5, 5]\\n[0, 32, 8, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 33, 8, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 34, 8, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 35, 8, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 36, 8, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 37, 9, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 38, 9, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 39, 9, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 40, 9, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 41, 9, 6, 6, 6, 6, 6, 6, 6, 6]\\n[0, 42, 9, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 43, 9, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 44, 9, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 45, 9, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 46, 10, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 47, 10, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 48, 10, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 49, 10, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 50, 10, 7, 6, 6, 6, 6, 6, 6, 6]\\n[0, 100, 14, 9, 8, 7, 7, 7, 7, 7, 7]\\n[0, 200, 20, 11, 9, 8, 8, 8, 8, 8, 8]\\n[0, 300, 24, 13, 10, 9, 9, 9, 9, 9, 9]\\n[0, 400, 28, 14, 11, 10, 9, 9, 9, 9, 9]\\n[0, 500, 32, 15, 11, 10, 10, 9, 9, 9, 9]\\n[0, 1000, 45, 19, 13, 11, 11, 11, 10, 10, 10]\\n
思路剖析
\\n🏹 关键词:
\\n\\n
\\n- 游戏: 博弈问题: 考虑数学规律,状态空间搜索(dp, 暴搜)
\\n- 以最佳状态参与游戏:注意在这种情况下如何实现状态转移(我们是从多种选择中选择最好的)
\\n🔑 解题思路:
\\n\\n
\\n- ①:一般对于这种题目,我选用dp进行状态转移,首先初始dp数组,确定好dp数组的含义
\\n- ②:确定胜负的条件,以及状态转移的情况
\\n- ③:记忆化搜索,减少时空复杂度
\\n解题思路具体剖析:
\\n$\\\\mathcal{A}$: 确定dp方程和结束条件(其中*代表任何数)
\\ndp方程我们设为dp[t][x][y],其中$t$代表目前所走的次数⏳,$x$代表当前老鼠🐀所在的位置,$y$代表🐱所在的位置。
\\n\\n
\\n- 如果猫和老鼠占据相同的结点,猫获胜: 显然$dp[*][y][y]$的情况下,🐱获胜🏆。
\\n- 如果老鼠躲入洞里,老鼠获胜。: 显然$dp[][0][]$的情况下,🐀获胜🏆。
\\n- 如果某一位置重复出现(即,玩家们的位置和移动顺序都与上一个回合相同),游戏平局。:这个看起来很难,但实际上根据迷宫问题的知识,当步数积累⏳大于可转移的状态空间,就代表着平局。简单可知一共n个节点,当$t$大于$2n$时,代表平局: $dp[2n][][]$代表平局
\\n
\\n$$
\\n\\\\boxed{\\\\left{
\\n\\\\begin{aligned}
\\ndp[][y][y] & ;return; 2 &🐱胜利 \\\\
\\ndp[][0][] & ;return; 1 &🐀胜利 \\\\
\\ndp[2n][][] & ;return; 0 &⏳平局 \\\\
\\n\\\\end{aligned}\\\\right.}
\\n$$$\\\\mathcal{B}$: 🐀老鼠和🐱猫的状态转移
\\n\\n
\\n 所以总结一下,猫和老鼠的状态转移就是下图:
\\n$\\\\mathcal{C}$: 记忆化搜索,减少复杂度
\\n因为要进行大范围搜索,因此可能会存在冗余搜索,因此在$return$时,改为$return$ $dp[][][*] = *$
\\n
\\n 下面就是代码啦~代码
\\n###cpp
\\n\\nusing VI = vector<int>;\\nusing VVI = vector<VI>;\\nusing VVVI = vector<VVI>;\\nclass Solution {\\npublic:\\n int n;\\n int helper(VVI& graph, int t, int x, int y, VVVI& dp){\\n if (t == 2 * n) return 0; // 🏆比赛结束的几大条件按,参考A部分.不要return dp[t][x][y] = 2,想想为啥\\n if (x == y) return dp[t][x][y] = 2;\\n if (x == 0) return dp[t][x][y] = 1;\\n \\n if (dp[t][x][y] != -1) return dp[t][x][y]; // “不要搜了,爷👴已经搜好了”,老爷爷对小伙子说\\n if (t % 2 == 0){ // 老鼠走🐀\\n bool catWin = true;\\n for (int i = 0; i < graph[x].size(); ++ i){\\n int nx = graph[x][i];\\n int next = helper(graph, t + 1, nx, y, dp);\\n if (next == 1) return dp[t][x][y] = 1; // 直接回家\\n else if (next != 2) catWin = false; // 假如出现平地且没有回家,就说明下一步🐱不可能赢\\n }\\n if (catWin) return dp[t][x][y] = 2;\\n else return dp[t][x][y] = 0;\\n }else{ // 猫猫走,和上面差不多啦\\n bool mouseWin = true;\\n for (int i = 0; i < graph[y].size(); ++ i){\\n int ny = graph[y][i];\\n if (ny == 0) continue;\\n int next = helper(graph, t + 1, x, ny, dp);\\n if (next == 2) return dp[t][x][y] = 2;\\n else if (next != 1) mouseWin = false;\\n }\\n if (mouseWin) return dp[t][x][y] = 1;\\n else return dp[t][x][y] = 0;\\n }\\n }\\n \\n \\n int catMouseGame(vector<vector<int>>& graph) {\\n n = graph.size();\\n VVVI dp(2 * n, VVI(n, VI(n, -1)));\\n return helper(graph, 0, 1, 2, dp);\\n }\\n};\\n
🏹 关键词复盘:
\\n\\n又写了个,淦。这个认真写了,大🔥们觉得有用就给我个赞👍吧!!!!
\\n","description":"思路剖析 🏹 关键词:\\n游戏: 博弈问题: 考虑数学规律,状态空间搜索(dp, 暴搜)\\n以最佳状态参与游戏:注意在这种情况下如何实现状态转移(我们是从多种选择中选择最好的)\\n🔑 解题思路:\\n①:一般对于这种题目,我选用dp进行状态转移,首先初始dp数组,确定好dp数组的含义\\n②:确定胜负的条件,以及状态转移的情况\\n③:记忆化搜索,减少时空复杂度\\n解题思路具体剖析:\\n$\\\\mathcal{A}$: 确定dp方程和结束条件(其中*代表任何数)\\n\\n dp方程我们设为dp[t][x][y],其中$t$代表目前所走的次数⏳,$x…","guid":"https://leetcode.cn/problems/cat-and-mouse//solution/c-si-lu-pou-xi-3-dpjie-jue-bo-yi-ce-lue-zui-jia-zh","author":"wen-mu-yang","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-04-10T03:36:29.400Z","media":[{"url":"https://pic.leetcode-cn.com/61739e0cb597a586cbd202d35ec9ed0e3bb3d1ae07bb13d5dd477673620d9efc-image.png","type":"photo","width":1183,"height":776,"blurhash":"L58g{MTE0xOm%#RPRjs.0NIW^P$j"},{"url":"https://pic.leetcode-cn.com/e06348bdff4cc08e8e79824f3caeadaf4be72fa7ed9e8cde4c0abbe5eba045e2-image.png","type":"photo","width":1180,"height":748,"blurhash":"L69HFEAAEdTH-qI.v~X80NVa=_V^"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"通俗讲解并查集,帮助小白快速理解","url":"https://leetcode.cn/problems/redundant-connection//solution/tong-su-jiang-jie-bing-cha-ji-bang-zhu-xiao-bai-ku","content":"官方题解把并查集的原理和用法讲得很明白了
\\n
\\n但是在具体实现上没有说。
\\n我这里简单说一下吧,希望能帮助对并查集不熟的小伙伴快速上手具体落实到这个题上,通俗讲解一下
\\n先明确几个概念
\\n1.集合树:所有节点以代表节点为父节点构成的多叉树
\\n
\\n2.节点的代表节点:可以理解为节点的父节点,从当前节点出发,可以向上找到的第一个节点
\\n3.集合的代表节点:可以理解为根节点,意味着该集合内所有节点向上走,最终都能到达的节点
\\n来个图帮助理解
\\n上图中是一棵集合树,树中有1-6总计6个节点
\\n
\\n整个集合的代表节点是1
\\n4节点的代表节点是3,6节点的代表节点是1
\\n无论沿着哪个节点向上走,最终都会达到集合代表节点的1节点然后具体到这个题上:
\\n
\\n我们以这个边集合为例子[[1,2], [3,4], [3,2], [1,4], [1,5]]一、首先,对于边集合edges的每个元素,我们将其看作两个节点集合
\\n比如边[2, 3],我们将其看作节点集合2,和节点集合3
\\n二、在没有添加边的时候,各个节点集合独立,我们需要初始化各个节点集合的代表节点为其自身
\\n所以,我们先初始化一个容器vector,使得vector[i]=i
\\n
\\n这里两个i意思不同,作为索引的i是指当前节点,作为值的i是指当前节点所在集合的代表节点
\\n比如vector[2] = 2,意味着2这个节点所在集合的代表节点就是2,没有添加边的情况下,所有节点单独成集合,自身就是代表节点
\\n初始化后,集合图如下图所示:
\\n三、然后我们开始遍历边集合,将边转化为集合的关系
\\n这里有一点很重要:边[a,b]意味着a所在集合可以和b所在集合合并。
\\n
\\n合并方法很多,这里我们简单地将a集合的代表节点戳到b集合的代表节点上
\\n这意味着,将b集合代表节点作为合并后大集合的代表节点
\\n对于一个集合的代表节点s,一定有s->s,意思是s如果是代表节点,那么它本身不存在代表节点
\\n假设我们的读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
\\n初始化vector[0, 1, 2, 3, 4, 5]
\\n对应的index [0, 1, 2, 3, 4, 5]
\\n##########################################################################1.读取[1,2]:
\\n读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
\\n
\\n当前vector[0, 1, 2, 3, 4, 5]
\\n当前index [0, 1, 2, 3, 4, 5]
\\n原本1->1,2->2,
\\n由1节点出发,vector[1]=1, 找到1所在集合的代表节点1
\\n由2节点出发,vector[2]=2, 找到2所在集合的代表节点2
\\n于是,将1的代表置为2,vector[1]=2, vector[2]=2
\\n对应的vector[0, 2, 2, 3, 4, 5]
\\n对应的index [0, 1, 2, 3, 4, 5]
\\n原集合变为下图:
\\n##########################################################################
\\n2.读取[3, 4]
\\n读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
\\n
\\n当前vector[0, 2, 2, 3, 4, 5]
\\n当前index [0, 1, 2, 3, 4, 5]
\\n同理,将3所在集合的的代表节点3的代表节点置为4
\\n对应的vector[0, 2, 2, 4, 4, 5]
\\n对应的index [0, 1, 2, 3, 4, 5]
\\n集合变化如下图:
\\n##########################################################################
\\n3.读取[3, 2]
\\n读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
\\n
\\n当前vector[0, 2, 2, 4, 4, 5]
\\n当前index [0, 1, 2, 3, 4, 5]
\\n从节点3出发,vector[3]=4, vector[4]=4,于是找到节点3所在集合的代表节点为4
\\n从节点2出发,vector[2]=2, 找到节点2所在集合的代表节点为2
\\n于是,将4的代表置为2,vector[4]=2, vector[2]=2
\\n对应的vector[0, 2, 2, 4, 2, 5]
\\n对应的index [0, 1, 2, 3, 4, 5]
\\n集合变化如下图:
\\n##########################################################################
\\n4.读取[1, 4]
\\n读取顺序为[[1,2], [3,4], [3,2], [1,4], [1,5]]
\\n
\\n当前vector[0, 2, 2, 4, 2, 5]
\\n当前index [0, 1, 2, 3, 4, 5]
\\n从节点1出发,vector[1]=2, vector[2]=2, 找到节点1所在集合代表节点为2
\\n从节点4出发,vector[4]=2, vector[2]=2, 找到节点4所在集合代表节点为2
\\n由于1和4的代表节点相同,说明这两个节点本身就在同一个集合中
\\n由于原图是无向图,路径是双向可达的,1能够到达2,而且2能够到达4,再加上1能够到达4
\\n说明1能通过两条路径到达4,,这也意味着这条边出现的时候,原图中一定出现了环
\\n至于题中要求的,返回最后一条边,其实这就是返回添加过后会构成环的那一条边
\\n直白解释就是,在这条边出现之前,图中没有环
\\n这条边出现,图中也出现环。包括这条边在内,构成环的边都是满足破圈条件的边
\\n然而谁是最后一条出现在边集合里的?当然,就是这条构成环的最后一条边
\\n##########################################################################到这里,对于此题的实现基本上说完了,直接上代码吧
\\n\\nclass Solution {\\npublic:\\n vector<int> findRedundantConnection(vector<vector<int>>& edges) {\\n vector<int> rp(1001);\\n int sz = edges.size();\\n // 初始化各元素为单独的集合,代表节点就是其本身\\n for(int i=0;i<sz;i++)\\n rp[i] = i;\\n for(int j=0;j<sz;j++){\\n // 找到边上两个节点所在集合的代表节点\\n int set1 = find(edges[j][0], rp);\\n int set2 = find(edges[j][1], rp);\\n if(set1 == set2) // 两个集合代表节点相同,说明出现环,返回答案\\n return edges[j]; \\n else // 两个集合独立,合并集合。将前一个集合代表节点戳到后一个集合代表节点上\\n rp[set1] = set2;\\n }\\n return {0, 0};\\n }\\n\\n // 查找路径并返回代表节点,实际上就是给定当前节点,返回该节点所在集合的代表节点\\n // 之前这里写的压缩路径,引起歧义,因为结果没更新到vector里,所以这里改成路径查找比较合适\\n // 感谢各位老哥的提议\\n int find(int n, vector<int> &rp){\\n int num = n;\\n while(rp[num] != num)\\n num = rp[num];\\n return num;\\n }\\n};\\n
#####################################################################
\\n证明部分
\\n下面是响应某大佬建议,增加的一部分证明,有需要的同学就看看吧
\\n
\\n证明一下为什么给定一条新的边,两头节点在同一个集合,就意味着出现了环
\\n这里有个大前提,因为是无向图,集合里不会同时出现[1,2]和[2,1]这种重合边
\\n上面的代码遇到集合里有重合边的情况是会出现误判的
\\n就拿这个[1,2]和[2,1]来举例
\\n给定[1,2]后,再读取[2,1],两个节点在同一个集合,然而并没有出现环。
\\n所以,这里代码工作的前提是不出现重合边
\\n#####################################################################
\\n下面回到最初的问题,为什么给定新边的两个节点在同一集合就意味着出现了环
\\n假设给定新边的两个节点分别为5,6,新边为[5,6]
\\n####对于一条新出现的边,总共有两种情况,两个节点之一单独成集合,两个节点均不单独成集合第一种情况,两个节点之一单独成集合
\\n\\n假设5单独成集合。这种情况下,两个节点不可能在一个集合里\\n
因为有一个独立集合(只有节点5),6所在的集合和这个集合必然没有交集
\\n
\\n之后这两个集合进行合并操作
\\n直观点理解就是,5单独成集合,意味着5第一次出现在图里
\\n这里只有新边和5相关,所以当前只给定一条和5相关的边,对5而言,就像只举起了一只手
\\n要形成环,环上每个节点都必须是举起两只手的
\\n所以这种情况下是不可能出现环的,程序中也是这样判定的第二种情况,两个节点均不单独成集合
\\n\\n这里也可以细分为5和6是否作为该集合的代表节点\\n
假设5所在集合代表节点为a
\\n
\\n6所在集合代表节点也为a2.1第一个分支,如果a不为5和6本身,那么就有5->...->a,6->...->a,路径双向可达
\\n\\n可以得到5->...->a->...->6,对于给定边[5,6]可得5->6\\n所以5到达6有两条路径,出现了环。\\n
2.2第二个分支,a为5和6之一
\\n\\n假设,a为5\\n
2.2.1 首先考虑,6直接指向5这种情况
\\n\\n出现这种情况,只能是在已经存在集合x->...->6时,出现[...,5]这样的边\\n其中,...为x->...->6路径上除6以外任意节点\\n此时...的代表节点为6,5的代表节点为5,合并,6戳到5上,于是出现了6直接指向5\\n这种情况下,已经存在6->...->5一条路径,再读取到[5,6],环出现了\\n
2.2.2 再考虑6不直接指向5的情况,就简单很多了,6->x->5
\\n\\n","description":"官方题解把并查集的原理和用法讲得很明白了 但是在具体实现上没有说。\\n 我这里简单说一下吧,希望能帮助对并查集不熟的小伙伴快速上手\\n\\n具体落实到这个题上,通俗讲解一下\\n\\n先明确几个概念\\n\\n1.集合树:所有节点以代表节点为父节点构成的多叉树\\n 2.节点的代表节点:可以理解为节点的父节点,从当前节点出发,可以向上找到的第一个节点\\n 3.集合的代表节点:可以理解为根节点,意味着该集合内所有节点向上走,最终都能到达的节点\\n 来个图帮助理解\\n\\n\\n上图中是一棵集合树,树中有1-6总计6个节点\\n 整个集合的代表节点是1\\n 4节点的代表节点是3,6节点的代表节点是1\\n 无论沿着哪个节点向上走…","guid":"https://leetcode.cn/problems/redundant-connection//solution/tong-su-jiang-jie-bing-cha-ji-bang-zhu-xiao-bai-ku","author":"Zhcode","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-04-01T03:59:13.925Z","media":[{"url":"https://pic.leetcode-cn.com/a01e4fa1fbb1513961b81ff3ea983a028b3c120a39d202527e7c9278e88cffa5-image.png","type":"photo","width":300,"height":271,"blurhash":"LASigQ?b_3~q~q%M%Mj[-=WBM{Rj"},{"url":"https://pic.leetcode-cn.com/df3fcfb47ac68f45aaf3d80834e8044a91e6a42e63baa60be521839083338f9b-image.png","type":"photo","width":416,"height":73,"blurhash":"LGSigQ~q9F-;?bj[j[j[?bM{%Mj["},{"url":"https://pic.leetcode-cn.com/c95f25c4df2dce2f872fdc076bc0c8168c3ad241b8bd18638da68112e013c908-image.png","type":"photo","width":379,"height":126,"blurhash":"LGS?DV-;of~qxuWBof%Mj[j[ayay"},{"url":"https://pic.leetcode-cn.com/52d1a28110784aee9b145cd725834a51a4e9e7e87e5ec5a2ba2c83f42c44dc79-image.png","type":"photo","width":300,"height":128,"blurhash":"LDSigQ-;%M~q_3%M-;M{xuofIUM{"},{"url":"https://pic.leetcode-cn.com/0df9d3c5f9647b040b001c42c2e946975ddcc282f0c805d85e3addb126f7b695-image.png","type":"photo","width":270,"height":196,"blurhash":"L8SigQ~q~q~q~qRjj[%M?bayxu-;"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"无需排序,遍历一遍, 8ms","url":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/wu-xu-pai-xu-bian-li-yi-bian-8ms-by-bugmaker-u","content":"再读取到[5,6],6可以通过两条路径到达5,出现环\\n综上,在边集合没有重合边的情况下,如果给定新边的两个节点在同一集合中,说明图中出现了环\\n
\\n
题解中有很多大佬研究怎么优化计算权重的问题,但是却很少研究算完权重之后找第K个的问题
\\n
\\n几乎都是直接排序,其实没有必要排序。
\\n思路如下,list_point数组的下标对应权重step,所以算完权重之后直接去list_point[step]里面找到对应的list
\\nlist中的head指向的是第一个权重为step的数,也是最小的数,pos指向最后一个权重为step的数
\\nsrc_num的下标就是是1~1000的数,里面的内容指向下一个相同权重的数。
\\n例如假设2,4,6的权重都是2,那么整个结构如下图:\\n
最后根据list中记录的size可以知道第K个落在哪个list中,再根据list的head一个一个往后找就行了
\\n\\n","description":"题解中有很多大佬研究怎么优化计算权重的问题,但是却很少研究算完权重之后找第K个的问题 几乎都是直接排序,其实没有必要排序。\\n 思路如下,list_point数组的下标对应权重step,所以算完权重之后直接去list_point[step]里面找到对应的list\\n list中的head指向的是第一个权重为step的数,也是最小的数,pos指向最后一个权重为step的数\\n src_num的下标就是是1~1000的数,里面的内容指向下一个相同权重的数。\\n 例如假设2,4,6的权重都是2,那么整个结构如下图:\\n\\n最后根据list中记录的size可以知道第K个落在哪个l…","guid":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/wu-xu-pai-xu-bian-li-yi-bian-8ms-by-bugmaker-u","author":"bugmaker-u","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-31T05:50:01.939Z","media":[{"url":"https://pic.leetcode-cn.com/79d70c5058c3d61d3d1d1edd333f181828e1f2aed9d77dd6cd2b8bd39ac56b6c-image.png","type":"photo","width":937,"height":526,"blurhash":"L7Ryvo%fRj~qNGInozt7~qWUt7of"},{"url":"https://pic.leetcode-cn.com/337799f5ea2e40545639995d02e18a81ac3abee592aedf4d70aaba8b37440b8d-image.png","type":"photo","width":724,"height":313,"blurhash":"L8Rp8-~qxt~q_3ofayofD%t7ayay"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"模拟题,按照题目意思实现即可(Java)","url":"https://leetcode.cn/problems/available-captures-for-rook//solution/mo-ni-ti-an-zhao-ti-mu-yi-si-shi-xian-ji-ke-java-b","content":"typedef struct {\\nunsigned short head;\\nunsigned short pos;\\nunsigned short size;\\n} LIST;\\nunsigned int get_step_num(unsigned int num)\\n{\\nunsigned int step = 0;\\nwhile (num != 1) {\\nif (num & 0x01) {\\nnum = num * 3 + 1;\\n}\\nelse {\\nnum = num >> 1;\\n}\\nstep++;\\n}\\nreturn step;\\n}\\nint getKth(int lo, int hi, int k) {\\nint i = 0, tail = 0, num;\\nunsigned int step = 0;\\nint src_num[1001];\\nint list_point[179];//最大权重为178\\nLIST list[179];//最大权重为178\\nmemset(list_point, 0xFF, sizeof(list_point));\\n\\nfor (i = lo; i <= hi; i++) {\\nstep = get_step_num(i);\\nif (list_point[step] == 0xFFFFFFFF) {\\nlist_point[step] = tail;\\nlist[tail].head = i;\\nlist[tail].pos = i;\\nlist[tail].size = 1;\\ntail++;\\n}\\nelse {\\nlist[list_point[step]].size++;\\nsrc_num[list[list_point[step]].pos] = i;\\nlist[list_point[step]].pos = i;\\n}\\n}\\ni = 0;\\nstep = 0;\\nwhile (1) {\\nif (list_point[step] == 0xFFFFFFFF) {\\nstep++;\\ncontinue;\\n}\\nif (i + list[list_point[step]].size >= k)break;\\ni += list[list_point[step]].size;\\nstep++;\\n}\\ni = k - i - 1;\\nnum = list[list_point[step]].head;\\nwhile (i > 0) {\\nnum = src_num[num];\\ni--;\\n}\\nreturn num;\\n}\\n
首先,先读懂题目的意思,文字读不懂,就看示例。通过读示例,我们发现:
\\n1、这个车子(
\\nrock
)的属性很重要,它有一个「颜色」属性,并且它肯定是「白色」的;
\\n2、这个棋盘虽然画成浅深交替出现的样子,但是棋盘没有「颜色」属性,这是一开始困扰我很久的地方(摊手);
\\n3、我方属性是白色的,白色的「象」(B
)和我们是队友,我们只能干掉黑色属性的「卒」(p
);
\\n4、题目说:\\n\\n直到它选择停止、到达棋盘的边缘或移动到同一方格来捕获该方格上颜色相反的卒。
\\n这里的「同一方格」和「颜色相反」我真的有很多问号。其实就是说:站在当前白色车子位置,只能朝「上下左右」四个方向横冲直撞,能消灭掉多少黑色的卒。
\\n题目说:
\\n\\n\\n返回车能够在一次移动中捕获到的卒的数量。
\\n这里的「一次移动」的一次是「上下左右」四个方向算「一次」。朝着一个方向干掉一个黑色属性的「卒」以后,就停下来,即使这个方向上还有黑色属性的「卒」都不管了,因此输出最多是 $4$。
\\n这种问题在业界(我也不知道哪来的业界)有一个说法(不要问我咋知道的,问就是水群),就是模拟题,意即:将题目的意思直接实现出来,没有使用到特定的数据结构和方法。这些问题常常作为竞赛问题的第一个问题,也叫「签到题」,这种问题就是让你证明:
\\n\\n
{:width=200}
\\n{:align=center}为此设计算法如下:
\\n先通过遍历棋盘,得到白车的坐标,然后对上下左右四个方向进行遍历;
\\n\\n
\\n- 如果遇到
\\n.
(表示空格)就可以继续朝同一个方向前进,直到不能再走为止(到达边缘);- 如果遇到
\\nB
(白色的象)就停止找(原话是:车不能与其他友方(白色)象进入同一个方格,意思就是:白色全是你的队友,我们不能误伤队友);- 如果遇到
\\np
(黑色的卒) 说明找到了,停止,计数加一。补充:如果面试中遇到这样会让人有很多问号的问题,请大家一定要和面试官确定:你对题目中给出的条件的理解是准确无误的,具体来说有两方面:
\\n1、避免误解题目中没有给出的条件,不要「无中生有」;
\\n这里「敌方」和「我方」就是我假想出来的理解这个问题的东西,要和面试官确认。
\\n2、避免题目中给出的条件被我们忽略,导致简单问题变复杂,这样其实是得不偿失的。
\\n这里棋盘单元格的颜色深浅,是无关因素,如果考虑进去,就会增加难度,也是要和面试官确认的。
\\n面试很多时候不会仅仅考察面试者的算法水平,有些时候,面试官只是借着面试的问题,想和面试者展开对话,这个时候我们要做的是:
\\n1、在心态上,和面试官是平等的,我们是同事,我们一起在讨论问题,提出合理的疑问是完全没有问题的;
\\n
\\n2、展现出积极的态度,避免暴露出消极情绪。工作和生活中遇到的问题,很多时候就是模棱两可,边界模糊的问题。不会像是在平时练习一样,输入输出都那么明显、规范,所以有些面试官会故意把问题说得很含糊其辞,希望我们能够在和他的对话中,一起通过讨论逐渐把问题弄清楚。或者有些问题可能就是面试官在工作中遇到的问题,答案是开放的,他那里也没有标准答案。
\\n我们和面试官的对话,在一定程度上决定了面试官是否愿意想和你成为同事。因此,合理提出疑问是我们的权利,不问就是我们的问题了。希望大家在工作中都能有积极主动搞清楚问题,并且解决问题的态度和能力。
\\n个人观点,仅供参考。
\\n参考代码:
\\n###Java
\\n\\npublic class Solution {\\n\\n public int numRookCaptures(char[][] board) {\\n // 因为题目已经明确给出 board.length == board[i].length == 8,所以不做输入检查\\n // 定义方向数组,可以认为是四个方向向量,在棋盘问题上是常见的做法\\n int[][] directions = {{-1, 0}, {1, 0}, {0, 1}, {0, -1}};\\n\\n for (int i = 0; i < 8; i++) {\\n for (int j = 0; j < 8; j++) {\\n \\n if (board[i][j] == \'R\') {\\n int res = 0;\\n for (int[] direction : directions) {\\n if (burnout(board, i, j, direction)) {\\n res++;\\n }\\n }\\n return res;\\n }\\n }\\n }\\n // 代码不会走到这里,返回 0 或者抛出异常均可\\n return 0;\\n }\\n\\n /**\\n * burnout 横冲直撞的意思(来自欧路词典)\\n *\\n * @param board 输入棋盘\\n * @param x 当前白象位置的横坐标\\n * @param y 当前白象位置的纵坐标\\n * @param direction 方向向量\\n * @return 消灭一个 p,就返回 true\\n */\\n private boolean burnout(char[][] board, int x, int y, int[] direction) {\\n int i = x;\\n int j = y;\\n while (inArea(i, j)) {\\n // 是友军,路被堵死,直接返回\\n if (board[i][j] == \'B\') {\\n break;\\n }\\n\\n // 是敌军,拿下一血(不知道一血这个词是不是这么用的)\\n if (board[i][j] == \'p\') {\\n return true;\\n }\\n\\n i += direction[0];\\n j += direction[1];\\n }\\n return false;\\n }\\n\\n /**\\n * @param i 当前位置横坐标\\n * @param j 当前位置纵坐标\\n * @return 是否在棋盘有效范围内\\n */\\n private boolean inArea(int i, int j) {\\n return i >= 0 && i < 8 && j >= 0 && j < 8;\\n }\\n\\n public static void main(String[] args) {\\n char[][] board = {\\n {\'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'p\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'R\', \'.\', \'.\', \'.\', \'p\'},\\n {\'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'p\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\'},\\n {\'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\', \'.\'}};\\n Solution solution = new Solution();\\n int res = solution.numRookCaptures(board);\\n System.out.println(res);\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n","description":"首先,先读懂题目的意思,文字读不懂,就看示例。通过读示例,我们发现: 1、这个车子(rock)的属性很重要,它有一个「颜色」属性,并且它肯定是「白色」的;\\n 2、这个棋盘虽然画成浅深交替出现的样子,但是棋盘没有「颜色」属性,这是一开始困扰我很久的地方(摊手);\\n 3、我方属性是白色的,白色的「象」(B)和我们是队友,我们只能干掉黑色属性的「卒」(p);\\n 4、题目说:\\n\\n直到它选择停止、到达棋盘的边缘或移动到同一方格来捕获该方格上颜色相反的卒。\\n\\n这里的「同一方格」和「颜色相反」我真的有很多问号。其实就是说:站在当前白色车子位置,只能朝「上下左右…","guid":"https://leetcode.cn/problems/available-captures-for-rook//solution/mo-ni-ti-an-zhao-ti-mu-yi-si-shi-xian-ji-ke-java-b","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-25T23:39:31.332Z","media":[{"url":"https://pic.leetcode-cn.com/9523e4877997d001d0c4fbcbf872388a9f099b0e0ef6fb70f47a408c69602b48-timg%20(4).jpeg","type":"photo","width":640,"height":595,"blurhash":"LkNdO7%M~qt7M{t7%MWBNGayofay"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"简单Java,100%","url":"https://leetcode.cn/problems/available-captures-for-rook//solution/jian-dan-java100-by-sweetiee","content":"- 时间复杂度:$(N^2)$,这里 $N$ 是输入棋盘的长(宽)。找到白色车,最差情况下需要遍历完整个数组。题目固定了输入是 $8 \\\\times 8$ 规格的棋盘,认为是 $O(1)$ 也是没有问题的。
\\n- 空间复杂度:$O(1)$,只使用到常数个临时变量。
\\n🙋打卡~
\\n\\nclass Solution {\\n public int numRookCaptures(char[][] board) {\\n // 定义上下左右四个方向\\n int[] dx = {-1, 1, 0, 0};\\n int[] dy = {0, 0, -1, 1};\\n \\n for (int i = 0; i < 8; i++) {\\n for (int j = 0; j < 8; j++) {\\n // 找到白车所在的位置\\n if (board[i][j] == \'R\') {\\n // 分别判断白车的上、下、左、右四个方向\\n int res = 0;\\n for (int k = 0; k < 4; k++) {\\n int x = i, y = j;\\n while (true) {\\n x += dx[k];\\n y += dy[k];\\n if (x < 0 || x >= 8 || y < 0 || y >= 8 || board[x][y] == \'B\') {\\n break;\\n }\\n if (board[x][y] == \'p\') {\\n res++;\\n break;\\n }\\n }\\n }\\n return res;\\n }\\n }\\n }\\n return 0;\\n }\\n}\\n
看见评论区有人说“不是移动一次吗怎么可以换方向”,其实题目的意思可以理解为移动一次吃到卒的方案有多少种
\\n
\\n(示意图如下,3个卒都可以移动一次车就吃到)
\\n所以就是先找到车的位置,然后从这个位置开始遍历上下左右4个方向,如果遇到了卒就res+1并退出循环,如果遇到了象或者出界了就退出循环。\\n
{:height=\\"40%\\" width=\\"40%\\"}
大佬们随手关注下我的wx公众号【甜姨的奇妙冒险】和 知乎专栏【甜姨的力扣题解】
\\n","description":"🙋打卡~ class Solution {\\n public int numRookCaptures(char[][] board) {\\n // 定义上下左右四个方向\\n int[] dx = {-1, 1, 0, 0};\\n int[] dy = {0, 0, -1, 1};\\n \\n for (int i = 0; i < 8; i++) {\\n for (int j = 0; j < 8; j++) {\\n // 找到白车所在的位置…","guid":"https://leetcode.cn/problems/available-captures-for-rook//solution/jian-dan-java100-by-sweetiee","author":"sweetiee","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-25T16:15:00.830Z","media":[{"url":"https://pic.leetcode-cn.com/2637aa62544ffc820f796c1be610e87f6609fc16e4f6727fffb5f2d6315da956-image.png","type":"photo","width":1226,"height":1244,"blurhash":"L7K1?byAIXY4~Vx[xaX902V[M|n+"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"将整数按权重排序","url":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/jiang-zheng-shu-an-quan-zhong-pai-xu-by-leetcode-s","content":"
\\n,更多干货快到碗里来~题目分析
\\n我们要按照权重为第一关键字,原值为第二关键字对区间
\\n[lo, hi]
进行排序,关键在于我们怎么求权重。方法一:递归
\\n思路
\\n记 $x$ 的权重为 $f(x)$,按照题意很明显我们可以构造这样的递归式:
\\n$$
\\n
\\nf(x) =
\\n\\\\left { \\\\begin{aligned}
\\n0 &, & x = 1 \\\\
\\nf(3x + 1) + 1 &, & x \\\\bmod{2} = 1 \\\\
\\nf(\\\\frac{x}{2}) + 1 &, & x \\\\bmod{2} = 0
\\n\\\\end{aligned} \\\\right .
\\n$$于是我们就可以递归求解每个数字的权重了。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int getF(int x) {\\n if (x == 1) return 0;\\n if (x & 1) return getF(x * 3 + 1) + 1;\\n else return getF(x / 2) + 1;\\n }\\n\\n int getKth(int lo, int hi, int k) {\\n vector <int> v;\\n for (int i = lo; i <= hi; ++i) v.push_back(i);\\n sort(v.begin(), v.end(), [&] (int u, int v) {\\n if (getF(u) != getF(v)) return getF(u) < getF(v);\\n else return u < v;\\n });\\n return v[k - 1];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public int getKth(int lo, int hi, int k) {\\n List<Integer> list = new ArrayList<Integer>();\\n for (int i = lo; i <= hi; ++i) {\\n list.add(i);\\n }\\n Collections.sort(list, new Comparator<Integer>() {\\n public int compare(Integer u, Integer v) {\\n if (getF(u) != getF(v)) {\\n return getF(u) - getF(v);\\n } else {\\n return u - v;\\n }\\n }\\n });\\n return list.get(k - 1);\\n }\\n\\n public int getF(int x) {\\n if (x == 1) {\\n return 0;\\n } else if ((x & 1) != 0) {\\n return getF(x * 3 + 1) + 1;\\n } else {\\n return getF(x / 2) + 1;\\n }\\n }\\n}\\n
###python
\\n\\nclass Solution:\\n def getKth(self, lo: int, hi: int, k: int) -> int:\\n def getF(x):\\n if x == 1:\\n return 0\\n return (getF(x * 3 + 1) if x % 2 == 1 else getF(x // 2)) + 1\\n \\n v = list(range(lo, hi + 1))\\n v.sort(key=lambda x: (getF(x), x))\\n return v[k - 1]\\n
###C#
\\n\\npublic class Solution {\\n public int GetKth(int lo, int hi, int k) {\\n List<int> v = new List<int>();\\n for (int i = lo; i <= hi; i++) {\\n v.Add(i);\\n }\\n v.Sort((u, v) => {\\n int f1 = GetF(u);\\n int f2 = GetF(v);\\n if (f1 != f2) {\\n return f1.CompareTo(f2);\\n }\\n return u.CompareTo(v);\\n });\\n return v[k - 1];\\n }\\n\\n public int GetF(int x) {\\n if (x == 1) {\\n return 0;\\n }\\n if ((x & 1) == 1) {\\n return GetF(x * 3 + 1) + 1;\\n } else {\\n return GetF(x / 2) + 1;\\n }\\n }\\n}\\n
###Go
\\n\\nfunc getKth(lo int, hi int, k int) int {\\n v := []int{}\\nfor i := lo; i <= hi; i++ {\\nv = append(v, i)\\n}\\nsort.Slice(v, func(i, j int) bool {\\nif getF(v[i]) != getF(v[j]) {\\nreturn getF(v[i]) < getF(v[j])\\n}\\nreturn v[i] < v[j]\\n})\\nreturn v[k - 1]\\n}\\n\\nfunc getF(x int) int {\\nif x == 1 {\\nreturn 0\\n}\\nif x & 1 == 1 {\\nreturn getF(x * 3 + 1) + 1\\n} else {\\nreturn getF(x / 2) + 1\\n}\\n}\\n
###C
\\n\\nint getF(int x) {\\n if (x == 1) {\\n return 0;\\n }\\n if (x & 1) {\\n return getF(x * 3 + 1) + 1;\\n } else {\\n return getF(x / 2) + 1;\\n }\\n}\\n\\nint compare(const void *a, const void *b) {\\n int u = *(int*)a;\\n int v = *(int*)b;\\n int f1 = getF(u);\\n int f2 = getF(v);\\n if (f1 != f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n}\\n\\nint getKth(int lo, int hi, int k) {\\n int size = hi - lo + 1;\\n int *v = (int *)malloc(size * sizeof(int));\\n for (int i = 0; i < size; ++i) v[i] = lo + i;\\n qsort(v, size, sizeof(int), compare);\\n int res = v[k - 1];\\n free(v);\\n return res;\\n}\\n
###JavaScript
\\n\\nfunction getF(x) {\\n if (x === 1) {\\n return 0;\\n }\\n if (x & 1) {\\n return getF(x * 3 + 1) + 1;\\n } else {\\n return getF(Math.floor(x / 2)) + 1;\\n }\\n}\\n\\nvar getKth = function(lo, hi, k) {\\n let v = [];\\n for (let i = lo; i <= hi; i++) {\\n v.push(i);\\n }\\n v.sort((u, v) => {\\n let f1 = getF(u);\\n let f2 = getF(v);\\n if (f1 !== f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n });\\n return v[k - 1];\\n};\\n
###TypeScript
\\n\\nfunction getKth(lo: number, hi: number, k: number): number {\\n let v: number[] = [];\\n for (let i = lo; i <= hi; i++) {\\n v.push(i);\\n }\\n v.sort((u, v) => {\\n let f1 = getF(u);\\n let f2 = getF(v);\\n if (f1 !== f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n });\\n return v[k - 1];\\n};\\n\\nfunction getF(x: number): number {\\n if (x === 1) {\\n return 0;\\n }\\n if (x & 1) {\\n return getF(x * 3 + 1) + 1;\\n } else {\\n return getF(Math.floor(x / 2)) + 1;\\n }\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn get_kth(lo: i32, hi: i32, k: i32) -> i32 {\\n let mut v: Vec<i32> = (lo..=hi).collect();\\n v.sort_by(|&u, &v| {\\n let f1 = Self::get_f(u);\\n let f2 = Self::get_f(v);\\n if f1 != f2 {\\n f1.cmp(&f2)\\n } else {\\n u.cmp(&v)\\n }\\n });\\n v[k as usize - 1]\\n }\\n\\n fn get_f(x: i32) -> i32 {\\n if x == 1 {\\n return 0;\\n }\\n if x & 1 == 1 {\\n Self::get_f(x * 3 + 1) + 1\\n } else {\\n Self::get_f(x / 2) + 1\\n }\\n }\\n}\\n
复杂度分析
\\n记区间长度为 $n$,等于
\\nhi - lo + 1
。\\n
\\n- \\n
\\n时间复杂度:这里的区间一定是 $[1, 1000]$ 的子集,在 $[1, 1000]$ 中权重最大数的权重为 $178$,即这个递归函数要执行 $178$ 次,所以排序的每次比较的时间代价为 $O(178)$,故渐进时间复杂度为 $O(178 \\\\times n \\\\log n)$。
\\n- \\n
\\n空间复杂度:我们使用了长度为 $n$ 的数组辅助进行排序,同时再使用递归计算权重时最多会使用 $178$ 层的栈空间,故渐进空间复杂度为 $O(n + 178)$。
\\n方法二:记忆化
\\n思路
\\n我们知道在求 $f(3)$ 的时候会调用到 $f(10)$,在求 $f(20)$ 的时候也会调用到 $f(10)$。同样的,如果单纯递归计算权重的话,会存在很多重复计算,我们可以用记忆化的方式来加速这个过程,即「先查表,再计算」和「先记忆,再返回」。我们可以用一个哈希映射作为这里的记忆化的「表」,这样保证每个元素的权值只被计算 $1$ 次。在 $[1, 1000]$ 中所有 $x$ 求 $f(x)$ 的值的过程中,只可能出现 $2228$ 种 $x$,于是效率就会大大提高。
\\n代码如下。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n unordered_map <int, int> f;\\n\\n int getF(int x) {\\n if (f.find(x) != f.end()) return f[x];\\n if (x == 1) return f[x] = 0;\\n if (x & 1) return f[x] = getF(x * 3 + 1) + 1;\\n else return f[x] = getF(x / 2) + 1;\\n }\\n\\n int getKth(int lo, int hi, int k) {\\n vector <int> v;\\n for (int i = lo; i <= hi; ++i) v.push_back(i);\\n sort(v.begin(), v.end(), [&] (int u, int v) {\\n if (getF(u) != getF(v)) return getF(u) < getF(v);\\n else return u < v;\\n });\\n return v[k - 1];\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n Map<Integer, Integer> f = new HashMap<Integer, Integer>();\\n\\n public int getKth(int lo, int hi, int k) {\\n List<Integer> list = new ArrayList<Integer>();\\n for (int i = lo; i <= hi; ++i) {\\n list.add(i);\\n }\\n Collections.sort(list, new Comparator<Integer>() {\\n public int compare(Integer u, Integer v) {\\n if (getF(u) != getF(v)) {\\n return getF(u) - getF(v);\\n } else {\\n return u - v;\\n }\\n }\\n });\\n return list.get(k - 1);\\n }\\n\\n public int getF(int x) {\\n if (!f.containsKey(x)) {\\n if (x == 1) {\\n f.put(x, 0);\\n } else if ((x & 1) != 0) {\\n f.put(x, getF(x * 3 + 1) + 1);\\n } else {\\n f.put(x, getF(x / 2) + 1);\\n }\\n }\\n return f.get(x);\\n }\\n}\\n
###python
\\n\\nclass Solution:\\n def getKth(self, lo: int, hi: int, k: int) -> int:\\n f = {1: 0}\\n\\n def getF(x):\\n if x in f:\\n return f[x]\\n f[x] = (getF(x * 3 + 1) if x % 2 == 1 else getF(x // 2)) + 1\\n return f[x]\\n \\n v = list(range(lo, hi + 1))\\n v.sort(key=lambda x: (getF(x), x))\\n return v[k - 1]\\n
###C#
\\n\\npublic class Solution {\\n Dictionary<int, int> f = new Dictionary<int, int>();\\n\\n public int GetKth(int lo, int hi, int k) {\\n List<int> v = new List<int>();\\n for (int i = lo; i <= hi; i++) {\\n v.Add(i);\\n }\\n v.Sort((u, v) => {\\n int f1 = GetF(u);\\n int f2 = GetF(v);\\n if (f1 != f2) {\\n return f1.CompareTo(f2);\\n }\\n return u.CompareTo(v);\\n });\\n return v[k - 1];\\n }\\n\\n public int GetF(int x) {\\n if (f.ContainsKey(x)) {\\n return f[x];\\n }\\n if (x == 1) {\\n return f[x] = 0;\\n }\\n if ((x & 1) == 1) {\\n return f[x] = GetF(x * 3 + 1) + 1;\\n } else {\\n return f[x] = GetF(x / 2) + 1;\\n }\\n }\\n}\\n
###Go
\\n\\nvar f = make(map[int]int)\\n\\nfunc getF(x int) int {\\nif val, exists := f[x]; exists {\\nreturn val\\n}\\nif x == 1 {\\nf[x] = 0\\nreturn 0\\n}\\nif x&1 == 1 {\\nf[x] = getF(x * 3 + 1) + 1\\n} else {\\nf[x] = getF(x / 2) + 1\\n}\\nreturn f[x]\\n}\\n\\nfunc getKth(lo int, hi int, k int) int {\\n v := make([]int, 0)\\nfor i := lo; i <= hi; i++ {\\nv = append(v, i)\\n}\\nsort.Slice(v, func(i, j int) bool {\\nif getF(v[i]) != getF(v[j]) {\\nreturn getF(v[i]) < getF(v[j])\\n}\\nreturn v[i] < v[j]\\n})\\nreturn v[k - 1]\\n}\\n
###C
\\n\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nHashItem *f = NULL;\\n\\nint getF(int x) {\\n int result = hashGetItem(&f, x, -1); \\n if (result != -1) {\\n return result;\\n }\\n\\n if (x == 1) {\\n result = 0;\\n } else if (x & 1) {\\n result = getF(x * 3 + 1) + 1;\\n } else {\\n result = getF(x / 2) + 1;\\n }\\n hashAddItem(&f, x, result);\\n return result;\\n}\\n\\nint compare(const void *a, const void *b) {\\n int u = *(int*)a;\\n int v = *(int*)b;\\n int f1 = getF(u);\\n int f2 = getF(v);\\n if (f1 != f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n}\\n\\nint getKth(int lo, int hi, int k) {\\n int size = hi - lo + 1;\\n int *v = (int *)malloc(size * sizeof(int));\\n for (int i = 0; i < size; ++i) {\\n v[i] = lo + i;\\n }\\n qsort(v, size, sizeof(int), compare);\\n int result = v[k - 1];\\n free(v);\\n hashFree(&f);\\n return result;\\n}\\n
###JavaScript
\\n\\nconst f = new Map();\\n\\nfunction getF(x) {\\n if (f.has(x)) {\\n return f.get(x);\\n }\\n if (x === 1) {\\n return f.set(x, 0).get(x);\\n }\\n if (x & 1) {\\n return f.set(x, getF(x * 3 + 1) + 1).get(x);\\n }\\n return f.set(x, getF(x / 2) + 1).get(x);\\n}\\n\\nvar getKth = function(lo, hi, k) {\\n let v = [];\\n for (let i = lo; i <= hi; i++) {\\n v.push(i);\\n }\\n v.sort((u, v) => {\\n let f1 = getF(u);\\n let f2 = getF(v);\\n if (f1 !== f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n });\\n return v[k - 1];\\n};\\n
###TypeScript
\\n\\nfunction getKth(lo: number, hi: number, k: number): number {\\n let v: number[] = [];\\n for (let i = lo; i <= hi; i++) {\\n v.push(i);\\n }\\n v.sort((u, v) => {\\n let f1 = getF(u);\\n let f2 = getF(v);\\n if (f1 !== f2) {\\n return f1 - f2;\\n }\\n return u - v;\\n });\\n return v[k - 1];\\n};\\n\\nconst f: Map<number, number> = new Map();\\n\\nfunction getF(x: number): number {\\n if (f.has(x)) {\\n return f.get(x)!;\\n }\\n if (x === 1) {\\n return f.set(x, 0).get(x)!;\\n }\\n if (x & 1) {\\n return f.set(x, getF(x * 3 + 1) + 1).get(x)!;\\n }\\n return f.set(x, getF(x / 2) + 1).get(x)!;\\n}\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n fn get_f(x: i32, f: &mut HashMap<i32, i32>) -> i32 {\\n if let Some(&val) = f.get(&x) {\\n return val;\\n }\\n let res = if x == 1 {\\n 0\\n } else if x & 1 == 1 {\\n Self::get_f(x * 3 + 1, f) + 1\\n } else {\\n Self::get_f(x / 2, f) + 1\\n };\\n f.insert(x, res);\\n res\\n }\\n\\n pub fn get_kth(lo: i32, hi: i32, k: i32) -> i32 {\\n let mut f: HashMap<i32, i32> = HashMap::new();\\n let mut v: Vec<i32> = (lo..=hi).collect();\\n v.sort_by(|&u, &v| {\\n let f1 = Self::get_f(u, &mut f);\\n let f2 = Self::get_f(v, &mut f);\\n if f1 != f2 {\\n f1.cmp(&f2)\\n } else {\\n u.cmp(&v)\\n }\\n });\\n v[k as usize - 1]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"题目分析 我们要按照权重为第一关键字,原值为第二关键字对区间 [lo, hi] 进行排序,关键在于我们怎么求权重。\\n\\n方法一:递归\\n\\n思路\\n\\n记 $x$ 的权重为 $f(x)$,按照题意很明显我们可以构造这样的递归式:\\n\\n$$\\n f(x) =\\n \\\\left { \\\\begin{aligned}\\n 0 &, & x = 1 \\\\\\n f(3x + 1) + 1 &, & x \\\\bmod{2} = 1 \\\\\\n f(\\\\frac{x}{2}) + 1 &, & x \\\\bmod{2} = 0\\n \\\\end{aligned} \\\\right .\\n $$\\n\\n于…","guid":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/jiang-zheng-shu-an-quan-zhong-pai-xu-by-leetcode-s","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-24T09:57:07.132Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"java 优先队列 + 记忆化","url":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/java-you-xian-dui-lie-ji-yi-hua-by-yuruiyin","content":"- \\n
\\n时间复杂度:平均情况下比较的次数为 $n \\\\log n$,把 $2228$ 次平摊到每一次的时间代价为 $O(\\\\frac{2228}{n \\\\log n})$,故总时间代价为 $O(\\\\frac{2228}{n \\\\log n} \\\\times n \\\\log n) = O(2228)$。
\\n- \\n
\\n空间复杂度:我们使用了长度为 $n$ 的数组辅助进行排序,哈希映射只可能存在 $2228$ 种键,故渐进空间复杂度为 $O(n + 2228)$。由于这里我们使用了记忆化,因此递归使用的栈空间层数会均摊到所有的 $n$ 中,由于 $n$ 的最大值为 $1000$,因此每一个 $n$ 使用的栈空间为 $O(\\\\frac{2228}{1000}) \\\\approx O(2)$,相较于排序的哈希映射需要的空间可以忽略不计。
\\n思路
\\n\\n
\\n- 看到第k个数,其实最简单的做法就是当成topK问题来处理,即一个时间复杂度为$O(nlogk)$的堆直接上。(题外话,理论上,若只是求第k小或第k大,快选才是最快的);
\\n- 然后另一个关键的是求权重的过程。反正模拟就完事了。但是,我们会发现,题目给的是$[lo, hi]$,是一个区间,在计算权重的过程中会有很多重复的子问题。比如说我们10的权重相当于5的权重+1。因此,我们可以引入记忆化,也就说把计算过的值的权重记录下来,当下次计算权重的过程中可以快速返回。
\\n###java
\\n\\n","description":"思路 看到第k个数,其实最简单的做法就是当成topK问题来处理,即一个时间复杂度为$O(nlogk)$的堆直接上。(题外话,理论上,若只是求第k小或第k大,快选才是最快的);\\n然后另一个关键的是求权重的过程。反正模拟就完事了。但是,我们会发现,题目给的是$[lo, hi]$,是一个区间,在计算权重的过程中会有很多重复的子问题。比如说我们10的权重相当于5的权重+1。因此,我们可以引入记忆化,也就说把计算过的值的权重记录下来,当下次计算权重的过程中可以快速返回。\\n\\n###java\\n\\nprivate Mapprivate Map<Integer, Integer> memo;\\n\\n private int getWeight(int num) {\\n if (num == 1) {\\n return 0;\\n }\\n\\n if (memo.containsKey(num)) {\\n return memo.get(num);\\n }\\n\\n int count = (num & 1) == 0 ? getWeight(num >>> 1) : getWeight(3 * num + 1);\\n count++;\\n memo.put(num, count);\\n return count;\\n }\\n\\n public int getKth(int lo, int hi, int k) {\\n // int[] = {值,权重}\\n PriorityQueue<int[]> heap = new PriorityQueue<>((o1, o2) -> o1[1] == o2[1] ? o2[0] - o1[0] : o2[1] - o1[1]);\\n memo = new HashMap<>();\\n for (int i = lo; i <= hi; i++) {\\n heap.offer(new int[]{i, getWeight(i)});\\n if (heap.size() > k) {\\n heap.poll();\\n }\\n }\\n\\n return heap.peek()[0];\\n }\\n
memo;…","guid":"https://leetcode.cn/problems/sort-integers-by-the-power-value//solution/java-you-xian-dui-lie-ji-yi-hua-by-yuruiyin","author":"yuruiyin","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-22T06:14:50.334Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动脑筋","url":"https://leetcode.cn/problems/break-a-palindrome//solution/java-by-gfu","content":" 思路
\\n首先如果字符串长度为奇数,则字符串中间的那个字符无论怎么改,字符串都是回文串。
\\n
\\n如:aba
,b
字符无论怎么改,字符串都还是回文串。回文串前半段和后半段是相互对应的,因此只要遍历一半就好了。
\\n首先遍历前半段,遇到不为
\\na
的字符就直接将其替换成a
,然后直接return结果。
\\n如果前半段都是a
,则说明后半段也都是a
,说明字符串要么类似aabaa
,要么类似aaaaaa
。
\\n直接将最后1个字符改成b
就好了。代码
\\n\\nclass Solution {\\n public String breakPalindrome(String palindrome) {\\n int len = palindrome.length(), half = (len - 2) >> 1;\\n if (len < 2) return \\"\\";\\n char[] ch_arr = palindrome.toCharArray();\\n for (int i = 0; i <= half; ++i)\\n if (ch_arr[i] > \'a\') {\\n ch_arr[i] = \'a\';\\n return String.valueOf(ch_arr);\\n }\\n ch_arr[len - 1] = \'b\';\\n return String.valueOf(ch_arr);\\n }\\n}\\n
\\nclass Solution {\\npublic:\\n string breakPalindrome(string palindrome) {\\n size_t len = palindrome.size(), half = (len - 2) >> 1;\\n if(len < 2) return \\"\\";\\n for (size_t i = 0; i <= half; ++i)\\n if (palindrome[i] > \'a\') {\\n palindrome[i] = \'a\';\\n return palindrome;\\n }\\n palindrome[len - 1] = \'b\';\\n return palindrome;\\n }\\n};\\n
\\n","description":"首先如果字符串长度为奇数,则字符串中间的那个字符无论怎么改,字符串都是回文串。 如:aba,b字符无论怎么改,字符串都还是回文串。\\n\\n回文串前半段和后半段是相互对应的,因此只要遍历一半就好了。\\n\\n首先遍历前半段,遇到不为a的字符就直接将其替换成a,然后直接return结果。\\n 如果前半段都是a,则说明后半段也都是a,说明字符串要么类似aabaa,要么类似aaaaaa。\\n 直接将最后1个字符改成b就好了。\\n\\nclass Solution {\\n public String breakPalindrome(String palindrome) {…","guid":"https://leetcode.cn/problems/break-a-palindrome//solution/java-by-gfu","author":"gfu","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-14T05:39:23.831Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最详细证明,一步步推导告诉你为何这么做","url":"https://leetcode.cn/problems/airplane-seat-assignment-probability//solution/zui-xiang-xi-zheng-ming-yi-bu-bu-tui-dao-gao-su-ni","content":"class Solution:\\n def breakPalindrome(self, palindrome: str) -> str:\\n str_len = len(palindrome)\\n if str_len < 2:\\n return \\"\\"\\n for idx in range(str_len >> 1):\\n if palindrome[idx] > \'a\':\\n return palindrome[:idx] + \'a\' + palindrome[idx+1:]\\n return palindrome[:str_len-1] + \'b\'\\n # str_len = len(palindrome)\\n # char_list = list(palindrome)\\n # for idx in range(str_len >> 1):\\n # if char_list[idx] > \'a\':\\n # char_list[idx] = \'a\'\\n # return \'\'.join(char_list)\\n # char_list[str_len - 1] = \'b\'\\n # return \'\'.join(char_list)\\n
题解
\\n这题呢代码相当之简单,但是我看了看题解区能真正理解的也不是很多,很多都是揣着糊涂装明白,稀里糊涂就当证过了。
\\n首先题目并没有说第一个乘客座位号就是 $1$ 啊?也没说最后一个乘客座位号就是 $n$ 啊?所以大家的假设是怎么来的?这一点没有说清。其实很简单,不管每个乘客编号是多少,我们不用管,我们只要看他入场的次序就行了,所以我们就按照入场次序给他们重新编个号,这样的话就是按照 $1$ 到 $n$ 的编号入场了(也就是这里的编号代表的是入场的次序,而不是实际的座位号)。
\\n然后就是 $1$ 号进场了,可以分为下面几种情况:
\\n\\n
\\n- 他有 $\\\\frac{1}{n}$ 的概率选择坐在 $1$ 号座位上。这样 $2$ 到 $n$ 号位置都不会被占,那么 $n$ 号坐在自己座位的概率就是 $1.0$ 。
\\n- 他有 $\\\\frac{1}{n}$ 的概率选择坐在 $n$ 号座位上。这样 $2$ 到 $n-1$ 号位置都不会被占,而 $n$ 号只能坐在 $1$ 号座位上,那么概率就是 $0.0$ 。
\\n- 他有 $\\\\frac{1}{n}$ 的概率选择坐在 $i$ 号座位上,其中 $2 \\\\le i \\\\le n-1$。这样 $2$ 到 $i-1$ 号位置都不会被占,他们都坐在自己的的位置上。而 $i$ 号乘客就犯难了,他的座位被 $1$ 号占了,他不知道坐哪了。这时候,如果他选择坐 $1$ 号座位,那么 $i+1$ 到 $n$ 号乘客还是坐在自己位置,相安无事。而如果他选择坐在 $i+1$ 到 $n$ 号中的某个位置,那么必然又会产生新的冲突,这样就不好求解了啊!
\\n对于第三种情况,我们可以换个角度看问题。现在面临的问题是,$i$ 号选择坐在哪?这时候还没入场的有 $i$ 到 $n$ 号乘客,而座位还剩 $1$ 和 $i+1$ 到 $n$ 号。那既然 $i$ 号乘客坐在 $1$ 号座位的话,后面的人都能坐回原位,那我们就把 $1$ 号座位当作是 $i$ 号乘客原本的座位就行了嘛,反正我最后又不要求 $i$ 号乘客坐回原位的概率,你坐哪都没事,只要别影响到其他人就行了。那么问题的规模就被缩小到了 $n-i+1$ ,我们递归求解就行了。
\\n令 $f(n)$ 表示 $n$ 个人的情况下,最后一个人坐回原位的概率,按照上面的分析,我们可以列出递推式:
\\n
\\n$$
\\nf(n) = \\\\frac{1}{n}\\\\left(1 + \\\\sum_{i=2}^{n-1}{f(n-i+1)}\\\\right)
\\n$$
\\n这个递推式想必大家高中就会求了,令 $n = n-1$再写出一项:
\\n$$
\\nf(n-1) = \\\\frac{1}{n-1}\\\\left(1 + \\\\sum_{i=2}^{n-2}{f(n-i)}\\\\right)
\\n$$
\\n然后两式相减得到:
\\n$$
\\nnf(n) - (n-1)f(n-1) = f(n-1)
\\n$$
\\n即:
\\n$$
\\nf(n) = f(n-1) = \\\\cdots = f(2)
\\n$$
\\n那么我们就可以得到最终的答案了,对任意的 $n \\\\ge 2$ 都有 $f(n) = f(2) = 0.5$ 。还有一个特例就是 $f(1) = 1.0$ ,这样这题就证好了。
\\n这题最关键的一步就是 $1$ 号坐在了 $i$ 号座位后,$i$ 号何去何从?如果你能换个角度,把 $1$ 号座位给 $i$ 号(因为给他之后,对后面的乘客座位没有任何影响,那么就能把 $1$ 号座位看成就是 $i$ 号乘客的),那么问题就能递归下去了。题解区许多人这一步为什么能递归下去?根本没有讲清楚。
\\n代码
\\nc++
\\n###cpp
\\n\\nclass Solution {\\npublic:\\n double nthPersonGetsNthSeat(int n) {\\n return n==1 ? 1 : .5;\\n }\\n};\\n
python
\\n###py
\\n\\n","description":"题解 这题呢代码相当之简单,但是我看了看题解区能真正理解的也不是很多,很多都是揣着糊涂装明白,稀里糊涂就当证过了。\\n\\n首先题目并没有说第一个乘客座位号就是 $1$ 啊?也没说最后一个乘客座位号就是 $n$ 啊?所以大家的假设是怎么来的?这一点没有说清。其实很简单,不管每个乘客编号是多少,我们不用管,我们只要看他入场的次序就行了,所以我们就按照入场次序给他们重新编个号,这样的话就是按照 $1$ 到 $n$ 的编号入场了(也就是这里的编号代表的是入场的次序,而不是实际的座位号)。\\n\\n然后就是 $1$ 号进场了,可以分为下面几种情况:\\n\\n他有 $\\\\frac{1…","guid":"https://leetcode.cn/problems/airplane-seat-assignment-probability//solution/zui-xiang-xi-zheng-ming-yi-bu-bu-tui-dao-gao-su-ni","author":"godweiyang","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-13T04:01:22.651Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"数组大小减半","url":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/shu-zu-da-xiao-jian-ban-by-leetcode-solution","content":"class Solution:\\n def nthPersonGetsNthSeat(self, n: int) -> float:\\n return 1 if n==1 else .5\\n
方法一:贪心算法
\\n思路与算法
\\n在每一步操作中,我们需要选择一个数 $x$,并且删除数组 $\\\\textit{arr}$ 中所有的 $x$。显然选择的数 $x$ 在数组 $\\\\textit{arr}$ 中出现的次数越多越好。因此我们可以统计数组 $\\\\textit{arr}$ 中每个数出现的次数,并进行降序排序。在得到了排序的结果之后,我们依次选择这些数进行删除,直到删除了至少一半的数。
\\n在统计数组 $\\\\textit{arr}$ 中每个数出现的次数时,我们可以借助哈希映射($\\\\text{HashMap}$),对于其中的每个键值对,键表示数 $x$,值表示数 $x$ 出现的次数。在统计结束后,我们只要取出哈希映射中的所有值进行降序排序即可。在进行删除时,我们实际上也只需要将删除的数的个数进行累加,直到累加的值达到数组 $\\\\textit{arr}$ 长度的一半,而不需要真正地将数组 $\\\\textit{arr}$ 中的数删除。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int minSetSize(vector<int>& arr) {\\n unordered_map<int, int> freq;\\n for (int num: arr) {\\n ++freq[num];\\n }\\n vector<int> occ;\\n for (auto& [k, v]: freq) {\\n occ.push_back(v);\\n }\\n sort(occ.begin(), occ.end(), greater<int>());\\n int cnt = 0, ans = 0;\\n for (int c: occ) {\\n cnt += c;\\n ans += 1;\\n if (cnt * 2 >= arr.size()) {\\n break;\\n }\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def minSetSize(self, arr: List[int]) -> int:\\n freq = collections.Counter(arr)\\n cnt, ans = 0, 0\\n for num, occ in freq.most_common():\\n cnt += occ\\n ans += 1\\n if cnt * 2 >= len(arr):\\n break\\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int minSetSize(int[] arr) {\\n Map<Integer, Integer> freq = new HashMap<>();\\n for (int num : arr) {\\n freq.put(num, freq.getOrDefault(num, 0) + 1);\\n }\\n\\n List<Integer> occ = new ArrayList<>(freq.values());\\n Collections.sort(occ, Collections.reverseOrder());\\n int cnt = 0, ans = 0;\\n for (int c : occ) {\\n cnt += c;\\n ans += 1;\\n if (cnt * 2 >= arr.length) {\\n break;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MinSetSize(int[] arr) {\\n Dictionary<int, int> freq = new Dictionary<int, int>();\\n foreach (int num in arr) {\\n if (freq.ContainsKey(num)) {\\n freq[num]++;\\n } else {\\n freq[num] = 1;\\n }\\n }\\n\\n List<int> occ = freq.Values.ToList();\\n occ.Sort((a, b) => b - a);\\n int cnt = 0, ans = 0;\\n foreach (int c in occ) {\\n cnt += c;\\n ans += 1;\\n if (cnt * 2 >= arr.Length) {\\n break;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc minSetSize(arr []int) int {\\n freq := make(map[int]int)\\n for _, num := range arr {\\n freq[num]++\\n }\\n\\n occ := []int{}\\n for _, v := range freq {\\n occ = append(occ, v)\\n }\\n sort.Sort(sort.Reverse(sort.IntSlice(occ)))\\n cnt, ans := 0, 0\\n for _, c := range occ {\\n cnt += c\\n ans++\\n if cnt * 2 >= len(arr) {\\n break\\n }\\n }\\n return ans\\n}\\n
###C
\\n\\ntypedef struct {\\n int key;\\n int val;\\n UT_hash_handle hh;\\n} HashItem; \\n\\nHashItem *hashFindItem(HashItem **obj, int key) {\\n HashItem *pEntry = NULL;\\n HASH_FIND_INT(*obj, &key, pEntry);\\n return pEntry;\\n}\\n\\nbool hashAddItem(HashItem **obj, int key, int val) {\\n if (hashFindItem(obj, key)) {\\n return false;\\n }\\n HashItem *pEntry = (HashItem *)malloc(sizeof(HashItem));\\n pEntry->key = key;\\n pEntry->val = val;\\n HASH_ADD_INT(*obj, key, pEntry);\\n return true;\\n}\\n\\nbool hashSetItem(HashItem **obj, int key, int val) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n hashAddItem(obj, key, val);\\n } else {\\n pEntry->val = val;\\n }\\n return true;\\n}\\n\\nint hashGetItem(HashItem **obj, int key, int defaultVal) {\\n HashItem *pEntry = hashFindItem(obj, key);\\n if (!pEntry) {\\n return defaultVal;\\n }\\n return pEntry->val;\\n}\\n\\nvoid hashFree(HashItem **obj) {\\n HashItem *curr = NULL, *tmp = NULL;\\n HASH_ITER(hh, *obj, curr, tmp) {\\n HASH_DEL(*obj, curr); \\n free(curr);\\n }\\n}\\n\\nstatic int compare(const void *a, const void *b) {\\n return *(int *)b - *(int *)a;\\n}\\n\\nint minSetSize(int* arr, int arrSize) {\\n HashItem *freq = NULL;\\n for (int i = 0; i < arrSize; i++) {\\n hashSetItem(&freq, arr[i], hashGetItem(&freq, arr[i], 0) + 1);\\n }\\n\\n int occSize = HASH_COUNT(freq);\\n int *occ = (int *)calloc(occSize, sizeof(int));\\n int pos = 0;\\n for (HashItem *pEntry = freq; pEntry; pEntry = pEntry->hh.next) {\\n occ[pos++] = pEntry->val;\\n }\\n qsort(occ, occSize, sizeof(int), compare);\\n int cnt = 0, ans = 0;\\n for (int i = 0; i < occSize; ++i) {\\n cnt += occ[i];\\n ans++;\\n if (cnt * 2 >= arrSize) {\\n break;\\n }\\n }\\n hashFree(&freq);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar minSetSize = function(arr) {\\n const freq = new Map();\\n for (const num of arr) {\\n freq.set(num, (freq.get(num) || 0) + 1);\\n }\\n const occ = [...freq.values()].sort((a, b) => b - a);\\n let cnt = 0, ans = 0;\\n for (const c of occ) {\\n cnt += c;\\n ans++;\\n if (cnt * 2 >= arr.length) {\\n break;\\n }\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction minSetSize(arr: number[]): number {\\n const freq: Map<number, number> = new Map();\\n for (const num of arr) {\\n freq.set(num, (freq.get(num) || 0) + 1);\\n }\\n\\n const occ = Array.from(freq.values()).sort((a, b) => b - a);\\n let cnt = 0, ans = 0;\\n for (const c of occ) {\\n cnt += c;\\n ans++;\\n if (cnt * 2 >= arr.length) {\\n break;\\n }\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::collections::HashMap;\\n\\nimpl Solution {\\n pub fn min_set_size(arr: Vec<i32>) -> i32 {\\n let mut freq = HashMap::new();\\n for &num in &arr {\\n *freq.entry(num).or_insert(0) += 1;\\n }\\n\\n let mut occ: Vec<i32> = freq.values().cloned().collect();\\n occ.sort_by(|a, b| b.cmp(a));\\n let mut cnt = 0;\\n let mut ans = 0;\\n for c in occ {\\n cnt += c;\\n ans += 1;\\n if cnt * 2 >= arr.len() as i32 {\\n break;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:贪心算法 思路与算法\\n\\n在每一步操作中,我们需要选择一个数 $x$,并且删除数组 $\\\\textit{arr}$ 中所有的 $x$。显然选择的数 $x$ 在数组 $\\\\textit{arr}$ 中出现的次数越多越好。因此我们可以统计数组 $\\\\textit{arr}$ 中每个数出现的次数,并进行降序排序。在得到了排序的结果之后,我们依次选择这些数进行删除,直到删除了至少一半的数。\\n\\n在统计数组 $\\\\textit{arr}$ 中每个数出现的次数时,我们可以借助哈希映射($\\\\text{HashMap}$),对于其中的每个键值对,键表示数 $x$,值表示数 $x…","guid":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/shu-zu-da-xiao-jian-ban-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-04T09:20:50.436Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"二叉树中的链表","url":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/er-cha-shu-zhong-de-lie-biao-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(NlogN)$,其中 $N$ 是数组 $\\\\textit{arr}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(N)$,其中 $N$ 是数组 $\\\\textit{arr}$ 的长度。
\\n方法一:枚举
\\n枚举二叉树中的每个节点为起点往下的路径是否有与链表相匹配的路径。为了判断是否匹配我们设计一个递归函数 $dfs(rt,\\\\textit{head})$ ,其中 $rt$ 表示当前匹配到的二叉树节点,$head$ 表示当前匹配到的链表节点,整个函数返回布尔值表示是否有一条该节点往下的路径与 $head$ 节点开始的链表匹配,如匹配返回 $\\\\textit{true}$,否则返回 $\\\\textit{false}$ ,一共有四种情况:
\\n\\n
\\n- \\n
\\n链表已经全部匹配完,匹配成功,返回 $\\\\textit{true}$
\\n- \\n
\\n二叉树访问到了空节点,匹配失败,返回 $\\\\textit{false}$
\\n- \\n
\\n当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败,返回 $\\\\textit{false}$
\\n- \\n
\\n前三种情况都不满足,说明匹配成功了一部分,我们需要继续递归匹配,所以先调用函数 $dfs(rt\\\\rightarrow left,head\\\\rightarrow next)$ ,其中 $rt\\\\rightarrow left$ 表示该节点的左儿子节点, $head\\\\rightarrow next$ 表示下一个链表节点,如果返回的结果是 $\\\\textit{false}$,说明没有找到相匹配的路径,需要继续在右子树中匹配,继续递归调用函数 $dfs(rt\\\\rightarrow right,head\\\\rightarrow next)$ 去找是否有相匹配的路径,其中 $rt\\\\rightarrow right$ 表示该节点的右儿子节点, $head\\\\rightarrow next$ 表示下一个链表节点。
\\n匹配函数确定了,剩下只要枚举即可,从根节点开始,如果当前节点匹配成功就直接返回 $\\\\textit{true}$ ,否则继续找它的左儿子和右儿子是否满足,也就是代码中的
\\ndfs(root,head) || isSubPath(head,root->left) || isSubPath(head,root->right)
,然后不断的递归调用。这样枚举所有节点去判断即能找出是否有一条与链表相匹配的路径。
\\n###C++
\\n\\nclass Solution {\\n bool dfs(TreeNode* rt, ListNode* head) {\\n // 链表已经全部匹配完,匹配成功\\n if (head == NULL) return true;\\n // 二叉树访问到了空节点,匹配失败\\n if (rt == NULL) return false;\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if (rt->val != head->val) return false;\\n return dfs(rt->left, head->next) || dfs(rt->right, head->next);\\n }\\npublic:\\n bool isSubPath(ListNode* head, TreeNode* root) {\\n if (root == NULL) return false;\\n return dfs(root, head) || isSubPath(head, root->left) || isSubPath(head, root->right);\\n }\\n};\\n
###Java
\\n\\nclass Solution {\\n public boolean isSubPath(ListNode head, TreeNode root) {\\n if (root == null) {\\n return false;\\n }\\n return dfs(root, head) || isSubPath(head, root.left) || isSubPath(head, root.right);\\n }\\n\\n public boolean dfs(TreeNode rt, ListNode head) {\\n // 链表已经全部匹配完,匹配成功\\n if (head == null) {\\n return true;\\n }\\n // 二叉树访问到了空节点,匹配失败\\n if (rt == null) {\\n return false;\\n }\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if (rt.val != head.val) {\\n return false;\\n }\\n return dfs(rt.left, head.next) || dfs(rt.right, head.next);\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public bool IsSubPath(ListNode head, TreeNode root) {\\n if (root == null) {\\n return false;\\n }\\n return Dfs(root, head) || IsSubPath(head, root.left) || IsSubPath(head, root.right);\\n }\\n\\n public bool Dfs(TreeNode rt, ListNode head) {\\n // 链表已经全部匹配完,匹配成功\\n if (head == null) {\\n return true;\\n }\\n // 二叉树访问到了空节点,匹配失败\\n if (rt == null) {\\n return false;\\n }\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if (rt.val != head.val) {\\n return false;\\n }\\n return Dfs(rt.left, head.next) || Dfs(rt.right, head.next);\\n }\\n}\\n
###Go
\\n\\nfunc isSubPath(head *ListNode, root *TreeNode) bool {\\n if root == nil {\\n return false\\n }\\n return dfs(root, head) || isSubPath(head, root.Left) || isSubPath(head, root.Right)\\n}\\n\\nfunc dfs(rt *TreeNode, head *ListNode) bool {\\n // 链表已经全部匹配完,匹配成功\\n if head == nil {\\n return true\\n }\\n // 二叉树访问到了空节点,匹配失败\\n if rt == nil {\\n return false\\n }\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if rt.Val != head.Val {\\n return false\\n }\\n return dfs(rt.Left, head.Next) || dfs(rt.Right, head.Next)\\n}\\n
###Python
\\n\\nclass Solution:\\n def dfs(self, head: ListNode, rt: TreeNode) -> bool:\\n if not head:\\n return True\\n if not rt:\\n return False\\n if rt.val != head.val:\\n return False\\n return self.dfs(head.next, rt.left) or self.dfs(head.next, rt.right)\\n\\n def isSubPath(self, head: ListNode, root: TreeNode) -> bool:\\n if not root:\\n return False\\n return self.dfs(head, root) or self.isSubPath(head, root.left) or self.isSubPath(head, root.right)\\n
###C
\\n\\nbool dfs(struct TreeNode* rt, struct ListNode* head) {\\n // 链表已经全部匹配完,匹配成功\\n if (head == NULL) {\\n return true;\\n }\\n // 二叉树访问到了空节点,匹配失败\\n if (rt == NULL) {\\n return false;\\n }\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if (rt->val != head->val) {\\n return false;\\n }\\n return dfs(rt->left, head->next) || dfs(rt->right, head->next);\\n}\\n\\nbool isSubPath(struct ListNode* head, struct TreeNode* root) {\\n if (root == NULL) {\\n return false;\\n }\\n return dfs(root, head) || isSubPath(head, root->left) || isSubPath(head, root->right);\\n}\\n
###Javascript
\\n\\nvar dfs = function(rt, head) {\\n if (head == null) return true;\\n if (rt == null) return false;\\n if (rt.val != head.val) return false;\\n return dfs(rt.left, head.next) || dfs(rt.right, head.next);\\n}\\nvar isSubPath = function(head, root) {\\n if (root == null) return 0;\\n return dfs(root, head) || isSubPath(head, root.left) || isSubPath(head, root.right);\\n};\\n
###TypeScript
\\n\\nfunction isSubPath(head: ListNode | null, root: TreeNode | null): boolean {\\n if (root === null) {\\n return false;\\n }\\n return dfs(root, head) || isSubPath(head, root.left) || isSubPath(head, root.right);\\n};\\n\\nfunction dfs(rt: TreeNode | null, head: ListNode | null): boolean {\\n // 链表已经全部匹配完,匹配成功\\n if (head === null) {\\n return true;\\n }\\n // 二叉树访问到了空节点,匹配失败\\n if (rt === null) {\\n return false;\\n }\\n // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n if (rt.val !== head.val) {\\n return false;\\n }\\n return dfs(rt.left, head.next) || dfs(rt.right, head.next);\\n}\\n
###Rust
\\n\\nuse std::rc::Rc;\\nuse std::cell::RefCell;\\n\\nimpl Solution {\\n pub fn is_sub_path(head: Option<Box<ListNode>>, root: Option<Rc<RefCell<TreeNode>>>) -> bool {\\n match (head.clone(), root.clone()) {\\n (None, _) => true,\\n (_, None) => false,\\n (Some(list_node), Some(tree_node)) => {\\n Self::dfs(&head, &root) || \\n Self::is_sub_path(head.clone(), tree_node.borrow().left.clone()) || \\n Self::is_sub_path(head.clone(), tree_node.borrow().right.clone())\\n }\\n }\\n }\\n\\n pub fn dfs(head: &Option<Box<ListNode>>, root: &Option<Rc<RefCell<TreeNode>>>) -> bool {\\n match (head, root) {\\n (None, _) => true, // 链表已经全部匹配完,匹配成功\\n (_, None) => false, // 二叉树访问到了空节点,匹配失败\\n (Some(list_node), Some(tree_node)) => {\\n list_node.val == tree_node.borrow().val && // 当前匹配的二叉树上节点的值与链表节点的值不相等,匹配失败\\n (Self::dfs(&list_node.next, &tree_node.borrow().left) || \\n Self::dfs(&list_node.next, &tree_node.borrow().right))\\n }\\n }\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举 枚举二叉树中的每个节点为起点往下的路径是否有与链表相匹配的路径。为了判断是否匹配我们设计一个递归函数 $dfs(rt,\\\\textit{head})$ ,其中 $rt$ 表示当前匹配到的二叉树节点,$head$ 表示当前匹配到的链表节点,整个函数返回布尔值表示是否有一条该节点往下的路径与 $head$ 节点开始的链表匹配,如匹配返回 $\\\\textit{true}$,否则返回 $\\\\textit{false}$ ,一共有四种情况:\\n\\n链表已经全部匹配完,匹配成功,返回 $\\\\textit{true}$\\n\\n二叉树访问到了空节点,匹配失败,返回 $…","guid":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/er-cha-shu-zhong-de-lie-biao-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-02T04:58:04.338Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Java排序就好","url":"https://leetcode.cn/problems/rank-teams-by-votes//solution/javapai-xu-jiu-hao-by-soap88","content":"- \\n
\\n时间复杂度:最坏情况下需要对所有节点进行匹配。假设一共有 $n$ 个节点,对于一个节点为根的子树,如果它是满二叉树,且每次匹配均为到链表的最后一个节点的时候匹配失败,那么一共被匹配到的节点数为 $2^{len+1}-1$ ,即这个节点为根的子树往下 $len$ 层的满二叉树的节点数,其中 $len$ 为链表的长度,而二叉树总节点数最多 $n$ 个,所以枚举节点最多匹配 $min(2^{len+1},n)$ 次,最坏情况下需要 $O(n* min(2^{len+1},n))$ 的时间复杂度。
\\n- \\n
\\n空间复杂度:由于递归函数在递归过程中需要为每一层递归函数分配栈空间,所以这里需要额外的空间且该空间取决于递归的深度。考虑枚举一个节点为起点递归判断所需的空间,假设该节点在第 $x$ 层,即递归枚举时已经用了 $O(x)$ 的空间,这个节点再往下匹配链表长度 $y$ 层节点时需要使用 $O(y)$ 的空间,所以一共需要 $O(x+y)$ 的空间,而 $x+y$ 必然不会超过树的高度,所以最后的空间复杂度为树的高度,即 $O(height)$ ,$height$ 为树的高度。
\\n\\n","description":"public String rankTeams(String[] votes) { //key是参赛团队,value是该团队每个排位获得的票数\\n Mappublic String rankTeams(String[] votes) {\\n //key是参赛团队,value是该团队每个排位获得的票数\\n Map<Character, int[]> teamRankMap = new HashMap<>();\\n\\n for (String vote : votes) {\\n for (int i = 0; i < vote.length(); i++) {\\n int[] rankArr = teamRankMap.getOrDefault(vote.charAt(i), new int[26]);\\n rankArr[i]++;\\n teamRankMap.put(vote.charAt(i), rankArr);\\n }\\n }\\n\\n List<Map.Entry<Character, int[]>> teamRankList = new ArrayList<>(teamRankMap.entrySet());\\n Collections.sort(teamRankList, (team1, team2) -> {\\n int[] ranks1 = team1.getValue();\\n int[] ranks2 = team2.getValue();\\n //根据投票排序\\n for (int i = 0; i < 26; i++) {\\n if (ranks1[i] != ranks2[i]) {\\n return ranks2[i] - ranks1[i];\\n }\\n }\\n //字母顺序排序\\n return team1.getKey() - team2.getKey();\\n });\\n\\n //转换为字符串输出\\n return teamRankList.stream().map(entry -> String.valueOf(entry.getKey())).collect(Collectors.joining());\\n}\\n
teamRankMap = new HashMap<>();\\n\\n for (String vote : votes) {\\n for (int i = 0; i < vote.length(); i++) {\\n int[] rankArr = teamRankMap.getOrDefault(vote.charAt…","guid":"https://leetcode.cn/problems/rank-teams-by-votes//solution/javapai-xu-jiu-hao-by-soap88","author":"soap88","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-01T04:12:29.992Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"c++ 利用greater排序","url":"https://leetcode.cn/problems/rank-teams-by-votes//solution/c-li-yong-greaterpai-xu-by-wu-bin-cong","content":" 因为只有A-Z 26个队伍,那排名也就只有26个
\\n
\\n完全可以弄一个 [27][27]的数组存放,每个队伍的排名数据当然,还有额外条件,如果全部的名次都相同,会按照队伍的字符排序,
\\n
\\n既然这样的话,我们就将每个队伍的最后一个空格,填上 26-(队伍名-\'A\')
\\n如果是A队的话 就是 26
\\nB队就是 25sort里面的默认排序是less,我们需要使用greater
\\n
\\n如果在排二维数组的时候,它的特性就是首先对比第二维数组中的第一个数的大小,如果相同就回去比第二个,第三个.....代码比较简洁
\\n\\n","description":"因为只有A-Z 26个队伍,那排名也就只有26个 完全可以弄一个 [27][27]的数组存放,每个队伍的排名数据\\n\\n当然,还有额外条件,如果全部的名次都相同,会按照队伍的字符排序,\\n 既然这样的话,我们就将每个队伍的最后一个空格,填上 26-(队伍名-\'A\')\\n 如果是A队的话 就是 26\\n B队就是 25\\n\\nsort里面的默认排序是less,我们需要使用greater\\n 如果在排二维数组的时候,它的特性就是首先对比第二维数组中的第一个数的大小,如果相同就回去比第二个,第三个.....\\n\\n代码比较简洁\\n\\nclass Solution {\\npublic:\\nstring…","guid":"https://leetcode.cn/problems/rank-teams-by-votes//solution/c-li-yong-greaterpai-xu-by-wu-bin-cong","author":"wu-bin-cong","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-01T04:11:42.698Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"新瓶装旧酒,这题就是subTree,一模一样,代码简洁","url":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/zhe-ti-jiu-shi-subtreeyi-mao-yi-yang-by-jerry_nju","content":"class Solution {\\npublic:\\nstring rankTeams(vector<string>& votes) {\\nvector<vector<int>> dw(27, vector<int>(27, 0));\\nstring res;\\nfor (auto p : votes) {\\nfor (int i = 0; i < p.length(); i++) {\\n//i是名次 p[i]是选择的队伍\\ndw[p[i] - \'A\'][i] ++;\\ndw[p[i] - \'A\'].back() = 26-(p[i] - \'A\'); \\n}\\n}\\nsort(dw.begin(), dw.end(), greater<vector<int>>());\\n\\nfor (int i = 0; i < dw.size(); i++) {\\nif (dw[i].back() != 0) \\nres.push_back(26-(dw[i].back()-\'A\'));\\n}\\nreturn res;\\n\\n}\\n};\\n
相关题目
\\n\\n代码
\\n周赛题,如果做过上面的就很容易啦,加点注释~~
\\n###java
\\n\\nclass Solution {\\n public boolean isSubPath(ListNode head, TreeNode root) {\\n if (head == null) {\\n return true;\\n }\\n if (root == null) {\\n return false;\\n }\\n //先判断当前的节点,如果不对,再看左子树和右子树呗\\n return isSub(head, root) || isSubPath(head, root.left) || isSubPath(head, root.right);\\n }\\n\\n private boolean isSub(ListNode head, TreeNode node) {\\n //特判:链表走完了,返回true\\n if (head == null) {\\n return true;\\n }\\n //特判:链表没走完,树走完了,这肯定不行,返回false\\n if (node == null) {\\n return false;\\n }\\n //如果值不同,必定不是啊\\n if (head.val != node.val) {\\n return false;\\n }\\n //如果值相同,继续看,左边和右边有一个满足即可\\n return isSub(head.next, node.left) || isSub(head.next, node.right);\\n }\\n}\\n
###cpp
\\n\\n","description":"相关题目 面试题 04.10. 检查子树\\n\\n\\n代码\\n\\n周赛题,如果做过上面的就很容易啦,加点注释~~\\n\\n###java\\n\\nclass Solution {\\n public boolean isSubPath(ListNode head, TreeNode root) {\\n if (head == null) {\\n return true;\\n }\\n if (root == null) {\\n return false;\\n }\\n //先判断当前的节点…","guid":"https://leetcode.cn/problems/linked-list-in-binary-tree//solution/zhe-ti-jiu-shi-subtreeyi-mao-yi-yang-by-jerry_nju","author":"jerry_nju","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-03-01T04:07:57.097Z","media":[{"url":"https://pic.leetcode-cn.com/727ceb126e3ec860c7fbd0497d52639388887cc357ba3156647dbf1340e1543f-image.png","type":"photo","width":2482,"height":1208,"blurhash":"L9SY~y%MIU~q.8jEogs.s.oeR*j["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"4状态,动态规划Python解,时间复杂度O(log(n))","url":"https://leetcode.cn/problems/knight-dialer//solution/4zhuang-tai-dong-tai-gui-hua-pythonjie-kong-jian-f","content":"class Solution {\\npublic:\\n bool isSubPath(ListNode* head, TreeNode* root) {\\n if(head == nullptr) return true;\\n if(root == nullptr) return false;\\n return dfs(head,root) || isSubPath(head,root->left) || isSubPath(head,root->right);\\n }\\n bool dfs(ListNode *head,TreeNode *node){\\n if(head == nullptr) return true;\\n if(node == nullptr) return false;\\n if(head->val != node->val) return false;\\n return dfs(head->next,node->left) || dfs(head-> next,node->right);\\n }\\n};\\n
解题思路
\\n在N>=2时,除数字5以外的9个数字都是可到达的。
\\n每跳一步,数字的变化如图所示:
\\n
\\n图片表示,当骑士处于“1”处时,下一跳将在“6”或“8”;骑士处于“4”处时,下一跳将在“3”或“0”或\\"9\\";骑士处于“0”处时,下一跳将在“4”或“6”…………
\\n我们可以发现,1、3、7、9处于对称位置;2,8处于对称位置;4,6处于对称位置。因此,我们可以将数字分为4个状态,命名为A、B、C、D。其中A:{1,3,7,9}, B:{2,8}, C:{4,6}, D:{0}。
\\n我们用f(X,n)表示:在状态X下,跳跃n步能够得到不同数字的个数。则状态转移方程为:
\\n\\nf(A,n)=f(B,n-1)+f(C,n-1)\\nf(B,n)=2*f(A,n-1)\\nf(C,n)=2*f(A,n-1)+f(D,n-1)\\nf(D,n)=2*f(C,n-1)\\n
解释为:
\\n
\\n处于状态A中的数字(1,3,7,9)通过一次跳跃要么变成状态B(2,8),要么变成状态C(4,6)
\\n处于状态B中的数字(2,8)通过一次跳跃有两种方式变成状态A(1,3,7,9)
\\n处于状态C中的数字(4,6)通过一次跳跃有两种方式变成状态A(1,3,7,9),还有一种方式变成状态D(0)
\\n处于状态D中的数字(0)通过一次跳跃有两种方式变成状态C(4,6)通过迭代,我们即可求解。
\\n代码
\\n\\nclass Solution:\\n def knightDialer(self, N: int) -> int:\\n if N==1: return 10\\n #分别为状态A,B,C,D\\n nums=[1,1,1,1]\\n for _ in range(N-1):\\n nums=[nums[1]+nums[2], 2*nums[0], 2*nums[0]+nums[3], 2*nums[2]]\\n #状态A有4个数字,B有2个数字,C有2个数字,D有1个数字\\n return (4*nums[0]+2*nums[1]+2*nums[2]+nums[3])%1000000007\\n
复杂度分析
\\n\\n
\\n- 时间复杂度:O(n)
\\n- 空间复杂度:O(1)
\\n矩阵快速幂
\\n如果我们把上面的状态转移过程使用矩阵来表示的话,那么我们可以得到这个式子:
\\n
\\n$$
\\n\\\\begin{bmatrix}
\\nA\\\\B\\\\C\\\\D
\\n\\\\end{bmatrix}=
\\n\\\\begin{bmatrix}
\\n0 & 1 & 1&0 \\\\
\\n2 & 0 & 0&0 \\\\
\\n2 & 0 & 0&1 \\\\
\\n0 & 0 & 2&0
\\n\\\\end{bmatrix}^{n-1}
\\n\\\\begin{bmatrix}
\\n1\\\\1\\\\1\\\\1
\\n\\\\end{bmatrix}
\\n$$其中,A、B、C、D代表在n-1次迭代结束后,处于状态A、B、C、D的可能个数。
\\n
\\n最终的答案即为(4*A+2*B+2*C+D)%1000000007
复杂度分析
\\n\\n
\\n","description":"在N>=2时,除数字5以外的9个数字都是可到达的。 每跳一步,数字的变化如图所示:\\n\\n\\n图片表示,当骑士处于“1”处时,下一跳将在“6”或“8”;骑士处于“4”处时,下一跳将在“3”或“0”或\\"9\\";骑士处于“0”处时,下一跳将在“4”或“6”…………\\n\\n我们可以发现,1、3、7、9处于对称位置;2,8处于对称位置;4,6处于对称位置。因此,我们可以将数字分为4个状态,命名为A、B、C、D。其中A:{1,3,7,9}, B:{2,8}, C:{4,6}, D:{0}。\\n\\n我们用f(X,n)表示:在状态X下,跳跃n步能够得到不同数字的个数。则状态转移方程为:…","guid":"https://leetcode.cn/problems/knight-dialer//solution/4zhuang-tai-dong-tai-gui-hua-pythonjie-kong-jian-f","author":"caticd","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-25T16:24:35.756Z","media":[{"url":"https://pic.leetcode-cn.com/7eaa506cae552733d5dc771c92d943a122e30eeef648460d6e3ec81fccfe76d8-QQ%E6%88%AA%E5%9B%BE20200226000022.png","type":"photo","width":410,"height":392,"blurhash":"L9Sijb_Noe_4~qj[oLt6oeaz-oof"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"太难了,只能画图凭直觉","url":"https://leetcode.cn/problems/smallest-range-ii//solution/tai-nan-liao-zhi-neng-hua-tu-ping-zhi-jue-by-user8","content":"- 时间复杂度:O(log(n)) (由于矩阵快速幂的时间复杂度为O(log(n)) )
\\n- 空间复杂度:O(1)
\\n解题思路
\\n至少对于我这种脑子来说,感觉贪心相关的题目都好难去严谨地证明或推论为什么要这么做,只能画画图,然后凭直觉去感觉,然后说“显然”要选xx的策略。。。
\\n首先,题目给出的原数组 A 里元素的排序显然不影响结果,因此为了便于分析我们把原数组先排一下序,然后用下面的图表示题目给出的这个升序数组:
\\n
\\n然后题目要求每个元素要么向上移动 K 的距离,要么向下移动 K 的距离,然后要求这个新数组的“最大最小值的距离尽可能地小”。此时我只能说,凭“直觉”,此时最优的策略是把这个数组拆成左右两半,把左边那一半上移,把右边那一半下移,也就是下图。黑色是原数组,红色是新数组:
\\n
\\n为了更生动形象一点,我们多画几个图体会一下,当 K 很小的时候,就是下面的情况,显然红色的新数组里,最大值是 D,最小值是 A。
\\n
\\n当 K 很大的时候,就是下面的情况,显然红色新数组里,最大值是 B,最小值是 C。
\\n
\\n当我们选择在 i 这一点“切一刀”的时候,也就是 A[0] ~ A[i] 的元素都上移,A[i + 1] ~ A[A.length - 1] 的元素都下移。
\\n
\\n此时 B 点的值是 A[i] + K,D 点的值是 A[A.length - 1] - K。
\\n新数组的最大值要么是 B 点要么是 D 点,也就是说新数组的最大值是 Max(A[i] + K, A[A.length - 1] - K)。同样道理,此时 A 点的值是 A[0] + K,C 点的值是 A[i + 1] - K。
\\n
\\n新数组的最小值要么是 A 点要么是 C 点,也就是说新数组的最小值是 Min(A[0] + K, A[i + 1] - K)。因此,题目需要的“新数组的最大值和最小值的差值”,就是 Max(A[i] + K, A[A.length - 1] - K) - Min(A[0] + K, A[i + 1] - K)。
\\nK 的值是题目给出的固定值,因此如果我们想让上面这个算式的结果尽可能小的话,就要靠改变 i 的值,也就是思考究竟要在哪里“切这一刀”。因此我们挨个遍历一下所有可能的 i 的值,然后取上面算式的最小值即可。
\\n代码
\\n###javascript
\\n\\n","description":"解题思路 至少对于我这种脑子来说,感觉贪心相关的题目都好难去严谨地证明或推论为什么要这么做,只能画画图,然后凭直觉去感觉,然后说“显然”要选xx的策略。。。\\n\\n首先,题目给出的原数组 A 里元素的排序显然不影响结果,因此为了便于分析我们把原数组先排一下序,然后用下面的图表示题目给出的这个升序数组:\\n\\n\\n然后题目要求每个元素要么向上移动 K 的距离,要么向下移动 K 的距离,然后要求这个新数组的“最大最小值的距离尽可能地小”。此时我只能说,凭“直觉”,此时最优的策略是把这个数组拆成左右两半,把左边那一半上移,把右边那一半下移,也就是下图。黑色是原数组…","guid":"https://leetcode.cn/problems/smallest-range-ii//solution/tai-nan-liao-zhi-neng-hua-tu-ping-zhi-jue-by-user8","author":"likai123","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T09:47:44.304Z","media":[{"url":"https://pic.leetcode-cn.com/9e4ccdf7359442c8b7707472541512e3f8a1e44ed0fb3d3f34ede92db99981ca-%E6%88%AA%E5%B1%8F2020-02-19%E4%B8%8B%E5%8D%885.29.57.png","type":"photo","width":624,"height":514,"blurhash":"UDS$r+?vxt?b_3-pt7RjxuxZIVNG~qNGIpxu"},{"url":"https://pic.leetcode-cn.com/a4a7dd4ba6ee617e1c701a32edd71d8e2d592748f931f007bb870be4f4d095ed-%E6%88%AA%E5%B1%8F2020-02-19%E4%B8%8B%E5%8D%884.47.08.png","type":"photo","width":908,"height":492,"blurhash":"UIS?46-;xa?b~qx^ofS#tlkCVtWW~qVsaesT"},{"url":"https://pic.leetcode-cn.com/0f17b59fdddc665851b53b58cd1ebb95b7bf997b80b8f2e3cbfe3e7addbac727-%E6%88%AA%E5%B1%8F2020-02-19%E4%B8%8B%E5%8D%884.48.31.png","type":"photo","width":614,"height":382,"blurhash":"UGS?AM?bxu?b~q%NofV@tSkCITRj~qV?WBx]"},{"url":"https://pic.leetcode-cn.com/b56e06b9728ab1e25352e82b6b6a6e8f3b606fac3570508dd47b1fe085ed9313-%E6%88%AA%E5%B1%8F2020-02-19%E4%B8%8B%E5%8D%884.50.19.png","type":"photo","width":586,"height":426,"blurhash":"UDS?AM_3xu_3~qxaj[XSx]WAWFkC~qX9oz$*"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"你能从盒子里获得的最大糖果数","url":"https://leetcode.cn/problems/maximum-candies-you-can-get-from-boxes//solution/ni-neng-cong-he-zi-li-huo-de-de-zui-da-tang-guo-2","content":"/**\\n * @param {number[]} A\\n * @param {number} K\\n * @return {number}\\n */\\nvar smallestRangeII = function(A, K) {\\nA.sort(function(a, b){\\nreturn a - b;\\n});\\nvar len = A.length;\\n// 注意这里有个特殊情况,就是我们压根“不切这一刀”,而是把整个数组全部上移或下移,这也是一种策略。这种策略下的差值是 A[len - 1] - A[0]\\nvar ans = A[len - 1] - A[0];\\nfor (var i = 0; i < len - 1; i++) {\\nvar max = Math.max(A[i] + K, A[len - 1] - K);\\nvar min = Math.min(A[0] + K, A[i + 1] - K);\\nvar diff = max - min;\\nans = Math.min(ans, diff);\\n}\\nreturn ans;\\n};\\n
方法一:广度优先搜索
\\n思路与算法
\\n我们可以使用广度优先搜索 + 队列的方法解决这个问题。
\\n对于第 $\\\\textit{i}$ 个盒子,我们只有拥有这个盒子(在初始时就拥有或从某个盒子中开出)并且能打开它(在初始时就是打开的状态或得到它的钥匙),才能获得其中的糖果。我们用数组 $\\\\text{hasbox}$ 表示每个盒子是否被拥有,数组 $\\\\text{canopen}$ 表示每个盒子是否能被打开。在搜索前,我们只拥有数组 $\\\\textit{initialBoxes}$ 中的那些盒子,并且能打开数组 $\\\\textit{status}$ 值为 $\\\\textit{0}$ 对应的那些盒子。如果一个盒子在搜索前满足这两条要求,就将其放入队列中。
\\n在进行广度优先搜索时,每一轮我们取出队首的盒子 $\\\\textit{k}$ 将其打开,得到其中的糖果、盒子 $\\\\textit{containedBoxes}[\\\\textit{k}]$ 以及钥匙 $\\\\textit{keys}[\\\\textit{k}]$。我们将糖果加入答案,并依次枚举每个盒子以及每把钥匙。在枚举盒子时,如果该盒子可以被打开,就将其加入队尾;同理,在枚举钥匙时,如果其对应的盒子已经被拥有,就将该盒子加入队尾。当队列为空时,搜索结束,我们就得到了得到糖果的最大数目。
\\n代码
\\n###C++
\\n\\nclass Solution {\\npublic:\\n int maxCandies(vector<int>& status, vector<int>& candies, vector<vector<int>>& keys, vector<vector<int>>& containedBoxes, vector<int>& initialBoxes) {\\n int n = status.size();\\n vector<bool> can_open(n), has_box(n), used(n);\\n for (int i = 0; i < n; ++i) {\\n can_open[i] = (status[i] == 1);\\n }\\n\\n queue<int> q;\\n int ans = 0;\\n for (int box: initialBoxes) {\\n has_box[box] = true;\\n if (can_open[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n \\n while (!q.empty()) {\\n int big_box = q.front();\\n q.pop();\\n for (int key: keys[big_box]) {\\n can_open[key] = true;\\n if (!used[key] && has_box[key]) {\\n q.push(key);\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n for (int box: containedBoxes[big_box]) {\\n has_box[box] = true;\\n if (!used[box] && can_open[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n \\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def maxCandies(self, status: List[int], candies: List[int], keys: List[List[int]], containedBoxes: List[List[int]], initialBoxes: List[int]) -> int:\\n n = len(status)\\n can_open = [status[i] == 1 for i in range(n)]\\n has_box, used = [False] * n, [False] * n\\n \\n q = collections.deque()\\n ans = 0\\n for box in initialBoxes:\\n has_box[box] = True\\n if can_open[box]:\\n q.append(box)\\n used[box] = True\\n ans += candies[box]\\n \\n while len(q) > 0:\\n big_box = q.popleft()\\n for key in keys[big_box]:\\n can_open[key] = True\\n if not used[key] and has_box[key]:\\n q.append(key)\\n used[key] = True\\n ans += candies[key]\\n for box in containedBoxes[big_box]:\\n has_box[box] = True\\n if not used[box] and can_open[box]:\\n q.append(box)\\n used[box] = True\\n ans += candies[box]\\n \\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int maxCandies(int[] status, int[] candies, int[][] keys, int[][] containedBoxes, int[] initialBoxes) {\\n int n = status.length;\\n boolean[] canOpen = new boolean[n];\\n boolean[] hasBox = new boolean[n];\\n boolean[] used = new boolean[n];\\n \\n for (int i = 0; i < n; ++i) {\\n canOpen[i] = (status[i] == 1);\\n }\\n \\n Queue<Integer> q = new LinkedList<>();\\n int ans = 0;\\n for (int box : initialBoxes) {\\n hasBox[box] = true;\\n if (canOpen[box]) {\\n q.offer(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n \\n while (!q.isEmpty()) {\\n int bigBox = q.poll();\\n for (int key : keys[bigBox]) {\\n canOpen[key] = true;\\n if (!used[key] && hasBox[key]) {\\n q.offer(key);\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n for (int box : containedBoxes[bigBox]) {\\n hasBox[box] = true;\\n if (!used[box] && canOpen[box]) {\\n q.offer(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n \\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int MaxCandies(int[] status, int[] candies, int[][] keys, int[][] containedBoxes, int[] initialBoxes) {\\n int n = status.Length;\\n bool[] canOpen = new bool[n];\\n bool[] hasBox = new bool[n];\\n bool[] used = new bool[n];\\n \\n for (int i = 0; i < n; ++i) {\\n canOpen[i] = (status[i] == 1);\\n }\\n Queue<int> q = new Queue<int>();\\n int ans = 0;\\n foreach (int box in initialBoxes) {\\n hasBox[box] = true;\\n if (canOpen[box]) {\\n q.Enqueue(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n \\n while (q.Count > 0) {\\n int bigBox = q.Dequeue();\\n foreach (int key in keys[bigBox]) {\\n canOpen[key] = true;\\n if (!used[key] && hasBox[key]) {\\n q.Enqueue(key);\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n foreach (int box in containedBoxes[bigBox]) {\\n hasBox[box] = true;\\n if (!used[box] && canOpen[box]) {\\n q.Enqueue(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n \\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc maxCandies(status []int, candies []int, keys [][]int, containedBoxes [][]int, initialBoxes []int) int {\\n n := len(status)\\ncanOpen := make([]bool, n)\\nhasBox := make([]bool, n)\\nused := make([]bool, n)\\n\\nfor i := 0; i < n; i++ {\\ncanOpen[i] = (status[i] == 1)\\n}\\nq := []int{}\\nans := 0\\nfor _, box := range initialBoxes {\\nhasBox[box] = true\\nif canOpen[box] {\\n q = append(q, box)\\nused[box] = true\\nans += candies[box]\\n}\\n}\\n\\nfor len(q) > 0 {\\nbigBox := q[0]\\n q = q[1:]\\nfor _, key := range keys[bigBox] {\\ncanOpen[key] = true\\nif !used[key] && hasBox[key] {\\n q = append(q, key)\\nused[key] = true\\nans += candies[key]\\n}\\n}\\nfor _, box := range containedBoxes[bigBox] {\\nhasBox[box] = true\\nif !used[box] && canOpen[box] {\\nq = append(q, box)\\nused[box] = true\\nans += candies[box]\\n}\\n}\\n}\\n\\nreturn ans\\n}\\n
###C
\\n\\nint maxCandies(int* status, int statusSize, int* candies, int candiesSize, int** keys, int keysSize, int* keysColSize, int** containedBoxes, int containedBoxesSize, int* containedBoxesColSize, int* initialBoxes, int initialBoxesSize) {\\n int n = statusSize;\\n bool* canOpen = (bool*)calloc(n, sizeof(bool));\\n bool* hasBox = (bool*)calloc(n, sizeof(bool));\\n bool* used = (bool*)calloc(n, sizeof(bool));\\n for (int i = 0; i < n; ++i) {\\n canOpen[i] = (status[i] == 1);\\n }\\n int* queue = (int*)malloc(n * sizeof(int));\\n int front = 0, rear = 0;\\n int ans = 0;\\n for (int i = 0; i < initialBoxesSize; ++i) {\\n int box = initialBoxes[i];\\n hasBox[box] = true;\\n if (canOpen[box]) {\\n queue[rear++] = box;\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n while (front < rear) {\\n int bigBox = queue[front++];\\n for (int i = 0; i < keysColSize[bigBox]; ++i) {\\n int key = keys[bigBox][i];\\n canOpen[key] = true;\\n if (!used[key] && hasBox[key]) {\\n queue[rear++] = key;\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n for (int i = 0; i < containedBoxesColSize[bigBox]; ++i) {\\n int box = containedBoxes[bigBox][i];\\n hasBox[box] = true;\\n if (!used[box] && canOpen[box]) {\\n queue[rear++] = box;\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n free(canOpen);\\n free(hasBox);\\n free(used);\\n free(queue);\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar maxCandies = function(status, candies, keys, containedBoxes, initialBoxes) {\\n const n = status.length;\\n const canOpen = new Array(n).fill(false);\\n const hasBox = new Array(n).fill(false);\\n const used = new Array(n).fill(false);\\n\\n for (let i = 0; i < n; ++i) {\\n canOpen[i] = (status[i] === 1);\\n }\\n const q = [];\\n let ans = 0;\\n for (const box of initialBoxes) {\\n hasBox[box] = true;\\n if (canOpen[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n while (q.length > 0) {\\n const bigBox = q.shift();\\n for (const key of keys[bigBox]) {\\n canOpen[key] = true;\\n if (!used[key] && hasBox[key]) {\\n q.push(key);\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n for (const box of containedBoxes[bigBox]) {\\n hasBox[box] = true;\\n if (!used[box] && canOpen[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction maxCandies(status: number[], candies: number[], keys: number[][], containedBoxes: number[][], initialBoxes: number[]): number {\\n const n = status.length;\\n const canOpen: boolean[] = new Array(n).fill(false);\\n const hasBox: boolean[] = new Array(n).fill(false);\\n const used: boolean[] = new Array(n).fill(false);\\n for (let i = 0; i < n; ++i) {\\n canOpen[i] = (status[i] === 1);\\n }\\n const q: number[] = [];\\n let ans = 0;\\n for (const box of initialBoxes) {\\n hasBox[box] = true;\\n if (canOpen[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n while (q.length > 0) {\\n const bigBox = q.shift()!;\\n for (const key of keys[bigBox]) {\\n canOpen[key] = true;\\n if (!used[key] && hasBox[key]) {\\n q.push(key);\\n used[key] = true;\\n ans += candies[key];\\n }\\n }\\n for (const box of containedBoxes[bigBox]) {\\n hasBox[box] = true;\\n if (!used[box] && canOpen[box]) {\\n q.push(box);\\n used[box] = true;\\n ans += candies[box];\\n }\\n }\\n }\\n\\n return ans;\\n};\\n
###Rust
\\n\\nuse std::collections::VecDeque;\\n\\nimpl Solution {\\n pub fn max_candies(status: Vec<i32>, candies: Vec<i32>, keys: Vec<Vec<i32>>, contained_boxes: Vec<Vec<i32>>, initial_boxes: Vec<i32>) -> i32 {\\n let n = status.len();\\n let mut can_open = vec![false; n];\\n let mut has_box = vec![false; n];\\n let mut used = vec![false; n];\\n\\n for i in 0..n {\\n can_open[i] = (status[i] == 1);\\n }\\n let mut q = VecDeque::new();\\n let mut ans = 0;\\n for box_id in initial_boxes {\\n has_box[box_id as usize] = true;\\n if can_open[box_id as usize] {\\n q.push_back(box_id);\\n used[box_id as usize] = true;\\n ans += candies[box_id as usize];\\n }\\n }\\n\\n while let Some(big_box) = q.pop_front() {\\n for &key in &keys[big_box as usize] {\\n can_open[key as usize] = true;\\n if !used[key as usize] && has_box[key as usize] {\\n q.push_back(key);\\n used[key as usize] = true;\\n ans += candies[key as usize];\\n }\\n }\\n for &box_id in &contained_boxes[big_box as usize] {\\n has_box[box_id as usize] = true;\\n if !used[box_id as usize] && can_open[box_id as usize] {\\n q.push_back(box_id);\\n used[box_id as usize] = true;\\n ans += candies[box_id as usize];\\n }\\n }\\n }\\n\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:广度优先搜索 思路与算法\\n\\n我们可以使用广度优先搜索 + 队列的方法解决这个问题。\\n\\n对于第 $\\\\textit{i}$ 个盒子,我们只有拥有这个盒子(在初始时就拥有或从某个盒子中开出)并且能打开它(在初始时就是打开的状态或得到它的钥匙),才能获得其中的糖果。我们用数组 $\\\\text{hasbox}$ 表示每个盒子是否被拥有,数组 $\\\\text{canopen}$ 表示每个盒子是否能被打开。在搜索前,我们只拥有数组 $\\\\textit{initialBoxes}$ 中的那些盒子,并且能打开数组 $\\\\textit{status}$ 值为 $\\\\textit…","guid":"https://leetcode.cn/problems/maximum-candies-you-can-get-from-boxes//solution/ni-neng-cong-he-zi-li-huo-de-de-zui-da-tang-guo-2","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T03:33:04.879Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"统计位数为偶数的数字","url":"https://leetcode.cn/problems/find-numbers-with-even-number-of-digits//solution/tong-ji-wei-shu-wei-ou-shu-de-shu-zi-by-leetcode-s","content":"- \\n
\\n时间复杂度:$O(N)$。题目保证了每一把钥匙在 $\\\\textit{keys}$ 中不会出现超过一次,并且每一个盒子在 $\\\\textit{containedBoxes}$ 中也不会出现超过一次,因此在广度优先搜索中最多会得到 $\\\\textit{N}$ 把钥匙和 $\\\\textit{N}$ 个盒子,总时间复杂度为 $O(N)$。
\\n- \\n
\\n空间复杂度:$O(N)$。
\\n方法一:枚举 + 字符串
\\n思路
\\n我们枚举数组 $\\\\textit{nums}$ 中的整数,并依次判断每个整数 $x$ 是否包含偶数个数字。
\\n
\\n一种简单的方法是使用语言内置的整数转字符串函数,将 $x$ 转换为字符串后,判断其长度是否为偶数即可。###C++
\\n\\nclass Solution {\\npublic:\\n int findNumbers(vector<int>& nums) {\\n int ans = 0;\\n for (int num: nums) {\\n if (to_string(num).size() % 2 == 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int findNumbers(vector<int>& nums) {\\n return accumulate(nums.begin(), nums.end(), 0, [](int ans, int num) {\\n return ans + (to_string(num).size() % 2 == 0);\\n });\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findNumbers(self, nums: List[int]) -> int:\\n return sum(1 for num in nums if len(str(num)) % 2 == 0)\\n
###Java
\\n\\nclass Solution {\\n public int findNumbers(int[] nums) {\\n int ans = 0;\\n for (int num : nums) {\\n if (String.valueOf(num).length() % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int FindNumbers(int[] nums) {\\n int ans = 0;\\n foreach (int num in nums) {\\n if (num.ToString().Length % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc findNumbers(nums []int) int {\\n ans := 0\\n for _, num := range nums {\\n if len(strconv.Itoa(num)) % 2 == 0 {\\n ans++\\n }\\n }\\n return ans\\n}\\n
###C
\\n\\nint findNumbers(int* nums, int numsSize) {\\n int ans = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n char str[16];\\n sprintf(str, \\"%d\\", nums[i]);\\n if (strlen(str) % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar findNumbers = function(nums) {\\n let ans = 0;\\n for (let num of nums) {\\n if (num.toString().length % 2 === 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction findNumbers(nums: number[]): number {\\n let ans = 0;\\n for (let num of nums) {\\n if (num.toString().length % 2 === 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn find_numbers(nums: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n for &num in &nums {\\n if num.to_string().len() % 2 == 0 {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组 $\\\\textit{nums}$ 的长度。这里假设将整数转换为字符串的时间复杂度为 $O(1)$。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:枚举 + 数学
\\n思路
\\n我们也可以使用语言内置的以 $10$ 为底的对数函数 $\\\\log_{10}()$ 来得到整数 $x$ 包含的数字个数。
\\n
\\n一个包含 $k$ 个数字的整数 $x$ 满足不等式 $10^{k−1} \\\\le x < 10^k$。将不等式取对数,得到 $k-1 \\\\le \\\\log_{10} (x) < k$,因此我们可以用 $k=\\\\lfloor \\\\log_{10}(x)+1 \\\\rfloor$ 得到 $x$ 包含的数字个数 $k$,其中 $\\\\lfloor a \\\\rfloor$ 表示将 $a$ 进行下取整,例如 $\\\\lfloor 5.2 \\\\rfloor =5$。###C++
\\n\\nclass Solution {\\npublic:\\n int findNumbers(vector<int>& nums) {\\n int ans = 0;\\n for (int num: nums) {\\n if ((int)(log10(num) + 1) % 2 == 0) {\\n ++ans;\\n }\\n }\\n return ans;\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int findNumbers(vector<int>& nums) {\\n return accumulate(nums.begin(), nums.end(), 0, [](int ans, int num) {\\n return ans + ((int)(log10(num) + 1) % 2 == 0);\\n });\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findNumbers(self, nums: List[int]) -> int:\\n return sum(1 for num in nums if int(math.log10(num) + 1) % 2 == 0)\\n
###Java
\\n\\nclass Solution {\\n public int findNumbers(int[] nums) {\\n int ans = 0;\\n for (int num : nums) {\\n if ((int) (Math.log10(num) + 1) % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int FindNumbers(int[] nums) {\\n int ans = 0;\\n foreach (int num in nums) {\\n if ((int) (Math.Log10(num) + 1) % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc findNumbers(nums []int) int {\\n ans := 0\\n for _, num := range nums {\\n if int(math.Log10(float64(num)) + 1) % 2 == 0 {\\n ans++\\n }\\n }\\n return ans\\n}\\n
###C
\\n\\nint findNumbers(int* nums, int numsSize) {\\n int ans = 0;\\n for (int i = 0; i < numsSize; ++i) {\\n if ((int)(log10(nums[i]) + 1) % 2 == 0) {\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar findNumbers = function(nums) {\\n let ans = 0;\\n for (let num of nums) {\\n if (Math.floor(Math.log10(num) + 1) % 2 === 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction findNumbers(nums: number[]): number {\\n let ans = 0;\\n for (let num of nums) {\\n if (Math.floor(Math.log10(num) + 1) % 2 === 0) {\\n ans++;\\n }\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn find_numbers(nums: Vec<i32>) -> i32 {\\n let mut ans = 0;\\n for &num in &nums {\\n let digits = (num as f64).log10().floor() as i32 + 1;\\n if digits % 2 == 0 {\\n ans += 1;\\n }\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:枚举 + 字符串 思路\\n\\n我们枚举数组 $\\\\textit{nums}$ 中的整数,并依次判断每个整数 $x$ 是否包含偶数个数字。\\n 一种简单的方法是使用语言内置的整数转字符串函数,将 $x$ 转换为字符串后,判断其长度是否为偶数即可。\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int findNumbers(vector- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组 $\\\\textit{nums}$ 的长度。
\\n- \\n
\\n空间复杂度:$O(1)$。
\\n& nums) {\\n int ans = 0;\\n for (int num: nums) {\\n if (to_string(num).size…","guid":"https://leetcode.cn/problems/find-numbers-with-even-number-of-digits//solution/tong-ji-wei-shu-wei-ou-shu-de-shu-zi-by-leetcode-s","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T03:29:34.590Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"将每个元素替换为右侧最大元素","url":"https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side//solution/jiang-mei-ge-yuan-su-ti-huan-wei-you-ce-zui-da-y-5","content":" 方法一:逆序遍历
\\n本题等价于对于数组
\\narr
中的每个元素arr[i]
,将其替换成arr[i + 1], arr[i + 2], ..., arr[n - 1]
中的最大值。因此我们可以逆序地遍历整个数组,同时维护从数组右端到当前位置所有元素的最大值。设
\\nans[i] = max(arr[i + 1], arr[i + 2], ..., arr[n - 1])
,那么在进行逆序遍历时,我们可以直接通过\\nans[i] = max(ans[i + 1], arr[i + 1])\\n
来递推地得到答案。
\\n###C++
\\n\\nclass Solution {\\npublic:\\n vector<int> replaceElements(vector<int>& arr) {\\n int n = arr.size();\\n vector<int> ans(n);\\n ans[n - 1] = -1;\\n for (int i = n - 2; i >= 0; --i) {\\n ans[i] = max(ans[i + 1], arr[i + 1]);\\n }\\n return ans;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def replaceElements(self, arr: List[int]) -> List[int]:\\n n = len(arr)\\n ans = [0] * (n - 1) + [-1]\\n for i in range(n - 2, -1, -1):\\n ans[i] = max(ans[i + 1], arr[i + 1])\\n return ans\\n
###Java
\\n\\nclass Solution {\\n public int[] replaceElements(int[] arr) {\\n int n = arr.length;\\n int[] ans = new int[n];\\n ans[n - 1] = -1;\\n for (int i = n - 2; i >= 0; --i) {\\n ans[i] = Math.max(ans[i + 1], arr[i + 1]);\\n }\\n return ans;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int[] ReplaceElements(int[] arr) {\\n int n = arr.Length;\\n int[] ans = new int[n];\\n ans[n - 1] = -1;\\n for (int i = n - 2; i >= 0; --i) {\\n ans[i] = Math.Max(ans[i + 1], arr[i + 1]);\\n }\\n return ans;\\n }\\n}\\n
###Go
\\n\\nfunc replaceElements(arr []int) []int {\\n n := len(arr)\\n ans := make([]int, n)\\n ans[n-1] = -1\\n for i := n - 2; i >= 0; i-- {\\n ans[i] = max(ans[i + 1], arr[i + 1])\\n }\\n return ans\\n}\\n
###C
\\n\\nint* replaceElements(int* arr, int arrSize, int* returnSize) {\\n int *ans = calloc(arrSize, sizeof(int));\\n ans[arrSize - 1] = -1;\\n *returnSize = arrSize;\\n for (int i = arrSize - 2; i >= 0; --i) {\\n ans[i] = (arr[i + 1] > ans[i + 1]) ? arr[i + 1] : ans[i + 1];\\n }\\n return ans;\\n}\\n
###JavaScript
\\n\\nvar replaceElements = function(arr) {\\n const n = arr.length;\\n const ans = new Array(n);\\n ans[n - 1] = -1;\\n for (let i = n - 2; i >= 0; i--) {\\n ans[i] = Math.max(ans[i + 1], arr[i + 1]);\\n }\\n return ans;\\n};\\n
###TypeScript
\\n\\nfunction replaceElements(arr: number[]): number[] {\\n const n = arr.length;\\n const ans: number[] = new Array(n);\\n ans[n - 1] = -1;\\n for (let i = n - 2; i >= 0; i--) {\\n ans[i] = Math.max(ans[i + 1], arr[i + 1]);\\n }\\n return ans;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn replace_elements(arr: Vec<i32>) -> Vec<i32> {\\n let mut ans = vec![0; arr.len()];\\n let n = arr.len();\\n ans[n - 1] = -1;\\n for i in (0..n - 1).rev() {\\n ans[i] = ans[i + 1].max(arr[i + 1]);\\n }\\n ans\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:逆序遍历 本题等价于对于数组 arr 中的每个元素 arr[i],将其替换成 arr[i + 1], arr[i + 2], ..., arr[n - 1] 中的最大值。因此我们可以逆序地遍历整个数组,同时维护从数组右端到当前位置所有元素的最大值。\\n\\n设 ans[i] = max(arr[i + 1], arr[i + 2], ..., arr[n - 1]),那么在进行逆序遍历时,我们可以直接通过\\n\\nans[i] = max(ans[i + 1], arr[i + 1])\\n\\n\\n来递推地得到答案。\\n\\n###C++\\n\\nclass Solution…","guid":"https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side//solution/jiang-mei-ge-yuan-su-ti-huan-wei-you-ce-zui-da-y-5","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T03:01:10.108Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分割回文串 III","url":"https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array//solution/you-xu-shu-zu-zhong-chu-xian-ci-shu-chao-guo-25d-3","content":"- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组
\\narr
的长度。- \\n
\\n空间复杂度:$O(1)$,除了存储答案的数组
\\nans
之外,额外的空间复杂度是 $O(1)$。方法一:遍历
\\n由于数组
\\narr
已经有序,那么相同的数在arr
中出现的位置也是连续的。因此我们可以对数组进行一次遍历,并统计每个数出现的次数。只要发现某个数出现的次数超过数组arr
长度的 25%,那么这个数即为答案。在计算数组
\\narr
长度的 25% 时,会涉及到浮点数。我们可以用整数运算count * 4 > arr.length
代替浮点数运算count > arr.length * 25%
,减少精度误差。###C++
\\n\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector<int>& arr) {\\n int n = arr.size();\\n int cur = arr[0], cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n if (arr[i] == cur) {\\n ++cnt;\\n if (cnt * 4 > n) {\\n return cur;\\n }\\n }\\n else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findSpecialInteger(self, arr: List[int]) -> int:\\n n = len(arr)\\n cur, cnt = arr[0], 0\\n for i in range(n):\\n if arr[i] == cur:\\n cnt += 1\\n if cnt * 4 > n:\\n return cur\\n else:\\n cur, cnt = arr[i], 1\\n return -1\\n
###Java
\\n\\nclass Solution {\\n public int findSpecialInteger(int[] arr) {\\n int n = arr.length;\\n int cur = arr[0], cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n if (arr[i] == cur) {\\n ++cnt;\\n if (cnt * 4 > n) {\\n return cur;\\n }\\n } else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int FindSpecialInteger(int[] arr) {\\n int n = arr.Length;\\n int cur = arr[0], cnt = 0;\\n for (int i = 0; i < n; ++i) {\\n if (arr[i] == cur) {\\n ++cnt;\\n if (cnt * 4 > n) {\\n return cur;\\n }\\n } else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n }\\n}\\n
###Go
\\n\\nfunc findSpecialInteger(arr []int) int {\\n n := len(arr)\\n cur := arr[0]\\n cnt := 0\\n for i := 0; i < n; i++ {\\n if arr[i] == cur {\\n cnt++\\n if cnt*4 > n {\\n return cur\\n }\\n } else {\\n cur = arr[i]\\n cnt = 1\\n }\\n }\\n return -1\\n}\\n
###C
\\n\\nint findSpecialInteger(int* arr, int arrSize) {\\n int cur = arr[0], cnt = 0;\\n for (int i = 0; i < arrSize; ++i) {\\n if (arr[i] == cur) {\\n ++cnt;\\n if (cnt * 4 > arrSize) {\\n return cur;\\n }\\n } else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n}\\n
###JavaScript
\\n\\nvar findSpecialInteger = function(arr) {\\n let n = arr.length;\\n let cur = arr[0], cnt = 0;\\n for (let i = 0; i < n; ++i) {\\n if (arr[i] === cur) {\\n ++cnt;\\n if (cnt * 4 > n) {\\n return cur;\\n }\\n } else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n};\\n
###TypeScript
\\n\\nfunction findSpecialInteger(arr: number[]): number {\\n let n: number = arr.length;\\n let cur: number = arr[0], cnt: number = 0;\\n for (let i: number = 0; i < n; ++i) {\\n if (arr[i] === cur) {\\n ++cnt;\\n if (cnt * 4 > n) {\\n return cur;\\n }\\n } else {\\n cur = arr[i];\\n cnt = 1;\\n }\\n }\\n return -1;\\n};\\n
###Rust
\\n\\nimpl Solution {\\n pub fn find_special_integer(arr: Vec<i32>) -> i32 {\\n let n = arr.len();\\n let mut cur = arr[0];\\n let mut cnt = 0;\\n for &item in arr.iter() {\\n if item == cur {\\n cnt += 1;\\n if cnt * 4 > n {\\n return cur;\\n }\\n } else {\\n cur = item;\\n cnt = 1;\\n }\\n }\\n -1\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N)$,其中 $N$ 是数组
\\narr
的长度。- \\n
\\n空间复杂度:$O(1)$。
\\n方法二:二分查找
\\n根据题目要求,满足条件的整数
\\nx
至少在数组arr
中出现了span = arr.length / 4 + 1
次,那么我们可以断定:数组arr
中的元素arr[0], arr[span], arr[span * 2], ...
一定包含x
。我们可以使用反证法证明上述的结论。假设
\\narr[0], arr[span], arr[span * 2], ...
均不为x
,由于数组arr
已经有序,那么x
只会连续地出现在arr[0], arr[span], arr[span * 2], ...
中某两个相邻元素的间隔中,因此其出现的次数最多为span - 1
次,这与它至少出现span
次相矛盾。有了上述的结论,我们就可以依次枚举
\\narr[0], arr[span], arr[span * 2], ...
中的元素,并将每个元素在数组arr
上进行二分查找,得到其在arr
中出现的位置区间。如果该区间的长度至少为span
,那么我们就得到了答案。###C++
\\n\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector<int>& arr) {\\n int n = arr.size();\\n int span = n / 4 + 1;\\n for (int i = 0; i < n; i += span) {\\n auto iter_l = lower_bound(arr.begin(), arr.end(), arr[i]);\\n auto iter_r = upper_bound(arr.begin(), arr.end(), arr[i]);\\n if (iter_r - iter_l >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n }\\n};\\n
###C++
\\n\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector<int>& arr) {\\n int n = arr.size();\\n int span = n / 4 + 1;\\n for (int i = 0; i < n; i += span) {\\n auto [iter_l, iter_r] = equal_range(arr.begin(), arr.end(), arr[i]);\\n if (iter_r - iter_l >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def findSpecialInteger(self, arr: List[int]) -> int:\\n n = len(arr)\\n span = n // 4 + 1\\n for i in range(0, n, span):\\n iter_l = bisect.bisect_left(arr, arr[i])\\n iter_r = bisect.bisect_right(arr, arr[i])\\n if iter_r - iter_l >= span:\\n return arr[i]\\n return -1\\n
###Java
\\n\\nclass Solution {\\n public int findSpecialInteger(int[] arr) {\\n int n = arr.length;\\n int span = n / 4 + 1;\\n for (int i = 0; i < n; i += span) {\\n int start = binarySearch(arr, arr[i]);\\n int end = binarySearch(arr, arr[i] + 1);\\n if (end - start >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n }\\n\\n private int binarySearch(int[] arr, int target) {\\n int lo = 0, hi = arr.length - 1;\\n int res = arr.length;\\n while (lo <= hi) {\\n int mid = (lo + hi) / 2;\\n if (arr[mid] >= target) {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n\\n return res;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int FindSpecialInteger(int[] arr) {\\n int n = arr.Length;\\n int span = n / 4 + 1;\\n for (int i = 0; i < n; i += span) {\\n int start = BinarySearch(arr, arr[i]);\\n int end = BinarySearch(arr, arr[i] + 1);\\n if (end - start >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n }\\n\\n private int BinarySearch(int[] arr, int target) {\\n int lo = 0, hi = arr.Length - 1;\\n int res = arr.Length;\\n while (lo <= hi) {\\n int mid = (lo + hi) / 2;\\n if (arr[mid] >= target) {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n return res;\\n }\\n}\\n
###Go
\\n\\nfunc findSpecialInteger(arr []int) int {\\n n := len(arr)\\nspan := n / 4 + 1\\nfor i := 0; i < n; i += span {\\nstart := binarySearch(arr, arr[i])\\nend := binarySearch(arr, arr[i] + 1)\\nif end - start >= span {\\nreturn arr[i]\\n}\\n}\\nreturn -1\\n}\\n\\nfunc binarySearch(arr []int, target int) int {\\nlo, hi := 0, len(arr) - 1\\nres := len(arr)\\nfor lo <= hi {\\nmid := (lo + hi) / 2\\nif arr[mid] >= target {\\nres = mid\\nhi = mid - 1\\n} else {\\nlo = mid + 1\\n}\\n}\\nreturn res\\n}\\n
###C
\\n\\nint binarySearch(int *arr, int length, int target) {\\n int lo = 0, hi = length - 1;\\n int res = length;\\n while (lo <= hi) {\\n int mid = (lo + hi) / 2;\\n if (arr[mid] >= target) {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n return res;\\n}\\n\\nint findSpecialInteger(int* arr, int arrSize) {\\n int span = arrSize / 4 + 1;\\n for (int i = 0; i < arrSize; i += span) {\\n int start = binarySearch(arr, arrSize, arr[i]);\\n int end = binarySearch(arr, arrSize, arr[i] + 1);\\n if (end - start >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n}\\n
###JavaScript
\\n\\nvar findSpecialInteger = function(arr) {\\n const n = arr.length;\\n const span = Math.floor(n / 4) + 1;\\n for (let i = 0; i < n; i += span) {\\n const start = binarySearch(arr, arr[i]);\\n const end = binarySearch(arr, arr[i] + 1);\\n if (end - start >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n};\\n\\nconst binarySearch = (arr, target) => {\\n let lo = 0, hi = arr.length - 1;\\n let res = arr.length;\\n while (lo <= hi) {\\n let mid = Math.floor((lo + hi) / 2);\\n if (arr[mid] >= target) {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n return res;\\n}\\n
###TypeScript
\\n\\nfunction findSpecialInteger(arr: number[]): number {\\n const n = arr.length;\\n const span = Math.floor(n / 4) + 1;\\n for (let i = 0; i < n; i += span) {\\n const start = binarySearch(arr, arr[i]);\\n const end = binarySearch(arr, arr[i] + 1);\\n if (end - start >= span) {\\n return arr[i];\\n }\\n }\\n return -1;\\n};\\n\\nfunction binarySearch(arr: number[], target: number): number {\\n let lo: number = 0, hi: number = arr.length - 1;\\n let res: number = arr.length;\\n while (lo <= hi) {\\n let mid: number = Math.floor((lo + hi) / 2);\\n if (arr[mid] >= target) {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n return res;\\n}\\n
###Rust
\\n\\nimpl Solution {\\n pub fn find_special_integer(arr: Vec<i32>) -> i32 {\\n let n = arr.len();\\n let span = (n as f64 / 4.0).floor() as usize + 1;\\n for i in (0..n).step_by(span) {\\n let start = Self::binary_search(&arr, arr[i]);\\n let end = Self::binary_search(&arr, arr[i] + 1);\\n if end - start >= span as i32 {\\n return arr[i];\\n }\\n }\\n -1\\n }\\n\\n fn binary_search(arr: &Vec<i32>, target: i32) -> i32 {\\n let mut lo = 0 as i32;\\n let mut hi = arr.len() as i32 - 1;\\n let mut res = arr.len() as i32;\\n while lo <= hi {\\n let mid = (lo + hi) / 2;\\n if arr[mid as usize] >= target {\\n res = mid;\\n hi = mid - 1;\\n } else {\\n lo = mid + 1;\\n }\\n }\\n res\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:遍历 由于数组 arr 已经有序,那么相同的数在 arr 中出现的位置也是连续的。因此我们可以对数组进行一次遍历,并统计每个数出现的次数。只要发现某个数出现的次数超过数组 arr 长度的 25%,那么这个数即为答案。\\n\\n在计算数组 arr 长度的 25% 时,会涉及到浮点数。我们可以用整数运算 count * 4 > arr.length 代替浮点数运算 count > arr.length * 25%,减少精度误差。\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int findSpecialInteger(vector…","guid":"https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array//solution/you-xu-shu-zu-zhong-chu-xian-ci-shu-chao-guo-25d-3","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T02:42:38.207Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分割回文串 III","url":"https://leetcode.cn/problems/palindrome-partitioning-iii//solution/fen-ge-hui-wen-chuan-iii-by-leetcode-solution","content":"- \\n
\\n时间复杂度:$O(\\\\log N)$,其中 $N$ 是数组
\\narr
的长度。我们枚举的元素个数为至多为 $4$ 个,可以视作 $O(1)$。对于每个元素,我们需要在数组arr
上进行二分查找,时间复杂度为 $O(\\\\log N)$。- \\n
\\n空间复杂度:$O(1)$。
\\n方法一:动态规划
\\n我们用
\\nf[i][j]
表示对于字符串S
的前i
个字符,将它分割成j
个非空且不相交的回文串,最少需要修改的字符数。在进行状态转移时,我们可以枚举第j
个回文串的起始位置i0
,那么就有如下的状态转移方程:\\nf[i][j] = min(f[i0][j - 1] + cost(S, i0 + 1, i))\\n
其中
\\ncost(S, l, r)
表示将S
的第l
个到第r
个字符组成的子串变成回文串,最少需要修改的字符数。cost(S, l, r)
可以通过双指针的方法求出:\\n
\\n- \\n
\\n初始时将第一个指针置于位置
\\nl
,第二个指针置于位置r
;- \\n
\\n每次比较时,判断两个指针指向的字符是否相等。若相等,则这两个位置构成回文,不需要进行修改;若不相等,则为了保证回文,需要修改其中的任意一个字符;
\\n- \\n
\\n在每次比较后,将第一个指针向右移动一步,第二个指针向左移动一步,如果第一个指针仍然在第二个指针的左侧,那么继续进行下一次比较。
\\n上述的状态转移方程中有一些边界情况需要考虑,例如只有当
\\ni >= j
时,f[i][j]
的值才有意义,这是因为i
个字符最多只能被分割成i
个非空且不相交的字符串,因此在状态转移时,必须要满足i >= j
且i0 >= j - 1
。此外,当j = 1
时,我们并不需要枚举i0
,这是因为将前i
个字符分割成j = 1
个非空字符串的方法是唯一的。###C++
\\n\\nclass Solution {\\npublic:\\n int cost(string& s, int l, int r) {\\n int ret = 0;\\n for (int i = l, j = r; i < j; ++i, --j) {\\n if (s[i] != s[j]) {\\n ++ret;\\n }\\n }\\n return ret;\\n }\\n\\n int palindromePartition(string& s, int k) {\\n int n = s.size();\\n vector<vector<int>> f(n + 1, vector<int>(k + 1, INT_MAX));\\n f[0][0] = 0;\\n for (int i = 1; i <= n; ++i) {\\n for (int j = 1; j <= min(k, i); ++j) {\\n if (j == 1) {\\n f[i][j] = cost(s, 0, i - 1);\\n }\\n else {\\n for (int i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost(s, i0, i - 1));\\n }\\n }\\n }\\n }\\n \\n return f[n][k];\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def palindromePartition(self, s: str, k: int) -> int:\\n def cost(l, r):\\n ret, i, j = 0, l, r\\n while i < j:\\n ret += (0 if s[i] == s[j] else 1)\\n i += 1\\n j -= 1\\n return ret\\n \\n n = len(s)\\n f = [[10**9] * (k + 1) for _ in range(n + 1)]\\n f[0][0] = 0\\n for i in range(1, n + 1):\\n for j in range(1, min(k, i) + 1):\\n if j == 1:\\n f[i][j] = cost(0, i - 1)\\n else:\\n for i0 in range(j - 1, i):\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost(i0, i - 1))\\n \\n return f[n][k]\\n
###Java
\\n\\nclass Solution {\\n public int palindromePartition(String s, int k) {\\n int n = s.length(); \\n int[][] f = new int[n + 1][k + 1];\\n for (int[] row : f) {\\n Arrays.fill(row, 1000000000);\\n }\\n f[0][0] = 0;\\n\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= Math.min(k, i); j++) {\\n if (j == 1) {\\n f[i][j] = cost(0, i - 1, s);\\n } else {\\n for (int i0 = j - 1; i0 < i; i0++) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost(i0, i - 1, s));\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n }\\n\\n private int cost(int l, int r, String s) {\\n int ret = 0;\\n int i = l, j = r;\\n while (i < j) {\\n ret += (s.charAt(i) == s.charAt(j) ? 0 : 1);\\n i++;\\n j--;\\n }\\n return ret;\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int PalindromePartition(string s, int k) {\\n int n = s.Length;\\n int[,] f = new int[n + 1, k + 1];\\n for (int i = 0; i <= n; i++) {\\n for (int j = 0; j <= k; j++) {\\n f[i, j] = int.MaxValue;\\n }\\n }\\n f[0, 0] = 0;\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= Math.Min(k, i); j++) {\\n if (j == 1) {\\n f[i, j] = Cost(0, i - 1, s);\\n } else {\\n for (int i0 = j - 1; i0 < i; i0++) {\\n f[i, j] = Math.Min(f[i, j], f[i0, j - 1] + Cost(i0, i - 1, s));\\n }\\n }\\n }\\n }\\n\\n return f[n, k];\\n }\\n\\n private int Cost(int l, int r, string s) {\\n int ret = 0;\\n int i = l, j = r;\\n while (i < j) {\\n ret += (s[i] == s[j] ? 0 : 1);\\n i++;\\n j--;\\n }\\n return ret;\\n }\\n}\\n
###Go
\\n\\nfunc palindromePartition(s string, k int) int {\\n n := len(s)\\n cost := func(l, r int) int {\\n ret := 0\\n i, j := l, r\\n for i < j {\\n if s[i] != s[j] {\\n ret++\\n }\\n i++\\n j--\\n }\\n return ret\\n }\\n f := make([][]int, n+1)\\n for i := range f {\\n f[i] = make([]int, k+1)\\n for j := range f[i] {\\n f[i][j] = math.MaxInt\\n }\\n }\\n f[0][0] = 0\\n for i := 1; i <= n; i++ {\\n for j := 1; j <= min(k, i); j++ {\\n if j == 1 {\\n f[i][j] = cost(0, i-1)\\n } else {\\n for i0 := j - 1; i0 < i; i0++ {\\n f[i][j] = min(f[i][j], f[i0][j-1]+cost(i0, i-1))\\n }\\n }\\n }\\n }\\n\\n return f[n][k]\\n}\\n
###C
\\n\\nint cost(int l, int r, const char *s) {\\n int ret = 0;\\n int i = l, j = r;\\n while (i < j) {\\n ret += (s[i] == s[j] ? 0 : 1);\\n i++;\\n j--;\\n }\\n return ret;\\n}\\n\\nint palindromePartition(char* s, int k) {\\n int n = strlen(s);\\n int f[n + 1][k + 1];\\n for (int i = 0; i <= n; i++) {\\n for (int j = 0; j <= k; j++) {\\n f[i][j] = INT_MAX;\\n }\\n }\\n f[0][0] = 0;\\n for (int i = 1; i <= n; i++) {\\n for (int j = 1; j <= (i < k ? i : k); j++) {\\n if (j == 1) {\\n f[i][j] = cost(0, i - 1, s);\\n } else {\\n for (int i0 = j - 1; i0 < i; i0++) {\\n f[i][j] = fmin(f[i][j], f[i0][j - 1] + cost(i0, i - 1, s));\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n}\\n
###JavaScript
\\n\\nvar palindromePartition = function(s, k) {\\n const n = s.length;\\n const cost = (l, r) => {\\n let ret = 0;\\n let i = l, j = r;\\n while (i < j) {\\n ret += (s[i] === s[j] ? 0 : 1);\\n i++;\\n j--;\\n }\\n return ret;\\n }\\n const f = Array.from({ length: n + 1 }, () => Array(k + 1).fill(1000000000));\\n f[0][0] = 0;\\n for (let i = 1; i <= n; i++) {\\n for (let j = 1; j <= Math.min(k, i); j++) {\\n if (j === 1) {\\n f[i][j] = cost(0, i - 1);\\n } else {\\n for (let i0 = j - 1; i0 < i; i0++) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost(i0, i - 1));\\n }\\n }\\n }\\n }\\n return f[n][k];\\n};\\n
###TypeScript
\\n\\nfunction palindromePartition(s: string, k: number): number {\\n const n = s.length;\\n const cost = (l: number, r: number): number => {\\n let ret = 0;\\n let i = l, j = r;\\n while (i < j) {\\n ret += (s[i] === s[j] ? 0 : 1);\\n i++;\\n j--;\\n }\\n return ret;\\n }\\n const f: number[][] = Array.from({ length: n + 1 }, () => Array(k + 1).fill(1000000000));\\n f[0][0] = 0;\\n for (let i = 1; i <= n; i++) {\\n for (let j = 1; j <= Math.min(k, i); j++) {\\n if (j === 1) {\\n f[i][j] = cost(0, i - 1);\\n } else {\\n for (let i0 = j - 1; i0 < i; i0++) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost(i0, i - 1));\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n};\\n
###Rust
\\n\\nuse std::cmp::min;\\n\\nimpl Solution {\\n pub fn palindrome_partition(s: String, k: i32) -> i32 {\\n let n = s.len();\\n let cost = |l: usize, r: usize| -> i32 {\\n let mut ret = 0;\\n let (mut i, mut j) = (l, r);\\n while i < j {\\n if s.as_bytes()[i] != s.as_bytes()[j] {\\n ret += 1;\\n }\\n i += 1;\\n j -= 1;\\n }\\n ret\\n };\\n\\n let mut f = vec![vec![i32::MAX; k as usize + 1]; n + 1];\\n f[0][0] = 0;\\n for i in 1..=n {\\n for j in 1..=std::cmp::min(k as usize, i) {\\n if j == 1 {\\n f[i][j] = cost(0, i - 1);\\n } else {\\n for i0 in (j - 1)..i {\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost(i0, i - 1));\\n }\\n }\\n }\\n }\\n f[n][k as usize]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n- \\n
\\n时间复杂度:$O(N^3K)$,其中 $N$ 是字符串
\\nS
的长度。在动态规划中,我们需要枚举i
,j
以及i0
,它们需要的时间分别为 $O(N)$,$O(K)$ 和 $O(N)$。我们还需要计算cost()
函数来进行状态转移,单次的时间复杂度为 $O(N)$,因此总的时间复杂度为 $O(N^3K)$。在Python
中,该方法可以卡着时间通过。- \\n
\\n空间复杂度:$O(NK)$。
\\n方法二:动态规划 + 预处理
\\n方法一中的时间复杂度瓶颈在于
\\ncost()
函数。在调用cost()
函数之前,我们枚举了i
,j
以及i0
,因此它一共被调用了 $O(N^2K)$ 次。然而观察cost()
函数本身的形式cost(S, l, r)
,不同的(l, r)
的数量只有 $O(N^2)$ 种,这说明在动态规划中,我们对cost()
函数进行了大量的重复调用。因此我们可以预处理出所有的cost(S, l, r)
,在后续调用cost()
函数时,我们只需要 $O(1)$ 的时间便可以返回结果。我们同样可以使用动态规划求出所有的
\\ncost(S, l, r)
。记cost[l][r] = cost(S, l, r)
,根据方法一中计算cost()
函数的双指针方法,我们可以得到如下的状态转移方程:\\ncost[l][r] = cost[l + 1][r - 1], if S[l] == S[r]\\ncost[l][r] = cost[l + 1][r - 1] + 1, if S[l] != S[r]\\ncost[l][r] = 0, if l >= r\\n
这是一个经典的区间动态规划,时间复杂度为 $O(N^2)$。在预处理出所有的
\\ncost(S, l, r)
后,下一步使用动态规划计算f[i][j]
的时间复杂度就从 $O(N^3K)$ 降低至 $O(N^2K)$。###C++
\\n\\nclass Solution {\\npublic:\\n int palindromePartition(string& s, int k) {\\n int n = s.size();\\n vector<vector<int>> cost(n, vector<int>(n));\\n for (int span = 2; span <= n; ++span) {\\n for (int i = 0; i <= n - span; ++i) {\\n int j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + (s[i] == s[j] ? 0 : 1);\\n }\\n }\\n\\n vector<vector<int>> f(n + 1, vector<int>(k + 1, INT_MAX));\\n f[0][0] = 0;\\n for (int i = 1; i <= n; ++i) {\\n for (int j = 1; j <= min(k, i); ++j) {\\n if (j == 1) {\\n f[i][j] = cost[0][i - 1];\\n }\\n else {\\n for (int i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n \\n return f[n][k];\\n }\\n};\\n
###Python
\\n\\nclass Solution:\\n def palindromePartition(self, s: str, k: int) -> int:\\n n = len(s)\\n cost = [[0] * n for _ in range(n)]\\n for span in range(2, n + 1):\\n for i in range(n - span + 1):\\n j = i + span - 1\\n cost[i][j] = cost[i + 1][j - 1] + (0 if s[i] == s[j] else 1)\\n\\n f = [[10**9] * (k + 1) for _ in range(n + 1)]\\n f[0][0] = 0\\n for i in range(1, n + 1):\\n for j in range(1, min(k, i) + 1):\\n if j == 1:\\n f[i][j] = cost[0][i - 1]\\n else:\\n for i0 in range(j - 1, i):\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1])\\n \\n return f[n][k]\\n
###Java
\\n\\nclass Solution {\\n public int palindromePartition(String s, int k) {\\n int n = s.length();\\n int[][] cost = new int[n][n];\\n for (int span = 2; span <= n; ++span) {\\n for (int i = 0; i <= n - span; ++i) {\\n int j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + (s.charAt(i) == s.charAt(j) ? 0 : 1);\\n }\\n }\\n int[][] f = new int[n + 1][k + 1];\\n for (int[] row : f) {\\n Arrays.fill(row, Integer.MAX_VALUE);\\n }\\n f[0][0] = 0;\\n for (int i = 1; i <= n; ++i) {\\n for (int j = 1; j <= Math.min(k, i); ++j) {\\n if (j == 1) {\\n f[i][j] = cost[0][i - 1];\\n } else {\\n for (int i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n }\\n}\\n
###C#
\\n\\npublic class Solution {\\n public int PalindromePartition(string s, int k) {\\n int n = s.Length;\\n int[,] cost = new int[n, n];\\n for (int span = 2; span <= n; ++span) {\\n for (int i = 0; i <= n - span; ++i) {\\n int j = i + span - 1;\\n cost[i, j] = cost[i + 1, j - 1] + (s[i] == s[j] ? 0 : 1);\\n }\\n }\\n int[,] f = new int[n + 1, k + 1];\\n for (int i = 0; i <= n; ++i) {\\n for (int j = 0; j <= k; ++j) {\\n f[i, j] = int.MaxValue;\\n }\\n }\\n f[0, 0] = 0;\\n for (int i = 1; i <= n; ++i) {\\n for (int j = 1; j <= Math.Min(k, i); ++j) {\\n if (j == 1) {\\n f[i, j] = cost[0, i - 1];\\n } else {\\n for (int i0 = j - 1; i0 < i; ++i0) {\\n f[i, j] = Math.Min(f[i, j], f[i0, j - 1] + cost[i0, i - 1]);\\n }\\n }\\n }\\n }\\n return f[n, k];\\n }\\n}\\n
###Go
\\n\\nfunc palindromePartition(s string, k int) int {\\n n := len(s)\\n cost := make([][]int, n)\\n for i := range cost {\\n cost[i] = make([]int, n)\\n }\\n for span := 2; span <= n; span++ {\\n for i := 0; i <= n - span; i++ {\\n j := i + span - 1\\n cost[i][j] = cost[i + 1][j - 1]\\n if s[i] != s[j] {\\n cost[i][j]++\\n }\\n }\\n }\\n\\n f := make([][]int, n + 1)\\n for i := range f {\\n f[i] = make([]int, k + 1)\\n for j := range f[i] {\\n f[i][j] = math.MaxInt\\n }\\n }\\n f[0][0] = 0\\n for i := 1; i <= n; i++ {\\n for j := 1; j <= min(k, i); j++ {\\n if j == 1 {\\n f[i][j] = cost[0][i - 1]\\n } else {\\n for i0 := j - 1; i0 < i; i0++ {\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1])\\n }\\n }\\n }\\n }\\n\\n return f[n][k]\\n}\\n
###C
\\n\\nint palindromePartition(char* s, int k) {\\n int n = strlen(s);\\n int cost[n][n];\\n memset(cost, 0, sizeof(cost));\\n for (int span = 2; span <= n; ++span) {\\n for (int i = 0; i <= n - span; ++i) {\\n int j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + (s[i] == s[j] ? 0 : 1);\\n }\\n }\\n int f[n + 1][k + 1];\\n for (int i = 0; i <= n; ++i) {\\n for (int j = 0; j <= k; ++j) {\\n f[i][j] = n;\\n }\\n }\\n f[0][0] = 0;\\n for (int i = 1; i <= n; ++i) {\\n for (int j = 1; j <= (i < k ? i : k); ++j) {\\n if (j == 1) {\\n f[i][j] = cost[0][i - 1];\\n } else {\\n for (int i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = fmin(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n return f[n][k];\\n}\\n
###JavaScript
\\n\\nvar palindromePartition = function(s, k) {\\n const n = s.length;\\n const cost = Array.from({ length: n }, () => Array(n).fill(0));\\n for (let span = 2; span <= n; ++span) {\\n for (let i = 0; i <= n - span; ++i) {\\n const j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + (s[i] === s[j] ? 0 : 1);\\n }\\n }\\n const f = Array.from({ length: n + 1 }, () => Array(k + 1).fill(Infinity));\\n f[0][0] = 0;\\n for (let i = 1; i <= n; ++i) {\\n for (let j = 1; j <= Math.min(k, i); ++j) {\\n if (j === 1) {\\n f[i][j] = cost[0][i - 1];\\n } else {\\n for (let i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n};\\n
###TypeScript
\\n\\nfunction palindromePartition(s: string, k: number): number {\\n const n = s.length;\\n const cost: number[][] = Array.from({ length: n }, () => Array(n).fill(0));\\n for (let span = 2; span <= n; ++span) {\\n for (let i = 0; i <= n - span; ++i) {\\n const j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + (s[i] === s[j] ? 0 : 1);\\n }\\n }\\n const f: number[][] = Array.from({ length: n + 1 }, () => Array(k + 1).fill(Infinity));\\n f[0][0] = 0;\\n for (let i = 1; i <= n; ++i) {\\n for (let j = 1; j <= Math.min(k, i); ++j) {\\n if (j === 1) {\\n f[i][j] = cost[0][i - 1];\\n } else {\\n for (let i0 = j - 1; i0 < i; ++i0) {\\n f[i][j] = Math.min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n\\n return f[n][k];\\n};\\n
###Rust
\\n\\nuse std::cmp::min;\\n\\nimpl Solution {\\n pub fn palindrome_partition(s: String, k: i32) -> i32 {\\n let n = s.len();\\n let mut cost = vec![vec![0; n]; n];\\n for span in 2..=n {\\n for i in 0..=n - span {\\n let j = i + span - 1;\\n cost[i][j] = cost[i + 1][j - 1] + if s.as_bytes()[i] == s.as_bytes()[j] { 0 } else { 1 };\\n }\\n }\\n let mut f = vec![vec![i32::MAX; k as usize + 1]; n + 1];\\n f[0][0] = 0;\\n for i in 1..=n {\\n for j in 1..=std::cmp::min(k as usize, i) {\\n if j == 1 {\\n f[i][j] = cost[0][i - 1];\\n } else {\\n for i0 in (j - 1)..i {\\n f[i][j] = min(f[i][j], f[i0][j - 1] + cost[i0][i - 1]);\\n }\\n }\\n }\\n }\\n f[n][k as usize]\\n }\\n}\\n
复杂度分析
\\n\\n
\\n","description":"方法一:动态规划 我们用 f[i][j] 表示对于字符串 S 的前 i 个字符,将它分割成 j 个非空且不相交的回文串,最少需要修改的字符数。在进行状态转移时,我们可以枚举第 j 个回文串的起始位置 i0,那么就有如下的状态转移方程:\\n\\nf[i][j] = min(f[i0][j - 1] + cost(S, i0 + 1, i))\\n\\n\\n其中 cost(S, l, r) 表示将 S 的第 l 个到第 r 个字符组成的子串变成回文串,最少需要修改的字符数。cost(S, l, r) 可以通过双指针的方法求出:\\n\\n初始时将第一个指针置于位置 l,第二个指针置于…","guid":"https://leetcode.cn/problems/palindrome-partitioning-iii//solution/fen-ge-hui-wen-chuan-iii-by-leetcode-solution","author":"LeetCode-Solution","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-19T02:26:40.219Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"cpp 一次遍历 O(n)","url":"https://leetcode.cn/problems/maximum-distance-in-arrays//solution/cpp-yi-ci-bian-li-on-by-klaxi","content":"- \\n
\\n时间复杂度:$O(N^2K)$,其中 $N$ 是字符串
\\nS
的长度。预处理cost()
函数需要的时间为 $O(N^2)$。在动态规划中,我们需要枚举i
,j
以及i0
,它们需要的时间分别为 $O(N)$,$O(K)$ 和 $O(N)$,整体复杂度为 $O(N^2K)$。由于 $O(N^2) < O(N^2K)$,因此算法的总时间复杂度为 $O(N^2K)$。- \\n
\\n空间复杂度:$O(N^2 + NK)$。存储
\\ncost()
函数需要的空间为 $O(N^2)$,存储动态规划的结果f
需要的空间为 $O(NK)$。容易出错的地方:
\\n可能最大最小的都在一个vector里面
\\n思路:
\\n1.保存当前最大和最小
\\n
\\n2.遍历,当前vector的最大和最小和之前保存的比较,更新距离
\\n3.更新最大和最小,继续遍历代码:
\\n\\n","description":"可能最大最小的都在一个vector里面 1.保存当前最大和最小\\n 2.遍历,当前vector的最大和最小和之前保存的比较,更新距离\\n 3.更新最大和最小,继续遍历\\n\\nclass Solution {\\npublic:\\n int maxDistance(vectorclass Solution {\\npublic:\\n int maxDistance(vector<vector<int>>& arrays) {\\n int dist = 0;\\n int max = arrays[0].back();\\n int min = arrays[0].front();\\n for (int i = 1; i < arrays.size(); i++) {\\n dist = std::max(dist, std::max(arrays[i].back() - min, max - arrays[i].front()));\\n max = std::max(arrays[i].back(), max);\\n min = std::min(arrays[i].front(), min);\\n }\\n return dist;\\n }\\n};\\n
>& arrays) {\\n int dist = 0;\\n int max = arrays[0].back();\\n int min = arrays[0].front();\\n for (int i = 1; i…","guid":"https://leetcode.cn/problems/maximum-distance-in-arrays//solution/cpp-yi-ci-bian-li-on-by-klaxi","author":"klaxxi","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-09T11:27:18.757Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"unordered_map存储出现次数+sort排序+判定","url":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/unordered_mapcun-chu-chu-xian-ci-shu-sortpai-xu-pa","content":" 解题思路
\\nunordered_map存储出现次数+sort排序+判定
\\n代码
\\n###cpp
\\n\\n","description":"解题思路 unordered_map存储出现次数+sort排序+判定\\n\\n代码\\n\\n###cpp\\n\\nclass Solution {\\npublic:\\n int minSetSize(vectorclass Solution {\\npublic:\\n int minSetSize(vector<int>& arr) {\\n unordered_map<int,int> mp;\\n for(int i=0;i<arr.size();i++)\\n mp[arr[i]]++;\\n vector<int> t;\\n for(unordered_map<int,int>::iterator it=mp.begin();it!=mp.end();it++)\\n t.push_back(it->second);\\n sort(t.begin(),t.end());\\n int res=0,len=arr.size();\\n for(int i=t.size()-1;i>=0;i--){\\n len-=t[i];\\n res++;\\n if(len<=arr.size()/2) \\n return res;\\n }\\n return res;\\n }\\n};\\n
& arr) {\\n unordered_map mp;\\n for(int i=0;i t;\\n for(unordered_map ::iterator it…","guid":"https://leetcode.cn/problems/reduce-array-size-to-the-half//solution/unordered_mapcun-chu-chu-xian-ci-shu-sortpai-xu-pa","author":"wandore","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-02-02T09:05:12.072Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【HOT 100】47.全排列 II Python3 回溯 考虑重复 --\x3e 46.全排列","url":"https://leetcode.cn/problems/permutations-ii//solution/hot-100-47quan-pai-lie-ii-python3-hui-su-kao-lu-zh","content":" 解题思路
\\n今天开始(2020/1/10),写题解先写大纲,然后再看一遍标记重点,看是不是写得能让人看着不烦能看下去,以及自己要整理笔记记录
\\n
\\n【还是下意识写了 2019,却惊觉已经到了 2020 年,那么上一年的自己到底学到了什么,输出了什么呢,2020 继续重新做人】\\n
\\n- 相似题型
\\n- 考虑重复
\\n- 回溯问题三个考虑方面
\\n- 附:两种不同剪枝条件的区别
\\n- 代码
\\n
\\n相似题型
\\n\\n看到 全排列,或者 枚举全部解,等类似的 搜索枚举类型题,基本就是 回溯 没跑了。
\\n
\\n因为回溯就是类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径
。回溯主要的考虑的 三要素 已经在 46.全排列 写过了,但是这道题有了一点改变,那么就是列表里面有重复数字,全排列的结果,相同答案只算一种,那么我们就要优先考虑重复,再思考 三要素,定下我们思路和代码的基调。
\\n
\\n【flag:上一篇写得有点乱,之后再去整理,顺便汇总回溯题,emmm 好像上一篇也有一个 flag(逃。。】考虑重复
\\n重复即为:存在相同数字,比如
\\n[1,2,2\']
,那么答案[1,2,2\']
和[1,2\',2]
就其实是一样的,在保存结果的时候,我们只选择其一,但是这不是字符串,在保存结果的时候再去判断是否答案里已经保存了这一种情况会比较麻烦,那么我们能不能在生成答案的过程中就将其 剪枝(类比用过的数字就不考虑),这样根本就不会生成重复的答案了。我们希望的是,如果发现数字重复了,当前的就不考虑了,比如
\\n[1,2,2\']
存在之后,当遇到[1]
遇到 2\',发现和 2 重复了,我就直接剪枝,不考虑之后的所有的情况,因为:
\\n
\\n两个相同数字,我可以:\\n
\\n- 两个都选
\\n- 两个都不选
\\n- 但是如果只选一个,那么选哪一个都可以,因为和选择另一个是相同情况,所以只有这种情况我们需要剪枝
\\n
\\n又到了小 trick 时间:
\\n考虑重复元素一定要优先排序,将重复的都放在一起,便于找到重复元素和剪枝!!!
\\n
\\n推广至 --\x3e 如果涉及考虑重复元素,或者大小比较的情况,对列表排序是一个不错的选择好了,我们知道要排序,重复元素要剪枝,那么该如何剪枝呢?
\\n
\\n首先我们得使用第一个元素,因为这时候是第一次使用,还没有重复,并且所有情况都回溯搜索答案,除了用过的元素不再使用,其余不做剪枝,直到我们遇到第一个重复元素,我们才要考虑剪枝,但是考虑剪枝的时候还要考虑跟它重复的元素有没有被用过:\\n
如果前一个重复元素没有使用过,那么在当前重复元素下一层的可选项中一定会存在,也就是绿色部分
\\n
\\n那么一定会重复,即出现2 X = X 2\'
的情况(X 为不选)
\\n也就是 2 和 2\' 以及 2\' 和 2 一定会重复,则整体剪枝,且是提前剪枝,在红色选择处就剪枝那么这部分剪枝的条件即为:和前一个元素值相同(此处隐含这个元素的 index>0),并且前一个元素还没有被使用过
\\n回溯问题三要素
\\n把和 46.全排列 的区别--重复,考虑了之后,这道题就是和我们之前写过的思考过程一样了,依然是回到老三样:
\\n\\n
\\n- 有效结果
\\n
\\n仍然是当前结果的长度==列表的长度,即为找到了一个有效结果\\nif len(sol) == len(nums):\\n self.res.append(sol)\\n
\\n
\\n- 回溯范围及答案更新\\n
\\n\\n
\\n- 仍然是全部遍历,因为每一层都要考虑全部元素
\\n- 答案更新仍然是在修改 check 之后回溯内部累加更新 sol
\\n\\nfor i in range(len(nums)):\\n check[i] = 1\\n self.backtrack(sol+[nums[i]], nums, check)\\n check[i] = 0\\n
\\n
\\n- 剪枝条件
\\n
\\n在之前的 剪枝条件 1:用过的元素不能再使用之外,又添加了一个新的剪枝条件,也就是我们考虑重复部分思考的结果,于是 剪枝条件 2:当当前元素和前一个元素值相同(此处隐含这个元素的index>0
),并且前一个元素还没有被使用过的时候,我们要剪枝\\nif check[i] == 1:\\n continue\\nif i > 0 and nums[i] == nums[i-1] and check[i-1] == 0:\\n continue\\n
代码
\\n考虑完三要素就可以写代码啦!
\\n###Python
\\n\\nclass Solution:\\n def permuteUnique(self, nums: List[int]) -> List[List[int]]:\\n nums.sort()\\n self.res = []\\n check = [0 for i in range(len(nums))]\\n \\n self.backtrack([], nums, check)\\n return self.res\\n \\n def backtrack(self, sol, nums, check):\\n if len(sol) == len(nums):\\n self.res.append(sol)\\n return\\n \\n for i in range(len(nums)):\\n if check[i] == 1:\\n continue\\n if i > 0 and nums[i] == nums[i-1] and check[i-1] == 0:\\n continue\\n check[i] = 1\\n self.backtrack(sol+[nums[i]], nums, check)\\n check[i] = 0\\n
\\n
附:两种不同剪枝条件的区别
\\n在考虑重复及剪枝条件的时候,我们考虑的是:当当前元素和前一个元素值相同(此处隐含这个元素的 index>0),并且前一个元素还没有被使用过的时候,我们要剪枝
\\n
\\n但实际,如果条件设为:并且前一个元素被使用过,我们要剪枝,其实也是可以的:\\nif i > 0 and nums[i] == nums[i-1] and check[i-1] == 1:\\n continue\\n
但是这两种有什么区别,又该如何选择呢?
\\n
\\n主要的区别还是在于在那个阶段剪枝,以及重复元素中选择哪一个。
\\n结论:选择 check[i-1] == 0 效率要高于 check[i-1] == 1
看了很多解释,但是还是一定要自己画图理解!回溯的图真的是天助,画图能很好理解递归:
\\n1. if i > 0 and nums[i] == nums[i-1] and check[i-1] == 0
\\n灰色为剪枝部分,蓝色为答案部分:
\\n
\\n2. if i > 0 and nums[i] == nums[i-1] and check[i-1] == 1
\\n灰色为剪枝部分,蓝色为答案部分:
\\n
\\n能够明显发现第一种能够提前剪枝,减少计算步骤和搜索次数,并且第一种选择了重复元素的第一个,而第二种选择了重复元素的最后一个,虽然答案都相同,我们成年人当然是要选效率更高一些的那一种对吧~
\\n总结
\\n哈哈,其实只是为了一个 ending~
\\n","description":"解题思路 今天开始(2020/1/10),写题解先写大纲,然后再看一遍标记重点,看是不是写得能让人看着不烦能看下去,以及自己要整理笔记记录\\n 【还是下意识写了 2019,却惊觉已经到了 2020 年,那么上一年的自己到底学到了什么,输出了什么呢,2020 继续重新做人】\\n\\n相似题型\\n考虑重复\\n回溯问题三个考虑方面\\n附:两种不同剪枝条件的区别\\n代码\\n \\n\\n相似题型\\n\\n46.全排列\\n\\n看到 全排列,或者 枚举全部解,等类似的 搜索枚举类型题,基本就是 回溯 没跑了。\\n 因为回溯就是类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就…","guid":"https://leetcode.cn/problems/permutations-ii//solution/hot-100-47quan-pai-lie-ii-python3-hui-su-kao-lu-zh","author":"sammy-4","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-01-10T10:03:19.466Z","media":[{"url":"https://pic.leetcode-cn.com/b99f92354583b0dbf289b180da5527ea5d71b26ca81f83229d7d96ab96c8b6ff-image.png","type":"photo","width":374,"height":216,"blurhash":"LCS$if_3.8~q_NRQs:o1%MofM{WB"},{"url":"https://pic.leetcode-cn.com/411c88e56c5538a787b4bd4c86d433f00a461d93c0af8ca3a74d7bed41055c34-image.png","type":"photo","width":766,"height":255,"blurhash":"LGSY{r-;xu^+~qtQRjj[xaoLWWRk"},{"url":"https://pic.leetcode-cn.com/95879d993505786c00bc03ecb1fc928fcf1fd25d0726aef0a0b23ea37fa7c4ef-image.png","type":"photo","width":458,"height":97,"blurhash":"LAR3Wd~X$mx@?]aLxJNFOls;VuR%"},{"url":"https://pic.leetcode-cn.com/ddb3425584f42015765c51bd5d85589842b74be2040a5ca8177283d060cf5dc9-image.png","type":"photo","width":901,"height":431,"blurhash":"LHSF-G?b-.xc~VW=NIxZS~s:xZbv"},{"url":"https://pic.leetcode-cn.com/b9129a3a086ad120ccb897cce3cdb085cc71b2e4e9a1441bc73644a5c5c65480-image.png","type":"photo","width":955,"height":520,"blurhash":"LCSY]k_2xWxd~WjuWCni%1RjRktQ"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"利用有序数组特性求解","url":"https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array//solution/li-yong-you-xu-shu-zu-te-xing-qiu-jie-by-user8300r","content":"
\\n那就要为了自己选择的路努力前进吧!解题思路
\\n\\n
\\n- 求出 25% 对应的出现次数threshold
\\n- 遍历数组
\\n- 由于是有序数组,只需比较 当前位置 i 值和 i + threshold的值是否相等即可
\\n
\\n代码
\\n###java
\\n\\n","description":"解题思路 求出 25% 对应的出现次数threshold\\n遍历数组\\n由于是有序数组,只需比较 当前位置 i 值和 i + threshold的值是否相等即可\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int findSpecialInteger(int[] arr) {\\n int threshold = arr.length / 4;\\n for (int i = 0; i < arr.length; i++) {\\n if (arr[i + threshold] =…","guid":"https://leetcode.cn/problems/element-appearing-more-than-25-in-sorted-array//solution/li-yong-you-xu-shu-zu-te-xing-qiu-jie-by-user8300r","author":"user8300R","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-01-10T09:04:14.982Z","media":[{"url":"https://pic.leetcode-cn.com/04500e357eb58050f541298a63926f969cec36f65d73c4d4fe0c4d85912cc660-image.png","type":"photo","width":680,"height":137,"blurhash":"LJRpB[-;sq-;~qofWBof-qfkkAbF"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「三路快排」应用(Java)","url":"https://leetcode.cn/problems/sort-colors//solution/kuai-su-pai-xu-partition-guo-cheng-she-ji-xun-huan","content":"class Solution {\\n public int findSpecialInteger(int[] arr) {\\n int threshold = arr.length / 4;\\n for (int i = 0; i < arr.length; i++) {\\n if (arr[i + threshold] == arr[i]) {\\n return arr[i];\\n }\\n }\\n return 0;\\n }\\n}\\n
本题其实是经典的荷兰国旗问题,最初由荷兰计算机科学家艾兹赫尔·迪克斯特拉(Edsger W. Dijkstra)提出,并以荷兰国旗的颜色(红、白、蓝)为类比。
\\n如果你学习过「快速排序」,知道「三路快排」,这道题就非常简单,本题考查的知识点就是「三路快排」。我以前录过视频讲解了「快速排序」:地址,在第 6 节讲到了「三路快排」。
\\n思路分析
\\n容易想到的做法:
\\n\\n
\\n- 排序。可以使用编程语言提供的排序函数,一般认为是「快速排序」或者「归并排序」,排序以后即为所求。但不符合题目的「进阶」要求「一趟扫描」和「常数空间」;
\\n- 使用「计数排序」。分别统计 0、1、2 的个数,再赋值回原数组。但不符合题目的「进阶」要求「一趟扫描」。
\\n「快速排序」的
\\npartition
,其中有一种partition
叫做「三路快排」,即通过一次partition
,把区间里的数根据基准元素pivot
分成 3 个部分:\\n
\\n- 第 1 个部分:小于
\\npivot
;- 第 2 个部分:等于
\\npivot
;- 第 3 个部分:大于
\\npivot
。本题的解法和「三路快排」几乎是一样,在「一趟扫描」的过程中,使用变量
\\ni
扫描,分别使用两个变量放在数组的头和尾,我们就分别命名为zero
和two
。其中\\n
\\n- \\n
zero
是 0 和 1 的分界;- \\n
two
是 2 和还未遍历到的数的分界。变量的定义如下图所示(「参考代码 1」采用的定义方式):
\\n\\n
变量
\\ni
看到 1 的时候直接 ++ ,看到 0 的时候交换到数组的前面,看到 2 的时候交换到数组的末尾。具体细节,可以先写出变量的定义,在编码就容易多了。这里给出两版代码,其实是一样的,它们的区别仅仅在于
\\nzero
指向 0 还是指向 1 ,two
指向 2 还是指向未看到的数。我们把zero
和two
的定义写成了区间的形式,写在注释中。由于区间定义不同,初始化,循环过程中,退出循环的条件就有细微差别。
\\n参考代码 1:
\\n###java
\\n\\npublic class Solution {\\n\\n public void sortColors(int[] nums) {\\n int n = nums.length;\\n if (n < 2) {\\n return;\\n }\\n\\n // all in [0..zero) = 0\\n // all in [zero..i) = 1\\n // all in [two..n - 1] = 2\\n // 初始化的时候,要满足上面 3 个区间全是空区间\\n int zero = 0;\\n int two = n;\\n int i = 0;\\n // i = two 的时候,[zero..i)、[two..len - 1] 已经接起来了,所以 while 里面是 i < two\\n while (i < two) {\\n if (nums[i] == 0) {\\n // zero 指向 1,所以先交换\\n swap(nums, i, zero);\\n zero++;\\n i++;\\n } else if (nums[i] == 1) {\\n // 直接划分到 1 所在的区间\\n i++;\\n } else {\\n // [two..n - 1] = 2,two 指向 2,所以先 -- ,再交换\\n two--;\\n swap(nums, i, two);\\n }\\n }\\n }\\n\\n private void swap(int[] nums, int index1, int index2) {\\n int temp = nums[index1];\\n nums[index1] = nums[index2];\\n nums[index2] = temp;\\n }\\n\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(n)$,这里 $n$ 是输入数组的长度;
\\n- 空间复杂度:$O(1)$。
\\n参考代码 2:
\\n###java
\\n\\npublic class Solution {\\n\\n public void sortColors(int[] nums) {\\n int n = nums.length;\\n if (n < 2) {\\n return;\\n }\\n\\n // all in [0, zero] = 0\\n // all in (zero, i) = 1\\n // all in (two, n - 1] = 2\\n int zero = -1;\\n int two = n - 1;\\n int i = 0;\\n while (i <= two) {\\n if (nums[i] == 0) {\\n zero++;\\n swap(nums, i, zero);\\n i++;\\n } else if (nums[i] == 1) {\\n i++;\\n } else {\\n swap(nums, i, two);\\n two--;\\n }\\n }\\n }\\n\\n private void swap(int[] nums, int index1, int index2) {\\n int temp = nums[index1];\\n nums[index1] = nums[index2];\\n nums[index2] = temp;\\n }\\n\\n}\\n
复杂度分析:(同「参考代码 1」)。
\\n","description":"本题其实是经典的荷兰国旗问题,最初由荷兰计算机科学家艾兹赫尔·迪克斯特拉(Edsger W. Dijkstra)提出,并以荷兰国旗的颜色(红、白、蓝)为类比。 如果你学习过「快速排序」,知道「三路快排」,这道题就非常简单,本题考查的知识点就是「三路快排」。我以前录过视频讲解了「快速排序」:地址,在第 6 节讲到了「三路快排」。\\n\\n容易想到的做法:\\n\\n排序。可以使用编程语言提供的排序函数,一般认为是「快速排序」或者「归并排序」,排序以后即为所求。但不符合题目的「进阶」要求「一趟扫描」和「常数空间」;\\n使用「计数排序」。分别统计 0、1、2 的个数…","guid":"https://leetcode.cn/problems/sort-colors//solution/kuai-su-pai-xu-partition-guo-cheng-she-ji-xun-huan","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2020-01-09T17:03:29.310Z","media":[{"url":"https://pic.leetcode.cn/1746110092-QAXFlO-image.png","type":"photo","width":1642,"height":340,"blurhash":"LCSPX_~qt7_3Rjt7ofof?bRjWBay"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"从右往左一次遍历","url":"https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side//solution/cong-you-wang-zuo-yi-ci-bian-li-by-lirunjun","content":"\\n
从右往左遍历,先记录右边最大值 rightMax 为最后一个值,向左每次更新 rightMax,使用变量 t 先记住当前 arr[i] 就可以了。
\\n\\n","description":"从右往左遍历,先记录右边最大值 rightMax 为最后一个值,向左每次更新 rightMax,使用变量 t 先记住当前 arr[i] 就可以了。 class Solution {\\n public int[] replaceElements(int[] arr) {\\n int rightMax = arr[arr.length - 1];\\n arr[arr.length - 1] = -1;\\n for (int i = arr.length - 2; i >= 0; i…","guid":"https://leetcode.cn/problems/replace-elements-with-greatest-element-on-right-side//solution/cong-you-wang-zuo-yi-ci-bian-li-by-lirunjun","author":"lirunjun","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-30T03:22:10.494Z","media":[{"url":"https://pic.leetcode-cn.com/1b378873c78840b5a07cf5b2ef3170a1d0f0aa3eefc0509bb7a8e17de9dd3b83-%E7%BB%93%E6%9E%9C.png","type":"photo","width":439,"height":233,"blurhash":"L9RfnJ_3s;_3_3bGNGR%00t7bYoy"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"「清晰&简单」BFS + 模拟","url":"https://leetcode.cn/problems/maximum-candies-you-can-get-from-boxes//solution/qing-xi-jian-dan-bfs-mo-ni-by-hlxing","content":"class Solution {\\n public int[] replaceElements(int[] arr) {\\n int rightMax = arr[arr.length - 1];\\n arr[arr.length - 1] = -1;\\n for (int i = arr.length - 2; i >= 0; i--) {\\n int t = arr[i];\\n arr[i] = rightMax;\\n if (t > rightMax)\\n rightMax = t;\\n }\\n return arr;\\n }\\n}\\n
思路
\\n通过广度优先搜索方式来模拟拆盒子的过程。使用队列装入可以被拆开的盒子(状态打开 or 拥有对应钥匙),每次从队列取出一个盒子,并遍历这个盒子里面的内含盒子以及钥匙,遍历的情况包括:
\\n\\n
\\n- 内含盒子没有被拆开过,并且有对应钥匙或状态打开,则把它加入队列
\\n- 钥匙所对应的的盒子存在(我们拥有这个盒子),并且盒子没有被拆开,则把它加入队列
\\nTalk is cheap。Show you the code。
\\n代码
\\n###java
\\n\\nclass Solution {\\n public int maxCandies(int[] status, int[] candies, int[][] keys, int[][] containedBoxes, int[] initialBoxes) {\\n int len = status.length;\\n // 记录盒子是否被拆开\\n boolean[] visited = new boolean[len];\\n // 记录我们拥有的所有盒子\\n Set<Integer> have_box = new HashSet<>();\\n // 记录我们拥有的所有钥匙\\n Set<Integer> have_key = new HashSet<>();\\n Queue<Integer> q = new LinkedList<>();\\n // 初始化\\n for (int i = 0; i < initialBoxes.length; i++) {\\n int idx = initialBoxes[i];\\n have_box.add(idx);\\n // 如果盒子可以打开则加入队列\\n if (status[idx] == 1) {\\n q.offer(idx);\\n visited[idx] = true;\\n }\\n }\\n int ans = 0;\\n while (!q.isEmpty()) {\\n Integer cur = q.poll();\\n ans += candies[cur];\\n int[] cur_keys = keys[cur];\\n int[] cur_boxs = containedBoxes[cur];\\n // 遍历钥匙\\n for (int key : cur_keys) {\\n // 加入我们拥有的钥匙列表\\n have_key.add(key);\\n // 盒子没被拆开过 且 我们有对应的钥匙\\n if (!visited[key] && have_box.contains(key)) {\\n q.offer(key);\\n visited[key] = true;\\n }\\n }\\n // 遍历内含盒子\\n for (int box : cur_boxs) {\\n // 加入我们拥有的盒子列表\\n have_box.add(box);\\n // 盒子没被拆开过 且 (我们有对应的钥匙 或者 盒子是打开状态)\\n if (!visited[box] && (have_key.contains(box) || status[box] == 1)) {\\n q.offer(box);\\n visited[box] = true;\\n }\\n }\\n }\\n return ans;\\n }\\n}\\n
\\n
\\n如果该题解对你有帮助,点个赞再走呗~
\\n","description":"思路 通过广度优先搜索方式来模拟拆盒子的过程。使用队列装入可以被拆开的盒子(状态打开 or 拥有对应钥匙),每次从队列取出一个盒子,并遍历这个盒子里面的内含盒子以及钥匙,遍历的情况包括:\\n\\n内含盒子没有被拆开过,并且有对应钥匙或状态打开,则把它加入队列\\n钥匙所对应的的盒子存在(我们拥有这个盒子),并且盒子没有被拆开,则把它加入队列\\n\\nTalk is cheap。Show you the code。\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int maxCandies(int[] status, int…","guid":"https://leetcode.cn/problems/maximum-candies-you-can-get-from-boxes//solution/qing-xi-jian-dan-bfs-mo-ni-by-hlxing","author":"hlxing","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-22T07:53:16.875Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"将int转为String,调用.length,然后%2==0即为偶数","url":"https://leetcode.cn/problems/find-numbers-with-even-number-of-digits//solution/jiang-intzhuan-wei-stringdiao-yong-lengthran-hou-2","content":"解题思路
\\n将int转为String,调用.length,然后%2==0即为偶数
\\n代码
\\n###java
\\n\\n","description":"解题思路 将int转为String,调用.length,然后%2==0即为偶数\\n\\n代码\\n\\n###java\\n\\nclass Solution {\\n public int findNumbers(int[] nums) {\\n int res=0;\\n for(int i:nums){\\n if(String.valueOf(i).length()%2==0){\\n res++;\\n }\\n }\\n\\n return res;\\n }\\n}","guid":"https://leetcode.cn/problems/find-numbers-with-even-number-of-digits//solution/jiang-intzhuan-wei-stringdiao-yong-lengthran-hou-2","author":"fmradio","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-22T06:18:06.434Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"使用图的思想分析该问题","url":"https://leetcode.cn/problems/gas-station//solution/shi-yong-tu-de-si-xiang-fen-xi-gai-wen-ti-by-cyayc","content":"class Solution {\\n public int findNumbers(int[] nums) {\\n int res=0;\\n for(int i:nums){\\n if(String.valueOf(i).length()%2==0){\\n res++;\\n }\\n }\\n\\n return res;\\n }\\n}\\n
解题思路:
\\n该题可以使用图的思想来分析,时间复杂度 $O(N)$,空间复杂度 $O(1)$。
\\n\\n以该题为例:\\ngas = [1,2,3,4,5]\\ncost = [3,4,5,1,2]\\n
下图的
\\n黑色折线图
即总油量剩余值
,若要满足题目的要求:跑完全程再回到起点,总油量剩余值
的任意部分都需要在X
轴以上,且跑到终点时:总剩余汽油量 >= 0
。为了让
\\n黑色折线图
任意部分都在X
轴以上,我们需要向上移动黑色折线图
,直到所有点都在X
轴或X
轴以上。此时,处在X
轴的点即为出发点。即黑色折线图
的最低值的位置:index = 3
。\\n
\\n\\n柱状图
\\n
\\n绿色:可添加的汽油gas[i]
\\n橙色:消耗的汽油cose[i]
折线图:
\\n
\\n虚线(蓝色):当前加油站的可用值
\\n实线(黑色):从0
开始的总剩余汽油量
执行用时: 0 ms,在所有 Java 提交中击败了 100.00% 的用户
\\n
\\n内存消耗: 37.2 MB,在所有 Java 提交中击败了 72.07% 的用户\\npublic int canCompleteCircuit(int[] gas, int[] cost) {\\n int len = gas.length;\\n int spare = 0;\\n int minSpare = Integer.MAX_VALUE;\\n int minIndex = 0;\\n\\n for (int i = 0; i < len; i++) {\\n spare += gas[i] - cost[i];\\n if (spare < minSpare) {\\n minSpare = spare;\\n minIndex = i;\\n }\\n }\\n\\n return spare < 0 ? -1 : (minIndex + 1) % len;\\n}\\n
时间复杂度:$O(N)$
\\n","description":"解题思路: 该题可以使用图的思想来分析,时间复杂度 $O(N)$,空间复杂度 $O(1)$。\\n\\n以该题为例:\\ngas = [1,2,3,4,5]\\ncost = [3,4,5,1,2]\\n\\n\\n下图的 黑色折线图 即 总油量剩余值,若要满足题目的要求:跑完全程再回到起点,总油量剩余值 的任意部分都需要在 X 轴以上,且跑到终点时:总剩余汽油量 >= 0。\\n\\n为了让 黑色折线图 任意部分都在 X 轴以上,我们需要向上移动 黑色折线图,直到所有点都在 X 轴或 X 轴以上。此时,处在 X 轴的点即为出发点。即 黑色折线图 的最低值的位置:index = 3。\\n\\n柱状…","guid":"https://leetcode.cn/problems/gas-station//solution/shi-yong-tu-de-si-xiang-fen-xi-gai-wen-ti-by-cyayc","author":"cyaycz","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-12T09:47:07.374Z","media":[{"url":"https://pic.leetcode-cn.com/98ee6782654518e1a33852e99825f1537869a542ee26738cf02d5fb6f0f0a899-%E6%97%A0%E6%A0%87%E9%A2%98.png","type":"photo","width":1310,"height":900,"blurhash":"LCS?AM~Xo#_3_Ms;Riof-=WUjYad"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划(Java、Python)","url":"https://leetcode.cn/problems/palindrome-partitioning-ii//solution/dong-tai-gui-hua-by-liweiwei1419-2","content":"
\\n空间复杂度:$O(1)$方法一:动态规划
\\n步骤 1:思考状态
\\n状态就尝试定义成题目问的那样,看看状态转移方程是否容易得到。
\\n\\n
dp[i]
:表示前缀子串s[0:i]
分割成若干个回文子串所需要最小分割次数。步骤 2:思考状态转移方程
\\n思考的方向是:大问题的最优解怎么由小问题的最优解得到。
\\n即
\\ndp[i]
如何与dp[i - 1]
、dp[i - 2]
、...、dp[0]
建立联系。比较容易想到的是:如果
\\ns[0:i]
本身就是一个回文串,那么不用分割,即dp[i] = 0
,这是首先可以判断的,否则就需要去遍历;接下来枚举可能分割的位置:即如果
\\ns[0:i]
本身不是一个回文串,就尝试分割,枚举分割的边界j
。如果
\\ns[j + 1, i]
不是回文串,尝试下一个分割边界。如果
\\ns[j + 1, i]
是回文串,则dp[i]
就是在dp[j]
的基础上多一个分割。于是枚举
\\nj
所有可能的位置,取所有dp[j]
中最小的再加 1 ,就是dp[i]
。得到状态转移方程如下:
\\n###java
\\n\\ndp[i] = min([dp[j] + 1 for j in range(i) if s[j + 1, i] 是回文])\\n
\\n
步骤 3:思考初始状态
\\n初始状态:单个字符一定是回文串,因此
\\ndp[0] = 0
。步骤 4:思考输出
\\n状态转移方程可以得到,并且状态就是题目问的,因此返回最后一个状态即可,即
\\ndp[len - 1]
。步骤 5:思考是否可以优化空间
\\n每一个状态值都与之前的状态值有关,因此不能优化空间。
\\n参考代码 1: (Python 代码会超时)
\\n###Java
\\n\\npublic class Solution {\\n\\n public int minCut(String s) {\\n int len = s.length();\\n if (len < 2) {\\n return 0;\\n }\\n\\n int[] dp = new int[len];\\n for (int i = 0; i < len; i++) {\\n dp[i] = i;\\n }\\n\\n for (int i = 1; i < len; i++) {\\n if (checkPalindrome(s, 0, i)) {\\n dp[i] = 0;\\n continue;\\n }\\n // 当 j == i 成立的时候,s[i] 就一个字符,一定是回文\\n // 因此,枚举到 i - 1 即可\\n for (int j = 0; j < i; j++) {\\n if (checkPalindrome(s, j + 1, i)) {\\n dp[i] = Math.min(dp[i], dp[j] + 1);\\n }\\n }\\n }\\n return dp[len - 1];\\n }\\n\\n private boolean checkPalindrome(String s, int left, int right) {\\n while (left < right) {\\n if (s.charAt(left) != s.charAt(right)) {\\n return false;\\n }\\n left++;\\n right--;\\n }\\n return true;\\n }\\n}\\n
###Python
\\n\\nclass Solution:\\n def minCut(self, s: str) -> int:\\n size = len(s)\\n if size < 2:\\n return 0\\n\\n dp = [i for i in range(size)]\\n\\n for i in range(1, size):\\n if self.__check_palindrome(s, 0, i):\\n dp[i] = 0\\n continue\\n # 枚举分割点\\n dp[i] = min([dp[j] + 1 for j in range(i) if self.__check_palindrome(s, j + 1, i)])\\n\\n return dp[size - 1]\\n\\n def __check_palindrome(self, s, left, right):\\n while left < right:\\n if s[left] != s[right]:\\n return False\\n left += 1\\n right -= 1\\n return True\\n
Python 代码在面对输入:
\\n\\n\\"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\\"\\n
的时候超时。
\\n方法二:动态规划(优化)
\\n上面判断回文串的时候方法
\\ncheckPalindrome()
是线性的,时间复杂度为 $O(N)$。我们可以借助「力扣」第 5 题:最长回文子串 的做法,依然是使用动态规划的做法,得到一个预处理的动态规划数组,这样就可以通过 $O(1)$ 的时间复杂度,得到一个子串是否是回文的结果了。参考代码 2:
\\n###Java
\\n\\npublic class Solution {\\n\\n public int minCut(String s) {\\n int len = s.length();\\n // 特判\\n if (len < 2) {\\n return 0;\\n }\\n\\n // 状态定义:dp[i]:前缀子串 s[0:i] (包括索引 i 处的字符)符合要求的最少分割次数\\n // 状态转移方程:\\n // dp[i] = min(dp[j] + 1 if s[j + 1: i] 是回文 for j in range(i))\\n\\n int[] dp = new int[len];\\n // 2 个字符最多分割 1 次;\\n // 3 个字符最多分割 2 次\\n // 初始化的时候,设置成为这个最多分割次数\\n\\n for (int i = 0; i < len; i++) {\\n dp[i] = i;\\n }\\n\\n // 参考「力扣」第 5 题:最长回文子串 动态规划 的解法\\n boolean[][] checkPalindrome = new boolean[len][len];\\n for (int right = 0; right < len; right++) {\\n // 注意:left <= right 取等号表示 1 个字符的时候也需要判断\\n for (int left = 0; left <= right; left++) {\\n if (s.charAt(left) == s.charAt(right) && (right - left <= 2 || checkPalindrome[left + 1][right - 1])) {\\n checkPalindrome[left][right] = true;\\n }\\n }\\n }\\n\\n // 1 个字符的时候,不用判断,因此 i 从 1 开始\\n for (int i = 1; i < len; i++) {\\n if (checkPalindrome[0][i]){\\n dp[i] = 0;\\n continue;\\n }\\n\\n // 注意:这里是严格,要保证 s[j + 1:i] 至少得有一个字符串\\n for (int j = 0; j < i; j++) {\\n if (checkPalindrome[j + 1][i]) {\\n dp[i] = Math.min(dp[i], dp[j] + 1);\\n }\\n }\\n }\\n return dp[len - 1];\\n }\\n}\\n
###Python
\\n\\n","description":"方法一:动态规划 步骤 1:思考状态\\n\\n状态就尝试定义成题目问的那样,看看状态转移方程是否容易得到。\\n\\ndp[i]:表示前缀子串 s[0:i] 分割成若干个回文子串所需要最小分割次数。\\n\\n步骤 2:思考状态转移方程\\n\\n思考的方向是:大问题的最优解怎么由小问题的最优解得到。\\n\\n即 dp[i] 如何与 dp[i - 1]、dp[i - 2]、...、dp[0] 建立联系。\\n\\n比较容易想到的是:如果 s[0:i] 本身就是一个回文串,那么不用分割,即 dp[i] = 0 ,这是首先可以判断的,否则就需要去遍历;\\n\\n接下来枚举可能分割的位置:即如果 s[0:i] 本身不是一个…","guid":"https://leetcode.cn/problems/palindrome-partitioning-ii//solution/dong-tai-gui-hua-by-liweiwei1419-2","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-12T09:45:49.810Z","media":[{"url":"https://pic.leetcode-cn.com/6e18ac84b634263ec0ebb30b223f767a09c6f4c6afa940c56ff36975c3ee8b67-image.png","type":"photo","width":1648,"height":582,"blurhash":"LVR{Z1-qtl-;tlRjs:of.mNGaKj["}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"回溯算法、优化(使用动态规划预处理数组)","url":"https://leetcode.cn/problems/palindrome-partitioning//solution/hui-su-you-hua-jia-liao-dong-tai-gui-hua-by-liweiw","content":"class Solution:\\n def minCut(self, s: str) -> int:\\n size = len(s)\\n if size < 2:\\n return 0\\n\\n dp = [i for i in range(size)]\\n check_palindrome = [[False for _ in range(size)] for _ in range(size)]\\n\\n for right in range(size):\\n for left in range(right + 1):\\n if s[left] == s[right] and (right - left <= 2 or check_palindrome[left + 1][right - 1]):\\n check_palindrome[left][right] = True\\n\\n for i in range(1, size):\\n if check_palindrome[0][i]:\\n dp[i] = 0\\n continue\\n # 枚举分割点\\n dp[i] = min([dp[j] + 1 for j in range(i) if check_palindrome[j + 1][i]])\\n\\n return dp[size - 1]\\n
思路分析:找到所有可能的解,提示我们可以使用「回溯算法」(采用深度优先遍历的方式遍历一棵隐式树结构)。
\\n对回溯算法还不太熟悉的朋友,可以参考:回溯算法入门级详解 + 练习(持续更新)。
\\n方法一:回溯算法
\\n回溯算法思考的步骤:
\\n\\n
\\n- 画出树型结构,本题的递归树模型是一棵多叉树;
\\n\\n\\n友情提示:强烈建议大家根据一个具体例子画出树形图。
\\n\\n
{:width=500}
\\n
\\n- 编码。
\\n\\n
\\n- 每一个结点表示剩余没有扫描到的字符串,产生分支是截取了剩余字符串的前缀;
\\n- 产生前缀字符串的时候,判断前缀字符串是否是回文。\\n
\\n\\n
\\n- 如果前缀字符串是回文,则可以产生分支和结点;
\\n- 如果前缀字符串不是回文,则不产生分支和结点,这一步是剪枝操作。
\\n- 在叶子结点是空字符串的时候结算,此时 从根结点到叶子结点的路径,就是结果集里的一个结果,使用深度优先遍历,记录下所有可能的结果。
\\n- 使用一个路径变量
\\npath
搜索,path
全局使用一个(注意结算的时候,要生成一个拷贝),因此在递归执行方法结束以后需要回溯,即将递归之前添加进来的元素拿出去;- \\n
path
的操作只在列表的末端,因此合适的数据结构是栈。参考代码 1:
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n public List<List<String>> partition(String s) {\\n int len = s.length();\\n List<List<String>> res = new ArrayList<>();\\n if (len == 0) {\\n return res;\\n }\\n\\n // Stack 这个类 Java 的文档里推荐写成 Deque<Integer> stack = new ArrayDeque<Integer>();\\n // 注意:只使用 stack 相关的接口\\n Deque<String> stack = new ArrayDeque<>();\\n char[] charArray = s.toCharArray();\\n dfs(charArray, 0, len, stack, res);\\n return res;\\n }\\n\\n /**\\n * @param charArray\\n * @param index 起始字符的索引\\n * @param len 字符串 s 的长度,可以设置为全局变量\\n * @param path 记录从根结点到叶子结点的路径\\n * @param res 记录所有的结果\\n */\\n private void dfs(char[] charArray, int index, int len, Deque<String> path, List<List<String>> res) {\\n if (index == len) {\\n res.add(new ArrayList<>(path));\\n return;\\n }\\n\\n for (int i = index; i < len; i++) {\\n // 因为截取字符串是消耗性能的,因此,采用传子串下标的方式判断一个子串是否是回文子串\\n if (!checkPalindrome(charArray, index, i)) {\\n continue;\\n }\\n path.addLast(new String(charArray, index, i + 1 - index));\\n dfs(charArray, i + 1, len, path, res);\\n path.removeLast();\\n }\\n }\\n\\n /**\\n * 这一步的时间复杂度是 O(N),优化的解法是,先采用动态规划,把回文子串的结果记录在一个表格里\\n *\\n * @param charArray\\n * @param left 子串的左边界,可以取到\\n * @param right 子串的右边界,可以取到\\n * @return\\n */\\n private boolean checkPalindrome(char[] charArray, int left, int right) {\\n while (left < right) {\\n if (charArray[left] != charArray[right]) {\\n return false;\\n }\\n left++;\\n right--;\\n }\\n return true;\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(N \\\\cdot 2^N)$;这里 $N$ 为输入字符串的长度,每一个位置可拆分,也可不拆分,尝试是否可以拆分的时间复杂度为 $O(2^N)$,判断每一个子串是否是回文子串,时间复杂度为 $O(N)$;
\\n- 空间复杂度:\\n
\\n\\n
\\n- 如果不计算保存结果的空间,空间复杂度为$O(N)$,递归调用栈的高度为 $N$;
\\n- 如果计算保存答案需要空间 $2^N \\\\times N$,这里 $2^N$ 为保守估计,实际情况不会这么多。空间复杂度为 $O(2^N \\\\times N)$。
\\n验证回文串的时候,每一次都得使用「双指针」的方式验证子串是否是回文子串。利用「力扣」第 5 题:最长回文子串 的思路,可以先用动态规划把结果算出来,这样就可以以 $O(1)$ 的时间复杂度直接得到一个子串是否是回文。
\\n
\\n方法二:回溯的优化(使用动态规划得到所有子串是否是回文)
\\n参考代码 2:
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n public List<List<String>> partition(String s) {\\n int len = s.length();\\n List<List<String>> res = new ArrayList<>();\\n if (len == 0) {\\n return res;\\n }\\n\\n char[] charArray = s.toCharArray();\\n // 预处理\\n // 状态:dp[i][j] 表示 s[i][j] 是否是回文\\n boolean[][] dp = new boolean[len][len];\\n // 状态转移方程:在 s[i] == s[j] 的时候,dp[i][j] 参考 dp[i + 1][j - 1]\\n for (int right = 0; right < len; right++) {\\n // 注意:left <= right 取等号表示 1 个字符的时候也需要判断\\n for (int left = 0; left <= right; left++) {\\n if (charArray[left] == charArray[right] && (right - left <= 2 || dp[left + 1][right - 1])) {\\n dp[left][right] = true;\\n }\\n }\\n }\\n\\n Deque<String> stack = new ArrayDeque<>();\\n dfs(s, 0, len, dp, stack, res);\\n return res;\\n }\\n\\n private void dfs(String s, int index, int len, boolean[][] dp, Deque<String> path, List<List<String>> res) {\\n if (index == len) {\\n res.add(new ArrayList<>(path));\\n return;\\n }\\n\\n for (int i = index; i < len; i++) {\\n if (dp[index][i]) {\\n path.addLast(s.substring(index, i + 1));\\n dfs(s, i + 1, len, dp, path, res);\\n path.removeLast();\\n }\\n }\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(2^N)$;这里 $N$ 为输入字符串的长度,每一个位置可拆分,也可不拆分,尝试是否可以拆分的时间复杂度为 $O(2^N)$,动态规划得到所有子串是否为回文子串的时间复杂度为 $O(N^2)$,所以总的时间复杂度为 $O(N^2 + 2^N) = O(2^N)$;
\\n- 空间复杂度:\\n
\\n\\n
\\n- 如果不计算保存结果的空间,动态规划表格的大小为 $N^2$,递归调用栈的高度为 $N$,空间复杂度为 $O(N^2 + N) = O(N)$。
\\n- 如果计算保存答案需要空间 $2^N \\\\times N$,这里 $2^N$ 为保守估计,实际情况不会这么多。空间复杂度为 $O(2^N \\\\times N + N^2 + N) = O(2^N \\\\times N)$。
\\n补充:
\\n事实上,还可以使用中心扩散的方法得到所有子串是否是回文,可以参考 评论。
\\n","description":"思路分析:找到所有可能的解,提示我们可以使用「回溯算法」(采用深度优先遍历的方式遍历一棵隐式树结构)。 对回溯算法还不太熟悉的朋友,可以参考:回溯算法入门级详解 + 练习(持续更新)。\\n\\n方法一:回溯算法\\n\\n回溯算法思考的步骤:\\n\\n画出树型结构,本题的递归树模型是一棵多叉树;\\n\\n友情提示:强烈建议大家根据一个具体例子画出树形图。\\n\\n{:width=500}\\n\\n编码。\\n每一个结点表示剩余没有扫描到的字符串,产生分支是截取了剩余字符串的前缀;\\n产生前缀字符串的时候,判断前缀字符串是否是回文。\\n如果前缀字符串是回文,则可以产生分支和结点;\\n如果前缀字符串不…","guid":"https://leetcode.cn/problems/palindrome-partitioning//solution/hui-su-you-hua-jia-liao-dong-tai-gui-hua-by-liweiw","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-12-12T08:38:07.695Z","media":[{"url":"https://pic.leetcode-cn.com/298a80282ac3505fec3710abdc1e656c591cf7acaa3ba976151480729244b649-image.png","type":"photo","width":1774,"height":1134,"blurhash":"LGR:Qi%h~U%NNPWY-lWE^+%KNFs:"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"题目理解 + 基本解法 + 进阶解法","url":"https://leetcode.cn/problems/super-egg-drop//solution/ji-ben-dong-tai-gui-hua-jie-fa-by-labuladong","content":"读完本文,你可以去力扣拿下如下题目:
\\n\\n-----------
\\n今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼。国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋太浪费,改成扔杯子,扔破碗什么的。
\\n具体的问题等会再说,但是这道题的解法技巧很多,光动态规划就好几种效率不同的思路,最后还有一种极其高效数学解法。秉承咱们号一贯的作风,拒绝奇技淫巧,拒绝过于诡异的技巧,因为这些技巧无法举一反三,学了也不划算。
\\n下面就来用我们一直强调的动态规划通用思路来研究一下这道题。
\\n一、解析题目
\\n题目是这样:你面前有一栋从 1 到
\\nN
共N
层的楼,然后给你K
个鸡蛋(K
至少为 1)。现在确定这栋楼存在楼层0 <= F <= N
,在这层楼将鸡蛋扔下去,鸡蛋恰好没摔碎(高于F
的楼层都会碎,低于F
的楼层都不会碎)。现在问你,最坏情况下,你至少要扔几次鸡蛋,才能确定这个楼层F
呢?也就是让你找摔不碎鸡蛋的最高楼层
\\nF
,但什么叫「最坏情况」下「至少」要扔几次呢?我们分别举个例子就明白了。比方说现在先不管鸡蛋个数的限制,有 7 层楼,你怎么去找鸡蛋恰好摔碎的那层楼?
\\n最原始的方式就是线性扫描:我先在 1 楼扔一下,没碎,我再去 2 楼扔一下,没碎,我再去 3 楼……
\\n以这种策略,最坏情况应该就是我试到第 7 层鸡蛋也没碎(
\\nF = 7
),也就是我扔了 7 次鸡蛋。先在你应该理解什么叫做「最坏情况」下了,鸡蛋破碎一定发生在搜索区间穷尽时,不会说你在第 1 层摔一下鸡蛋就碎了,这是你运气好,不是最坏情况。
\\n现在再来理解一下什么叫「至少」要扔几次。依然不考虑鸡蛋个数限制,同样是 7 层楼,我们可以优化策略。
\\n最好的策略是使用二分查找思路,我先去第
\\n(1 + 7) / 2 = 4
层扔一下:如果碎了说明
\\nF
小于 4,我就去第(1 + 3) / 2 = 2
层试……如果没碎说明
\\nF
大于等于 4,我就去第(5 + 7) / 2 = 6
层试……以这种策略,最坏情况应该是试到第 7 层鸡蛋还没碎(
\\nF = 7
),或者鸡蛋一直碎到第 1 层(F = 0
)。然而无论那种最坏情况,只需要试log7
向上取整等于 3 次,比刚才尝试 7 次要少,这就是所谓的至少要扔几次。PS:这有点像 Big O 表示法计算算法的复杂度。
\\n实际上,如果不限制鸡蛋个数的话,二分思路显然可以得到最少尝试的次数,但问题是,现在给你了鸡蛋个数的限制
\\nK
,直接使用二分思路就不行了。比如说只给你 1 个鸡蛋,7 层楼,你敢用二分吗?你直接去第 4 层扔一下,如果鸡蛋没碎还好,但如果碎了你就没有鸡蛋继续测试了,无法确定鸡蛋恰好摔不碎的楼层
\\nF
了。这种情况下只能用线性扫描的方法,算法返回结果应该是 7。有的读者也许会有这种想法:二分查找排除楼层的速度无疑是最快的,那干脆先用二分查找,等到只剩 1 个鸡蛋的时候再执行线性扫描,这样得到的结果是不是就是最少的扔鸡蛋次数呢?
\\n很遗憾,并不是,比如说把楼层变高一些,100 层,给你 2 个鸡蛋,你在 50 层扔一下,碎了,那就只能线性扫描 1~49 层了,最坏情况下要扔 50 次。
\\n如果不要「二分」,变成「五分」「十分」都会大幅减少最坏情况下的尝试次数。比方说第一个鸡蛋每隔十层楼扔,在哪里碎了第二个鸡蛋一个个线性扫描,总共不会超过 20 次。
\\n最优解其实是 14 次。最优策略非常多,而且并没有什么规律可言。
\\n说了这么多废话,就是确保大家理解了题目的意思,而且认识到这个题目确实复杂,就连我们手算都不容易,如何用算法解决呢?
\\n二、思路分析
\\n对动态规划问题,直接套我们以前多次强调的框架即可:这个问题有什么「状态」,有什么「选择」,然后穷举。
\\n「状态」很明显,就是当前拥有的鸡蛋数
\\nK
和需要测试的楼层数N
。随着测试的进行,鸡蛋个数可能减少,楼层的搜索范围会减小,这就是状态的变化。「选择」其实就是去选择哪层楼扔鸡蛋。回顾刚才的线性扫描和二分思路,二分查找每次选择到楼层区间的中间去扔鸡蛋,而线性扫描选择一层层向上测试。不同的选择会造成状态的转移。
\\n现在明确了「状态」和「选择」,动态规划的基本思路就形成了:肯定是个二维的
\\ndp
数组或者带有两个状态参数的dp
函数来表示状态转移;外加一个 for 循环来遍历所有选择,择最优的选择更新状态:###python
\\n\\n# 当前状态为 K 个鸡蛋,面对 N 层楼\\n# 返回这个状态下的最优结果\\ndef dp(K, N):\\n int res\\n for 1 <= i <= N:\\n res = min(res, 这次在第 i 层楼扔鸡蛋)\\n return res\\n
这段伪码还没有展示递归和状态转移,不过大致的算法框架已经完成了。
\\n我们选择在第
\\ni
层楼扔了鸡蛋之后,可能出现两种情况:鸡蛋碎了,鸡蛋没碎。注意,这时候状态转移就来了:如果鸡蛋碎了,那么鸡蛋的个数
\\nK
应该减一,搜索的楼层区间应该从[1..N]
变为[1..i-1]
共i-1
层楼;如果鸡蛋没碎,那么鸡蛋的个数
\\nK
不变,搜索的楼层区间应该从[1..N]
变为[i+1..N]
共N-i
层楼。\\n
{:width=450}{:align=center}
PS:细心的读者可能会问,在第i层楼扔鸡蛋如果没碎,楼层的搜索区间缩小至上面的楼层,是不是应该包含第i层楼呀?不必,因为已经包含了。开头说了 F 是可以等于 0 的,向上递归后,第i层楼其实就相当于第 0 层,可以被取到,所以说并没有错误。
\\n因为我们要求的是最坏情况下扔鸡蛋的次数,所以鸡蛋在第
\\ni
层楼碎没碎,取决于那种情况的结果更大:###python
\\n\\ndef dp(K, N):\\n for 1 <= i <= N:\\n # 最坏情况下的最少扔鸡蛋次数\\n res = min(res, \\n max( \\n dp(K - 1, i - 1), # 碎\\n dp(K, N - i) # 没碎\\n ) + 1 # 在第 i 楼扔了一次\\n )\\n return res\\n
递归的 base case 很容易理解:当楼层数
\\nN
等于 0 时,显然不需要扔鸡蛋;当鸡蛋数K
为 1 时,显然只能线性扫描所有楼层:###python
\\n\\ndef dp(K, N):\\n if K == 1: return N\\n if N == 0: return 0\\n ...\\n
至此,其实这道题就解决了!只要添加一个备忘录消除重叠子问题即可:
\\n###python
\\n\\ndef superEggDrop(K: int, N: int):\\n\\n memo = dict()\\n def dp(K, N) -> int:\\n # base case\\n if K == 1: return N\\n if N == 0: return 0\\n # 避免重复计算\\n if (K, N) in memo:\\n return memo[(K, N)]\\n\\n res = float(\'INF\')\\n # 穷举所有可能的选择\\n for i in range(1, N + 1):\\n res = min(res, \\n max(\\n dp(K, N - i), \\n dp(K - 1, i - 1)\\n ) + 1\\n )\\n # 记入备忘录\\n memo[(K, N)] = res\\n return res\\n \\n return dp(K, N)\\n
这个算法的时间复杂度是多少呢?动态规划算法的时间复杂度就是子问题个数 × 函数本身的复杂度。
\\n函数本身的复杂度就是忽略递归部分的复杂度,这里
\\ndp
函数中有一个 for 循环,所以函数本身的复杂度是 O(N)。子问题个数也就是不同状态组合的总数,显然是两个状态的乘积,也就是 O(KN)。
\\n所以算法的总时间复杂度是 O(K*N^2), 空间复杂度 O(KN)。
\\n三、疑难解答
\\n这个问题很复杂,但是算法代码却十分简洁,这就是动态规划的特性,穷举加备忘录/DP table 优化,真的没啥新意。
\\n首先,有读者可能不理解代码中为什么用一个 for 循环遍历楼层
\\n[1..N]
,也许会把这个逻辑和之前探讨的线性扫描混为一谈。其实不是的,这只是在做一次「选择」。比方说你有 2 个鸡蛋,面对 10 层楼,你这次选择去哪一层楼扔呢?不知道,那就把这 10 层楼全试一遍。至于下次怎么选择不用你操心,有正确的状态转移,递归会算出每个选择的代价,我们取最优的那个就是最优解。
\\n另外,这个问题还有更好的解法,比如修改代码中的 for 循环为二分搜索,可以将时间复杂度降为 O(K*N*logN);再改进动态规划解法可以进一步降为 O(KN);使用数学方法解决,时间复杂度达到最优 O(K*logN),空间复杂度达到 O(1)。
\\n二分的解法也有点误导性,你很可能以为它跟我们之前讨论的二分思路扔鸡蛋有关系,实际上没有半毛钱关系。能用二分搜索是因为状态转移方程的函数图像具有单调性,可以快速找到最值。
\\n二分搜索的优化思路也许是我们可以尽力尝试写出的,而修改状态转移的解法可能是不容易想到的,可以借此见识一下动态规划算法设计的玄妙,当做思维拓展。
\\n二分搜索优化
\\n之前提到过这个解法,核心是因为状态转移方程的单调性,这里可以具体展开看看。
\\n首先简述一下原始动态规划的思路:
\\n1、暴力穷举尝试在所有楼层
\\n1 <= i <= N
扔鸡蛋,每次选择尝试次数最少的那一层;2、每次扔鸡蛋有两种可能,要么碎,要么没碎;
\\n3、如果鸡蛋碎了,
\\nF
应该在第i
层下面,否则,F
应该在第i
层上面;4、鸡蛋是碎了还是没碎,取决于哪种情况下尝试次数更多,因为我们想求的是最坏情况下的结果。
\\n核心的状态转移代码是这段:
\\n###python
\\n\\n# 当前状态为 K 个鸡蛋,面对 N 层楼\\n# 返回这个状态下的最优结果\\ndef dp(K, N):\\n for 1 <= i <= N:\\n # 最坏情况下的最少扔鸡蛋次数\\n res = min(res, \\n max( \\n dp(K - 1, i - 1), # 碎\\n dp(K, N - i) # 没碎\\n ) + 1 # 在第 i 楼扔了一次\\n )\\n return res\\n
这个 for 循环就是下面这个状态转移方程的具体代码实现:
\\n\\n\\n
{:width=450}{:align=center}
如果能够理解这个状态转移方程,那么就很容易理解二分查找的优化思路。
\\n首先我们根据
\\ndp(K, N)
数组的定义(有K
个鸡蛋面对N
层楼,最少需要扔几次),很容易知道K
固定时,这个函数随着N
的增加一定是单调递增的,无论你策略多聪明,楼层增加测试次数一定要增加。那么注意
\\ndp(K - 1, i - 1)
和dp(K, N - i)
这两个函数,其中i
是从 1 到N
单增的,如果我们固定K
和N
,把这两个函数看做关于i
的函数,前者随着i
的增加应该也是单调递增的,而后者随着i
的增加应该是单调递减的:\\n
{:width=450}{:align=center}
这时候求二者的较大值,再求这些最大值之中的最小值,其实就是求这两条直线交点,也就是红色折线的最低点嘛。
\\n我们前文「二分查找只能用来查找元素吗」讲过,二分查找的运用很广泛,形如下面这种形式的 for 循环代码:
\\n###java
\\n\\nfor (int i = 0; i < n; i++) {\\n if (isOK(i))\\n return i;\\n}\\n
都很有可能可以运用二分查找来优化线性搜索的复杂度,回顾这两个
\\ndp
函数的曲线,我们要找的最低点其实就是这种情况:###java
\\n\\nfor (int i = 1; i <= N; i++) {\\n if (dp(K - 1, i - 1) == dp(K, N - i))\\n return dp(K, N - i);\\n}\\n
熟悉二分搜索的同学肯定敏感地想到了,这不就是相当于求 Valley(山谷)值嘛,可以用二分查找来快速寻找这个点的,直接看代码吧,整体的思路还是一样,只是加快了搜索速度:
\\n###python
\\n\\ndef superEggDrop(self, K: int, N: int) -> int:\\n \\n memo = dict()\\n def dp(K, N):\\n if K == 1: return N\\n if N == 0: return 0\\n if (K, N) in memo:\\n return memo[(K, N)]\\n \\n # for 1 <= i <= N:\\n # res = min(res, \\n # max( \\n # dp(K - 1, i - 1), \\n # dp(K, N - i) \\n # ) + 1 \\n # )\\n\\n res = float(\'INF\')\\n # 用二分搜索代替线性搜索\\n lo, hi = 1, N\\n while lo <= hi:\\n mid = (lo + hi) // 2\\n broken = dp(K - 1, mid - 1) # 碎\\n not_broken = dp(K, N - mid) # 没碎\\n # res = min(max(碎,没碎) + 1)\\n if broken > not_broken:\\n hi = mid - 1\\n res = min(res, broken + 1)\\n else:\\n lo = mid + 1\\n res = min(res, not_broken + 1)\\n\\n memo[(K, N)] = res\\n return res\\n \\n return dp(K, N)\\n
这个算法的时间复杂度是多少呢?动态规划算法的时间复杂度就是子问题个数 × 函数本身的复杂度。
\\n函数本身的复杂度就是忽略递归部分的复杂度,这里
\\ndp
函数中用了一个二分搜索,所以函数本身的复杂度是 O(logN)。子问题个数也就是不同状态组合的总数,显然是两个状态的乘积,也就是 O(KN)。
\\n所以算法的总时间复杂度是 O(K*N*logN), 空间复杂度 O(KN)。效率上比之前的算法 O(KN^2) 要高效一些。
\\n重新定义状态转移
\\n前文「不同定义有不同解法」就提过,找动态规划的状态转移本就是见仁见智,比较玄学的事情,不同的状态定义可以衍生出不同的解法,其解法和复杂程度都可能有巨大差异。这里就是一个很好的例子。
\\n再回顾一下我们之前定义的
\\ndp
数组含义:###python
\\n\\ndef dp(k, n) -> int\\n# 当前状态为 k 个鸡蛋,面对 n 层楼\\n# 返回这个状态下最少的扔鸡蛋次数\\n
用 dp 数组表示的话也是一样的:
\\n###python
\\n\\ndp[k][n] = m\\n# 当前状态为 k 个鸡蛋,面对 n 层楼\\n# 这个状态下最少的扔鸡蛋次数为 m\\n
按照这个定义,就是确定当前的鸡蛋个数和面对的楼层数,就知道最小扔鸡蛋次数。最终我们想要的答案就是
\\ndp(K, N)
的结果。这种思路下,肯定要穷举所有可能的扔法的,用二分搜索优化也只是做了「剪枝」,减小了搜索空间,但本质思路没有变,还是穷举。
\\n现在,我们稍微修改
\\ndp
数组的定义,确定当前的鸡蛋个数和最多允许的扔鸡蛋次数,就知道能够确定F
的最高楼层数。具体来说是这个意思:###python
\\n\\ndp[k][m] = n\\n# 当前有 k 个鸡蛋,可以尝试扔 m 次鸡蛋\\n# 这个状态下,最坏情况下最多能确切测试一栋 n 层的楼\\n\\n# 比如说 dp[1][7] = 7 表示:\\n# 现在有 1 个鸡蛋,允许你扔 7 次;\\n# 这个状态下最多给你 7 层楼,\\n# 使得你可以确定楼层 F 使得鸡蛋恰好摔不碎\\n# (一层一层线性探查嘛)\\n
这其实就是我们原始思路的一个「反向」版本,我们先不管这种思路的状态转移怎么写,先来思考一下这种定义之下,最终想求的答案是什么?
\\n我们最终要求的其实是扔鸡蛋次数
\\nm
,但是这时候m
在状态之中而不是dp
数组的结果,可以这样处理:###java
\\n\\nint superEggDrop(int K, int N) {\\n\\n int m = 0;\\n while (dp[K][m] < N) {\\n m++;\\n // 状态转移...\\n }\\n return m;\\n}\\n
题目不是给你
\\nK
鸡蛋,N
层楼,让你求最坏情况下最少的测试次数m
吗?while
循环结束的条件是dp[K][m] == N
,也就是给你K
个鸡蛋,测试m
次,最坏情况下最多能测试N
层楼。注意看这两段描述,是完全一样的!所以说这样组织代码是正确的,关键就是状态转移方程怎么找呢?还得从我们原始的思路开始讲。之前的解法配了这样图帮助大家理解状态转移思路:
\\n\\n
{:width=450}{:align=center}
这个图描述的仅仅是某一个楼层
\\ni
,原始解法还得线性或者二分扫描所有楼层,要求最大值、最小值。但是现在这种dp
定义根本不需要这些了,基于下面两个事实:1、无论你在哪层楼扔鸡蛋,鸡蛋只可能摔碎或者没摔碎,碎了的话就测楼下,没碎的话就测楼上。
\\n2、无论你上楼还是下楼,总的楼层数 = 楼上的楼层数 + 楼下的楼层数 + 1(当前这层楼)。
\\n根据这个特点,可以写出下面的状态转移方程:
\\n\\n
dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1
\\n
dp[k][m - 1]
就是楼上的楼层数,因为鸡蛋个数k
不变,也就是鸡蛋没碎,扔鸡蛋次数m
减一;\\n
dp[k - 1][m - 1]
就是楼下的楼层数,因为鸡蛋个数k
减一,也就是鸡蛋碎了,同时扔鸡蛋次数m
减一。PS:这个
\\nm
为什么要减一而不是加一?之前定义得很清楚,这个m
是一个允许的次数上界,而不是扔了几次。\\n
{:width=450}{:align=center}
至此,整个思路就完成了,只要把状态转移方程填进框架即可:
\\n###java
\\n\\nint superEggDrop(int K, int N) {\\n // m 最多不会超过 N 次(线性扫描)\\n int[][] dp = new int[K + 1][N + 1];\\n // base case:\\n // dp[0][..] = 0\\n // dp[..][0] = 0\\n // Java 默认初始化数组都为 0\\n int m = 0;\\n while (dp[K][m] < N) {\\n m++;\\n for (int k = 1; k <= K; k++)\\n dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;\\n }\\n return m;\\n}\\n
如果你还觉得这段代码有点难以理解,其实它就等同于这样写:
\\n###java
\\n\\nfor (int m = 1; dp[K][m] < N; m++)\\n for (int k = 1; k <= K; k++)\\n dp[k][m] = dp[k][m - 1] + dp[k - 1][m - 1] + 1;\\n
看到这种代码形式就熟悉多了吧,因为我们要求的不是
\\ndp
数组里的值,而是某个符合条件的索引m
,所以用while
循环来找到这个m
而已。这个算法的时间复杂度是多少?很明显就是两个嵌套循环的复杂度 O(KN)。
\\n另外注意到
\\ndp[m][k]
转移只和左边和左上的两个状态有关,所以很容易优化成一维dp
数组,这里就不写了。还可以再优化
\\n再往下就要用一些数学方法了,不具体展开,就简单提一下思路吧。
\\n在刚才的思路之上,注意函数
\\ndp(m, k)
是随着m
单增的,因为鸡蛋个数k
不变时,允许的测试次数越多,可测试的楼层就越高。这里又可以借助二分搜索算法快速逼近
\\ndp[K][m] == N
这个终止条件,时间复杂度进一步下降为 O(KlogN),我们可以设g(k, m) =
……算了算了,打住吧。我觉得我们能够写出 O(K*N*logN) 的二分优化算法就行了,后面的这些解法呢,听个响鼓个掌就行了,把欲望限制在能力的范围之内才能拥有快乐!
\\n不过可以肯定的是,根据二分搜索代替线性扫描
\\nm
的取值,代码的大致框架肯定是修改穷举m
的 for 循环:###java
\\n\\n// 把线性搜索改成二分搜索\\n// for (int m = 1; dp[K][m] < N; m++)\\nint lo = 1, hi = N;\\nwhile (lo < hi) {\\n int mid = (lo + hi) / 2;\\n if (... < N) {\\n lo = ...\\n } else {\\n hi = ...\\n }\\n \\n for (int k = 1; k <= K; k++)\\n // 状态转移方程\\n}\\n
简单总结一下吧,第一个二分优化是利用了
\\ndp
函数的单调性,用二分查找技巧快速搜索答案;第二种优化是巧妙地修改了状态转移方程,简化了求解了流程,但相应的,解题逻辑比较难以想到;后续还可以用一些数学方法和二分搜索进一步优化第二种解法,不过看了看镜子中的发量,算了。本文终,希望对你有一点启发。
\\n_____________
\\n点击 我的主页 看更多优质文章。
\\n","description":"读完本文,你可以去力扣拿下如下题目: 887.鸡蛋掉落\\n\\n-----------\\n\\n今天要聊一个很经典的算法问题,若干层楼,若干个鸡蛋,让你算出最少的尝试次数,找到鸡蛋恰好摔不碎的那层楼。国内大厂以及谷歌脸书面试都经常考察这道题,只不过他们觉得扔鸡蛋太浪费,改成扔杯子,扔破碗什么的。\\n\\n具体的问题等会再说,但是这道题的解法技巧很多,光动态规划就好几种效率不同的思路,最后还有一种极其高效数学解法。秉承咱们号一贯的作风,拒绝奇技淫巧,拒绝过于诡异的技巧,因为这些技巧无法举一反三,学了也不划算。\\n\\n下面就来用我们一直强调的动态规划通用思路来研究一下这道题。\\n\\n一…","guid":"https://leetcode.cn/problems/super-egg-drop//solution/ji-ben-dong-tai-gui-hua-jie-fa-by-labuladong","author":"labuladong","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-11-17T02:46:44.659Z","media":[{"url":"https://pic.leetcode-cn.com/1601009732-UZCCXJ-file_1601009732120","type":"photo","width":720,"height":1280,"blurhash":"LCR{x*~W^+Xn?bbat6oMxuofWBj@"},{"url":"https://pic.leetcode-cn.com/1601009731-wGdcgJ-file_1601009731838","type":"photo","width":1392,"height":99,"blurhash":"LKR:HG_3%M?b~qofayof~qIUM{M{"},{"url":"https://pic.leetcode-cn.com/1601009732-AqPWoe-file_1601009732619","type":"photo","width":1280,"height":720,"blurhash":"LBS$id_3t8_3~Wt5j@WCxcs:RiWU"},{"url":"https://pic.leetcode-cn.com/1601009732-UZCCXJ-file_1601009732120","type":"photo","width":720,"height":1280,"blurhash":"LCR{x*~W^+Xn?bbat6oMxuofWBj@"},{"url":"https://pic.leetcode-cn.com/1601009732-ntuTCJ-file_1601009732163","type":"photo","width":720,"height":1280,"blurhash":"LMRW9x?a}]SPxuofs:WUxuofWWoe"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最靠谱的分析来一波","url":"https://leetcode.cn/problems/airplane-seat-assignment-probability//solution/ju-ti-fen-xi-lai-yi-bo-by-jobhunter-4","content":"对于第一个乘客来说 他有三种选择
\\n\\n
\\n- 坐在正确的(自己的位置), 那么后面的乘客都不会乱,所以第n个乘客可以坐到自己的位置, 1/n * 1.
\\n- 坐在第n个乘客的位置,那么第n个乘客肯定无法坐到自己的位置, 1/n * 0.
\\n- 坐在[1,n-1]之间的某个位置K.
\\n
\\n对于第K个乘客而言,自己的位置K已经被乘客1给占了,而[2,K-1]的乘客先于K乘客 上飞机,能找到自己的位置并坐下,所以当K乘客上飞机时,留给他的选择是
\\n第1个座位,以及[K+1,n]的座位。此时K乘客同样有3个选择,
\\n\\n
\\n- 如果他坐在正确的座位,那么后面的乘客都不会乱,第n个乘客可以坐到自己的位置,
\\n
\\n只不过此时对于K乘客而言,正确的座位就是座位1。- 坐在第n个乘客的位置,那么第n个乘客肯定无法坐到自己的位置
\\n- 坐在[K+1,n-1]之间的某个位置。
\\n可以发现对于第一个乘客和第K个乘客,他们面临的选择是一样的,只不过问题的规模不一样。第K个乘客时,问题的规模只有n-K+1. (为何, 上面已经解释过了,对于第K个乘客而言,自己的位置K已经被乘客1给占了,而[2,K-1]的乘客先于K乘客 上飞机,能找到自己的位置并坐下)。
\\n所以此题公式为
\\n
\\np[1] = 1.0;
\\np[2] = 0.5;p[3] = 1/3 + p[2]/3 = 0.5;
\\n
\\np[4] = 1/4 + p[2]/4 + p[3]/4 = 0.5
\\n...\\nclass Solution {\\npublic:\\n double p[100001];\\n double nthPersonGetsNthSeat(int n) {\\n\\n p[1] = 1.0;\\n p[2] = 0.5;\\n\\n for (int i = 3; i <= n; i++) {\\n double sum = 1.0 / i;\\n for (int k = 2; k < i; k++) {\\n sum += p[i - k + 1] / i;\\n }\\n p[i] = sum;\\n }\\n return p[n];\\n }\\n};\\n
但是我的这段代码在输入为100000的情况下超时,只能摸索规律然后直接返回1.0或者0.5了。
\\n我感觉很多人答案虽然是对的 但是转换成子问题这一步解释得比较牵强。
\\n","description":"对于第一个乘客来说 他有三种选择 坐在正确的(自己的位置), 那么后面的乘客都不会乱,所以第n个乘客可以坐到自己的位置, 1/n * 1.\\n坐在第n个乘客的位置,那么第n个乘客肯定无法坐到自己的位置, 1/n * 0.\\n坐在[1,n-1]之间的某个位置K.\\n 对于第K个乘客而言,自己的位置K已经被乘客1给占了,而[2,K-1]的乘客先于K乘客 上飞机,能找到自己的位置并坐下,所以当K乘客上飞机时,留给他的选择是\\n 第1个座位,以及[K+1,n]的座位。\\n\\n此时K乘客同样有3个选择,\\n\\n如果他坐在正确的座位,那么后面的乘客都不会乱…","guid":"https://leetcode.cn/problems/airplane-seat-assignment-probability//solution/ju-ti-fen-xi-lai-yi-bo-by-jobhunter-4","author":"IamValuable","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-11-12T22:05:29.696Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"100% 找到上下限与左右限。","url":"https://leetcode.cn/problems/image-smoother//solution/100-zhao-dao-shang-xia-xian-yu-zuo-you-xian-by-ooo","content":"
\\n从问题分析来看,规模N的概率无法从规模N-1直接得来。
\\n本文的解释可能更加好接受理解一些。思路
\\n示例给的做法很直观,区分边,角,其他点,然后分类讨论。但写出来有太多的细节需要注意,太耗费时间而且容易出错。
\\n\\n\\n九个点能否加入计算,取决于它是否越界。
\\n我们通过锁定上下限与左右限,将越界的点排除,然后遍历求和即可。
\\n代码
\\n最佳152ms 100%
\\n###c++
\\n\\n","description":"思路 示例给的做法很直观,区分边,角,其他点,然后分类讨论。但写出来有太多的细节需要注意,太耗费时间而且容易出错。\\n\\n九个点能否加入计算,取决于它是否越界。\\n\\n我们通过锁定上下限与左右限,将越界的点排除,然后遍历求和即可。\\n\\n代码\\n\\n最佳152ms 100%\\n\\n###c++\\n\\nclass Solution {\\npublic:\\n vectorclass Solution {\\npublic:\\n vector<vector<int>> imageSmoother(vector<vector<int>>& M) {\\n int r=M.size(),c=M.back().size();\\n vector<vector<int>> res(r,vector<int>(c,-1));\\n \\n for(int i=0;i<r*c;i++){\\n int cur_r=i/c;\\n int cur_c=i%c;\\n int x1=cur_c,x2=cur_c,y1=cur_r,y2=cur_r,sum=0;\\n if(cur_c-1>=0)x1=cur_c-1;//左限\\n if(cur_c+1<c)x2=cur_c+1;//右限\\n if(cur_r-1>=0)y1=cur_r-1;//上限\\n if(cur_r+1<r)y2=cur_r+1;//下限\\n for(int i=y1;i<=y2;i++){//遍历求和\\n for(int j=x1;j<=x2;j++){\\n sum+=M[i][j];\\n }\\n }\\n int count=(y2-y1+1)*(x2-x1+1);\\n res[cur_r][cur_c]=sum/count;\\n }\\n return res;\\n }\\n
> imageSmoother(vector >& M) {\\n int r=M.size(),c=M.back().size();\\n vector…","guid":"https://leetcode.cn/problems/image-smoother//solution/100-zhao-dao-shang-xia-xian-yu-zuo-you-xian-by-ooo","author":"ooolize-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-11-05T10:14:46.347Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"个人题解[dp][c++][O(n^2)]","url":"https://leetcode.cn/problems/largest-divisible-subset//solution/ge-ren-ti-jie-dpcon2-by-leolee","content":" 做题前的思考:假如已经有一个两两倍数关系的序列
\\n2 4 8
,若此时来一个8的倍数x,那么x一定是8 4 2
的倍数,2 4 8 x
依然保持了两两倍数关系的序列,到了此处就发现可以大问题转换成子问题,于是联想到动态规划。并且在扩展序列的时候是有序的,所以我们需要给nums排序.对于已经排序后的nums
\\n
\\n设定状态:dp[i]
: 以nums[i]结尾的序列最大长度
\\nlast[i]
: 在最大序列中 nums[i]的上一个元素在nums出现的下标
\\n状态转移方程:
\\n使用二重循环,对于每一个nums[i],看他可以接在之前的哪个序列dp[j]上,使得dp[i]最长
\\nnums[i]%nums[j] == 0
是可以接的条件,dp[i]<=dp[j]
是使得dp[i]变长的条件
\\n初始状态:dp[i] = 1 (i:1 - n)
每一个只有自己的序列长度为1###cpp
\\n\\nfor(int i = 0;i<sz;i++){\\n for(int j = 0;j<i;j++)\\n if(nums[i]%nums[j] == 0 && dp[i]<=dp[j]){\\n dp[i] = dp[j]+1;\\n last[i] = j;\\n }\\n
###cpp
\\n\\n","description":"做题前的思考:假如已经有一个两两倍数关系的序列2 4 8,若此时来一个8的倍数x,那么x一定是8 4 2的倍数,2 4 8 x依然保持了两两倍数关系的序列,到了此处就发现可以大问题转换成子问题,于是联想到动态规划。并且在扩展序列的时候是有序的,所以我们需要给nums排序. 对于已经排序后的nums\\n 设定状态: dp[i]: 以nums[i]结尾的序列最大长度\\n last[i]: 在最大序列中 nums[i]的上一个元素在nums出现的下标\\n 状态转移方程:\\n 使用二重循环,对于每一个nums[i],看他可以接在之前的哪个序列dp[j]上,使得dp[i]最长\\n n…","guid":"https://leetcode.cn/problems/largest-divisible-subset//solution/ge-ren-ti-jie-dpcon2-by-leolee","author":"leolee","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-10-27T09:44:50.188Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【跳跃游戏 II】别想那么多,就挨着跳吧 II","url":"https://leetcode.cn/problems/jump-game-ii//solution/45-by-ikaruga","content":"class Solution {\\npublic:\\n vector<int> largestDivisibleSubset(vector<int>& nums) {\\n int sz = nums.size(),mx = 0,end = -1;\\n vector<int> dp(sz,1),last(sz,-1),res;\\n sort(nums.begin(),nums.end());\\n for(int i = 0;i<sz;i++){\\n for(int j = 0;j<i;j++){\\n if(nums[i]%nums[j] == 0 && dp[i]<=dp[j]){\\n dp[i] = dp[j]+1;\\n last[i] = j;\\n }\\n }\\n if(dp[i]>mx){\\n mx = dp[i];\\n end = i;\\n }\\n }\\n for(int i = end;i!=-1;i = last[i]){//倒序输出\\n res.push_back(nums[i]);\\n }\\n return res;\\n }\\n};\\n
思路
\\n\\n
\\n- \\n
\\n如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点。
\\n
\\n11. 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新。- \\n
\\n如果从这个 起跳点 起跳叫做第 1 次 跳跃,那么从后面 3 个格子起跳 都 可以叫做第 2 次 跳跃。
\\n- \\n
\\n所以,当一次 跳跃 结束时,从下一个格子开始,到现在 能跳到最远的距离,都 是下一次 跳跃 的 起跳点。
\\n
\\n31. 对每一次 跳跃 用 for 循环来模拟。
\\n32. 跳完一次之后,更新下一次 起跳点 的范围。
\\n33. 在新的范围内跳,更新 能跳到最远的距离。- \\n
\\n记录 跳跃 次数,如果跳到了终点,就得到了结果。
\\n图解
\\n\\n
代码
\\n\\nint jump(vector<int> &nums)\\n{\\n int ans = 0;\\n int start = 0;\\n int end = 1;\\n while (end < nums.size())\\n {\\n int maxPos = 0;\\n for (int i = start; i < end; i++)\\n {\\n // 能跳到最远的距离\\n maxPos = max(maxPos, i + nums[i]);\\n }\\n start = end; // 下一次起跳点范围开始的格子\\n end = maxPos + 1; // 下一次起跳点范围结束的格子\\n ans++; // 跳跃次数\\n }\\n return ans;\\n}\\n
优化
\\n\\n
\\n- \\n
\\n从上面代码观察发现,其实被 while 包含的 for 循环中,i 是从头跑到尾的。
\\n- \\n
\\n只需要在一次 跳跃 完成时,更新下一次 能跳到最远的距离。
\\n- \\n
\\n并以此刻作为时机来更新 跳跃 次数。
\\n- \\n
\\n就可以在一次 for 循环中处理。
\\n\\nint jump(vector<int>& nums)\\n{\\n int ans = 0;\\n int end = 0;\\n int maxPos = 0;\\n for (int i = 0; i < nums.size() - 1; i++)\\n {\\n maxPos = max(nums[i] + i, maxPos);\\n if (i == end)\\n {\\n end = maxPos;\\n ans++;\\n }\\n }\\n return ans;\\n}\\n
致谢
\\n感谢您的观看,希望对您有帮助,欢迎热烈的交流!
\\n","description":"思路 如果某一个作为 起跳点 的格子可以跳跃的距离是 3,那么表示后面 3 个格子都可以作为 起跳点。\\n 11. 可以对每一个能作为 起跳点 的格子都尝试跳一次,把 能跳到最远的距离 不断更新。\\n\\n如果从这个 起跳点 起跳叫做第 1 次 跳跃,那么从后面 3 个格子起跳 都 可以叫做第 2 次 跳跃。\\n\\n所以,当一次 跳跃 结束时,从下一个格子开始,到现在 能跳到最远的距离,都 是下一次 跳跃 的 起跳点。\\n 31. 对每一次 跳跃 用 for 循环来模拟。\\n 32. 跳完一次之后,更新下一次 起跳点 的范围。\\n 33. 在新的范围内跳,更新 能跳到最远…","guid":"https://leetcode.cn/problems/jump-game-ii//solution/45-by-ikaruga","author":"ikaruga","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-10-19T08:54:25.163Z","media":[{"url":"https://pic.leetcode-cn.com/9d5016c6e660a452991185d23b7b4d98853b7c300453d79715b5e9a206085e44-%E5%9B%BE%E7%89%87.png","type":"photo","width":436,"height":416,"blurhash":"LDRpI8?X~A?c~qIVIS$^sDn,S5I."}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"【鸡蛋掉落】5 行代码,从求扔几次变为求多少层楼 =附面试经历=","url":"https://leetcode.cn/problems/super-egg-drop//solution/887-by-ikaruga","content":"思路
\\n\\n
\\n- \\n
\\nN 和 F 的关系
\\n
\\n11. N 的定义:使用一栋从 1 到 N 共有 N 层楼的建筑
\\n12. F 的定义:满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破
\\n13. 因此得知,F 比 N 多一个 0 层- \\n
\\n问题转换
\\n
\\n21. 将问题从: N 个楼层,有 K 个蛋,求最少要扔 T 次,才能保证当 F 无论是 0 <= F <= N 中哪个值,都能测试出来
\\n22. 转变为:有 K 个蛋,扔 T 次,求可以确定 F 的个数,然后得出 N 个楼层- \\n
\\n通过扔蛋测试,怎样才能确定 F ,怎样才能确定全部的 F
\\n\\n\\n比如: N = 1 层楼
\\n
\\n在 1 层扔,碎了,因为楼层高于 F 才会碎,所以 F < 1 。又因为 0 <= F <= N ,所以能确定 F = 0
\\n在 1 层扔,没碎,因为从 F 楼层或比它低的楼层落下的鸡蛋都不会碎,所以 F >= 1 。又因为 0 <= F <= N ,所以能确定 F = 1\\n\\n再比如: N = 2 层楼
\\n
\\n在 1 层扔,碎了,F < 1,所以确定 F = 0
\\n在 1 层扔,没碎,但在 2 层扔,碎了, F >= 1 && F < 2,所以确定 F = 1
\\n在 2 层扔,没碎,F >= 2,所以确定 F = 2<
\\n,
,
>
\\n
\\n- 如果只有 1 个蛋\\n
\\n\\n\\n如果唯一的 1 个蛋碎了,就无法继续测试了
\\n
\\n如果从中间开始测,万一蛋碎了,连 1 个 F 都无法确定
\\n只能从低到高,一层一层的判断
\\n所以有 T 次机会,只可以确定出 T + 1 个 F<
\\n,
,
,
,
>
\\n
\\n- \\n
\\n如果只有 1 次机会
\\n\\n\\n这个好理解,只有 1 次机会,就算有很多蛋也派不上用场,所以等同于只有 1 个蛋,并且扔一次,根据上边的例子,只能确定 2 个 F
\\n
\\n也就是只能确定 T(1) + 1 个 F- \\n
\\n计算能确定 F 的个数
\\n
\\n61. 如果只有 1 个蛋,或只有 1 次机会时,只可以确定出 T + 1 个 F
\\n62. 其他情况时,递归。【蛋碎了减 1 个,机会减 1 次】 + 【蛋没碎,机会减 1 次】<
\\n,
,
>
\\n
\\n- 题目给出了 K ,不断增大 T ,计算出来的 F 的个数已经超过了 N + 1 时,就找到了答案
\\n答题
\\n\\nclass Solution {\\npublic:\\n int calcF(int K, int T)\\n {\\n if (T == 1 || K == 1) return T + 1;\\n return calcF(K - 1, T - 1) + calcF(K, T - 1);\\n }\\n\\n int superEggDrop(int K, int N)\\n {\\n int T = 1;\\n while (calcF(K, T) < N + 1) T++;\\n return T;\\n }\\n};\\n
面试中遇到的子问题
\\n一次面试中,面试官问了这道题的子问题。
\\n\\n有 2 个蛋,用一座 100 层的楼,要使用最少次数测试出蛋几层会碎(F)。\\n问第一次应该从几层扔。\\n
分析题意,其实本质上是和本题是一样的。
\\n
\\n相当于int ans = superEggDrop(2, 100);
,得到 14 次。
\\n因为最少需要 14 次,所以第 1 次扔在 14 层,如果蛋碎了,接下来 1~13 这个区间就只能一次一次尝试了。\\n\\n接下来第二次扔蛋,因为扔过 1 次了,接下来的区间大小只有 12 ,只能够 15~26 ,所以扔到 27 层。
\\n但是现场没有纸笔,不考代码,脑容量不够递归。
\\n
\\n考虑到上面分析,从后往前推,推到最后一次扔蛋,会确定 F = 100 和 F = 99 。
\\n再往前一次,应该是留出了 T(1) + 1 的个数,
\\n再往前一次,留出了 T(2) + 1 的个数,
\\n…………
\\n所以在蛋的数量固定为 2 的情况下,T 和 F(N) 的关系应该是:
\\n扔 1 次,确定 2 个 F,也就是 (2 - 1 = 1) 个 N。
\\n扔 2 次,确定 2 + 2 个 F,也就是 (4 - 1 = 3) 个 N。
\\n扔 3 次,确定 2 + 2 + 3 个 F,也就是 (7 - 1 = 6) 个 N。
\\n即:1 + 2 + 3 + ... + 14 > 100
\\n所以答案是 14 次。\\n
其它思考
\\n\\n\\n这个问题简化后,其实和
\\n猜数字
(猜一个数字,返回大了小了还是对了)是一个类型的,可以对比着思考。致谢
\\n感谢您的观看,希望对您有帮助,欢迎热烈的交流!
\\n如果感觉还不错就点个赞吧~
\\n","description":"思路 N 和 F 的关系\\n 11. N 的定义:使用一栋从 1 到 N 共有 N 层楼的建筑\\n 12. F 的定义:满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破\\n 13. 因此得知,F 比 N 多一个 0 层\\n\\n问题转换\\n 21. 将问题从: N 个楼层,有 K 个蛋,求最少要扔 T 次,才能保证当 F 无论是 0 <= F <= N 中哪个值,都能测试出来\\n 22. 转变为:有 K 个蛋,扔 T 次,求可以确定 F 的个数,然后得出 N 个楼层\\n\\n通过扔蛋测试,怎样才能确定 F…","guid":"https://leetcode.cn/problems/super-egg-drop//solution/887-by-ikaruga","author":"ikaruga","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-09-20T05:39:19.515Z","media":[{"url":"https://pic.leetcode-cn.com/0bb87d083c1c8d99a8463bac8c252309e00421217f2d4090335e7f01145598ea-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":517,"blurhash":"LOQmFzM{D$M{t8j[s:j@00oet7js"},{"url":"https://pic.leetcode-cn.com/9e628923014b5f9e7d9219fe7b1faeb5629a32bd29b0be903dd70a6ef9144c29-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":517,"blurhash":"LPQvwRIoD%M{t8fks:fP00oeofj["},{"url":"https://pic.leetcode-cn.com/18721cd7d06c9c18f194d761746ba3d98f270bd38b673d54507a16207bd4a47e-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":517,"blurhash":"LQQm9j_2D%_N?btRWBjF4TRkofRj"},{"url":"https://pic.leetcode-cn.com/e8adda20509809f7713ab10dce6c4553d8076df99ee8abfb7ce903c8fdb40471-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":521,"blurhash":"LPQmCrIUDiM{o#j[s:j@01s:ozj@"},{"url":"https://pic.leetcode-cn.com/2abbedf13fd97df3f0f189be87ee9cda0108fb81396d1cfaf384488c4c3beda4-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":521,"blurhash":"LPRMYs%Lxv-q?bR*RPWB01W=WUR*"},{"url":"https://pic.leetcode-cn.com/83d3ec12d3b67e2b47222d33627eca28247106431a73c84246c8a82bc152741a-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":521,"blurhash":"LORC;|-ox^-p?bRkRPWB01WrR$S2"},{"url":"https://pic.leetcode-cn.com/554edbed44412eae7c3b70f68c9625396522f8e7d5c3a349bcc8e077b9bfecbb-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":521,"blurhash":"LMR3QM-:x_-p?vRkR5R*01R,RiS$"},{"url":"https://pic.leetcode-cn.com/9bf67967d49eaabbfa0067675c8696a41b90ad1a51dba049ac5b8657e02719c6-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":521,"blurhash":"LKR3K5-:pK-p_1RlM{R-00R.jEXT"},{"url":"https://pic.leetcode-cn.com/dec63b4512630abf0de386d11600addfa25a445e5fe14c604d9a24caa3e28edd-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":525,"blurhash":"LOQmFy_29F_N?bozRkoJ00Rkt7WA"},{"url":"https://pic.leetcode-cn.com/b3b09876a0daaa33eca78ef59b74c9c72098445f92fea2dab389eedec604ba9a-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":525,"blurhash":"LQQcr3_2D%_4?bozR*oL4nRlt7aJ"},{"url":"https://pic.leetcode-cn.com/e74f24d92f6db77c4a2e47b8c245f35d0644647c453e8d77bdfa7dc08770f578-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":525,"blurhash":"LLQJiuIVD%Rj%Njsofae00t6j]WB"},{"url":"https://pic.leetcode-cn.com/250d60226ecabc4776304c04395bba60810184b44fa87437c6845c44dbcaab84-%E5%9B%BE%E7%89%87.png","type":"photo","width":1051,"height":649,"blurhash":"LOR3TWWE9FWBx]WBt6ay00axt7kC"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"详细通俗的思路分析,多解法","url":"https://leetcode.cn/problems/gas-station//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--30","content":"题目描述(中等难度)
\\n把这个题理解成下边的图就可以。
\\n\\n
每个节点表示添加的油量,每条边表示消耗的油量。题目的意思就是问我们从哪个节点出发,还可以回到该节点。只能顺时针方向走。
\\n解法一 暴力解法
\\n考虑暴力破解,一方面是验证下自己对题目的理解是否正确,另一方面后续的优化也可以从这里入手。
\\n考虑从第
\\n0
个点出发,能否回到第0
个点。考虑从第
\\n1
个点出发,能否回到第 1 个点。考虑从第
\\n2
个点出发,能否回到第2
个点。... ...
\\n考虑从第
\\nn
个点出发,能否回到第n
个点。由于是个圆,得到下一个点的时候我们需要取余数。
\\n\\npublic int canCompleteCircuit(int[] gas, int[] cost) {\\n int n = gas.length;\\n //考虑从每一个点出发\\n for (int i = 0; i < n; i++) {\\n int j = i;\\n int remain = gas[i];\\n //当前剩余的油能否到达下一个点\\n while (remain - cost[j] >= 0) {\\n //减去花费的加上新的点的补给\\n remain = remain - cost[j] + gas[(j + 1) % n];\\n j = (j + 1) % n;\\n //j 回到了 i\\n if (j == i) {\\n return i;\\n }\\n }\\n }\\n //任何点都不可以\\n return -1;\\n}\\n
解法二 优化尝试一
\\n暴力破解慢的原因就是会进行很多重复的计算。比如下边的情况:
\\n###Java
\\n\\n假设当前在考虑 i,先初始化 j = i\\n* * * * * *\\n ^\\n i\\n ^\\n j\\n \\n随后 j 会进行后移\\n* * * * * *\\n ^ ^\\n i j\\n \\n继续后移\\n* * * * * *\\n ^ ^\\n i j\\n \\n继续后移\\n* * * * * *\\n^ ^ \\nj i \\n\\n此时 j 又回到了第 0 个位置,我们在之前已经考虑过了这个位置。\\n如果之前考虑第 0 个位置的时候,最远到了第 2 个位置。\\n那么此时 j 就可以直接跳到第 2 个位置,同时加上当时的剩余汽油,继续考虑\\n* * * * * *\\n ^ ^ \\n j i \\n
利用上边的思想我们可以进行一个优化,就是每考虑一个点,就将当前点能够到达的最远距离记录下来,同时到达最远距离时候的剩余汽油也要记下来。
\\n\\npublic int canCompleteCircuit(int[] gas, int[] cost) {\\n int n = gas.length;\\n //记录能到的最远距离\\n int[] farIndex = new int[n];\\n for (int i = 0; i < farIndex.length; i++) {\\n farIndex[i] = -1;\\n }\\n //记录到达最远距离时候剩余的汽油\\n int[] farIndexRemain = new int[n];\\n for (int i = 0; i < n; i++) {\\n int j = i;\\n int remain = gas[i];\\n while (remain - cost[j] >= 0) {\\n //到达下个点后的剩余\\n remain = remain - cost[j];\\n j = (j + 1) % n;\\n //判断之前有没有考虑过这个点\\n if (farIndex[j] != -1) {\\n //加上当时剩余的汽油\\n remain = remain + farIndexRemain[j];\\n //j 进行跳跃\\n j = farIndex[j];\\n } else {\\n //加上当前点的补给\\n remain = remain + gas[j];\\n }\\n if (j == i) {\\n return i;\\n }\\n }\\n //记录当前点最远到达哪里\\n farIndex[i] = j;\\n //记录当前点的剩余\\n farIndexRemain[i] = remain;\\n }\\n return -1;\\n\\n}\\n
遗憾的是,这个想法针对
\\nleetcode
的测试集速度上没有带来很明显的提升。不过记录已经求出来的解进行优化,这个思想还是经常用的,也就是空间换时间。让我们换个思路继续优化。
\\n解法三 优化尝试二
\\n我们考虑一下下边的情况。
\\n###java
\\n\\n* * * * * *\\n^ ^\\ni j\\n
当考虑
\\ni
能到达的最远的时候,假设是j
。那么
\\ni + 1
到j
之间的节点是不是就都不可能绕一圈了?假设
\\ni + 1
的节点能绕一圈,那么就意味着从i + 1
开始一定能到达j + 1
。又因为从
\\ni
能到达i + 1
,所以从i
也能到达j + 1
。但事实上,
\\ni
最远到达j
。产生矛盾,所以i + 1
的节点一定不能绕一圈。同理,其他的也是一样的证明。所以下一次的
\\ni
我们不需要从i + 1
开始考虑,直接从j + 1
开始考虑即可。还有一种情况,就是因为到达末尾的时候,会回到
\\n0
。如果对于下边的情况。
\\n###java
\\n\\n* * * * * *\\n ^ ^\\n j i\\n
如果
\\ni
最远能够到达j
,根据上边的结论i + 1
到j
之间的节点都不可能绕一圈了。想象成一个圆,所以i
后边的节点就都不需要考虑了,直接返回-1
即可。\\npublic int canCompleteCircuit(int[] gas, int[] cost) {\\n int n = gas.length;\\n for (int i = 0; i < n; i++) {\\n int j = i;\\n int remain = gas[i];\\n while (remain - cost[j] >= 0) {\\n //减去花费的加上新的点的补给\\n remain = remain - cost[j] + gas[(j + 1) % n];\\n j = (j + 1) % n;\\n //j 回到了 i\\n if (j == i) {\\n return i;\\n }\\n }\\n //最远距离绕到了之前,所以 i 后边的都不可能绕一圈了\\n if (j < i) {\\n return -1;\\n }\\n //i 直接跳到 j,外层 for 循环执行 i++,相当于从 j + 1 开始考虑\\n i = j;\\n\\n }\\n return -1;\\n}\\n
总
\\n写题的时候先写出暴力的解法,然后再考虑优化,有时候是一种不错的选择。
\\n","description":"把这个题理解成下边的图就可以。 每个节点表示添加的油量,每条边表示消耗的油量。题目的意思就是问我们从哪个节点出发,还可以回到该节点。只能顺时针方向走。\\n\\n考虑暴力破解,一方面是验证下自己对题目的理解是否正确,另一方面后续的优化也可以从这里入手。\\n\\n考虑从第 0 个点出发,能否回到第 0 个点。\\n\\n考虑从第 1 个点出发,能否回到第 1 个点。\\n\\n考虑从第 2 个点出发,能否回到第 2 个点。\\n\\n... ...\\n\\n考虑从第 n 个点出发,能否回到第 n 个点。\\n\\n由于是个圆,得到下一个点的时候我们需要取余数。\\n\\npublic int…","guid":"https://leetcode.cn/problems/gas-station//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--30","author":"windliang","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-09-06T03:47:10.934Z","media":[{"url":"https://pic.leetcode-cn.com/a3b1d46544160566b53ff291a5de91e2a3452a5da118569f92df94b95f869a44.jpg","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"搜索旋转排序数组 II","url":"https://leetcode.cn/problems/search-in-rotated-sorted-array-ii//solution/zai-javazhong-ji-bai-liao-100de-yong-hu-by-reedfan","content":"解题思路:
\\n本题是需要使用二分查找,怎么分是关键,举个例子:
\\n\\n
\\n- 第一类
\\n
\\n$10111$ 和 $11101$ 这种。此种情况下nums[start] == nums[mid]
,分不清到底是前面有序还是后面有序,此时start++
即可。相当于去掉一个重复的干扰项。- 第二类
\\n
\\n$2$ $3$ $4$ $5$ $6$ $7$ $1$ 这种,也就是nums[start] < nums[mid]
。此例子中就是2 < 5
;
\\n这种情况下,前半部分有序。因此如果nums[start] <=target<nums[mid]
,则在前半部分找,否则去后半部分找。- 第三类
\\n
\\n$6$ $7$ $1$ $2$ $3$ $4$ $5$ 这种,也就是nums[start] > nums[mid]
。此例子中就是6 > 2
;
\\n这种情况下,后半部分有序。因此如果nums[mid] <target<=nums[end]
。则在后半部分找,否则去前半部分找。代码:
\\n###Java
\\n\\n","description":"解题思路: 本题是需要使用二分查找,怎么分是关键,举个例子:\\n\\n第一类\\n $10111$ 和 $11101$ 这种。此种情况下 nums[start] == nums[mid],分不清到底是前面有序还是后面有序,此时 start++ 即可。相当于去掉一个重复的干扰项。\\n第二类\\n $2$ $3$ $4$ $5$ $6$ $7$ $1$ 这种,也就是 nums[start] < nums[mid]。此例子中就是 2 < 5;\\n 这种情况下,前半部分有序。因此如果 nums[start] <=targetpublic boolean search(int[] nums, int target) {\\n if (nums == null || nums.length == 0) {\\n return false;\\n }\\n int start = 0;\\n int end = nums.length - 1;\\n int mid;\\n while (start <= end) {\\n mid = start + (end - start) / 2;\\n if (nums[mid] == target) {\\n return true;\\n }\\n if (nums[start] == nums[mid]) {\\n start++;\\n continue;\\n }\\n //前半部分有序\\n if (nums[start] < nums[mid]) {\\n //target在前半部分\\n if (nums[mid] > target && nums[start] <= target) {\\n end = mid - 1;\\n } else { //否则,去后半部分找\\n start = mid + 1;\\n }\\n } else {\\n //后半部分有序\\n //target在后半部分\\n if (nums[mid] < target && nums[end] >= target) {\\n start = mid + 1;\\n } else { //否则,去后半部分找\\n end = mid - 1;\\n\\n }\\n }\\n }\\n //一直没找到,返回false\\n return false;\\n\\n }\\n
解题思路:\\n 一句话题解:回溯算法是一种遍历算法,以 深度优先遍历 的方式尝试所有的可能性。有些教程上也叫「暴力搜索」。回溯算法是 有方向地 搜索,区别于多层循环实现的暴力法。
\\n\\n\\n许多复杂的、规模较大的问题都可以使用回溯法,有「通用解题方法」的美称。(来自 百度百科)
\\n说明:
\\n\\n
\\n- N 皇后问题很多时候作为例题出现在教科书中,可以当做理解回溯算法的例题进行学习;
\\n- 对于回溯算法还比较陌生的朋友,可以参考我的题解 《回溯算法入门级详解 + 练习(持续更新)》。
\\n
\\n
\\n思路分析:以 $4$ 皇后问题为例,它的「搜索」过程如下。大家可以在纸上模拟下面这个过程:
\\n<
\\n,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
,
>
(早期写的题解配色较差,请大家谅解。)
\\n理解树形结构
\\n先尝试画出递归树,以 $4$ 皇后问题为例,画出的递归树如下:
\\n\\n
搜索的过程蕴含了 剪枝 的思想。「剪枝」的依据是:题目中给出的 「N 皇后」 的摆放规则:1、不在同一行;2、不在同一列;3、不在同一主对角线方向上;4、不在同一副对角线方向上。
\\n小技巧:记住已经摆放的皇后的位置
\\n这里记住已经摆放的位置不能像 Flood Fill 一样,简单地使用
\\nvisited
布尔数组。放置的规则是:一行一行考虑皇后可以放置在哪一个位置上,某一行在考虑某一列是否可以放置皇后的时候,需要根据前面已经放置的皇后的位置。由于是一行一行考虑放置皇后,摆放的这些皇后肯定不在同一行,为了避免它们在同一列,需要一个长度为 $N$ 的布尔数组
\\ncols
,已经放置的皇后占据的列,就需要在对应的列的位置标注为True
。考虑对角线(找规律)
\\n下面我们研究一下主对角线或者副对角线上的元素有什么特性。在每一个单元格里写下行和列的 下标。
\\n\\n
为了保证至少两个皇后不同时出现在 同一主对角线方向 或者 同一副对角线方向。检查策略是,只要「检测」到新摆放的「皇后」与已经摆放好的「皇后」冲突,就尝试摆放同一行的下一个位置,到行尾还不能放置皇后,就退回到上一行。
\\n可以像全排列
\\nused
数组那样,再为 「主对角线(Main diagonal)」 和 「副对角线(Sub diagonal)」 设置相应的 布尔数组变量,只要排定一个 「皇后」 的位置,就需要占住对应的位置。编码
\\n我们使用一个 $1$ 到 $4$ 的排列表示一个 $4 \\\\times 4$ 的棋盘,例如:
\\n\\n
{:width=\\"500px\\"}
得到一个符合要求的全排列以后,生成棋盘的代码就很简单了。
\\n说明:
\\n\\n
\\n- 将「行状态」、「主对角线状态」、「副对角线状态」 设置为成员变量,以避免递归方法参数冗长(参考代码 2、参考代码 3 类似看待)。至于是否有必要这样做,以后在项目开发中需要遵守项目文档的规定;
\\n- Java 中
\\nStack
已经废弃,推荐使用ArrayDeque
,可以查阅文档。方法:回溯搜索算法(深度优先遍历)
\\n下面虽然给出了 3 版代码,但都只是在如何记住已经摆放的皇后的位置上做文章,代码结构都一样,大家只看不同的部分就可以了。
\\n参考代码 1:
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n private int n;\\n /**\\n * 记录某一列是否放置了皇后\\n */\\n private boolean[] col;\\n /**\\n * 记录主对角线上的单元格是否放置了皇后\\n */\\n private boolean[] main;\\n /**\\n * 记录了副对角线上的单元格是否放置了皇后\\n */\\n private boolean[] sub;\\n private List<List<String>> res;\\n\\n public List<List<String>> solveNQueens(int n) {\\n res = new ArrayList<>();\\n if (n == 0) {\\n return res;\\n }\\n\\n // 设置成员变量,减少参数传递,具体作为方法参数还是作为成员变量,请参考团队开发规范\\n this.n = n;\\n this.col = new boolean[n];\\n this.main = new boolean[2 * n - 1];\\n this.sub = new boolean[2 * n - 1];\\n Deque<Integer> path = new ArrayDeque<>();\\n dfs(0, path);\\n return res;\\n }\\n\\n private void dfs(int row, Deque<Integer> path) {\\n if (row == n) {\\n // 深度优先遍历到下标为 n,表示 [0.. n - 1] 已经填完,得到了一个结果\\n List<String> board = convert2board(path);\\n res.add(board);\\n return;\\n }\\n\\n // 针对下标为 row 的每一列,尝试是否可以放置\\n for (int j = 0; j < n; j++) {\\n if (!col[j] && !main[row - j + n - 1] && !sub[row + j]) {\\n path.addLast(j);\\n col[j] = true;\\n main[row - j + n - 1] = true;\\n sub[row + j] = true;\\n\\n\\n dfs(row + 1, path);\\n sub[row + j] = false;\\n main[row - j + n - 1] = false;\\n col[j] = false;\\n path.removeLast();\\n }\\n }\\n }\\n\\n private List<String> convert2board(Deque<Integer> path) {\\n List<String> board = new ArrayList<>();\\n for (Integer num : path) {\\n StringBuilder row = new StringBuilder();\\n row.append(\\".\\".repeat(Math.max(0, n)));\\n row.replace(num, num + 1, \\"Q\\");\\n board.add(row.toString());\\n }\\n return board;\\n }\\n}\\n
参考代码 2:其实已经摆放皇后的列下标、占据了哪一条主对角线、哪一条副对角线也可以使用哈希表来记录。
\\n实际上哈希表底层也是数组,使用哈希表可以不用处理已经占据位置的皇后的主对角线、副对角线的下标偏移问题。
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Deque;\\nimport java.util.HashSet;\\nimport java.util.List;\\nimport java.util.Set;\\n\\npublic class Solution {\\n\\n private Set<Integer> col;\\n private Set<Integer> main;\\n private Set<Integer> sub;\\n private int n;\\n private List<List<String>> res;\\n\\n public List<List<String>> solveNQueens(int n) {\\n this.n = n;\\n res = new ArrayList<>();\\n if (n == 0) {\\n return res;\\n }\\n\\n col = new HashSet<>();\\n main = new HashSet<>();\\n sub = new HashSet<>();\\n\\n Deque<Integer> path = new ArrayDeque<>();\\n dfs(0, path);\\n return res;\\n }\\n\\n private void dfs(int row, Deque<Integer> path) {\\n if (row == n) {\\n List<String> board = convert2board(path);\\n res.add(board);\\n return;\\n }\\n\\n // 针对每一列,尝试是否可以放置\\n for (int i = 0; i < n; i++) {\\n if (!col.contains(i) && !main.contains(row - i) && !sub.contains(row + i)) {\\n path.addLast(i);\\n col.add(i);\\n main.add(row - i);\\n sub.add(row + i);\\n\\n dfs(row + 1, path);\\n\\n sub.remove(row + i);\\n main.remove(row - i);\\n col.remove(i);\\n path.removeLast();\\n }\\n }\\n }\\n\\n private List<String> convert2board(Deque<Integer> path) {\\n List<String> board = new ArrayList<>();\\n for (Integer num : path) {\\n StringBuilder row = new StringBuilder();\\n row.append(\\".\\".repeat(Math.max(0, n)));\\n row.replace(num, num + 1, \\"Q\\");\\n board.add(row.toString());\\n }\\n return board;\\n }\\n}\\n
参考代码 3:搜索问题一般来说复杂度很高,因此在线测评系统的后台测试数据不会很大。因此布尔数组可以只用一个整数来代替(
\\nint
或者long
根据情况决定),int
类型整数等价于一个 $32$ 位布尔数组,long
类型整数等价于一个 $64$ 位布尔数组。使用一个整数代表一个布尔数组,在比较布尔数组所有的位的值是否相等时,只需要 $O(1)$,并且传递参数、复制也是相对方便的。这样的技巧叫做「状态压缩」,动态规划问题里有一类问题就叫做状态压缩 dp(我学不过来了,以后学会了再向大家介绍,请谅解)。
\\n状态压缩的缺点是:编码得很细心,不容易调试。
\\n「力扣」第 1371 题:每个元音包含偶数次的最长子字符串,是每日一题出现过的状态压缩的经典问题,综合使用了很多算法思想和技巧,感兴趣的朋友可以复习一下。
\\n说明:使用 状态压缩 技巧,可以完成 「力扣」第 52 题:「N皇后 II」。
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n private List<List<String>> res;\\n private int n;\\n\\n public List<List<String>> solveNQueens(int n) {\\n this.n = n;\\n res = new ArrayList<>();\\n if (n == 0) {\\n return res;\\n }\\n\\n int col = 0;\\n int main = 0;\\n int sub = 0;\\n Deque<Integer> path = new ArrayDeque<>();\\n\\n dfs(0, col, main, sub, path);\\n return res;\\n }\\n\\n private void dfs(int row, int col, int sub, int main, Deque<Integer> path) {\\n if (row == n) {\\n List<String> board = convert2board(path);\\n res.add(board);\\n return;\\n }\\n\\n // 针对每一列,尝试是否可以放置\\n for (int i = 0; i < n; i++) {\\n if (((col >> i) & 1) == 0 \\n && ((main >> (row - i + n - 1)) & 1) == 0 \\n && ((sub >> (row + i)) & 1) == 0) {\\n path.addLast(i);\\n col ^= (1 << i);\\n main ^= (1 << (row - i + n - 1));\\n sub ^= (1 << (row + i));\\n\\n dfs(row + 1, col, sub, main, path);\\n\\n sub ^= (1 << (row + i));\\n main ^= (1 << (row - i + n - 1));\\n col ^= (1 << i);\\n path.removeLast();\\n }\\n }\\n }\\n\\n private List<String> convert2board(Deque<Integer> path) {\\n List<String> board = new ArrayList<>();\\n for (Integer num : path) {\\n StringBuilder row = new StringBuilder();\\n row.append(\\".\\".repeat(Math.max(0, n)));\\n row.replace(num, num + 1, \\"Q\\");\\n board.add(row.toString());\\n }\\n return board;\\n }\\n}\\n
同类问题
\\n\\n
\\n- 37. 解数独
\\n- 679. 24 点游戏
\\n- 529. 扫雷游戏
\\n- 488. 祖玛游戏
\\n参考资料
\\n\\n
\\n- liuyubobobo 老师在慕课网上开设的课程《玩转算法面试》代码仓库;
\\n- 《剑指 Offer(第 2 版)》面试题 38 :字符串的排列,相关题目 2。
\\n一个回溯算法可视化的小项目
\\n通过可视化帮助理解回溯算法的思想。写 Java 的朋友可以看看,这是我写的一个练习的项目,学习的价值不大,不用点赞。
\\n\\n
\\n- GitHub 地址:Backtracking-Visualization
\\n\\n
\\n","description":"解题思路: 一句话题解:回溯算法是一种遍历算法,以 深度优先遍历 的方式尝试所有的可能性。有些教程上也叫「暴力搜索」。回溯算法是 有方向地 搜索,区别于多层循环实现的暴力法。\\n\\n许多复杂的、规模较大的问题都可以使用回溯法,有「通用解题方法」的美称。(来自 百度百科)\\n\\n说明:\\n\\nN 皇后问题很多时候作为例题出现在教科书中,可以当做理解回溯算法的例题进行学习;\\n对于回溯算法还比较陌生的朋友,可以参考我的题解 《回溯算法入门级详解 + 练习(持续更新)》。\\n\\n\\n\\n思路分析:以 $4$ 皇后问题为例,它的「搜索」过程如下。大家可以在纸上模拟下面这个过程:\\n\\n<,…","guid":"https://leetcode.cn/problems/n-queens//solution/gen-ju-di-46-ti-quan-pai-lie-de-hui-su-suan-fa-si-","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-08-21T06:48:37.926Z","media":[{"url":"https://pic.leetcode-cn.com/1597914538-JMqrTI-%E5%B9%BB%E7%81%AF%E7%89%871.png","type":"photo","width":2000,"height":1125,"blurhash":"LWOzMu~m?YIx4TV@W;R+s_9JD,-+"},{"url":"https://pic.leetcode-cn.com/1597914538-ZjQXmZ-%E5%B9%BB%E7%81%AF%E7%89%872.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP%I:?Z_IN#4njrf+R-%SIX4r${"},{"url":"https://pic.leetcode-cn.com/1597914538-tmYgmS-%E5%B9%BB%E7%81%AF%E7%89%873.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP%I:?Z_IS84njrf+WE%SIX4r${"},{"url":"https://pic.leetcode-cn.com/1597914538-wBTCML-%E5%B9%BB%E7%81%AF%E7%89%874.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP%Fz%L~mx]8_f5kCWWx%WV4rae"},{"url":"https://pic.leetcode-cn.com/1597914538-EREunU-%E5%B9%BB%E7%81%AF%E7%89%875.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP?wR%L_Jtl8_f5kCWXx%WV4rni"},{"url":"https://pic.leetcode-cn.com/1597914538-OtaRLU-%E5%B9%BB%E7%81%AF%E7%89%876.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP?zY?Z_JWa4njskCWCy0IX8~xr"},{"url":"https://pic.leetcode-cn.com/1597914538-rucjiO-%E5%B9%BB%E7%81%AF%E7%89%877.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP?wR%L_Jxv8_jYkCWXx%WV8~WB"},{"url":"https://pic.leetcode-cn.com/1597914538-fgPyCK-%E5%B9%BB%E7%81%AF%E7%89%878.png","type":"photo","width":2000,"height":1125,"blurhash":"LWPj4M?Z~ma*9Fj?X8WCtYM#4rtO"},{"url":"https://pic.leetcode-cn.com/1597914538-DMbNDR-%E5%B9%BB%E7%81%AF%E7%89%879.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPj7U?Z_Jjx9FaxbHR+x%M}8~xr"},{"url":"https://pic.leetcode-cn.com/1597914538-frDGXq-%E5%B9%BB%E7%81%AF%E7%89%8710.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP%Fz%M~lxv8_f6kBWVx%ax4rWC"},{"url":"https://pic.leetcode-cn.com/1597914538-yfroRO-%E5%B9%BB%E7%81%AF%E7%89%8711.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP?zY?Z~lS84nfPkCWCy0IW4r$}"},{"url":"https://pic.leetcode-cn.com/1597914538-wpzwVG-%E5%B9%BB%E7%81%AF%E7%89%8712.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPj7T?Z_Ja+9Ff6a{WCtZMz8~xr"},{"url":"https://pic.leetcode-cn.com/1597914538-eTNDtj-%E5%B9%BB%E7%81%AF%E7%89%8713.png","type":"photo","width":2000,"height":1125,"blurhash":"LWQ0K~?Z_JS84na{oeWCtsIW8}$~"},{"url":"https://pic.leetcode-cn.com/1597914538-AyRRDe-%E5%B9%BB%E7%81%AF%E7%89%8714.png","type":"photo","width":2000,"height":1125,"blurhash":"LWQ0K~?Z_JWb4nfPoeWCtsMz8}xr"},{"url":"https://pic.leetcode-cn.com/1597914538-CTojIY-%E5%B9%BB%E7%81%AF%E7%89%8715.png","type":"photo","width":2000,"height":1125,"blurhash":"LWQ0K~?Z_Jn:4njsoeR,tsMz8}tN"},{"url":"https://pic.leetcode-cn.com/1597914538-mxIubY-%E5%B9%BB%E7%81%AF%E7%89%8716.png","type":"photo","width":2000,"height":1125,"blurhash":"LWPsk:?Z~ma+9Fj@baWDtZMz4qtO"},{"url":"https://pic.leetcode-cn.com/1597914538-ZnPNRW-%E5%B9%BB%E7%81%AF%E7%89%8717.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPsn_?Z_Jjy9FaybFR+tZM|8~xr"},{"url":"https://pic.leetcode-cn.com/1597914538-voxIfs-%E5%B9%BB%E7%81%AF%E7%89%8718.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP?wR%M_I%M8_f6kBWVx%f54rR+"},{"url":"https://pic.leetcode-cn.com/1597914538-xyeSxZ-%E5%B9%BB%E7%81%AF%E7%89%8719.png","type":"photo","width":2000,"height":1125,"blurhash":"LWPj4M?Z~la+9FfjbbWCtYMz4rtO"},{"url":"https://pic.leetcode-cn.com/1597914538-Gcnqba-%E5%B9%BB%E7%81%AF%E7%89%8720.png","type":"photo","width":2000,"height":1125,"blurhash":"LZPZiu%M~lx]9Fayofazx%ax4rWC"},{"url":"https://pic.leetcode-cn.com/1597914538-VYBADD-%E5%B9%BB%E7%81%AF%E7%89%8721.png","type":"photo","width":2000,"height":1125,"blurhash":"LXPZl*?Z~lWb9FaxbGR+%9IX4s${"},{"url":"https://pic.leetcode-cn.com/1597914538-wfFbbG-%E5%B9%BB%E7%81%AF%E7%89%8722.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPQ5G?Z~ma*9FayWqWB%9IX4rxq"},{"url":"https://pic.leetcode-cn.com/1597914538-gjhYeV-%E5%B9%BB%E7%81%AF%E7%89%8723.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP%I:?Z_IS74nfPbbWB%SIX4r$|"},{"url":"https://pic.leetcode-cn.com/1597914538-dMiyLp-%E5%B9%BB%E7%81%AF%E7%89%8724.png","type":"photo","width":2000,"height":1125,"blurhash":"LWP%I:?Z_Ia*4na{bbWB%SIX4rxp"},{"url":"https://pic.leetcode-cn.com/1597914538-tJmHAY-%E5%B9%BB%E7%81%AF%E7%89%8725.png","type":"photo","width":2000,"height":1125,"blurhash":"LZP%F$%L_I%M8_ayf+WVx%ay4rWB"},{"url":"https://pic.leetcode-cn.com/1597914538-CJPMHD-%E5%B9%BB%E7%81%AF%E7%89%8726.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPZl%.7~mn.9FazWqWBx%Mz4rtP"},{"url":"https://pic.leetcode-cn.com/1597914538-zHblmY-%E5%B9%BB%E7%81%AF%E7%89%8727.png","type":"photo","width":2000,"height":1125,"blurhash":"LYPj7T?Z_Ja*9Faza|WBtZMz8~xp"},{"url":"https://pic.leetcode-cn.com/1597914538-MApsVg-%E5%B9%BB%E7%81%AF%E7%89%8728.png","type":"photo","width":2000,"height":1125,"blurhash":"LWQ0K~?Z_JS84na|ofjFtsIW8}$|"},{"url":"https://pic.leetcode-cn.com/1597914538-YtEjVg-%E5%B9%BB%E7%81%AF%E7%89%8729.png","type":"photo","width":2000,"height":1125,"blurhash":"LWQ0K~?Z_Ja*4na|ofaetsMz8}xp"},{"url":"https://pic.leetcode-cn.com/1597914538-ORsUfU-%E5%B9%BB%E7%81%AF%E7%89%8730.png","type":"photo","width":2000,"height":1125,"blurhash":"LRP6,X_1_KIbE2tQjFn*S;Dk8}%d"},{"url":"https://pic.leetcode-cn.com/1598117469-RXhjxi-image.png","type":"photo","width":1684,"height":1600,"blurhash":"LTQ0j*-;_0%Mx^fkRjoe~TWVofj@"},{"url":"https://pic.leetcode-cn.com/1599142979-VEuEDb-image.png","type":"photo","width":2090,"height":978,"blurhash":"LxO4Y*-:?E%2kEoLocj[~SNIIqWC"},{"url":"https://pic.leetcode-cn.com/1599142434-RjIWEI-image.png","type":"photo","width":1858,"height":1000,"blurhash":"LOR3Tb_3~os=-?WA?FRlogfPocWD"},{"url":"https://leetcode.cn/problems/n-queens//solution/a10557e6-3ce9-4215-b8f0-8b445ade9992","type":"photo"},{"url":"https://leetcode.cn/problems/n-queens//solution/166f7cd6-ccef-430b-a575-57c02876ec53","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最小差值 II","url":"https://leetcode.cn/problems/smallest-range-ii//solution/zui-xiao-chai-zhi-ii-by-leetcode","content":"
方法 1:线性扫描
\\n想法
\\n如 最小差值 I 问题的解决方法,较小的
\\nA[i]
将增加,较大的A[i]
将变小。算法
\\n我们可以对上述想法形式化表述:如果
\\nA[i] < A[j]
,我们不必考虑当A[i]
增大时A[j]
会减小。这是因为区间(A[i] + K, A[j] - K)
是(A[i] - K, A[j] + K)
的子集(这里,当a > b
时(a, b)
表示(b, a)
)。这意味着对于
\\n(up, down)
的选择一定不会差于(down, up)
。我们可以证明其中一个区间是另一个的子集,通过证明A[i] + K
和A[j] - K
是在A[i] - K
和A[j] + K
之间。对于有序的
\\nA
,设A[i]
是最大的需要增长的i
,那么A[0] + K, A[i] + K, A[i+1] - K, A[A.length - 1] - K
就是计算结果的唯一值。###Java
\\n\\nclass Solution {\\n public int smallestRangeII(int[] A, int K) {\\n int N = A.length;\\n Arrays.sort(A);\\n int ans = A[N-1] - A[0];\\n\\n for (int i = 0; i < A.length - 1; ++i) {\\n int a = A[i], b = A[i+1];\\n int high = Math.max(A[N-1] - K, a + K);\\n int low = Math.min(A[0] + K, b - K);\\n ans = Math.min(ans, high - low);\\n }\\n return ans;\\n }\\n}\\n
###Python
\\n\\nclass Solution(object):\\n def smallestRangeII(self, A, K):\\n A.sort()\\n mi, ma = A[0], A[-1]\\n ans = ma - mi\\n for i in xrange(len(A) - 1):\\n a, b = A[i], A[i+1]\\n ans = min(ans, max(ma-K, a+K) - min(mi+K, b-K))\\n return ans\\n
复杂度分析
\\n\\n
\\n","description":"方法 1:线性扫描 想法\\n\\n如 最小差值 I 问题的解决方法,较小的 A[i] 将增加,较大的 A[i] 将变小。\\n\\n算法\\n\\n我们可以对上述想法形式化表述:如果 A[i] < A[j],我们不必考虑当 A[i] 增大时 A[j] 会减小。这是因为区间 (A[i] + K, A[j] - K) 是 (A[i] - K, A[j] + K) 的子集(这里,当 a > b 时 (a, b) 表示 (b, a) )。\\n\\n这意味着对于 (up, down) 的选择一定不会差于 (down, up)。我们可以证明其中一个区间是另一个的子集,通过证明 A[i] + K 和…","guid":"https://leetcode.cn/problems/smallest-range-ii//solution/zui-xiao-chai-zhi-ii-by-leetcode","author":"LeetCode","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-08-17T15:26:21.460Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"详细通俗的思路分析,多解法","url":"https://leetcode.cn/problems/pascals-triangle-ii//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--28","content":"- 时间复杂度:$O(N \\\\log N)$,其中 $N$ 是
\\nA
的长度。- 空间复杂度:$O(1)$,额外空间就是自带排序算法的空间。
\\n题目描述(简单难度)
\\n\\n
和 118 题 一样,依旧是杨辉三角。区别在于之前是输出所有层的数,这道题只需要输出第
\\nk
层的数。解法一
\\n和 118 题 一样,我们只需要一层一层的求。但是不需要把每一层的结果都保存起来,只需要保存上一层的结果,就可以求出当前层的结果了。
\\n###java
\\n\\npublic List<Integer> getRow(int rowIndex) {\\n List<Integer> pre = new ArrayList<>();\\n List<Integer> cur = new ArrayList<>();\\n for (int i = 0; i <= rowIndex; i++) {\\n cur = new ArrayList<>();\\n for (int j = 0; j <= i; j++) {\\n if (j == 0 || j == i) {\\n cur.add(1);\\n } else {\\n cur.add(pre.get(j - 1) + pre.get(j));\\n } \\n }\\n pre = cur;\\n }\\n return cur;\\n}\\n
参考 这里,其实我们可以优化一下,我们可以把
\\npre
的List
省去。这样的话,
\\ncur
每次不去新建List
,而是把cur
当作pre
。又因为更新当前
\\nj
的时候,就把之前j
的信息覆盖掉了。而更新j + 1
的时候又需要之前j
的信息,所以在更新前,我们需要一个变量把之前j
的信息保存起来。###java
\\n\\npublic List<Integer> getRow(int rowIndex) {\\n int pre = 1;\\n List<Integer> cur = new ArrayList<>();\\n cur.add(1);\\n for (int i = 1; i <= rowIndex; i++) {\\n for (int j = 1; j < i; j++) {\\n int temp = cur.get(j);\\n cur.set(j, pre + cur.get(j));\\n pre = temp;\\n }\\n cur.add(1);\\n }\\n return cur;\\n}\\n
区别在于我们用了
\\nset
函数来修改值,由于当前层比上一层多一个元素,所以对于最后一层的元素如果用set
方法的话会造成越界。此外,每层的第一个元素始终为1
。基于这两点,我们把之前j == 0 || j == i
的情况移到了for
循环外进行处理。除了上边优化的思路,还有一种想法,那就是倒着进行,这样就不会存在覆盖的情况了。
\\n因为更新完
\\nj
的信息后,虽然把j
之前的信息覆盖掉了。但是下一次我们更新的是j - 1
,需要的是j - 1
和j - 2
的信息,j
信息覆盖就不会造成影响了。###java
\\n\\npublic List<Integer> getRow(int rowIndex) {\\n int pre = 1;\\n List<Integer> cur = new ArrayList<>();\\n cur.add(1);\\n for (int i = 1; i <= rowIndex; i++) {\\n for (int j = i - 1; j > 0; j--) {\\n cur.set(j, cur.get(j - 1) + cur.get(j));\\n }\\n cur.add(1);//补上每层的最后一个 1 \\n }\\n return cur;\\n}\\n
解法二 公式法
\\n如果熟悉杨辉三角,应该记得杨辉三角其实可以看做由组合数构成。
\\n\\n
根据组合数的公式,将
\\n(n-k)!
约掉,化简就是下边的结果。$$C^k_n = n!/(k!(n-k)!) = (n*(n-1)(n-2)...(n-k+1))/k!$$
\\n然后我们就可以利用组合数解决这道题。
\\n###java
\\n\\npublic List<Integer> getRow(int rowIndex) {\\n List<Integer> ans = new ArrayList<>();\\n int N = rowIndex;\\n for (int k = 0; k <= N; k++) {\\n ans.add(Combination(N, k));\\n }\\n return ans;\\n}\\n\\nprivate int Combination(int N, int k) {\\n long res = 1;\\n for (int i = 1; i <= k; i++)\\n res = res * (N - k + i) / i;\\n return (int) res;\\n}\\n
参考 这里,我们可以优化一下。
\\n上边的算法对于每个组合数我们都重新求了一遍,但事实上前后的组合数其实是有联系的。
\\n$$C_n^k=C_n^{k-1}\\\\times(n-k+1)/k $$
\\n代码的话,我们只需要用
\\npre
变量保存上一次的组合数结果。计算过程中,可能越界,所以用到了long
。###java
\\n\\npublic List<Integer> getRow(int rowIndex) {\\n List<Integer> ans = new ArrayList<>();\\n int N = rowIndex;\\n long pre = 1;\\n ans.add(1);\\n for (int k = 1; k <= N; k++) {\\n long cur = pre * (N - k + 1) / k;\\n ans.add((int) cur);\\n pre = cur;\\n }\\n return ans;\\n}\\n
总
\\n这道题其实还是比较简单的,只是优化的两种方法是比较常用的,一种就是用
\\npre
变量将要被覆盖的变量存起来,另一种就是倒着进行。另外求组合数的时候,要防止int
的溢出。之前自己在博客总结的,更多题解可以在原地址 https://leetcode.wang。
\\n","description":"和 118 题 一样,依旧是杨辉三角。区别在于之前是输出所有层的数,这道题只需要输出第 k 层的数。 和 118 题 一样,我们只需要一层一层的求。但是不需要把每一层的结果都保存起来,只需要保存上一层的结果,就可以求出当前层的结果了。\\n\\n###java\\n\\npublic ListgetRow(int rowIndex) {\\n List pre = new ArrayList<>();\\n List cur = new ArrayList<>();\\n for (int i = 0; i…","guid":"https://leetcode.cn/problems/pascals-triangle-ii//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by--28","author":"windliang","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-08-04T08:33:08.794Z","media":[{"url":"https://pic.leetcode-cn.com/66f908ce6df80e85da35d94a6c2f2453309b482ae17e031fd8a772d67ef542d0.jpg","type":"photo"},{"url":"https://pic.leetcode-cn.com/195de01eae91e09de14dd13daafbef986c42345f2bdef405153a1742175079f4.jpg","type":"photo"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"分发糖果 (贪心思想,线性复杂度,清晰图解)","url":"https://leetcode.cn/problems/candy//solution/candy-cong-zuo-zhi-you-cong-you-zhi-zuo-qu-zui-da-","content":" 解题思路:
\\n\\n
\\n- 规则定义: 设学生 $A$ 和学生 $B$ 左右相邻,$A$ 在 $B$ 左边;\\n
\\n\\n
\\n- 左规则: 当 $ratings_B>ratings_A$时,$B$ 的糖比 $A$ 的糖数量多。
\\n- 右规则: 当 $ratings_A>ratings_B$时,$A$ 的糖比 $B$ 的糖数量多。
\\n\\n\\n相邻的学生中,评分高的学生必须获得更多的糖果 等价于 所有学生满足左规则且满足右规则。
\\n\\n
\\n- \\n
\\n算法流程:
\\n\\n
\\n- \\n
\\n先从左至右遍历学生成绩
\\nratings
,按照以下规则给糖,并记录在left
中:\\n
\\n- 先给所有学生 $1$ 颗糖;
\\n- 若 $ratings_i>ratings_{i-1}$,则第 $i$ 名学生糖比第 $i - 1$ 名学生多 $1$ 个。
\\n- 若 $ratings_i<=ratings_{i-1}$,则第 $i$ 名学生糖数量不变。(交由从右向左遍历时处理。)
\\n\\n
\\n- 经过此规则分配后,可以保证所有学生糖数量 满足左规则 。
\\n- \\n
\\n同理,在此规则下从右至左遍历学生成绩并记录在
\\nright
中,可以保证所有学生糖数量 满足右规则 。- \\n
\\n最终,取以上 $2$ 轮遍历
\\nleft
和right
对应学生糖果数的 最大值 ,这样则 同时满足左规则和右规则 ,即得到每个同学的最少糖果数量。- \\n
\\n复杂度分析:
\\n\\n
\\n- 时间复杂度 $O(N)$ : 遍历两遍数组即可得到结果;
\\n- 空间复杂度 $O(N)$ : 需要借用
\\nleft
,right
的线性额外空间。<
\\n,
,
,
,
,
,
,
,
,
,
>
代码:
\\n\\nclass Solution:\\n def candy(self, ratings: List[int]) -> int:\\n left = [1 for _ in range(len(ratings))]\\n right = left[:]\\n for i in range(1, len(ratings)):\\n if ratings[i] > ratings[i - 1]: left[i] = left[i - 1] + 1\\n count = left[-1]\\n for i in range(len(ratings) - 2, -1, -1):\\n if ratings[i] > ratings[i + 1]: right[i] = right[i + 1] + 1\\n count += max(left[i], right[i])\\n return count\\n
\\nclass Solution {\\n public int candy(int[] ratings) {\\n int[] left = new int[ratings.length];\\n int[] right = new int[ratings.length];\\n Arrays.fill(left, 1);\\n Arrays.fill(right, 1);\\n for(int i = 1; i < ratings.length; i++)\\n if(ratings[i] > ratings[i - 1]) left[i] = left[i - 1] + 1;\\n int count = left[ratings.length - 1];\\n for(int i = ratings.length - 2; i >= 0; i--) {\\n if(ratings[i] > ratings[i + 1]) right[i] = right[i + 1] + 1;\\n count += Math.max(left[i], right[i]);\\n }\\n return count;\\n }\\n}\\n
\\n\\n本学习计划配有代码仓,内含测试样例与数据结构封装,便于本地调试。可前往我的个人主页获取。
\\n","description":"解题思路: 规则定义: 设学生 $A$ 和学生 $B$ 左右相邻,$A$ 在 $B$ 左边;\\n左规则: 当 $ratings_B>ratings_A$时,$B$ 的糖比 $A$ 的糖数量多。\\n右规则: 当 $ratings_A>ratings_B$时,$A$ 的糖比 $B$ 的糖数量多。\\n\\n相邻的学生中,评分高的学生必须获得更多的糖果 等价于 所有学生满足左规则且满足右规则。\\n\\n算法流程:\\n\\n先从左至右遍历学生成绩 ratings,按照以下规则给糖,并记录在 left 中:\\n\\n先给所有学生 $1$ 颗糖;\\n若 $ratings_i…","guid":"https://leetcode.cn/problems/candy//solution/candy-cong-zuo-zhi-you-cong-you-zhi-zuo-qu-zui-da-","author":"jyd","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-08-01T02:14:37.572Z","media":[{"url":"https://pic.leetcode-cn.com/d86caec88575aa1cd162c76401b3cc67f25105c178b9f99c51fdd34d877413d7-Picture1.png","type":"photo","width":1403,"height":824,"blurhash":"L00vSMj]WDj[obfQj[j@Inayj?a{"},{"url":"https://pic.leetcode-cn.com/03f9b526fb71f0f06b98c2a6b68ba1032fa377c8847fa8879919d50f684786f2-Picture2.png","type":"photo","width":1403,"height":824,"blurhash":"L00]Xjj[R+a{xbaykCayEKofoJj["},{"url":"https://pic.leetcode-cn.com/a86dffd2c9800768229d87a3bdf4d0a3a3b71098faee7c85bdff68413ae46914-Picture3.png","type":"photo","width":1403,"height":824,"blurhash":"L012{IR*WBk9s;j[bHaxI.oMofj]"},{"url":"https://pic.leetcode-cn.com/62ea88df951fd517365a0cb131ecb3268c1e96f7daaa71f63e888fa6b763cd1c-Picture4.png","type":"photo","width":1403,"height":824,"blurhash":"L01Cc-oKWXWVxbs:j]WUI.j]oea|"},{"url":"https://pic.leetcode-cn.com/7f03142760a933dae495e460bb64bc0f7cc88c195d83d14579734880d189***-Picture5.png","type":"photo"},{"url":"https://pic.leetcode-cn.com/239ed1c324499af7774a0086d32f2ba7a95de58ebad605f29c5c2574cb9c344f-Picture6.png","type":"photo","width":1403,"height":824,"blurhash":"L01Cc,%2Rjog$+xas:ofSgofxbof"},{"url":"https://pic.leetcode-cn.com/a19a2034c7ff1e2b8255338732a3f5417a5e2ed8aa2e672b2cd083fb2776ce5a-Picture7.png","type":"photo","width":1403,"height":824,"blurhash":"L01L}X$*NGog-VxabHj]Shogs;bF"},{"url":"https://pic.leetcode-cn.com/6cc40c14fa425f8aa7e771f19c61c487c1eef78efc675b029716998cb92645c9-Picture8.png","type":"photo","width":1403,"height":824,"blurhash":"L01L}X$*NGog-VxabHj]Shogs;ax"},{"url":"https://pic.leetcode-cn.com/1312e225c83d51fadf246a9683b7c169fa3993bd169016ce93323466c85bfa1b-Picture9.png","type":"photo","width":1403,"height":824,"blurhash":"L01L}X$*NGog-VxabHj]Shogs:a{"},{"url":"https://pic.leetcode-cn.com/164a4bae4eacfcbd0d5bad0aaf11f215fb2847470f6cb1d5eaca2d8b13c1dc61-Picture10.png","type":"photo","width":1403,"height":824,"blurhash":"L01fC{%MIoo#?HxbV[ozS5n+niWU"},{"url":"https://pic.leetcode-cn.com/9e026380b05a72950a2056d6db588600f60701ec563da72e59fa1f8d6a810c95-Picture11.png","type":"photo","width":1403,"height":824,"blurhash":"L00+|Go$RhkDpLo$acogo$bJjrog"},{"url":"https://pic.leetcode.cn/1692032516-LSqzdC-760_100_3.png","type":"photo","width":1520,"height":200,"blurhash":"LsB#fFa$M{ogpMj]t7fkIafkofay"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"等价多米诺骨牌","url":"https://leetcode.cn/problems/number-of-equivalent-domino-pairs//solution/deng-jie-duo-mi-nuo-gu-pai-by-coldme-2","content":"暴力法
\\n\\n
\\n- 非常自然的思路:令
\\npairNum
表示匹配数目,我们自前向后双重循环,每遇到匹配的牌,pairNum
就加 $1$。最后返回pairNum
。- 不分好差情况,时间复杂度都是 $O(n2)$;空间复杂度是 $O(1)$。
\\n- 但答案没通过,会超时。
\\n<
\\n,
,
,
,
,
>
\\nclass Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n \\n if len(dominoes) == 0 or len(dominoes[0]) == 0:\\n return 0\\n \\n pairNum = 0\\n \\n for i in range(len(dominoes)-1):\\n for j in range(i+1, len(dominoes)):\\n if dominoes[i][0] == dominoes[j][0] and dominoes[i][1] == dominoes[j][1]:\\n pairNum += 1\\n elif dominoes[i][0] == dominoes[j][1] and dominoes[i][1] == dominoes[j][0]:\\n pairNum += 1\\n return pairNum\\n
删除法
\\n\\n
\\n- 我们想想会发现,等价具有传递性,即如果
\\na=b
,a=c
,那么b=c
。- 暴力法耗时长是因为,在它知道
\\na=b
,a=c
之后,还去比较b
和c
是否等价。而如果我们把a,b
和c
看成一组,就可以省去不必要的比较;$3$ 张牌互为等价,对pairNum
的贡献是3*(3-1)/2=3
张。- 所以在暴力法的基础上我做了小改进。对于每张牌,令
\\ncnt
为 $0$,自后向前找它的匹配牌,每找到一个就再列表里删除一个,并让cnt+1
。然后令pairNum += cnt*(cnt-1)/2
。再开始找下一张牌的匹配牌。- 这里删除操作是为了减小后面的遍历操作次数;而选择自后向前,是为了使得在删除操作之后索引值不混乱。
\\n<
\\n,
,
,
,
,
,
>
\\nclass Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n \\n if len(dominoes) == 0 or len(dominoes[0]) == 0:\\n return 0\\n \\n pairNum = 0\\n \\n for i in range(len(dominoes)-1):\\n cnt = 0\\n if i >= len(dominoes):# 因为我们有删除操作,所以dominoes的长度小了,i会超出索引值\\n break\\n for j in range(len(dominoes)-1, i, -1):# 从最后一张牌向前遍历,直到i+1\\n if dominoes[i][0] == dominoes[j][0] and dominoes[i][1] == dominoes[j][1]:\\n cnt += 1\\n del dominoes[j]\\n elif dominoes[i][0] == dominoes[j][1] and dominoes[i][1] == dominoes[j][0]:\\n cnt += 1\\n del dominoes[j]\\n pairNum += int(cnt*(cnt+1)/2)\\n return pairNum\\n \\n
hashmap
\\n\\n
\\n- 设计一种
\\nlist->int
映射方式,较小的数字作十位,较大的数字作个位。比如,(1, 2)
和(2, 1)
都会映射为数字 $12$。这样一来,等价的骨牌都会有相同的映射。- 通过一次遍历,用字典来记录{映射结果:出现次数}。
\\n- 最后,遍历字典计算次数。代码给出了两种方法,一种是跟上面的一样,求
\\nk*(k-1)/2
;另一种是实时计算:当你遇到(1, 2)
时,如果 $12$ 之前出现了 $m$ 次,那么新加入的 $12$ 就能再多贡献 $m$ 个匹配对数。- 时间复杂度:$O(n)$
\\n- 空间复杂度:$O(n)$
\\n这道题的核心就是要想到:将等价牌映射到同一数值
\\n<
\\n,
,
,
,
,
>
\\nclass Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n ans = 0\\n d = collections.defaultdict(int) \\n # step1 \\n for i,j in dominoes:\\n num = 10 * i + j if i < j else 10 * j+ i\\n d[num] += 1\\n # step2\\n for k in d.values():\\n ans += int(k*(k-1)/2)\\n return ans\\n
\\n","description":"暴力法 非常自然的思路:令 pairNum 表示匹配数目,我们自前向后双重循环,每遇到匹配的牌,pairNum 就加 $1$。最后返回 pairNum。\\n不分好差情况,时间复杂度都是 $O(n2)$;空间复杂度是 $O(1)$。\\n但答案没通过,会超时。\\n\\n<,,,,,>\\n\\nclass Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n \\n if len(dominoes) == 0 or len(dominoes[0])…","guid":"https://leetcode.cn/problems/number-of-equivalent-domino-pairs//solution/deng-jie-duo-mi-nuo-gu-pai-by-coldme-2","author":"coldme-2","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-07-27T01:46:00.605Z","media":[{"url":"https://pic.leetcode-cn.com/7280293bd5c51f59b1ee68ccbb7f93f8976b7273034183cb778573333e74fbf1-IMG_0220.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LBRp2l^*xa~q.7WAsls:%hX9WBV@"},{"url":"https://pic.leetcode-cn.com/7d894f5784f8fd0b941f91825a795c7c23223eaae39fb28e41555159e7009d81-IMG_0221.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rp2l~VtQ_N?baxs8jF.8NyWBsm"},{"url":"https://pic.leetcode-cn.com/e33032bec152f0bd42956ae97233dc9f2651fbb9ce02c9e38c70476570b64717-IMG_0222.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LBRp2l~VR%~q.7oIV@s:%hNes:V@"},{"url":"https://pic.leetcode-cn.com/f39e4a956eeb412839275b2d4ba59acfbeea8de252e085935e48a1b3e172e438-IMG_0223.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LARymL_2kC~q?bWBjEsn%hSijZni"},{"url":"https://pic.leetcode-cn.com/d1325219b8340814dc91f7b413c4255266c014f9cc033b2b2945f7f88ff03bcd-IMG_0225.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LCRp2l_3S%_N-:kWWXs:%NNws9aJ"},{"url":"https://pic.leetcode-cn.com/93e6a61ee8045da66d90522c0b8e195b1c5d62e8996f81e6ec77a2a9889937a6-IMG_0227.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9RyjE~qb_~p?bXSoMr=%NS1nhnl"},{"url":"https://pic.leetcode-cn.com/2b3c8f57363f64cdc472a68942d0511196deb6bec5cf6f497189614cafe57aa4-IMG_0228.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rfg~~pxa_Mx]ofxaoLtSNaxts:"},{"url":"https://pic.leetcode-cn.com/690854a5fe2bc520722c0096effdbbaac06cc5dce4e5ff5ad669be2c42f3a6ef-IMG_0231.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L8RW0X~q?H~q%ga|%2WVx]WVWBs:"},{"url":"https://pic.leetcode-cn.com/e113a4713e40a36dc291fbcc87e059219ec4dd726ce5fc7d0cacb27748dde708-IMG_0232.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9RW0X~W-o_2x]oIxZR%tRogR-NH"},{"url":"https://pic.leetcode-cn.com/2190380106743a8379090614dfe8deb15976e30c5fb9f407ca8ce93e63b5d84c-IMG_0233.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LARW0W~VxZ_3x[s+s-s:tStRNHWC"},{"url":"https://pic.leetcode-cn.com/aa30ec1da6e691d1a51cd65ae2ceb176366207b862f8f20e6d848da3b9f5701e-IMG_0234.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rfk7~q-o~q?bkCt6ofW;Rjt6ax"},{"url":"https://pic.leetcode-cn.com/58434859acf6fe26d6312d0cd4a6c8aa8436e63a624d6bbda69827052cd90037-IMG_0235.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LBRfk6~Wxu_4x]oct7t8o#NHs.n#"},{"url":"https://pic.leetcode-cn.com/01a97c988713cf105d061619535d3bf561b98ad628f92b2b112666a965afc084-IMG_0236.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rfk7~p-p~q?bR*xat7WVWCs:Rj"},{"url":"https://pic.leetcode-cn.com/26ba053c41fe089494b824cf2429c70d36c0f7ab9148464260390c5f15141f8a-IMG_0241.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rp2m_Nt7_2.SV@oLX8tlV@jZf+"},{"url":"https://pic.leetcode-cn.com/71bd57c9f890a0e9c976aa3792d6f6d06f1a827ec25b0a9a101a4f85ec45caa4-IMG_0242.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L8Rp2l~ps%~V?uVsnhWUt-WGkEg4"},{"url":"https://pic.leetcode-cn.com/d60457c2b3558436b4ad099df96e17f4ed14f9b42769c851b7f114b64bdc1f31-IMG_0243.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rp2l_Mo#_3.RV@oMX9t,WDjYfh"},{"url":"https://pic.leetcode-cn.com/3a809c790010fc06ae85f1d11dfd8530644da13452ac235f54004c67a35d2d85-IMG_0244.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L9Rp2l_No~^+.8V[ogW.t,aKnhkW"},{"url":"https://pic.leetcode-cn.com/1d7ae13907b2f87e78244238173f53065d4faa61c941cd249d2f57eb40ff3245-IMG_0245.PNG","type":"photo","width":2048,"height":1536,"blurhash":"LBRp2l?^od?a.7ago0bHtmaJoMkD"},{"url":"https://pic.leetcode-cn.com/7ccf35397d2a1844a296f94e01bdb6dc0a083ab29597e7762a9191711d06eb28-IMG_0246.PNG","type":"photo","width":2048,"height":1536,"blurhash":"L7Rp5u~qoz~q?vaysAWBx]jaofbb"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"回溯算法 + 剪枝(Java、Python)","url":"https://leetcode.cn/problems/combination-sum-ii//solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3","content":"class Solution:\\n def numEquivDominoPairs(self, dominoes: List[List[int]]) -> int:\\n ans = 0\\n d = collections.defaultdict(int) \\n for i,j in dominoes:\\n num = 10 * i + j if i < j else 10 * j + i\\n ans += d[num] # 原来num出现了几次,那么新加入1个num就能新组成几个匹配对\\n d[num] += 1\\n return ans\\n
解题思路:
\\n一句话题解:按顺序搜索,设置合理的变量,在搜索的过程中判断是否会出现重复集结果。重点理解对输入数组排序的作用和 参考代码 中大剪枝和小剪枝 的意思。
\\n
\\n与第 39 题(组合之和)的差别
\\n这道题与上一问的区别在于:
\\n\\n
\\n- 第 39 题:
\\ncandidates
中的数字可以无限制重复被选取;- 第 40 题:
\\ncandidates
中的每个数字在每个组合中只能使用一次。相同点是:相同数字列表的不同排列视为一个结果。
\\n如何去掉重复的集合(重点)
\\n为了使得解集不包含重复的组合。有以下 $2$ 种方案:
\\n\\n
\\n- 使用 哈希表 天然的去重功能,但是编码相对复杂;
\\n- 这里我们使用和第 39 题和第 15 题(三数之和)类似的思路:不重复就需要按 顺序 搜索, 在搜索的过程中检测分支是否会出现重复结果 。注意:这里的顺序不仅仅指数组
\\ncandidates
有序,还指按照一定顺序搜索结果。\\n
{:width=600}
\\n{:align=center}\\n
{:width=600}
\\n{:align=center}由第 39 题我们知道,数组
\\ncandidates
有序,也是 深度优先遍历 过程中实现「剪枝」的前提。
\\n将数组先排序的思路来自于这个问题:去掉一个数组中重复的元素。很容易想到的方案是:先对数组 升序 排序,重复的元素一定不是排好序以后相同的连续数组区域的第 $1$ 个元素。也就是说,剪枝发生在:同一层数值相同的结点第 $2$、$3$ ... 个结点,因为数值相同的第 $1$ 个结点已经搜索出了包含了这个数值的全部结果,同一层的其它结点,候选数的个数更少,搜索出的结果一定不会比第 $1$ 个结点更多,并且是第 $1$ 个结点的子集。(说明:这段文字很拗口,大家可以结合具体例子,在纸上写写画画进行理解。)说明:
\\n\\n
\\n- 解决这个问题可能需要解决 第 15 题(三数之和)、 第 47 题(全排列 II)、 第 39 题(组合之和)的经验;
\\n- 对于如何去重还不太清楚的朋友,可以参考当前题解的 高赞置顶评论 。
\\n感谢用户 @rmokerone 提供的 C++ 版本的参考代码。
\\n参考代码 1:
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Arrays;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n public List<List<Integer>> combinationSum2(int[] candidates, int target) {\\n int len = candidates.length;\\n List<List<Integer>> res = new ArrayList<>();\\n if (len == 0) {\\n return res;\\n }\\n\\n // 关键步骤\\n Arrays.sort(candidates);\\n\\n Deque<Integer> path = new ArrayDeque<>(len);\\n dfs(candidates, len, 0, target, path, res);\\n return res;\\n }\\n\\n /**\\n * @param candidates 候选数组\\n * @param len 冗余变量\\n * @param begin 从候选数组的 begin 位置开始搜索\\n * @param target 表示剩余,这个值一开始等于 target,基于题目中说明的\\"所有数字(包括目标数)都是正整数\\"这个条件\\n * @param path 从根结点到叶子结点的路径\\n * @param res\\n */\\n private void dfs(int[] candidates, int len, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {\\n if (target == 0) {\\n res.add(new ArrayList<>(path));\\n return;\\n }\\n for (int i = begin; i < len; i++) {\\n // 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break\\n if (target - candidates[i] < 0) {\\n break;\\n }\\n\\n // 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue\\n if (i > begin && candidates[i] == candidates[i - 1]) {\\n continue;\\n }\\n\\n path.addLast(candidates[i]);\\n // 调试语句 ①\\n // System.out.println(\\"递归之前 => \\" + path + \\",剩余 = \\" + (target - candidates[i]));\\n\\n // 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i\\n dfs(candidates, len, i + 1, target - candidates[i], path, res);\\n\\n path.removeLast();\\n // 调试语句 ②\\n // System.out.println(\\"递归之后 => \\" + path + \\",剩余 = \\" + (target - candidates[i]));\\n }\\n }\\n\\n public static void main(String[] args) {\\n int[] candidates = new int[]{10, 1, 2, 7, 6, 1, 5};\\n int target = 8;\\n Solution solution = new Solution();\\n List<List<Integer>> res = solution.combinationSum2(candidates, target);\\n System.out.println(\\"输出 => \\" + res);\\n }\\n}\\n
###Python
\\n\\nfrom typing import List\\n\\n\\nclass Solution:\\n\\n def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:\\n def dfs(begin, path, residue):\\n if residue == 0:\\n res.append(path[:])\\n return\\n\\n for index in range(begin, size):\\n if candidates[index] > residue:\\n break\\n\\n if index > begin and candidates[index - 1] == candidates[index]:\\n continue\\n\\n path.append(candidates[index])\\n dfs(index + 1, path, residue - candidates[index])\\n path.pop()\\n\\n size = len(candidates)\\n if size == 0:\\n return []\\n\\n candidates.sort()\\n res = []\\n dfs(0, [], target)\\n return res\\n
###C++
\\n\\n// author:rmokerone\\n#include <iostream>\\n#include <vector>\\n\\nusing namespace std;\\n\\nclass Solution {\\n\\nprivate:\\n vector<int> candidates;\\n vector<vector<int>> res;\\n vector<int> path;\\npublic:\\n void DFS(int start, int target) {\\n if (target == 0) {\\n res.push_back(path);\\n return;\\n }\\n\\n for (int i = start; i < candidates.size() && target - candidates[i] >= 0; i++) {\\n if (i > start && candidates[i] == candidates[i - 1])\\n continue;\\n path.push_back(candidates[i]);\\n // 元素不可重复利用,使用下一个即i+1\\n DFS(i + 1, target - candidates[i]);\\n path.pop_back();\\n }\\n }\\n\\n vector<vector<int>> combinationSum2(vector<int> &candidates, int target) {\\n sort(candidates.begin(), candidates.end());\\n this->candidates = candidates;\\n DFS(0, target);\\n return res;\\n }\\n};\\n
打开上面的调试语句(Java 版代码),针对输入
\\nint[] candidates = new int[]{10, 1, 2, 7, 6, 1, 5};
和int target = 8;
控制台输出如下:\\n","description":"解题思路: 一句话题解:按顺序搜索,设置合理的变量,在搜索的过程中判断是否会出现重复集结果。重点理解对输入数组排序的作用和 参考代码 中大剪枝和小剪枝 的意思。\\n\\n与第 39 题(组合之和)的差别\\n\\n这道题与上一问的区别在于:\\n\\n第 39 题:candidates 中的数字可以无限制重复被选取;\\n第 40 题:candidates 中的每个数字在每个组合中只能使用一次。\\n\\n相同点是:相同数字列表的不同排列视为一个结果。\\n\\n如何去掉重复的集合(重点)\\n\\n为了使得解集不包含重复的组合。有以下 $2$ 种方案:\\n\\n使用 哈希表 天然的去重功能,但是编码相对复杂;\\n这…","guid":"https://leetcode.cn/problems/combination-sum-ii//solution/hui-su-suan-fa-jian-zhi-python-dai-ma-java-dai-m-3","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-07-16T11:54:21.889Z","media":[{"url":"https://pic.leetcode-cn.com/1599718525-iXEiiy-image.png","type":"photo","width":1638,"height":1366,"blurhash":"LARo~k%eyD~q%$b]M|oJ$yQ-M|R%"},{"url":"https://pic.leetcode-cn.com/1599716342-gGiISM-image.png","type":"photo","width":2076,"height":808,"blurhash":"LARfd{~Drs_3*0KQOZo#-p%Mxat7"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"画解算法:219. 存在重复元素 II","url":"https://leetcode.cn/problems/contains-duplicate-ii//solution/hua-jie-suan-fa-219-cun-zai-zhong-fu-yuan-su-ii-by","content":"递归之前 => [1],剩余 = 7\\n递归之前 => [1, 1],剩余 = 6\\n递归之前 => [1, 1, 2],剩余 = 4\\n递归之后 => [1, 1],剩余 = 4\\n递归之前 => [1, 1, 5],剩余 = 1\\n递归之后 => [1, 1],剩余 = 1\\n递归之前 => [1, 1, 6],剩余 = 0\\n递归之后 => [1, 1],剩余 = 0\\n递归之后 => [1],剩余 = 6\\n递归之前 => [1, 2],剩余 = 5\\n递归之前 => [1, 2, 5],剩余 = 0\\n递归之后 => [1, 2],剩余 = 0\\n递归之后 => [1],剩余 = 5\\n递归之前 => [1, 5],剩余 = 2\\n递归之后 => [1],剩余 = 2\\n递归之前 => [1, 6],剩余 = 1\\n递归之后 => [1],剩余 = 1\\n递归之前 => [1, 7],剩余 = 0\\n递归之后 => [1],剩余 = 0\\n递归之后 => [],剩余 = 7\\n递归之前 => [2],剩余 = 6\\n递归之前 => [2, 5],剩余 = 1\\n递归之后 => [2],剩余 = 1\\n递归之前 => [2, 6],剩余 = 0\\n递归之后 => [2],剩余 = 0\\n递归之后 => [],剩余 = 6\\n递归之前 => [5],剩余 = 3\\n递归之后 => [],剩余 = 3\\n递归之前 => [6],剩余 = 2\\n递归之后 => [],剩余 = 2\\n递归之前 => [7],剩余 = 1\\n递归之后 => [],剩余 = 1\\n输出 => [[1, 1, 6], [1, 2, 5], [1, 7], [2, 6]]\\n
解题方案
\\n思路
\\n\\n
\\n- 标签:哈希
\\n- 维护一个哈希表,里面始终最多包含
\\nk
个元素,当出现重复值时则说明在k
距离内存在重复元素- 每次遍历一个元素则将其加入哈希表中,如果哈希表的大小大于
\\nk
,则移除最前面的数字- 时间复杂度:$O(n)$,$n$ 为数组长度
\\n代码
\\n###Java
\\n\\nclass Solution {\\n public boolean containsNearbyDuplicate(int[] nums, int k) {\\n HashSet<Integer> set = new HashSet<>();\\n for(int i = 0; i < nums.length; i++) {\\n if(set.contains(nums[i])) {\\n return true;\\n }\\n set.add(nums[i]);\\n if(set.size() > k) {\\n set.remove(nums[i - k]);\\n }\\n }\\n return false;\\n }\\n}\\n
###JavaScript
\\n\\n/**\\n * @param {number[]} nums\\n * @param {number} k\\n * @return {boolean}\\n */\\nvar containsNearbyDuplicate = function(nums, k) {\\n const set = new Set();\\n for(let i = 0; i < nums.length; i++) {\\n if(set.has(nums[i])) {\\n return true;\\n }\\n set.add(nums[i]);\\n if(set.size > k) {\\n set.delete(nums[i - k]);\\n }\\n }\\n return false;\\n};\\n
画解
\\n<
\\n,
,
,
,
,
>
想看大鹏画解更多高频面试题,欢迎阅读大鹏的 LeetBook:《画解剑指 Offer 》,O(∩_∩)O
\\n","description":"解题方案 思路\\n标签:哈希\\n维护一个哈希表,里面始终最多包含 k 个元素,当出现重复值时则说明在 k 距离内存在重复元素\\n每次遍历一个元素则将其加入哈希表中,如果哈希表的大小大于 k,则移除最前面的数字\\n时间复杂度:$O(n)$,$n$ 为数组长度\\n代码\\n\\n###Java\\n\\nclass Solution {\\n public boolean containsNearbyDuplicate(int[] nums, int k) {\\n HashSetset = new HashSet<>();\\n for…","guid":"https://leetcode.cn/problems/contains-duplicate-ii//solution/hua-jie-suan-fa-219-cun-zai-zhong-fu-yuan-su-ii-by","author":"guanpengchn","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-07-10T09:59:52.191Z","media":[{"url":"https://pic.leetcode-cn.com/932bce02af68cb2b2c630dbe555d298e7ffeb42becbc5be1c1b4654a6d084956-1.png","type":"photo","width":1280,"height":679,"blurhash":"LxO|eA004mD%xuWBWBazWBj[j[j@"},{"url":"https://pic.leetcode-cn.com/ab0688468085573db2d3d185ba1fa597de547661db5bd25d1717daa729f4392d-2.png","type":"photo","width":1280,"height":679,"blurhash":"LxO|b30000D%xuWBWBazaefkoLaz"},{"url":"https://pic.leetcode-cn.com/bda226c4009e410876020b18cd516211b7ade11a25b159cb5c700e92cb96fcb5-3.png","type":"photo","width":1280,"height":679,"blurhash":"LwO|b30000D%xuWBWBazWBj[j[ay"},{"url":"https://pic.leetcode-cn.com/2c30c9b9d3663214071943fcabf4644ddc9ca4be0ada2f54f45ec5530c1f594b-4.png","type":"photo","width":1280,"height":679,"blurhash":"LwO|b30000D%xuWBWBj@WBj[j[ay"},{"url":"https://pic.leetcode-cn.com/337a9ad9e0e0e5ef29a516bbcef27c9ef6c4cb5ddd67f8ccee72fe1a56e5f1f6-5.png","type":"photo","width":1280,"height":679,"blurhash":"L?ON8,004nRjt7WBWBfkbHayj[bH"},{"url":"https://pic.leetcode-cn.com/4df77fd07c73e4a5ac4217761ae5d3511a852e513e12d1079de6012b1fa89655-6.png","type":"photo","width":1280,"height":720,"blurhash":"L00000a|azjta|fQjtfQWWjtj@a|"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"59. 螺旋矩阵 II(模拟,清晰图解)","url":"https://leetcode.cn/problems/spiral-matrix-ii//solution/spiral-matrix-ii-mo-ni-fa-she-ding-bian-jie-qing-x","content":" 解题思路:
\\n初始化一个
\\nn×n
大小的矩阵mat
,然后模拟整个向内环绕的填入过程:\\n
\\n- 定义当前左右上下边界
\\nl,r,t,b
,初始值num = 1
,迭代终止值tar = n * n
;- 当
\\nnum <= tar
时,始终按照从左到右
从上到下
从右到左
从下到上
填入顺序循环,每次填入后:\\n\\n
\\n- 执行
\\nnum += 1
:得到下一个需要填入的数字;- 更新边界:例如从左到右填完后,上边界
\\nt += 1
,相当于上边界向内缩 1。- 使用
\\nnum <= tar
而不是l < r || t < b
作为迭代条件,是为了解决当n
为奇数时,矩阵中心数字无法在迭代过程中被填充的问题。- 最终返回
\\nmat
即可。\\n
{:width=500}
代码:
\\n\\nclass Solution {\\n public int[][] generateMatrix(int n) {\\n int l = 0, r = n - 1, t = 0, b = n - 1;\\n int[][] mat = new int[n][n];\\n int num = 1, tar = n * n;\\n while(num <= tar){\\n for(int i = l; i <= r; i++) mat[t][i] = num++; // left to right.\\n t++;\\n for(int i = t; i <= b; i++) mat[i][r] = num++; // top to bottom.\\n r--;\\n for(int i = r; i >= l; i--) mat[b][i] = num++; // right to left.\\n b--;\\n for(int i = b; i >= t; i--) mat[i][l] = num++; // bottom to top.\\n l++;\\n }\\n return mat;\\n }\\n}\\n
\\nclass Solution:\\n def generateMatrix(self, n: int) -> [[int]]:\\n l, r, t, b = 0, n - 1, 0, n - 1\\n mat = [[0 for _ in range(n)] for _ in range(n)]\\n num, tar = 1, n * n\\n while num <= tar:\\n for i in range(l, r + 1): # left to right\\n mat[t][i] = num\\n num += 1\\n t += 1\\n for i in range(t, b + 1): # top to bottom\\n mat[i][r] = num\\n num += 1\\n r -= 1\\n for i in range(r, l - 1, -1): # right to left\\n mat[b][i] = num\\n num += 1\\n b -= 1\\n for i in range(b, t - 1, -1): # bottom to top\\n mat[i][l] = num\\n num += 1\\n l += 1\\n return mat\\n
\\n\\n本学习计划配有代码仓,内含测试样例与数据结构封装,便于本地调试。可前往我的个人主页获取。
\\n","description":"解题思路: 初始化一个 n×n 大小的矩阵 mat,然后模拟整个向内环绕的填入过程:\\n\\n定义当前左右上下边界 l,r,t,b,初始值 num = 1,迭代终止值 tar = n * n;\\n当 num <= tar 时,始终按照 从左到右 从上到下 从右到左 从下到上 填入顺序循环,每次填入后:\\n执行 num += 1:得到下一个需要填入的数字;\\n更新边界:例如从左到右填完后,上边界 t += 1,相当于上边界向内缩 1。\\n使用num <= tar而不是l < r || t < b作为迭代条件,是为了解决当n为奇数时…","guid":"https://leetcode.cn/problems/spiral-matrix-ii//solution/spiral-matrix-ii-mo-ni-fa-she-ding-bian-jie-qing-x","author":"jyd","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-07-05T08:30:23.478Z","media":[{"url":"https://pic.leetcode-cn.com/ccff416fa39887c938d36fec8e490e1861813d3bba7836eda941426f13420759-Picture1.png","type":"photo","width":1070,"height":714,"blurhash":"L36[2GIU4oD%00M{t6Mx-:RPRjM{"},{"url":"https://pic.leetcode.cn/1692032516-LSqzdC-760_100_3.png","type":"photo","width":1520,"height":200,"blurhash":"LsB#fFa$M{ogpMj]t7fkIafkofay"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"动态规划(转换为 0-1 背包问题)","url":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo","content":"关于背包问题的介绍,大家可以在互联网上搜索「背包九讲」进行学习,其中「0-1」背包问题是这些问题的基础。「力扣」上涉及的背包问题有「0-1」背包问题、完全背包问题、多重背包问题。
\\n本题解有些地方使用了「0-1」背包问题的描述,因此会不加解释的使用「背包」、「容量」这样的名词。
\\n说明:这里感谢很多朋友在这篇题解下提出的建议,对我的启发很大。本题解的阅读建议是:先浏览代码,然后再看代码之前的分析,能更有效理解知识点和整个问题的思考路径。题解后也增加了「总结」,供大家参考。
\\n
\\n转换为 「0 - 1」 背包问题
\\n这道问题是我学习「背包」问题的入门问题,做这道题需要做一个等价转换:是否可以从输入数组中挑选出一些正整数,使得这些数的和 等于 整个数组元素的和的一半。很坦白地说,如果不是我的老师告诉我可以这样想,我很难想出来。容易知道:数组的和一定得是偶数。
\\n本题与 0-1 背包问题有一个很大的不同,即:
\\n\\n
\\n- 0-1 背包问题选取的物品的容积总量 不能超过 规定的总量;
\\n- 本题选取的数字之和需要 恰好等于 规定的和的一半。
\\n这一点区别,决定了在初始化的时候,所有的值应该初始化为
\\nfalse
。 (《背包九讲》的作者在介绍 「0-1 背包」问题的时候,有强调过这点区别。)「0 - 1」 背包问题的思路
\\n作为「0-1 背包问题」,它的特点是:「每个数只能用一次」。解决的基本思路是:物品一个一个选,容量也一点一点增加去考虑,这一点是「动态规划」的思想,特别重要。
\\n
\\n在实际生活中,我们也是这样做的,一个一个地尝试把候选物品放入「背包」,通过比较得出一个物品要不要拿走。具体做法是:画一个
\\nlen
行,target + 1
列的表格。这里len
是物品的个数,target
是背包的容量。len
行表示一个一个物品考虑,target + 1
多出来的那1
列,表示背包容量从0
开始考虑。很多时候,我们需要考虑这个容量为0
的数值。状态与状态转移方程
\\n\\n
\\n- 状态定义:
\\ndp[i][j]
表示从数组的[0, i]
这个子区间内挑选一些正整数,每个数只能用一次,使得这些数的和恰好等于j
。- 状态转移方程:很多时候,状态转移方程思考的角度是「分类讨论」,对于「0-1 背包问题」而言就是「当前考虑到的数字选与不选」。\\n
\\n\\n
\\n- 不选择
\\nnums[i]
,如果在[0, i - 1]
这个子区间内已经有一部分元素,使得它们的和为j
,那么dp[i][j] = true
;- 选择
\\nnums[i]
,如果在[0, i - 1]
这个子区间内就得找到一部分元素,使得它们的和为j - nums[i]
。状态转移方程:
\\n###java
\\n\\ndp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i]]\\n
一般写出状态转移方程以后,就需要考虑初始化条件。
\\n\\n
\\n- \\n
j - nums[i]
作为数组的下标,一定得保证大于等于0
,因此nums[i] <= j
;- 注意到一种非常特殊的情况:
\\nj
恰好等于nums[i]
,即单独nums[i]
这个数恰好等于此时「背包的容积」j
,这也是符合题意的。因此完整的状态转移方程是:
\\n$$
\\n
\\n\\\\text{dp}[i][j]=
\\n\\\\begin{cases}
\\n\\\\text{dp}[i - 1][j], & 至少是这个答案,如果 \\\\ \\\\text{dp}[i - 1][j] \\\\ 为真,直接计算下一个状态 \\\\
\\n\\\\text{true}, & \\\\text{nums[i] = j} \\\\
\\n\\\\text{dp}[i - 1][j - nums[i]]. & \\\\text{nums[i] < j}
\\n\\\\end{cases}
\\n$$说明:虽然写成花括号,但是它们的关系是 或者 。
\\n\\n
\\n- 初始化:
\\ndp[0][0] = false
,因为候选数nums[0]
是正整数,凑不出和为 $0$;- 输出:
\\ndp[len - 1][target]
,这里len
表示数组的长度,target
是数组的元素之和(必须是偶数)的一半。说明:
\\n\\n
\\n- 事实上
\\ndp[0][0] = true
也是可以的,相应地状态转移方程有所变化,请见下文;- 如果觉得这个初始化非常难理解,解释性差的朋友,我个人觉得可以不用具体解释它的意义,初始化的值保证状态转移能够正确完成即可。
\\n参考代码 1:
\\n###Java
\\n\\npublic class Solution {\\n\\n public boolean canPartition(int[] nums) {\\n int len = nums.length;\\n // 题目已经说非空数组,可以不做非空判断\\n int sum = 0;\\n for (int num : nums) {\\n sum += num;\\n }\\n // 特判:如果是奇数,就不符合要求\\n if ((sum & 1) == 1) {\\n return false;\\n }\\n\\n int target = sum / 2;\\n // 创建二维状态数组,行:物品索引,列:容量(包括 0)\\n boolean[][] dp = new boolean[len][target + 1];\\n\\n // 先填表格第 0 行,第 1 个数只能让容积为它自己的背包恰好装满\\n if (nums[0] <= target) {\\n dp[0][nums[0]] = true;\\n }\\n // 再填表格后面几行\\n for (int i = 1; i < len; i++) {\\n for (int j = 0; j <= target; j++) {\\n // 直接从上一行先把结果抄下来,然后再修正\\n dp[i][j] = dp[i - 1][j];\\n\\n if (nums[i] == j) {\\n dp[i][j] = true;\\n continue;\\n }\\n if (nums[i] < j) {\\n dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];\\n }\\n }\\n }\\n return dp[len - 1][target];\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(NC)$:这里 $N$ 是数组元素的个数,$C$ 是数组元素的和的一半。
\\n- 空间复杂度:$O(NC)$。
\\n
\\n解释设置
\\ndp[0][0] = true
的合理性(重点)修改状态数组初始化的定义:
\\ndp[0][0] = true
。考虑容量为 $0$ 的时候,即dp[i][0]
。按照本意来说,应该设置为false
,但是注意到状态转移方程(代码中):###java
\\n\\ndp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];\\n
当
\\nj - nums[i] == 0
成立的时候,根据上面分析,就说明单独的nums[i]
这个数就恰好能够在被分割为单独的一组,其余的数分割成为另外一组。因此,我们把初始化的dp[i][0]
设置成为true
是没有问题的。注意:观察状态转移方程,
\\nor
的结果只要为真,表格 这一列 下面所有的值都为真。因此在填表的时候,只要表格的最后一列是true
,代码就可以结束,直接返回true
。参考代码 2:
\\n###Java
\\n\\npublic class Solution {\\n\\n public boolean canPartition(int[] nums) {\\n int len = nums.length;\\n int sum = 0;\\n for (int num : nums) {\\n sum += num;\\n }\\n if ((sum & 1) == 1) {\\n return false;\\n }\\n\\n int target = sum / 2;\\n boolean[][] dp = new boolean[len][target + 1];\\n \\n // 初始化成为 true 虽然不符合状态定义,但是从状态转移来说是完全可以的\\n dp[0][0] = true;\\n\\n if (nums[0] <= target) {\\n dp[0][nums[0]] = true;\\n }\\n for (int i = 1; i < len; i++) {\\n for (int j = 0; j <= target; j++) {\\n dp[i][j] = dp[i - 1][j];\\n if (nums[i] <= j) {\\n dp[i][j] = dp[i - 1][j] || dp[i - 1][j - nums[i]];\\n }\\n }\\n\\n // 由于状态转移方程的特殊性,提前结束,可以认为是剪枝操作\\n if (dp[i][target]) {\\n return true;\\n }\\n }\\n return dp[len - 1][target];\\n }\\n}\\n
复杂度分析:(同上)
\\n
\\n考虑空间优化(重要)
\\n说明:这个技巧很常见、很基础,请一定要掌握。
\\n「0-1 背包问题」常规优化:「状态数组」从二维降到一维,减少空间复杂度。
\\n\\n
\\n- \\n
\\n在「填表格」的时候,当前行只参考了上一行的值,因此状态数组可以只设置 $2$ 行,使用「滚动数组」的技巧「填表格」即可;
\\n- \\n
\\n实际上,在「滚动数组」的基础上还可以优化,在「填表格」的时候,当前行总是参考了它上面一行 「头顶上」 那个位置和「左上角」某个位置的值。因此,我们可以只开一个一维数组,从后向前依次填表即可。
\\n\\n\\n友情提示:这一点在刚开始学习的时候,可能会觉得很奇怪。理解的办法是:拿题目中的示例,画一个表格,自己模拟一遍程序是如何「填表」的行为,就很清楚为什么状态数组降到 1 行的时候,需要「从后前向」填表。
\\n\\n
\\n- 「从后向前」 写的过程中,一旦
\\nnums[i] <= j
不满足,可以马上退出当前循环,因为后面的j
的值肯定越来越小,没有必要继续做判断,直接进入外层循环的下一层。相当于也是一个剪枝,这一点是「从前向后」填表所不具备的。说明:如果对空间优化技巧还有疑惑的朋友,本题解下的精选评论也解释了如何理解这个空间优化的技巧,请大家前往观看。
\\n参考代码 3:只展示了使用一维表格,并且「从后向前」填表格的代码。
\\n###Java
\\n\\npublic class Solution {\\n\\n public boolean canPartition(int[] nums) {\\n int len = nums.length;\\n int sum = 0;\\n for (int num : nums) {\\n sum += num;\\n }\\n if ((sum & 1) == 1) {\\n return false;\\n }\\n\\n int target = sum / 2;\\n boolean[] dp = new boolean[target + 1];\\n dp[0] = true;\\n\\n if (nums[0] <= target) {\\n dp[nums[0]] = true;\\n }\\n for (int i = 1; i < len; i++) {\\n for (int j = target; nums[i] <= j; j--) {\\n if (dp[target]) {\\n return true;\\n }\\n dp[j] = dp[j] || dp[j - nums[i]];\\n }\\n }\\n return dp[target];\\n }\\n}\\n
复杂度分析:
\\n\\n
\\n- 时间复杂度:$O(NC)$:这里 $N$ 是数组元素的个数,$C$ 是数组元素的和的一半;
\\n- 空间复杂度:$O(C)$:减少了物品那个维度,无论来多少个数,用一行表示状态就够了。
\\n
\\n总结
\\n\\n
「0-1 背包」问题是一类非常重要的动态规划问题,一开始学习的时候,可能会觉得比较陌生。建议动笔计算,手动模拟填表的过程,其实就是画表格。这个过程非常重要,自己动手填过表,更能加深体会程序是如何执行的,也能更好地理解「空间优化」技巧的思路和好处。
\\n\\n
在编写代码完成以后,把数组
\\ndp
打印出来,看看是不是与自己手算的一样。以加深体会动态规划的设计思想:「不是直接面对问题求解,而是从一个最小规模的问题开始,新问的最优解均是由比它规模还小的子问题的最优解转换得到,在求解的过程中记录每一步的结果,直至所要求的问题得到解」。
\\n最后思考为什么题目说是正整数,有 $0$ 是否可以,有实数可以吗,有负数可以吗?
\\n\\n
\\n- $0$ 的存在意义不大,放在哪个子集都是可以的;
\\n- 实数有可能是无理数,也可能是无限不循环小数,在计算整个数组元素的和的一半,要除法,然后在比较两个子集元素的和是否相等的时候,就会遇到精度的问题;
\\n- 再说负数,负数其实也是可以存在的,但要用到「回溯搜算法」解决。
\\n
\\n相关问题
\\n「力扣」上的 0-1 背包问题:
\\n\\n
\\n- 「力扣」第 416 题:分割等和子集(中等);
\\n- 「力扣」第 474 题:一和零(中等);
\\n- 「力扣」第 494 题:目标和(中等);
\\n- 「力扣」第 879 题:盈利计划(困难);
\\n「力扣」上的 完全背包问题:
\\n\\n
\\n- 「力扣」第 322 题:零钱兑换(中等);
\\n- 「力扣」第 518 题:零钱兑换 II(中等);
\\n- 「力扣」第 1449 题:数位成本和为目标值的最大数字(困难)。
\\n这里要注意鉴别:「力扣」第 377 题,不是「完全背包」问题。
\\n参考资料
\\n\\n
\\n","description":"关于背包问题的介绍,大家可以在互联网上搜索「背包九讲」进行学习,其中「0-1」背包问题是这些问题的基础。「力扣」上涉及的背包问题有「0-1」背包问题、完全背包问题、多重背包问题。 本题解有些地方使用了「0-1」背包问题的描述,因此会不加解释的使用「背包」、「容量」这样的名词。\\n\\n说明:这里感谢很多朋友在这篇题解下提出的建议,对我的启发很大。本题解的阅读建议是:先浏览代码,然后再看代码之前的分析,能更有效理解知识点和整个问题的思考路径。题解后也增加了「总结」,供大家参考。\\n\\n转换为 「0 - 1」 背包问题\\n\\n这道问题是我学习「背包」问题的入门问题…","guid":"https://leetcode.cn/problems/partition-equal-subset-sum//solution/0-1-bei-bao-wen-ti-xiang-jie-zhen-dui-ben-ti-de-yo","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-07-04T12:27:30.528Z","media":[{"url":"https://pic.leetcode-cn.com/1598148197-CnWYMq-image.png","type":"photo","width":1922,"height":756,"blurhash":"LERypc_4W=~q?HozWBs:-=ogRiIV"},{"url":"https://pic.leetcode-cn.com/1602418903-UcdsWL-image.png","type":"photo","width":1778,"height":910,"blurhash":"LBLgtu.TxA%NRjNdt6xuxukBV[xu"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"最笨最好理解的方法","url":"https://leetcode.cn/problems/sort-array-by-parity-ii//solution/zui-ben-zui-hao-li-jie-de-fang-fa-by-wecode","content":"- 背包问题资料下载,链接:百度云下载,密码:sjop 。
\\n定义两个指针index1和index2,分别记录偶数和奇数对应位置的元素,每次各加2,并把奇偶排序后的结果存在B中。
\\n###java
\\n\\n","description":"定义两个指针index1和index2,分别记录偶数和奇数对应位置的元素,每次各加2,并把奇偶排序后的结果存在B中。 ###java\\n\\nclass Solution {\\n public int[] sortArrayByParityII(int[] A) {\\n int[] B = new int[A.length];\\n int index1 = 0, index2 = 1;\\n for(int i = 0; i < A.length; i++){\\n if(A[i] % 2 == 0){ /…","guid":"https://leetcode.cn/problems/sort-array-by-parity-ii//solution/zui-ben-zui-hao-li-jie-de-fang-fa-by-wecode","author":"wecode","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-06-28T14:48:53.256Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"golang o(n)","url":"https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row//solution/golang-on-by-resara-2","content":"class Solution {\\n public int[] sortArrayByParityII(int[] A) {\\n int[] B = new int[A.length];\\n int index1 = 0, index2 = 1;\\n for(int i = 0; i < A.length; i++){\\n if(A[i] % 2 == 0){ // A中出现偶数\\n B[index1] = A[i];\\n index1 += 2; // 偶数索引指针更新\\n }else{ // A中出现奇数\\n B[index2] = A[i];\\n index2 += 2; // 奇数索引指针更新\\n }\\n }\\n return B;\\n }\\n}\\n
计算1-6的个数,如果A[i] == B[i] 只要记录一次
\\n如果某个num的个数等于len(A),说明可以达到目的。然后判断是旋转A还是旋转B就行。
\\n\\n","description":"计算1-6的个数,如果A[i] == B[i] 只要记录一次 如果某个num的个数等于len(A),说明可以达到目的。然后判断是旋转A还是旋转B就行。\\n\\nfunc minDominoRotations(A []int, B []int) int {\\nnums := [7]int{}\\nas := [7]int{}\\nbs := [7]int{}\\nfor i := 0; i < len(A); i++ {\\nif A[i] == B[i] {\\nnums[A[i]]++\\n} else {\\nnums[A[i]]++\\nas[A[i]]++\\nnums[B[i]]++\\nbs…","guid":"https://leetcode.cn/problems/minimum-domino-rotations-for-equal-row//solution/golang-on-by-resara-2","author":"resara","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-06-24T11:49:44.407Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"详细通俗的思路分析,多解法","url":"https://leetcode.cn/problems/subsets-ii//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-19","content":"func minDominoRotations(A []int, B []int) int {\\nnums := [7]int{}\\nas := [7]int{}\\nbs := [7]int{}\\nfor i := 0; i < len(A); i++ {\\nif A[i] == B[i] {\\nnums[A[i]]++\\n} else {\\nnums[A[i]]++\\nas[A[i]]++\\nnums[B[i]]++\\nbs[B[i]]++\\n}\\n}\\nfor k, v := range nums {\\nif v == len(A) {\\nreturn min(as[k], bs[k])\\n}\\n}\\nreturn -1\\n}\\n\\nfunc min(x, y int) int {\\nif x < y {\\nreturn x\\n}\\nreturn y\\n}\\n\\n\\n
题目描述(中等难度)
\\n\\n
直接根据 78 题 的思路去做的,可以先去看一下。
\\n解法一 回溯法
\\n这个比较好改,我们只需要判断当前数字和上一个数字是否相同,相同的话跳过即可。当然,要把数字首先进行排序。
\\n###java
\\n\\npublic List<List<Integer>> subsetsWithDup(int[] nums) {\\n List<List<Integer>> ans = new ArrayList<>();\\n Arrays.sort(nums); //排序\\n getAns(nums, 0, new ArrayList<>(), ans);\\n return ans;\\n}\\n\\nprivate void getAns(int[] nums, int start, ArrayList<Integer> temp, List<List<Integer>> ans) {\\n ans.add(new ArrayList<>(temp));\\n for (int i = start; i < nums.length; i++) {\\n //和上个数字相等就跳过\\n if (i > start && nums[i] == nums[i - 1]) {\\n continue;\\n }\\n temp.add(nums[i]);\\n getAns(nums, i + 1, temp, ans);\\n temp.remove(temp.size() - 1);\\n }\\n}\\n
时间复杂度:
\\n空间复杂度:
\\n解法二 迭代法
\\n根据 78题 解法二修改。我们看一下如果直接按照 78 题的思路会出什么问题。之前的思路是,先考虑 0 个数字的所有子串,再考虑 1 个的所有子串,再考虑 2 个的所有子串。而求 n 个的所有子串,就是 【n - 1 的所有子串】和 【n - 1 的所有子串加上 n】。例如,
\\n###java
\\n\\n数组 [ 1 2 3 ] \\n[ ]的所有子串 [ ]\\n[ 1 ] 个的所有子串 [ ] [ 1 ] \\n[ 1 2 ] 个的所有子串 [ ] [ 1 ] [ 2 ][ 1 2 ]\\n[ 1 2 3 ] 个的所有子串 [ ] [ 1 ] [ 2 ] [ 1 2 ] [ 3 ] [ 1 3 ] [ 2 3 ] [ 1 2 3 ] \\n
但是如果有重复的数字,会出现什么问题呢
\\n###java
\\n\\n数组 [ 1 2 2 ] \\n[ ] 的所有子串 [ ]\\n[ 1 ] 的所有子串 [ ] [ 1 ] \\n[ 1 2 ] 的所有子串 [ ] [ 1 ] [ 2 ][ 1 2 ]\\n[ 1 2 2 ] 的所有子串 [ ] [ 1 ] [ 2 ] [ 1 2 ] [ 2 ] [ 1 2 ] [ 2 2 ] [ 1 2 2 ] \\n
我们发现出现了重复的数组,那么我们可不可以像解法一那样,遇到重复的就跳过这个数字呢?答案是否定的,如果最后一步 [ 1 2 2 ] 增加了 2 ,跳过后,最终答案会缺少 [ 2 2 ]、[ 1 2 2 ] 这两个解。我们仔细观察这两个解是怎么产生的。
\\n\\n
我们看到第 4 行黑色的部分,重复了,是怎么造成的呢?
\\n第 4 行新添加的 2 要加到第 3 行的所有解中,而第 3 行的一部分解是旧解,一部分是新解。可以看到,我们黑色部分是由第 3 行的旧解产生的,橙色部分是由新解产生的。
\\n而第 1 行到第 2 行,已经在旧解中加入了 2 产生了第 2 行的橙色部分,所以这里如果再在旧解中加 2 产生黑色部分就造成了重复。
\\n所以当有重复数字的时候,我们只考虑上一步的新解,算法中用一个指针保存每一步的新解开始的位置即可。
\\n###java
\\n\\npublic List<List<Integer>> subsetsWithDup(int[] nums) {\\n List<List<Integer>> ans = new ArrayList<>();\\n ans.add(new ArrayList<>());// 初始化空数组\\n Arrays.sort(nums);\\n int start = 1; //保存新解的开始位置\\n for (int i = 0; i < nums.length; i++) {\\n List<List<Integer>> ans_tmp = new ArrayList<>();\\n // 遍历之前的所有结果\\n for (int j = 0; j < ans.size(); j++) {\\n List<Integer> list = ans.get(j);\\n //如果出现重复数字,就跳过所有旧解\\n if (i > 0 && nums[i] == nums[i - 1] && j < start) {\\n continue;\\n }\\n List<Integer> tmp = new ArrayList<>(list);\\n tmp.add(nums[i]); // 加入新增数字\\n ans_tmp.add(tmp);\\n }\\n\\n start = ans.size(); //更新新解的开始位置\\n ans.addAll(ans_tmp);\\n }\\n return ans;\\n}\\n
时间复杂度:
\\n空间复杂度:O(1)。
\\n还有一种思路,参考这里,当有重复数字出现的时候我们不再按照之前的思路走,而是单独考虑这种情况。
\\n当有 n 个重复数字出现,其实就是在出现重复数字之前的所有解中,分别加 1 个重复数字, 2 个重复数字,3 个重复数字 ... 什么意思呢,看一个例子。
\\n###java
\\n\\n数组 [ 1 2 2 2 ] \\n[ ]的所有子串 [ ]\\n[ 1 ] 个的所有子串 [ ] [ 1 ] \\n然后出现了重复数字 2,那么我们记录重复的次数。然后遍历之前每个解即可\\n对于 [ ] 这个解,\\n加 1 个 2,变成 [ 2 ] \\n加 2 个 2,变成 [ 2 2 ]\\n加 3 个 2,变成 [ 2 2 2 ]\\n对于 [ 1 ] 这个解\\n加 1 个 2,变成 [ 1 2 ] \\n加 2 个 2,变成 [ 1 2 2 ]\\n加 3 个 2,变成 [ 1 2 2 2 ]\\n
代码的话,就很好写了。
\\n###java
\\n\\npublic List<List<Integer>> subsetsWithDup(int[] num) {\\n List<List<Integer>> result = new ArrayList<List<Integer>>();\\n List<Integer> empty = new ArrayList<Integer>();\\n result.add(empty);\\n Arrays.sort(num);\\n\\n for (int i = 0; i < num.length; i++) {\\n int dupCount = 0;\\n //判断当前是否是重复数字,并且记录重复的次数\\n while( ((i+1) < num.length) && num[i+1] == num[i]) {\\n dupCount++;\\n i++;\\n }\\n int prevNum = result.size();\\n //遍历之前几个结果的每个解\\n for (int j = 0; j < prevNum; j++) {\\n List<Integer> element = new ArrayList<Integer>(result.get(j));\\n //每次在上次的结果中多加 1 个重复数字\\n for (int t = 0; t <= dupCount; t++) {\\n element.add(num[i]); //加入当前重复的数字\\n result.add(new ArrayList<Integer>(element));\\n }\\n }\\n }\\n return result;\\n}\\n
解法三 位操作
\\n本以为这个思路想不出来怎么去改了。
\\n回顾一下,这个题的思想就是每一个数字,考虑它的二进制表示。
\\n例如,nums = [ 1, 2 , 3 ]。用 1 代表在,0 代表不在。
\\n###java
\\n\\n1 2 3\\n0 0 0 -> [ ]\\n0 0 1 -> [ 3]\\n0 1 0 -> [ 2 ] \\n0 1 1 -> [ 2 3] \\n1 0 0 -> [1 ]\\n1 0 1 -> [1 3] \\n1 1 0 -> [1 2 ]\\n1 1 1 -> [1 2 3]\\n
但是如果有了重复数字,很明显就行不通了。例如对于 nums = [ 1 2 2 2 3 ]。
\\n###java
\\n\\n1 2 2 2 3\\n0 1 1 0 0 -> [ 2 2 ]\\n0 1 0 1 0 -> [ 2 2 ]\\n0 0 1 1 0 -> [ 2 2 ]\\n
上边三个数产生的数组重复的了。三个中我们只取其中 1 个,取哪个呢?取从重复数字的开头连续的数字。什么意思呢?就是下边的情况是我们所保留的。
\\n###java
\\n\\n2 2 2 2 2 \\n1 0 0 0 0 -> [ 2 ]\\n1 1 0 0 0 -> [ 2 2 ]\\n1 1 1 0 0 -> [ 2 2 2 ]\\n1 1 1 1 0 -> [ 2 2 2 2 ]\\n1 1 1 1 1 -> [ 2 2 2 2 2 ] \\n
而对于 [ 2 2 ] 来说,除了 1 1 0 0 0 可以产生,下边的几种情况,都是产生的 [ 2 2 ]
\\n###java
\\n\\n2 2 2 2 2 \\n1 1 0 0 0 -> [ 2 2 ]\\n1 0 1 0 0 -> [ 2 2 ]\\n0 1 1 0 0 -> [ 2 2 ]\\n0 1 0 1 0 -> [ 2 2 ]\\n0 0 0 1 1 -> [ 2 2 ]\\n......\\n
怎么把 1 1 0 0 0 和上边的那么多种情况区分开来呢?我们来看一下出现了重复数字,并且当前是 1 的前一个的二进位。
\\n对于 1 1 0 0 0 ,是 1。
\\n对于 1 0 1 0 0 , 是 0。
\\n对于 0 1 1 0 0 ,是 0。
\\n对于 0 1 0 1 0 ,是 0。
\\n对于 0 0 0 1 1 ,是 0。
\\n......
\\n可以看到只有第一种情况对应的是 1 ,其他情况都是 0。其实除去从开头是连续的 1 的话,就是两种情况。
\\n第一种就是,占据了开头,类似于这种 10...1....
\\n第二种就是,没有占据开头,类似于这种 0...1...
\\n这两种情况,除了第一位,其他位的 1 的前边一定是 0。所以的话,我们的条件是看出现了重复数字,并且当前位是 1 的前一个的二进位。
\\n所以可以改代码了。
\\n###java
\\n\\npublic List<List<Integer>> subsetsWithDup(int[] num) {\\n Arrays.sort(num);\\n List<List<Integer>> lists = new ArrayList<>();\\n int subsetNum = 1<<num.length;\\n for(int i=0;i<subsetNum;i++){\\n List<Integer> list = new ArrayList<>();\\n boolean illegal=false;\\n for(int j=0;j<num.length;j++){\\n //当前位是 1\\n if((i>>j&1)==1){\\n //当前是重复数字,并且前一位是 0,跳过这种情况\\n if(j>0&&num[j]==num[j-1]&&(i>>(j-1)&1)==0){\\n illegal=true;\\n break;\\n }else{\\n list.add(num[j]);\\n }\\n }\\n }\\n if(!illegal){\\n lists.add(list); \\n }\\n\\n }\\n return lists;\\n}\\n
总
\\n解法一和解法二怎么改,分析一下比较容易想到。解法三就比较难了,突破口就是选一个特殊的结构做代表,和其他情况区分出来。而从头开始的连续 1 可能就会是我们第一个想到的数,然后分析一下,发现果然可以和其他所有情况区分开来。
\\n","description":"直接根据 78 题 的思路去做的,可以先去看一下。 这个比较好改,我们只需要判断当前数字和上一个数字是否相同,相同的话跳过即可。当然,要把数字首先进行排序。\\n\\n###java\\n\\npublic List> subsetsWithDup(int[] nums) {\\n List
> ans = new ArrayList<>();\\n Arrays.sort(nums); //排序\\n getAns(nums, 0, new ArrayList<>(), ans);\\n return…","guid":"https://leetcode.cn/problems/subsets-ii//solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-19","author":"windliang","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-06-21T10:21:10.171Z","media":[{"url":"https://pic.leetcode-cn.com/1f35e8defdbafe9bec732fbf7f14572960efe9706ac0c8f469fd654ccf53202d-image.png","type":"photo","width":709,"height":429,"blurhash":"L9SPb4?HM__3~WV@jZWBxtofs:oe"},{"url":"https://pic.leetcode-cn.com/87ba90075a0a54e867ee05a65612d6f00766624c9f50f92beb9004e8b5a3ff27-image.png","type":"photo","width":626,"height":333,"blurhash":"LMRW6uyE-T~V_3IVkCt6.8rqNHSP"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"回溯搜索 + 剪枝(Java、Python)","url":"https://leetcode.cn/problems/permutations-ii//solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liwe-2","content":"
\\n
这一题在「力扣」第 46 题: 全排列 的基础上增加了 序列中的元素可重复 这一条件,但要求:返回的结果又不能有重复元素。
\\n思路是:在遍历的过程中,一边遍历一遍检测,在一定会产生重复结果集的地方剪枝。
\\n
\\n一个比较容易想到的办法是在结果集中去重。但是问题来了,这些结果集的元素是一个又一个列表,对列表去重不像用哈希表对基本元素去重那样容易。
\\n如果要比较两个列表是否一样,一个容易想到的办法是对列表分别排序,然后逐个比对。既然要排序,我们就可以 在搜索之前就对候选数组排序,一旦发现某个分支搜索下去可能搜索到重复的元素就停止搜索,这样结果集中不会包含重复列表。
\\n画出树形结构如下:重点想象深度优先遍历在这棵树上执行的过程,哪些地方遍历下去一定会产生重复,这些地方的状态的特点是什么?
\\n
\\n对比图中标注 ① 和 ② 的地方。相同点是:这一次搜索的起点和上一次搜索的起点一样。不同点是:\\n
\\n- 标注 ① 的地方上一次搜索的相同的数刚刚被撤销;
\\n- 标注 ② 的地方上一次搜索的相同的数刚刚被使用。
\\n\\n
产生重复结点的地方,正是图中标注了「剪刀」,且被绿色框框住的地方。
\\n大家也可以把第 2 个
\\n1
加上\'
,即[1, 1\', 2]
去想象这个搜索的过程。只要遇到起点一样,就有可能产生重复。这里还有一个很细节的地方:\\n
\\n- 在图中 ② 处,搜索的数也和上一次一样,但是上一次的
\\n1
还在使用中;- 在图中 ① 处,搜索的数也和上一次一样,但是上一次的
\\n1
刚刚被撤销,正是因为刚被撤销,下面的搜索中还会使用到,因此会产生重复,剪掉的就应该是这样的分支。代码实现方面,在第 46 题的基础上,要加上这样一段代码:
\\n###Java
\\n\\nif (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {\\n continue;\\n}\\n
这段代码就能检测到标注为 ① 的两个结点,跳过它们。注意:这里
\\nused[i - 1]
不加!
,测评也能通过。有兴趣的朋友可以想一想这是为什么。建议大家做这样几个对比实验:\\n
\\n- 干脆就不写
\\n!used[i - 1]
结果是什么样?- 写
\\nused[i - 1]
结果是什么,代码又是怎样执行的。这里给出的结论是:!used[i - 1]
这样的剪枝更彻底。附录会分析原因。参考代码 1:
\\n###Java
\\n\\nimport java.util.ArrayDeque;\\nimport java.util.ArrayList;\\nimport java.util.Arrays;\\nimport java.util.Deque;\\nimport java.util.List;\\n\\npublic class Solution {\\n\\n public List<List<Integer>> permuteUnique(int[] nums) {\\n int len = nums.length;\\n List<List<Integer>> res = new ArrayList<>();\\n if (len == 0) {\\n return res;\\n }\\n\\n // 排序(升序或者降序都可以),排序是剪枝的前提\\n Arrays.sort(nums);\\n\\n boolean[] used = new boolean[len];\\n // 使用 Deque 是 Java 官方 Stack 类的建议\\n Deque<Integer> path = new ArrayDeque<>(len);\\n dfs(nums, len, 0, used, path, res);\\n return res;\\n }\\n\\n private void dfs(int[] nums, int len, int depth, boolean[] used, Deque<Integer> path, List<List<Integer>> res) {\\n if (depth == len) {\\n res.add(new ArrayList<>(path));\\n return;\\n }\\n\\n for (int i = 0; i < len; ++i) {\\n if (used[i]) {\\n continue;\\n }\\n\\n // 剪枝条件:i > 0 是为了保证 nums[i - 1] 有意义\\n // 写 !used[i - 1] 是因为 nums[i - 1] 在深度优先遍历的过程中刚刚被撤销选择\\n if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {\\n continue;\\n }\\n\\n path.addLast(nums[i]);\\n used[i] = true;\\n\\n dfs(nums, len, depth + 1, used, path, res);\\n // 回溯部分的代码,和 dfs 之前的代码是对称的\\n used[i] = false;\\n path.removeLast();\\n }\\n }\\n}\\n
###Python
\\n\\nfrom typing import List\\n\\n\\nclass Solution:\\n\\n def permuteUnique(self, nums: List[int]) -> List[List[int]]:\\n\\n def dfs(nums, size, depth, path, used, res):\\n if depth == size:\\n res.append(path.copy())\\n return\\n for i in range(size):\\n if not used[i]:\\n\\n if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:\\n continue\\n\\n used[i] = True\\n path.append(nums[i])\\n dfs(nums, size, depth + 1, path, used, res)\\n used[i] = False\\n path.pop()\\n\\n size = len(nums)\\n if size == 0:\\n return []\\n\\n nums.sort()\\n\\n used = [False] * len(nums)\\n res = []\\n dfs(nums, size, 0, [], used, res)\\n return res\\n
复杂度分析:(理由同第 46 题,重复元素越多,剪枝越多。但是计算复杂度的时候需要考虑最差情况。)
\\n\\n
\\n- 时间复杂度:$O(N \\\\times N!)$,这里 $N$ 为数组的长度。
\\n- 空间复杂度:$O(N \\\\times N!)$。
\\n
\\n
\\n
\\n补充说明(这部分内容不太重要,只要理解上面深搜是怎么剪枝的就行)
\\n写
\\nused[i - 1]
代码正确,但是不推荐的原因。思路是根据深度优先遍历的执行流程,看一看那些状态变量(布尔数组
\\nused
)的值。1、如果剪枝写的是:
\\n###Java
\\n\\nif (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {\\n continue;\\n}\\n
那么,对于数组
\\n[1, 1’, 1’’, 2]
,回溯的过程如下:\\n
得到的全排列是:
\\n[[1, 1\', 1\'\', 2], [1, 1\', 2, 1\'\'], [1, 2, 1\', 1\'\'], [2, 1, 1\', 1\'\']]
。特点是:1
、1\'
、1\'\'
出现的顺序只能是1
、1\'
、1\'\'
。2、如果剪枝写的是:
\\n###Java
\\n\\nif (i > 0 && nums[i] == nums[i - 1] && used[i - 1]) {\\n continue;\\n}\\n
那么,对于数组
\\n[1, 1’, 1’’, 2]
,回溯的过程如下(因为过程稍显繁琐,所以没有画在一张图里):(1)先选第 1 个数字,有 4 种取法。
\\n\\n
(2)对第 1 步的第 1 个分支,可以继续搜索,但是发现,没有搜索到合适的叶子结点。
\\n\\n
(3)对第 1 步的第 2 个分支,可以继续搜索,但是同样发现,没有搜索到合适的叶子结点。
\\n\\n
(4)对第 1 步的第 3 个分支,继续搜索发现搜索到合适的叶子结点。
\\n\\n
(5)对第 1 步的第 4 个分支,继续搜索发现搜索到合适的叶子结点。
\\n\\n
因此,
\\n","description":"这一题在「力扣」第 46 题: 全排列 的基础上增加了 序列中的元素可重复 这一条件,但要求:返回的结果又不能有重复元素。 思路是:在遍历的过程中,一边遍历一遍检测,在一定会产生重复结果集的地方剪枝。\\n\\n一个比较容易想到的办法是在结果集中去重。但是问题来了,这些结果集的元素是一个又一个列表,对列表去重不像用哈希表对基本元素去重那样容易。\\n\\n如果要比较两个列表是否一样,一个容易想到的办法是对列表分别排序,然后逐个比对。既然要排序,我们就可以 在搜索之前就对候选数组排序,一旦发现某个分支搜索下去可能搜索到重复的元素就停止搜索,这样结果集中不会包含重复列表。…","guid":"https://leetcode.cn/problems/permutations-ii//solution/hui-su-suan-fa-python-dai-ma-java-dai-ma-by-liwe-2","author":"liweiwei1419","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-06-20T11:18:47.865Z","media":[{"url":"https://leetcode.cn/problems/permutations-ii//solution/6f5c76cc-66cd-4248-9ef6-b8e50dc1ccc9","type":"photo"},{"url":"https://pic.leetcode-cn.com/1600386643-uhkGmW-image.png","type":"photo","width":1652,"height":880,"blurhash":"LTQch#s+=|o#_4xvg4t7o|tRogjY"},{"url":"https://pic.leetcode-cn.com/9b0bf81d73d18e890491e1c6d6c3b40d85ebe37ca38934759e86e84d1d421e22-image.png","type":"photo","width":1220,"height":642,"blurhash":"LGQJDoo$In^+xzRpxbITT_MxVrIU"},{"url":"https://pic.leetcode-cn.com/06bf98f9da46f538554aa17d5cc1063761557fb358e1e12dc10c55858cc2f5fe-image.png","type":"photo","width":1834,"height":472,"blurhash":"LVQJce9YIot7.mE1Ioxu-htRbvjE"},{"url":"https://pic.leetcode-cn.com/41fff3d76a09688798f2cc37c638d799aca0d234fb6df1ac60ee99654934c745-image.png","type":"photo","width":2064,"height":1168,"blurhash":"LJS60#~q.8tRtnrWxZSi*0b_VrVX"},{"url":"https://pic.leetcode-cn.com/b9163201e31e038df7cafef3fbe56f6d7c393c28b57679da7dc0511a19fc5115-image.png","type":"photo","width":2110,"height":1020,"blurhash":"LLR..F%g?v.8pKMdVXoz*0IBNexZ"},{"url":"https://pic.leetcode-cn.com/70098e931f78388622eb6705c25ddfa3bd8a912c3934546d5f244c2d1825cc95-image.png","type":"photo","width":2134,"height":1436,"blurhash":"LNR:A{~Xx]%f-=S0xaV@%$tRNFV@"},{"url":"https://pic.leetcode-cn.com/299ba9a47a9ae93c2331f6ce9f93c614a647397665f57bf072a11fe0a387b35f-image.png","type":"photo","width":2088,"height":992,"blurhash":"LVSPCJ?btR-;.TaJWAt7%fXTRjVs"}],"categories":null,"attachments":null,"extra":null,"language":null},{"title":"Python 解法","url":"https://leetcode.cn/problems/best-sightseeing-pair//solution/python-jie-fa-by-jiayangwu","content":"used[i - 1]
前面加不加感叹号的区别仅在于保留的是相同元素的顺序索引,还是倒序索引。很明显,顺序索引(即使用!used[i - 1]
作为剪枝判定条件得到)的递归树剪枝更彻底,思路也相对较自然。思路:
\\n已知题目要求
\\nres = A[i] + A[j] + i - j (i < j)
的最大值,而对于输入中的每一个
\\nA[j]
来说, 它的值A[j]
和它的下标j
都是固定的,所以
\\nA[j] - j
的值也是固定的。因此,对于每个
\\nA[j]
而言, 想要求res
的最大值,也就是要求A[i] + i (i < j)
的最大值,所以不妨用一个变量
\\npre_max
记录当前元素A[j]
之前的A[i] + i
的最大值,这样对于每个
\\nA[j]
来说,都有最大得分 = pre_max + A[j] - j
,再从所有
\\nA[j]
的最大得分里挑出最大值返回即可。代码:
\\n###python
\\n\\nclass Solution(object):\\n def maxScoreSightseeingPair(self, A):\\n \\"\\"\\"\\n :type A: List[int]\\n :rtype: int\\n \\"\\"\\"\\n res = 0\\n pre_max = A[0] + 0 #初始值\\n for j in range(1, len(A)):\\n res = max(res, pre_max + A[j] - j) #判断能否刷新res\\n pre_max = max(pre_max, A[j] + j) #判断能否刷新pre_max, 得到更大的A[i] + i\\n \\n return res\\n
复杂度分析
\\n时间复杂度 $O(N)$
\\n空间复杂度 $O(1)$
\\n","description":"思路: 已知题目要求 res = A[i] + A[j] + i - j (i < j) 的最大值,\\n\\n而对于输入中的每一个 A[j] 来说, 它的值 A[j] 和它的下标 j 都是固定的,\\n\\n所以 A[j] - j 的值也是固定的。\\n\\n因此,对于每个 A[j] 而言, 想要求 res 的最大值,也就是要求 A[i] + i (i < j) 的最大值,\\n\\n所以不妨用一个变量 pre_max 记录当前元素 A[j] 之前的 A[i] + i 的最大值,\\n\\n这样对于每个 A[j] 来说,都有 最大得分 = pre_max + A[j] - j,\\n\\n再从所有 A[j]…","guid":"https://leetcode.cn/problems/best-sightseeing-pair//solution/python-jie-fa-by-jiayangwu","author":"JiayangWu","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-06-06T12:25:51.265Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null},{"title":"DFS + 位运算剪枝","url":"https://leetcode.cn/problems/n-queens-ii//solution/dfs-wei-yun-suan-jian-zhi-by-makeex","content":"解题思路:
\\n这个解法非常经典,所以我觉得应该放到题解里面,核心思路是这样:
\\n\\n
\\n- 使用常规深度优先一层层搜索
\\n- 使用三个整形分别标记每一层哪些格子可以放置皇后,这三个整形分别代表列、左斜下、右斜下
\\n(_col, ld, rd_)
,二进制位为 $1$ 代表不能放置,$0$ 代表可以放置- 核心两个位运算:\\n
\\n\\n
\\n- \\n
x & -x
代表除最后一位 $1$ 保留,其它位全部为 $0$- \\n
x & (x - 1)
代表将最后一位 $1$ 变成 $0$代码:
\\n###C++
\\n\\n","description":"解题思路: 这个解法非常经典,所以我觉得应该放到题解里面,核心思路是这样:\\n\\n使用常规深度优先一层层搜索\\n使用三个整形分别标记每一层哪些格子可以放置皇后,这三个整形分别代表列、左斜下、右斜下(_col, ld, rd_),二进制位为 $1$ 代表不能放置,$0$ 代表可以放置\\n核心两个位运算:\\nx & -x 代表除最后一位 $1$ 保留,其它位全部为 $0$\\nx & (x - 1) 代表将最后一位 $1$ 变成 $0$\\n代码:\\n\\n###C++\\n\\nclass Solution {\\npublic:\\n int totalNQueens(int n)…","guid":"https://leetcode.cn/problems/n-queens-ii//solution/dfs-wei-yun-suan-jian-zhi-by-makeex","author":"makeex","authorUrl":null,"authorAvatar":null,"publishedAt":"2019-05-22T02:15:16.448Z","media":null,"categories":null,"attachments":null,"extra":null,"language":null}],"readCount":9047,"subscriptionCount":90,"analytics":{"feedId":"56597410818564096","updatesPerWeek":14,"subscriptionCount":90,"latestEntryPublishedAt":null,"view":0}}')class Solution {\\npublic:\\n int totalNQueens(int n) {\\n dfs(n, 0, 0, 0, 0);\\n \\n return this->res;\\n }\\n \\n void dfs(int n, int row, int col, int ld, int rd) {\\n if (row >= n) { res++; return; }\\n \\n // 将所有能放置 Q 的位置由 0 变成 1,以便进行后续的位遍历\\n int bits = ~(col | ld | rd) & ((1 << n) - 1);\\n while (bits > 0) {\\n int pick = bits & -bits; // 注: x & -x\\n dfs(n, row + 1, col | pick, (ld | pick) << 1, (rd | pick) >> 1);\\n bits &= bits - 1; // 注: x & (x - 1)\\n }\\n }\\n\\nprivate:\\n int res = 0;\\n};\\n