LeetCode 小水题选做(更新中)

LeetCode 小水题选做

4. 寻找两个正序数组的中位数

题目链接

题目大意

给定两个排好序的数组 a,b,长度分别为 n,m。设 c 为把 ab 合并后再排好序的数组,求 c 的中位数。要求时间复杂度 O(log(n+m))(不计输入)。


一、初步转化:如何避免关于中位数的繁杂分类讨论

首先,根据 n+m 的奇偶性不同,中位数的定义略有不同。考虑将所有 n+m 个数放在一起,排好序,记为数列 c。则当 n+m 为奇数时,中位数是 c 里第 n+m2+1 个数;当 n+m 为偶数时,中位数是 c 里第 n+m2 个数和第 n+m2+1 个数的平均数。如果我们能实现一个函数,传入 k,返回 c 中第 k 个数,那么原问题自然也就迎刃而解了。

至此,我们把原问题转化为:

给出数列 a,b 和数字 k,要求在 O(log(n+m)) 时间里求出 c 中第 k 个数。

于是我们成功避免了关于中位数的繁杂分类讨论。接下来考虑转化后的问题。

二、一些不成熟的想法(想看正解可以跳过本节)

第一想法是先把两个数组归并排序,但是这样做时间复杂度至少为 O(n+m)

另一想法是二分:不妨假设答案在 a 中,那么我们在 a 里二分答案。在 check 时,我们要求出当前的 amidc 里排第几,也就是说 ab 中一共有多少个比 amid 小的数。a 里显然有 mid1 个,而求 b 里有多少个,则需要在 b 里再次二分(或者用 C++ 自带的 lower_bound,时间复杂度是一样的)。于是,两次二分套起来,总时间复杂度是 O(log2(n+m)),仍不能令人满意。

三、真正解法

c 里的数要么来自 a,要么来自 b。考虑 c 的前 k 个数中,有多少个数来自 a,记为 x。则有 kx 个数来自 b。那么,必定有:max(ax,bkx)min(ax+1,bkx+1),因为 ck 个数中任意一个,一定小于等于后 n+mk 中任意一个(注:此处默认在 a,b 的最前面、最后面分别塞一个 ,+,这样可以避免类似 x=0x=n 的这种尴尬的边界情况)。

另外,如果知道了 x,显然也就知道了我们要求的 c 中第 k 个数(它就是 max(ax,bkx))。

怎么求 x 呢?我们先随便猜一个值 x。那么:

  • x=x,根据刚才的讨论,有:max(ax,bkx)min(ax+1,bkx+1)
  • x<x,相当于前 k 个数里来自 a 的太少了,来自 b 的太多了,所以 ax 太小,而 bkx 太大。应该从后 n+mk 个数里把一些来自 a 的数拿到前面来,替换掉前 k 个数里来自 b 的数。因此,此时有:bkx>ax+1
  • x>x,与上一条同理:前 k 个数里来自 a 的太多了,来自 b 的太少了,所以 ax 太大,而 bkx 太小。应该从后 n+mk 个数里把一些来自 b 的数拿到前面来,替换掉前 k 个数里来自 a 的数。因此,此时有:ax>bkx+1

想明白这些以后,我们发现,只要我们随便猜一个 x,就能 O(1) 判断出它是大了还是小了。于是可以二分 x。至此,我们在 O(log(n+m)) 的时间复杂度内解决了本题。

一个需要注意的细节是,可能的 x 的范围是 [0,k][km,n]=[max(0,km),min(k,n)]。二分开始前这样设置好范围,就可以避免下标越界的问题。

参考代码

复制class Solution {
public:
	const int INF = 1e6 + 5;
	int n, m;
	double find(vector<int>& a, vector<int>& b, int K) {
		int l = max(0, K - m), r = min(n, K);
		// 前 K 个里有多少个来自 a
		while (l <= r) {
			int from_a = (l + r) >> 1; // mid
			int from_b = K - from_a;
			
			assert(from_b >= 0);
			assert(from_b <= m);
			
			int la, lb, ra, rb;
			if (from_a == 0) la = -INF;
			else la = a[from_a - 1];
			if (from_b == 0) lb = -INF;
			else lb = b[from_b - 1];
			if (from_a == n) ra = INF;
			else ra = a[from_a];
			if (from_b == m) rb = INF;
			else rb = b[from_b];
			
			if (max(la, lb) <= min(ra, rb)) {
				return max(la, lb);
			} else {
				if (la > rb) {
					r = from_a - 1;
				} else {
					assert(lb > ra);
					l = from_a + 1;
				}
			}
		}
		assert(0);
	}
	double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
		n = nums1.size();
		m = nums2.size();
		
		if ((n + m) & 1) {
			return find(nums1, nums2, (n + m) / 2 + 1);
		} else {
			double x = find(nums1, nums2, (n + m) / 2);
			double y = find(nums1, nums2, (n + m) / 2 + 1);
			return (x + y) / 2.0;
		}
	}
};

