算法周赛笔记(7月第3周)— LeetCode 第57场双周赛

·  阅读 259

这周只参加了一场周赛。先说一下战况,真的太垃圾了,只做出第一题 (ㄒoㄒ)

题目

1941

检查是否所有字符出现次数相同

给你一个字符串 s ,如果 s 是一个 字符串,请你返回 true ,否则请返回 false

如果 s 中出现过的 所有 字符的出现次数 相同 ,那么我们称字符串 s 字符串。

示例

输入:s = "abacbc" 输出:true 解释:s 中出现过的字符为 'a','b' 和 'c' 。s 中所有字符均出现 2 次。

提示:

  • 1 <= s.length <= 1000
  • s 只包含小写英文字母

题解

签到题。简单模拟即可。由于只包含小写字母,所以一共只可能出现26个字母。思路是,先把字符串扫描一遍,统计每个字符的次数,然后再把26个字母的次数遍历一遍,当出现不同的次数时就返回false,否则最后返回true

Java代码如下

class Solution {
    public boolean areOccurrencesEqual(String s) {
		int[] ctn = new int[26];
		for (int i = 0; i < s.length(); i++) {
			int j = s.charAt(i) - 'a';
			ctn[j]++;
		}
		int r = 0;
		for (int i = 0; i < 26; i++) {
			if (ctn[i] > 0) {
				if (r == 0) r = ctn[i];
				else if (r != ctn[i]) return false;
			}
		}
		return true;
	}
}
复制代码

C++代码如下

class Solution {
public:
    bool areOccurrencesEqual(string s) {
        int occurrences[26] = {0};
        for (int i = 0; i < s.size(); i++) {
            int j = s[i] - 'a';
            occurrences[j]++;
        }
        int r = 0;
        for (int i = 0; i < 26; i++) {
            if (occurrences[i] > 0) {
                if (r == 0) r = occurrences[i];
                else if (r != occurrences[i]) return false;
            }
        }
	return true;
    }
};
复制代码

C++的局部变量数组,记得用{0}初始化为全0,这跟Java不同,Java局部变量数组默认初始值就是全0。

1942

最小未被占据椅子的编号

n 个朋友在举办一个派对,这些朋友从 0n - 1 编号。派对里有 无数 张椅子,编号为 0infinity 。当一个朋友到达派对时,他会占据 编号最小 且未被占据的椅子。

比方说,当一个朋友到达时,如果椅子 015 被占据了,那么他会占据 2 号椅子。 当一个朋友离开派对时,他的椅子会立刻变成未占据状态。如果同一时刻有另一个朋友到达,可以立即占据这张椅子。

给你一个下标从 0开始的二维整数数组 timestimes ,其中 times[i]=[arrivali,leavingi]times[i] = [arrival_i, leaving_i] 表示第 ii 个朋友到达离开的时刻,同时给你一个整数 targetFriendtargetFriend 。所有到达时间 互不相同

请你返回编号为 targetFriendtargetFriend 的朋友占据的 椅子编号

示例

输入:times = [[1,4],[2,3],[4,6]], targetFriend = 1 输出:1 解释:

  • 朋友 0 时刻 1 到达,占据椅子 0 。
  • 朋友 1 时刻 2 到达,占据椅子 1 。
  • 朋友 1 时刻 3 离开,椅子 1 变成未占据。
  • 朋友 0 时刻 4 离开,椅子 0 变成未占据。
  • 朋友 2 时刻 4 到达,占据椅子 0 。 朋友 1 占据椅子 1 ,所以返回 1 。

提示

  • n == times.length
  • 2 <= n <= 10^4^
  • times[i].length == 2
  • 1arrivalileavingi1051 \le arrival_i \le leaving_i \le 10^5
  • 0 <= targetFriend <= n - 1
  • 每个 arrivaliarrival_i 时刻 互不相同

题解

当天晚上做的时候,想了很久。我当时只想到,这是一道区间类的题,需要根据到达和离开的时间来排序。然后就是需要维护已被占用的椅子,和当前可用的椅子。一个人 ii 到达派对时,他要么按照到达时间的顺序,占据第 ii 个椅子,要么在他之前到达的人,在他到达之前离开了,释放出了前面编号更小的椅子。所以关键在于,维护当前可用的椅子,以及,对每个到达时间,需要判断在这个时间之前,有没有人离开。

官方题解的思路如下:

每个人只有在2个时间节点会对椅子的状态产生影响

  • 到达时,占据某个椅子
  • 离开时,释放某个椅子