11. 盛最多水的容器

题目链接

题目大意

给定一个长度为 n 的数组 h,求 max1j<in{(ij)min{hi,hj}}

数据范围:2n1050hi104


解法

不妨假设左端点的 h 值大于等于右端点的,即 hjhi (j<i)。然后再把数组翻转,用同样的方法再做一遍,就能涵盖所有情况了。

枚举右端点 i。我们只考虑 i 左边的、hjhij。显然,在这些 j 里,最小j 是最优的(与 hj 具体是几无关,因为取 min 后对式子产生贡献的是 hi,而不是 hj)。

于是,问题转化为:每次给定一个数 x(其实就是 hi),求序列里第一个满足 hjx 的数的位置。一个显然的想法是二分,然后用线段树求区间最大值,看是否大于等于 x。这样做总时间复杂度是 O(nlog2n)。可以优化为线段树上二分,或者二分后用 RMQ 算法求区间最大值,总时间复杂度 O(nlogn)。还有一种更简便的做法:将序列里所有数取出来,按数值从小到大排序,然后将它们依次放回序列中(放到原位置),假设当前放回的数是 hi,那么我们要求的实际上是数列里的第一个空位置。可以用一个“指针”维护:用 while 循环,只要位置不为空就把“指针”暴力往右移,这样总移动次数是 O(n) 的。时间复杂度 O(nlogn),瓶颈是排序。

时间复杂度可以继续优化到线性。我们现在从右向左依次枚举 hi,考虑如果让 j 单调不降会怎么样。也就是说,原来我们对 j 的定义是 j=min{jhjhi}。现在假设 i+1 对应的“j”是 j,那么我们将 j 的定义修改为:j=min{jjj and hjhi}。此时我们面临的问题是:如果 hi<hi+1,我们可能会漏掉一个 j<j,它满足 hihj<hi+1。但是这样的一组 (j,i) 真的会成为最优答案吗?并不会!因为 (j,i+1) 必然优于 (j,i),所以这样的 (j,i) 根本不需要考虑。于是,由于 j 单调不降,最多只会把整个数组扫一遍,总时间复杂度就是 O(n) 的了。

参考代码

复制class Solution {
public:
    int maxArea(vector<int>& height) {
        int n = height.size();
        int ans = 0;
        for (int i = 0, j = n - 1; i < n; ++i) {
            while (height[j] < height[i])
                --j;
            ans = max(ans, (j - i) * height[i]);
            if (i == j)
                break;
        }
        for (int i = n - 1, j = 0; i >= 0; --i) {
            while (height[j] < height[i])
                ++j;
            ans = max(ans, (i - j) * height[i]);
            if (i == j)
                break;
        }
        return ans;
    }
};

41. 缺失的第一个正数

题目链接

题目大意

给你一个长度为 n 的、未排序的整数数组 a,请你找出其中没有出现的最小的正整数。

请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。


一个提示

可以修改原数组。

方法一

首先,一个观察是:答案只可能在 [1,n+1] 中。比方说,如果答案为 n+2,那么需要 1,2,n+1n+1 个数全都出现过,而数组里只有 n 个数,故答案不可能为 n+2

于是可以有一个时间复杂度 O(n),但是需要 O(n) 额外空间的做法:开一个大小为 nbool 型数组 b1n。对于每个 ai,若 ai[1,n],就令 bai=1。最后从 1n 遍历 b,遇到的第一个 bj=0 的位置 j 就是答案(如果所有 bj 都是 1,答案就是 n+1)。

进一步优化,注意到在上述做法中,所有 ai[1,n]ai,它具体是几,其实不重要。不妨令这些 ai 都为 0。那么 a 数组里的数值范围就只有 [0,n]。但是,a 可是一个 int 类型的数组啊!这岂不是大大的浪费?于是想到,可以用 ai 的前 19 个二进制位来存储原本要存的数(2191=524287>n,足够存储);第 20 个二进制位,把它当成 bi 去使用。前 19 个二进制位和第 20 个二进制位互不影响,完全就和开两个数组效果一样。然后沿用之前的做法即可。时间复杂度 O(n),额外空间复杂度 O(1)

参考代码(方法一)

复制class Solution {
public:
	int n;
	int firstMissingPositive(vector<int>& nums) {
		n = nums.size();
		
		for (int i = 0; i < n; ++i) {
			if (nums[i] <= 0 || nums[i] > n) {
				nums[i] = 0;
			}
		}
		
		for (int i = 0; i < n; ++i) {
			int x = nums[i] & ((1 << 19) - 1);
			if (x) {
				assert(x <= n);
				nums[x - 1] |= (1 << 19);
			}
		}
		
		for (int i = 1; i <= n; ++i) {
			if ((nums[i - 1] >> 19) == 0) {
				return i;
			}
		}
		
		return n + 1;
	}
};

方法二

我们考虑将 a 数组“恢复”成下面的形式:

如果数组中包含 x[1,n],那么恢复后,数组的第 x 个元素为 x。(此处认为下标从 1 开始)

形式化地说,设恢复后的数组为 a。那么 {ai}={ai}(这里是可重集),并且对于所有 x[1,n]i 使得 ai=xx,有 ax=x

在恢复后,数组应当为 1,2,,n 的形式,但其中有若干个位置上的数是错误的,每一个错误的位置就代表了一个缺失的正数。

如何恢复?考虑依次遍历所有位置。假设当前遍历到 ai。记 ai=x

  • x[1,n],直接跳过不管,继续遍历。
  • x[1,n],我们需要把它放到 ax 的位置上。考虑 ax 位置上现在的数。
    • 如果 ax 已经等于 x,那么说明数组里原本就有多个 xx 已经被恢复好了,而当前的 ai 是多余的,我们跳过不管,继续遍历。
    • 如果 axx,我们 swap(ai,ax)。这样就把 x 放到 ax 上了。不过现在 ai 上又多了一个新的数。令 x 等于新的 ai,我们重复上述操作,直到 x[1,n] 或者 ax=x

因为每次交换都会使得一个数回到正确的位置,而已经回到正确位置上的数不会再移动,所以总交换次数最多为 O(n)

时间复杂度 O(n),额外空间复杂度 O(1)

参考代码(方法二)

复制class Solution {
public:
	int n;
	int firstMissingPositive(vector<int>& nums) {
		n = nums.size();
		for (int i = 0; i < n; ++i) {
			while (nums[i] >= 1 && nums[i] <= n) {
				int x = nums[i];
				if (nums[x - 1] == x) {
					break;
				}
				swap(nums[x - 1], nums[i]);
			}
		}
		for (int i = 0; i < n; ++i) {
			if (nums[i] != i + 1) {
				return i + 1;
			}
		}
		return n + 1;
	}
};

328. 奇偶链表

题目链接

这题本身很简单,我是想借这题讲一点链表操作中的小技巧。平时在 OI 中我很少使用这种用指针实现的“真正的链表”,所以对它不是很熟悉。

  1. 链表题往往让你返回链表的首元素地址。而你操作完之后手上拿着的往往是最后一个元素的地址。所以在一开始要把首元素地址备份一下。
  2. 要新建节点时,可以选择“把数值填写到当前节点上,再新建一个空白节点”,或者“先新建一个节点,把数值填写到新节点上”。这有点类似于 OI 里输出一个序列时,cout << a[i] << " "; 还是 cout << " " << a[i];。在链表题中,我推荐使用后一种做法。因为无论哪种做法,最后都涉及到多出来一个节点,要删掉,前一种做法多出来的是尾节点,后一种做法多出来的是首节点。要删除首节点是很容易的,令 首节点 = 首节点 -> next 即可;而要删除尾节点就比较麻烦了,因为我们不知道倒数第二个节点是谁,需要用一个 last 记录。

参考代码