由于实际只有 n 个人参加派对,则需要的椅子编号最多就是 0n-1(在整个过程中,所有人都不释放椅子),而每到达一个人,我们需要当前可用的,且编号最小的椅子。

于是,我们可以用小根堆来维护当前可用椅子这个信息(小根堆中获取最小值的时间复杂度为O(1)O(1))。

  • 当某人到达时,弹出小根堆的堆顶,且此人占据该椅子。

  • 当某人离开时,将该人占据的椅子,重新插入小根堆,表明该椅子可用。

我们还需要一个哈希表,来存储某个人,以及他所占据的椅子的编号。

每个人只有一次占据椅子的机会,所以每个人占据的椅子是确定的。为了判断某个人占据的椅子的编号,我们需要在这个人到达时,判断可用椅子的情况。先到达的人先判断,所以我们根据 到达时间 进行升序排列。在某个人到达时,我们判断这个到达时间之前,有没有人离开,所以我们还需要把离开时间升序排列。

然后从小到大,遍历到达时间时,对离开时间的遍历不会往回走。即,对升序排列后的到达时间数组,升序排列后的离开时间数组,遍历时指针移动的方向是一致的,指针不会往回移动。所以我们可以用双指针的思路来进行操作。

这样以来,我们具体的代码思路如下:

用一个小根堆来存储当前可用的椅子,初始时,将编号为0n-1,共n个椅子,全部插入小根堆,此时全部椅子都可用。设两个指针 ij,用指针 i 遍历按照到达时间升序排列好的数组,j则用来遍历按照离开时间升序排列好的数组。每遍历到一个i,尝试将 j 往后移动,处理离开时间小于当前到达时间的那些人,将这些人的椅子重新插入小根堆。处理完毕后,弹出小根堆的堆顶,即是当前可用的最小编号的椅子。我们开一个map(哈希表),来存储某个人占据的椅子编号。把当前到达的这个人占据的椅子保存起来。双指针遍历结束后,从map中获取目标个人的椅子编号即可。

Java代码如下

class Solution {

    public int smallestChair(int[][] times, int targetFriend) {
        int n = times.length;
        // 按照离开时间升序排列的数组
        Pair[] leaving = new Pair[n];
        // 按照到达时间升序排列的数组
        Pair[] arrival = new Pair[n];
        // 某个人占据的椅子
        Map<Integer, Integer> seatsMap = new HashMap<>();
        // 当前可用的椅子
        PriorityQueue<Integer> availableSeats = new PriorityQueue<>();
        for (int i = 0; i < n; i++) {
            // 初始时, 0 到 n - 1 的全部椅子都可用
            availableSeats.offer(i);
            // 到达时间数组(时间+人的编号)
            arrival[i] = new Pair(times[i][0], i);
            // 离开时间数组
            leaving[i] = new Pair(times[i][1], i);
        }
        // 根据时间, 进行升序排列
        Arrays.sort(leaving, (Comparator.comparingInt(o -> o.first)));
        Arrays.sort(arrival, (Comparator.comparingInt(o -> o.first)));
        // 双指针, 遍历到达数组, 并处理在它之前的离开数组
        for (int i = 0, j = 0; i < n; i++) {
            // 当离开数组未遍历完, 且离开时间小于等于当前到达时间时
            while (j < n && leaving[j].first <= arrival[i].first) {
                // 处理在当前到达时间之前离开的
                int s = seatsMap.get(leaving[j].second); // 获取这个人占据的椅子
                availableSeats.offer(s); // 椅子重新变为可用
                j++;
            }
            // 从当前的可用椅子中拿出一个
            int s = availableSeats.poll();
            seatsMap.put(arrival[i].second, s); // 当前编号的人, 占据的椅子
        }
        return seatsMap.get(targetFriend);
    }
}

class Pair {
    int first;
    int second;

    public Pair(int first, int second) {
        this.first = first;
        this.second = second;
    }
}
复制代码

C++代码如下