复制/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* oddEvenList(ListNode* head) {
		ListNode* a = new ListNode;
		ListNode* b = new ListNode;
		ListNode* heada = a;
		ListNode* headb = b;
		int x = 1;
		while (head != NULL) {
			if (x & 1) {
				a -> next = new ListNode;
				a = a -> next;
				a -> val = head -> val;
			} else {
				b -> next = new ListNode;
				b = b -> next;
				b -> val = head -> val;
			}
			
			head = head -> next;
			++x;
		}
		b -> next = NULL;
		a -> next = headb -> next;
		return heada -> next;
    }
};

2183. 统计可以被 K 整除的下标对数目

题目链接

题目大意

给定一个长度为 n 的正整数数组 a 和一个正整数 k。求有多少对下标 (i,j) 满足 1i<jn(ai×aj)modk=0

数据范围:1n,k,ai105。(注:实测 O(nn) 做法会超时)。


解法

首先,可以令 aigcd(ai,k),不会影响答案。

bi=kai。问题转化为,对每个 i,求有多少 j<i 满足 ajbi 的倍数。

不妨先忽略 j<i 这个要求(即 j 可以是 [1,n] 中任意数),设此时求出的答案为 ans。此时,每对合法的下标会被算两次(也就是 (i,j)(j,i)),此外 (i,i) 的情况也会被算到。所以,设真正的答案为 ans,则 ans'=ans2+i=1n[ai2modk=0]。我们求出 ans 就能推出 ans

考虑如何求 ans。因为数值 k,ai 的范围不大,我们可以预处理出一个数组 g,满足 gx=i=1n[aimodx=0],也就是整个 a 中有多少数是 x 的倍数。再枚举 i,每次把 gbi 累加到 ans 里即可。

如何预处理 g?先弄一个数组 f,满足 fy=i=1n[ai=y],也就是数值 ya 中出现了多少次。再对每个 x,暴力枚举 x 的所有倍数 y,把 fy 累加到 gx 里。这样做的时间复杂度是 O(i=1nni)=O(nlogn)

其他部分都是线性的,所以整个算法的时间复杂度就是 O(nlogn)

参考代码

复制class Solution {
public:
	typedef long long ll;
	static const int MAXN = 1e5;
	int gcd(int a, int b) {
		return (!b) ? a : gcd(b, a % b);
	}
	int n, f[MAXN + 5], g[MAXN + 5];
	long long coutPairs(vector<int>& a, int K) {
		n = a.size();
		for (int i = 0; i < n; ++i) {
			a[i] = gcd(a[i], K);
			f[a[i]]++;
		}
		for (int i = 1; i <= MAXN; ++i) {
			for (int j = i; j <= MAXN; j += i) {
				g[i] += f[j];
			}
		}
		ll ans = 0;
		for (int i = 0; i < n; ++i) {
			ans += g[K / a[i]];
		}
		for (int i = 0; i < n; ++i) {
			ans -= ((ll)a[i] * a[i] % K == 0);
		}
		assert(ans % 2 == 0);
		ans /= 2;
		return ans;
	}
};

2386. Find the K-Sum of an Array

题目链接

题目大意

给定一个长度为 n 的数组 nums 和一个正整数 k,求 nums 中第 k 大的子序列元素和。

数据范围:1n105109numsi1091kmin(2000,2n)


解法

首先,选子序列等价于选子集。因此数组中的元素顺序无关紧要,我们可以将数组随意排序。

数组中可能既有负数,又有正数,这让我们比较头疼。考虑最大的子序列元素和,一定是把所有正数都选上,所有负数都不选。记这个和为 base。那么其他所有子序列元素和,都可以看做在 base 的基础上做修改。具体来说,要么是在 base 的基础上放弃一些正数,即减去一些正数的值,要么是在 base 的基础上再选一些负数,即加上一些负数的值。为了把“减正数”和“加负数”统一,我们将数组里所有正数改成它的相反数。那么现在数组里所有元素就都变成了正数,而子序列元素和就可以看做是在 base 的基础上,减去数组里一个子集的和。问题转化为,对一个全是正数的数组,求其第 k 的子集和。

我们考虑用堆来求。当然,总共有 2n 个子集,肯定不能一开始就全放入堆里。在任意时刻,堆里只能存放一部分方案。我们希望在第 t (1tk) 次弹出堆顶时,弹出的恰好是第 t 优的方案(也就是第 t 小的子集和)。这本质上是要求,我们要设计出一种入堆的顺序,使得对于每个尚未入堆的方案,堆里至少存在一个比它更优的方案。这样就不会出现,应该弹出第 t 优的方案,而第 t 优的方案还未被放入堆中的情况。

我们的设计是:先将 nums 数组(现在全是正数)从小到大排序。最优的方案肯定是空集,它比较特殊,我们不管它。第二优的方案肯定是只选第一个数,我们将该方案加入堆中。在接下来的 k1 个时刻,我们每次从堆中弹出最优的方案。设该方案子集和为 s。考虑该方案里所选的,最大的数,设它是 numsi。我们将两种新方案放入堆中:s+numsi+1(也就是选上第 i+1 个数)和 snumsi+numsi+1(也就是把第 i 个数换成第 i+1 个数)。为什么这样是对的?因为在集合里其他元素不变的情况下,选 numsi 一定比选 numsi+1 更优,所以在 snumsi+numsi+1 加入之前,堆里一直存在 s 或比 s 更优的方案。此外,每个集合都会被考虑到,因为任意一个集合,都能唯一确定它是被谁生成的(也就是去掉最大元素,加上最大元素上一个元素),如此一直往前推,必定能推到最开始的集合。

综上所述,在具体实现中,我们对每个方案,只需要记录它的元素和,以及最大的元素的下标。也就是在堆里存一个 pair 即可。

因为堆操作只会进行 k 轮,每轮弹出一个元素,加入两个元素,堆里的元素数量是 O(k) 的,所以每轮操作的时间复杂度是 O(logk),整个过程的时间复杂度为 O(klogk)。因为一开始还要给 n 个数排序,所以总时间复杂度是 O(nlogn+klogk)

参考代码

复制class Solution {
public:
    long long kSum(vector<int>& nums, int k) {
        long long base = 0;
        for (int i = 0; i < (int)nums.size(); ++i) {
            if (nums[i] > 0) {
                base += nums[i];
            } else {
                nums[i] = -nums[i];
            }
        }
        
        sort(nums.begin(), nums.end());
        
        long long res = 0;
        typedef pair<long long, int> PR;
        priority_queue<PR, vector<PR>, greater<PR> > que; // 小根堆
        que.push(make_pair(nums[0], 0));
        for (int t = 0; t < k - 1; ++t) {
            long long s = que.top().first;
            int i = que.top().second;
            que.pop();
            
            if (i + 1 < (int)nums.size()) {
                que.push(make_pair(s + nums[i + 1], i + 1));
                que.push(make_pair(s - nums[i] + nums[i + 1], i + 1));
            }
            
            res = s;
        }
        return base - res;
    }
};

2289. Steps to Make Array Non-decreasing

题目链接

题目大意

给定一个长度为 n 的数组 a(下标为 0n1)。每次操作,我们会同时删去所有满足如下条件的元素:

  • 0<i<nai1>ai

注意,每次操作都是先找出所有要删的元素,再同时删去,而不是判定一个删去一个。

我们将一直进行操作,直到数组单调不下降(即,i>0:ai1ai)。求操作次数。

数据范围:1n1051ai109


解法

注:本题我的做法不是最优做法,比较麻烦。官网讨论区有更简单的做法。

fi 表示原数组里位置 i 上的元素,会在第几次操作中被删去。特别地,如果它永远不会被删去,则 fi=。显然,答案就是 max{fi | fi}

注意到一个性质,一个元素的 fi,只与它前面的元素有关。所以可以从左到右,依次求 fi。也就是在访问到 i 时,可以假设所有 0j<ifj 已知。

如果元素 i 在被删去时,它前面那个比它大的元素,在原数组里位置为 j,我们就称 ji 吃掉了。如果知道了 i 是被 j 吃掉的,那么 fi=max{fk+1 | j<k<i}。问题转化为求每个元素是被谁吃掉的。

发现 j 要吃掉 i (j<i),必须满足如下两个性质:

  1. aj>ai
  2. fjmax{fk+1 | j<k<i}。也就是说,j 不能在遇到 i 前自己先被吃掉。

我们发现,i 一定会被满足这两个条件的,最大的 j 吃掉。如果 i 前面没有满足这两个条件的 j,那么 fi=