typedef pair<int,int> PII;
class Solution {
public:
    int smallestChair(vector<vector<int>>& times, int targetFriend) {
        int n = times.size();
        vector<PII> leaving;
        vector<PII> arrival;
        priority_queue<int, vector<int>, greater<int>> availableSeats; // 小根堆
        unordered_map<int, int> seatsMap;

        for (int i = 0; i < n; i++) {
            arrival.push_back( { times[i][0], i } );
            leaving.push_back( { times[i][1], i } );
            availableSeats.push(i);
        }
        sort(arrival.begin(), arrival.end());
        sort(leaving.begin(), leaving.end());

        for (int i = 0, j = 0; i < n; i++) {
            while (j < n && leaving[j].first <= arrival[i].first) {
                int p = leaving[j].second;
                availableSeats.push(seatsMap[p]);
                j++;
            }
            seatsMap[arrival[i].second] = availableSeats.top();
            availableSeats.pop();
        }
        return seatsMap[targetFriend];
    }
};
复制代码

在用C++写代码的过程中发现一些问题,这里记录一下,需要注意(由于工作中用的都是Java,所以对C++不是太熟)

  • 关于vector容器初始化

    可以通过变量来初始化,比如

    int n = times.size();
    vector<PII> leaving(n); // 这句的意思是, 往这个vector容器中插入10个元素, 每个元素默认值是0
    复制代码

    若初始化vector时传入了一个大小(比如10),则vector中已有了10个元素,且全为0。后续在调用push_back往容器里添加元素时,是插入到第10个元素之后了。这跟Java里的容器,传入一个大小,只是用来指明初始容量,而不插入任何元素,有所不同。

  • 关于 typedef pair<int, int> PII;

    在vs中写代码时一开始报错了。事实证明pair是属于std的,这句typedef pair<int,int> PII; 应当放在 using namespace std; 之后

1943

描述绘画结果

给你一个细长的画,用数轴表示。这幅画由若干有重叠的线段表示,每个线段有 独一无二 的颜色。给你二维整数数组 segments ,其中 segments[i] = [start_i, end_i, color_i] ,表示线段为 半开区间 [start_i, end_i) 且颜色为 color_i

线段间重叠部分的颜色会被 混合 。如果有两种或者更多颜色混合时,它们会形成一种新的颜色,用一个 集合 表示这个混合颜色。

比方说,如果颜色 2 ,4 和 6 被混合,那么结果颜色为 {2,4,6} 。 为了简化题目,你不需要输出整个集合,只需要用集合中所有元素的 来表示颜色集合。

你想要用 最少数目 不重叠 半开区间表示 这幅混合颜色的画。这些线段可以用二维数组 painting 表示,其中 painting[j] = [left_j, right_j, mix_j] 表示一个 半开区间[left_j, right_j) 的颜色 mix_j

比方说,这幅画由 segments = [[1,4,5],[1,7,7]] 组成,那么它可以表示为 painting = [[1,4,12],[4,7,7]] ,因为: [1,4) 由颜色 {5,7} 组成(和为 12),分别来自第一个线段和第二个线段。 [4,7) 由颜色 {7} 组成,来自第二个线段。 请你返回二维数组 painting ,它表示最终绘画的结果(没有 被涂色的部分不出现在结果中)。你可以按 任意顺序 返回最终数组的结果。

半开区间 [a, b) 是数轴上点 a 和点 b 之间的部分,包含 点 a 且 不包含 点 b 。

示例1

输入:segments = [[1,4,5],[4,7,7],[1,7,9]] 输出:[[1,4,14],[4,7,16]] 解释:绘画结果可以表示为:

  • [1,4) 颜色为 {5,9} (和为 14),分别来自第一和第二个线段
  • [4,7) 颜色为 {7,9} (和为 16),分别来自第二和第三个线段

示例2

输入:segments = [[1,7,9],[6,8,15],[8,10,7]] 输出:[[1,6,9],[6,7,24],[7,8,15],[8,10,7]] 解释:绘画结果可以以表示为:

  • [1,6) 颜色为 9 ,来自第一个线段
  • [6,7) 颜色为 {9,15} (和为 24),来自第一和第二个线段
  • [7,8) 颜色为 15 ,来自第二个线段
  • [8,10) 颜色为 7 ,来自第三个线段

提示

  • 1 <= segments.length <= 2 * 10^4^
  • segments[i].length == 3
  • 1 <= start_i < end_i <= 10^5^
  • 1 <= color_i <= 10^9^
  • 每种颜色 color_i 互不相同

题解

当天晚上,我想了一会儿,意识到这其实是个类似区间合并的问题(内心OS:怎么又是一道区间的题)。由于相同区间内的颜色需要相加(一个区间内的所有位置都要加),所以我当时考虑到用差分前缀和的思路,但是发现每个线段都是左闭右开的区间,在进行区间合并时,对于端点的计算会出现错误。我当时考虑将每个点用2个点来表示,比如4,就用44_左44_右 两个点来表示,那么就需要做原点扩展后的点之间的映射。但是还有很多细节很容易出错,比如需要判断一个位置是否是某个线段的端点位置,不能只通过差分数组为0来判断。

周赛12点结束,这道题我就差一点!不甘心啊!于是我继续debug,终于在12点40通过了这道题。虽然代码很丑,但还是先上一个我自己的解法吧。

class Solution {
    final int SEGMENT_MAX = 100000;

	public List<List<Long>> splitPainting(int[][] segments) {

		long[] segmentValues = new long[2 * SEGMENT_MAX];
		boolean[] flags = new boolean[2 * SEGMENT_MAX];
		long max = 0;
		for (int[] segment : segments) {
			max = Math.max(max, 2 * segment[1] - 1);
			add(segment[0], segment[1], segment[2], segmentValues, flags);
		}
		List<List<Long>> res = new ArrayList<>();
		// 根据差分数组, 依次统计每个区间段的颜色和
		long l = -1, r = -1, c = -1;
		for (int i = 1; i <= max ; i++) {
			if (l == -1 && segmentValues[i] != 0) {
                // 区间起始位置
				segmentValues[i] += segmentValues[i - 1];
				l = i / 2 + 1; // 扩展的点还原为原先的点
				c = segmentValues[i]; // 差分数组还原, 累加
			} else if (flags[i]) {
                // 通过flag来判断当前位置是否是线段的端点位置, 是否进行合并
                // 一开始我是通过差分数组是否为0来判断, 发现这样是错误的
                // 可能差分数组恰好被加和被减, 导致这个位置为0
				r = i / 2 + 1;
                long t = c; // 这个区间的颜色和, 暂存
				List<Long> item = new ArrayList<>();
				item.add(l);
				item.add(r);
				item.add(c);
                // 由于是开区间, 所以不将这个点的差分值纳入到当前区间, 在后面这里再更新
                segmentValues[i] += segmentValues[i - 1];
				c = segmentValues[i];
				l = i / 2 + 1;
                if(t == 0) continue; // 特判, 如果这个区间内的颜色和为0, 跳过
                res.add(item);
			} else segmentValues[i] += segmentValues[i - 1];
		}
		return res;
	}

	// 差分数组
	private void add(int l, int r, int c, long[] val, boolean[] flags) {
		val[2 * l - 1] += c; // 1个点用2个点表示后, 扩展后的点的位置
		val[2 * r - 1] -= c;
		flags[2 * l - 1] = flags[2 * r - 1] = true; // 用flag来表示线段的端点位置
	}
}
复制代码

官方题解:

就是差分+前缀和的思路,但是注意差分数组为0可能是左右的增量导致的,并不代表这个位置不是一个线段端点。所以我们需要维护所有线段的端点。官方题解直接使用哈希表来存储了线段的端点,然后转变为差分数组。对线段中间的部分直接进行了忽略(压缩),这是类似于离散化的操作。然后对于右端点是开区间的这一点,计算时只要取右端点前一个位置的前缀和,即可把右端点的差分量排除。(我怎么没想到呢😓)

Java代码如下

class Solution {
    public List<List<Long>> splitPainting(int[][] segments) {
        // 直接采用差分+前缀和即可
        Map<Integer, Long> d = new HashMap<>();
        // 记录所有端点和端点上的增量
        for (int[] s : segments) {
            int l = s[0], r = s[1], c = s[2];
            long l_c = d.get(l) == null ? 0L : d.get(l);
            long r_c = d.get(r) == null ? 0L : d.get(r);
            d.put(l, l_c + c);
            d.put(r, r_c - c);
        }
        // 转为数组 (差分数组)
        List<Pair> list = d.entrySet().stream()
                .map(e -> new Pair(e.getKey(), e.getValue()))
                .collect(Collectors.toList());
        // 按照端点位置从小到大排序
        list.sort(Comparator.comparingLong(o -> o.i));

        List<List<Long>> res = new ArrayList<>();
        // 差分数组还原为前缀和, 并同时求解答案
        for (int i = 1; i < list.size(); i++) {
            list.get(i).c += list.get(i - 1).c;
            if (list.get(i - 1).c != 0) {
                res.add(Arrays.asList(list.get(i - 1).i, list.get(i).i, list.get(i - 1).c));
            }
        }
        return res;
    }
}
class Pair {
        long i;
        long c;

    public Pair(long i, long c) {
        this.i = i;
        this.c = c;
    }
}
复制代码