考虑如何求出满足这两个条件的,最大的 j。先考虑第一个条件。将 a 数组离散化。然后在值域上建立线段树。线段树每个叶子节点 pai=p 的最大的 i,用线段树维护区间最大值。这样,每次查询区间 [ai+1,maxValue] 里的最大值,就是我们想要的 j。对于第二个条件,我们发现因为我们是从左到右考虑所有 i 的,如果一个 j 在某一个 i 那里不满足第二个条件,在后面的所有 i 里也一定都不满足。所以只需要每次把不满足第二个条件的 j 全部删去(即在线段树上给 aj 赋值为 )即可。可以用小根堆来维护,每次弹出 fj 最小的 j,看是否满足第二个条件。

我们已经求出每个 i 对应的 j。接下来只需要查询 [j+1,i1] 区间里 fk 的最大值即可。可以再用另一棵线段树维护 f 数组,操作和前面一样,都是需要支持单点修改、查询区间最大值。

时间复杂度 O(nlogn)

参考代码

复制class Solution {
public:
    static const int INF = 1e9;

    struct SegmentTree {
        vector<int> mx;
        void init(int n) {
            mx.resize(n * 4 + 5);
        }

        void push_up(int p) {
            mx[p] = max(mx[p << 1], mx[p << 1 | 1]);
        }
        void build(int p, int l, int r) {
            if (l == r) {
                mx[p] = -INF;
                return;
            }
            int mid = (l + r) >> 1;
            build(p << 1, l, mid);
            build(p << 1 | 1, mid + 1, r);
            push_up(p);
        }
        void modify(int p, int l, int r, int pos, int val) {
            if (l == r) {
                mx[p] = val;
                return;
            }
            int mid = (l + r) >> 1;
            if (pos <= mid) {
                modify(p << 1, l, mid, pos, val);
            } else {
                modify(p << 1 | 1, mid + 1, r, pos, val);
            }
            push_up(p);
        }
        int query(int p, int l, int r, int ql, int qr) {
            if (ql <= l && qr >= r) {
                return mx[p];
            }
            int mid = (l + r) >> 1;
            int res = -INF;
            if (ql <= mid) {
                res = query(p << 1, l, mid, ql, qr);
            }
            if (qr > mid) {
                res = max(res, query(p << 1 | 1, mid + 1, r, ql, qr));
            }
            return res;
        }
        SegmentTree() {}
    } Tj, Tf;

    int totalSteps(vector<int>& a) {
        int n = a.size();

        // discretization
        vector<int> vals;
        for (int i = 0; i < n; ++i) {
            vals.push_back(a[i]);
        }
        sort(vals.begin(), vals.end());
        vals.erase(unique(vals.begin(), vals.end()), vals.end());
        for (int i = 0; i < n; ++i) {
            a[i] = lower_bound(vals.begin(), vals.end(), a[i]) - vals.begin();
        }

        int m = vals.size();
        Tj.init(m);
        Tj.build(1, 0, m - 1);

        Tf.init(n);
        Tf.build(1, 0, n - 1);

        vector<int> f(n);
        int ans = 0;
        priority_queue<pair<int, int>, vector<pair<int, int> >, greater<pair<int, int> > > pque; // min heap
        for (int i = 0; i < n; ++i) {
            int killer = -INF;
            if (a[i] != m - 1) {
                killer = Tj.query(1, 0, m - 1, a[i] + 1, m - 1);
            }

            if (killer == -INF) {
                f[i] = INF;
            } else {
                if (killer + 1 <= i - 1) {
                    f[i] = Tf.query(1, 0, n - 1, killer + 1, i - 1) + 1;
                } else {
                    assert(killer == i - 1);
                    f[i] = 1;
                }
                ans = max(ans, f[i]);
            }
            // cout << f[i] << " ";

            while (!pque.empty() && pque.top().first < f[i] + 1) {
                int j = pque.top().second;
                Tj.modify(1, 0, m - 1, a[j], -INF);
                pque.pop();
            }

            pque.push(make_pair(f[i], i));
            Tj.modify(1, 0, m - 1, a[i], i);
            Tf.modify(1, 0, n - 1, i, f[i]);
        }
        // cout << endl;
        return ans;
    }
};
posted @   duyiblue  阅读(404)  评论(3编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
点击右上角即可分享
微信分享提示