C++代码如下

class Solution {
public:
    vector<vector<long long>> splitPainting(vector<vector<int>>& segments) {
        unordered_map<int, long long> delta;
        for (auto &s : segments) {
            int l = s[0], r = s[1], c = s[2];
            delta[l] += c;
            delta[r] -= c;
        }
        vector<pair<int, long long>> deltaList;
        for (auto& d : delta) {
            deltaList.push_back({d.first, d.second});
        }
        sort(deltaList.begin(), deltaList.end());
        vector<vector<long long>> res;
        for (int i = 1; i < deltaList.size(); i++) {
            deltaList[i].second += deltaList[i - 1].second;
            if (deltaList[i - 1].second != 0) {
                res.push_back({deltaList[i - 1].first, deltaList[i].first, deltaList[i - 1].second});
            }
        }
        return res;
    }
};
复制代码

1944

队列中可以看到的人数

有 n 个人排成一个队列,从左到右 编号为 0 到 n - 1 。给你以一个整数数组 heights ,每个整数 互不相同,heights[i] 表示第 i 个人的高度。

一个人能 看到 他右边另一个人的条件是这两人之间的所有人都比他们两人 。更正式的,第 i 个人能看到第 j 个人的条件是 i < j 且 min(heights[i], heights[j]) > max(heights[i+1], heights[i+2], ..., heights[j-1]) 。

请你返回一个长度为 n 的数组 answer ,其中 answer[i] 是第 i 个人在他右侧队列中能 看到人数

示例

输入:heights = [10,6,8,5,11,9] 输出:[3,1,2,1,1,0] 解释: 第 0 个人能看到编号为 1 ,2 和 4 的人。 第 1 个人能看到编号为 2 的人。 第 2 个人能看到编号为 3 和 4 的人。 第 3 个人能看到编号为 4 的人。 第 4 个人能看到编号为 5 的人。 第 5 个人谁也看不到因为他右边没人。

提示

  • n == heights.length
  • 1 <= n <= 10^5^
  • 1 <= heights[i] <= 10^5^
  • heights 中所有数 互不相同

题解

这道题当天晚上连题目都没读到。今天重新做一遍,尝试了一下,写出了暴力解法。

class Solution {
    public int[] canSeePersonsCount(int[] heights) {
        int n = heights.length;
        int[] ans = new int[n];
        for (int i = 0; i < n - 1; i++) {
            int j = i + 1;
            int max = 0; // max 用来存 i, j之间的最大高度
            do {
                if (Math.min(heights[i], heights[j]) > max) ans[i]++;
                max = Math.max(max, heights[j++]);
            } while (j < n);
        }
        return ans;
    }
}
复制代码

但是暴力解法的时间复杂度较高,所以需要进行优化。

但是,想了很久,我只能想到:若两个相邻的人aba在左侧,b在右侧,若height[a] > height[b],那么对于所有a左边的人,都无法看到b,更一般的说,当有个区间从左到右高度递减,那么对于这个区间左端点的左侧所有人,都无法看到这个区间左端点右侧的全部人(因为这个区间最左侧的人最高,把区间内的其他人全部挡住了)。所以,对于一个人 A,可能需要预处理出其右侧的高度递增序列,然后只要找到递增序列中第一个超过A高度的位置,递增序列前面的人的个数就是 A 能看到的人数。但是再往后就不知道具体怎么操作了。憋了1小时,还是没憋出来,看题解了!/(ㄒoㄒ)/~~

官方题解:用单调栈(是yxc前面几章的内容,学过又忘了,练的不够,不熟,还需要经常炒回锅肉才行!)

具体思路:一个人能看到的所有人,按照位置从左到右,一定都是高度递增的。(这跟我自己想的一样)

采用一个单调栈,并且从右往左遍历全部的人,栈里存的是:当前遍历的这个人,其右侧的递增序列。

根据前面提到的,对于当前这个人,我们需要找到其右侧单调递增的序列中,第一个比当前这个人高的人。

所以,我们栈顶存的应当是递增序列高度最小的(也是递增序列中离当前的人距离最近的),栈底存的是高度最大的。这样,当栈顶比当前的人小,就可以给当前的人计数+1,并弹出栈顶(因为栈顶比当前的小,则当前的人比栈顶大,则当前的人的左侧的人,看过来,会被当前这个人挡住),一直到遇到栈顶比当前的人高,说明找到递增序列中第一个比这个人高的人,则当前这个人的计数结束,并且找到栈中的位置比当前这个人高,当前这个人插入栈中,不会挡住当前这个人左侧的人,在后续遍历到更左侧的人,保证了栈中存储的是右边单调递增的序列。

举个实际的例子,对于序列 10,2,4,3,7,2,9,8,11,13,17,11,画一个柱状图如下

对于1这个人,其高度是10,将其右侧递增的序列标记出来,如下

在上方橙色标记的高度递增序列中,中间有3个人,由于高度比前一个人矮,所以被前一个人挡住了,而递增序列中,第9个人的高度是11,是第一个高度大于10的人,由于其高度大于10,则第9个人之后的那些人,无论高度是多少,都不可能被第1个人看见了。因为第1个人的高度已经小于了中间位置的某个人的高度。

所以在递增序列中,高度为11以及前面的人的数量,就是第1个人向右看过去,能看到的人的数量,也就是5

对于每个人,都是如此,找到这个人右侧的高度递增的序列,并找到递增序列中第一个高度大于等于他的人,停止,递增序列从左到这个停止位置的人数,就是当前这个人能看到的人数。

由于要维护一个人右侧的递增序列,所以遍历时我们从右往左遍历,并用一个结构来保存递增序列,用什么结构呢?栈!

栈顶存的是高度最小的人(也是递增序列中离当前位置最近的人),栈底是高度最大的人。比如从右往左遍历到第8个人时,栈的状态应该如下

栈顶是9号这个人,其高度为11,栈底是11号这个人,其高度为17。当栈非空时,拿栈顶的人的高度,与当前这个人(8号人)作比较,无论比较结果如何,先把当前这个人的计数+1,然后判断,

  • 若栈顶的人的高度大于等于当前这个人的高度,则根据上面的思路,找到了第一个高度大于当前这个人的位置,计数结束,并且把当前这个人入栈(保持了栈的单调性)

  • 若栈顶的人的高度小于当前这个人的高度,则说明

    • 还未找到递增序列中第一个高度大于当前这个人的位置,则需要继续计数
    • 并且由于当前这个人比栈顶的高,对随后会被遍历到的,当前这个人左侧的人来说,当前这个人会把后面的人挡住,并且由于需要维护栈的单调性

    所以,把栈顶弹出,继续计数,并作循环判断。

    直到栈为空,或者遇到栈顶的人高度大于等于当前这个人,说明当前这个人计数完毕,并且当前这个人被插入到栈中可以维持栈的单调性,则入栈。

对于8号人,栈顶9号高度11已经大于其高度8,则计数结束,计数结果为1,并把8号人入栈。继续遍历到第7号人做判断。

如此,思路就清晰了,无非就是

  • 从右往左遍历每个人
  • 用一个栈,来存储当前遍历的人的右侧的递增序列
    • 栈顶是高度最小的人
    • 栈底是高度最大的人
  • 每次尝试弹出栈顶,比较栈顶的人的高度与当前的人的高度,直到栈为空,或者找到栈顶的人的高度大于等于当前人的高度时,停止;当前人的计数结束,并把当前人入栈

Java代码

class Solution {
    public int[] canSeePersonsCount(int[] heights) {
        int n = heights.length;
        Stack<Integer> stack = new Stack<>();
        int[] ans = new int[n];
        for (int i = n - 1; i >= 0 ; i--) {
            while (!stack.empty()) {
                ans[i]++;
                // 这里要特别注意, heights数组各不相同,这保证了单调栈里是严格单调的
                if (heights[i] > heights[stack.peek()]) stack.pop();
                else break;
            }
            stack.push(i);
        }
        return ans;
    }
}
复制代码

C++代码

class Solution {
public:
    vector<int> canSeePersonsCount(vector<int>& heights) {
        int n = heights.size();
        stack<int> stk;
        vector<int> ans(n);
        for (int i = n - 1; i >= 0; i--) {
            while (!stk.empty()) {
                ans[i]++;
                if (heights[i] > heights[stk.top()]) stk.pop();
                else break;
            }
            stk.push(i);
        }
        return ans;
    }
};
复制代码

单调栈这一类的题都有一些共同的特性,那就是单调性,可以参考我之前的文章:Acwing - 算法基础课笔记(四) 中的 单调栈 小节,把对应的练习题好好再做一遍,即可对单调栈类型的题目做到心中有数。

(完)

(2021/07/29更新:把每道题用C++重新做了一遍)

分类:
后端
标签: