#P3550. 第2题-消失的山
          
                        
                                    
                      
        
              - 
          
          
                      1000ms
            
          
                      Tried: 100
            Accepted: 31
            Difficulty: 5
            
          
          
          
                       所属公司 : 
                              百度
                                
            
                        
              时间 :2025年9月2日
                              
                      
          
 
- 
                        算法标签>模拟          
 
第2题-消失的山
思路
这是一个典型的模拟题。我们需要按照题目描述的规则,一年一年地模拟山峰和山谷消失的过程,直到某一年不再有任何山消失为止。
核心模拟流程:
- 
数据结构:由于山会不断被移除,我们需要一个能够高效执行删除操作的数据结构。数组(如C++的
std::vector或Java的ArrayList)在删除中间元素时效率较低(时间复杂度为 O(N)),因为需要移动后续所有元素。相比之下,双向链表(如C++的std::list或Java的LinkedList)是更理想的选择,因为它可以在 O(1) 的时间内删除一个已知位置的元素。 - 
模拟循环:我们用一个变量
year从 1 开始计数,进入一个主循环。这个循环的终止条件是“某一年没有任何山消失”。 - 
识别山峰/山谷:在每一年中,我们首先需要判断是“山谷年”(
year为奇数)还是“山峰年”(year为偶数)。然后,我们遍历当前所有的山(注意要排除首尾两座),根据定义(山谷:hi<hi−1 且 hi<hi+1;山峰:hi>hi−1 且 hi>hi+1)来找出所有符合条件的山。 - 
同步删除:这是一个关键点。题目要求“所有”山谷或山峰“同时”消失。这意味着我们不能在遍历过程中发现一个就立即删除一个,因为删除一个山会改变其邻居的相邻关系,从而影响后续的判断。
- 正确做法是:先遍历一遍,将所有待删除的山的位置(例如,在链表中的迭代器或在数组中的索引)记录在一个临时列表中。
 - 遍历结束后,再根据这个临时列表,将这些山从主数据结构中统一删除。
 
 - 
终止条件:在一次完整的识别过程后,如果发现临时列表是空的,说明在当前这一年,没有符合消失条件的山。这正是我们寻找的时刻。此时,循环结束,当前的
year值就是答案。 - 
循环继续:如果临时列表不为空,说明今年有山消失了。我们就执行删除操作,然后将
year加 1,进入下一年的模拟。 
算法总结:
- 
初始化
year = 1。 - 
使用一个双向链表存储山的高度。
 - 
while (true)循环:- 创建一个空的临时列表 
to_remove来存储待删除的山的位置。 - 判断 
year的奇偶性,确定是寻找山谷还是山峰。 - 遍历链表(从第二个到倒数第二个元素),找到所有符合条件的元素,将其迭代器存入 
to_remove。 if (to_remove.empty()):说明本年度没有山消失,跳出循环。else:遍历to_remove列表,从主链表中删除这些山。year++。
 - 创建一个空的临时列表 
 - 
输出最终的
year。 
C++
#include <iostream>
#include <vector>
#include <list>
#include <numeric>
int main() {
    // 优化输入输出速度
    std::ios_base::sync_with_stdio(false);
    std::cin.tie(NULL);
    int n;
    std::cin >> n;
    // 使用 std::list (双向链表),因为删除操作更高效
    std::list<int> mountains;
    for (int i = 0; i < n; ++i) {
        int h;
        std::cin >> h;
        mountains.push_back(h);
    }
    int year = 1;
    while (true) {
        // 如果山的总数少于3,不可能形成山谷或山峰
        if (mountains.size() < 3) {
            break;
        }
        // 存储待删除山的迭代器
        std::vector<std::list<int>::iterator> to_remove;
        // 判断是山谷年(奇数年)还是山峰年(偶数年)
        bool is_valley_year = (year % 2 == 1);
        // 遍历链表,从第二个元素到倒数第二个元素
        for (auto it = std::next(mountains.begin()); it != std::prev(mountains.end()); ++it) {
            auto prev_it = std::prev(it);
            auto next_it = std::next(it);
            if (is_valley_year) {
                // 山谷的条件: h[i] < h[i-1] && h[i] < h[i+1]
                if (*it < *prev_it && *it < *next_it) {
                    to_remove.push_back(it);
                }
            } else { // 山峰年
                // 山峰的条件: h[i] > h[i-1] && h[i] > h[i+1]
                if (*it > *prev_it && *it > *next_it) {
                    to_remove.push_back(it);
                }
            }
        }
        // 如果没有山需要被移除,则找到了答案
        if (to_remove.empty()) {
            break;
        }
        // 统一删除所有标记的山
        for (auto it : to_remove) {
            mountains.erase(it);
        }
        // 年份增加
        year++;
    }
    // 输出第一个没有山消失的年份
    std::cout << year << std::endl;
    return 0;
}
Python
import sys
def solve():
    """
    主解决函数
    """
    try:
        # 从标准输入读取数据
        n_str = sys.stdin.readline()
        if not n_str: return
        n = int(n_str)
        heights = list(map(int, sys.stdin.readline().split()))
    except (IOError, ValueError):
        return
    year = 1
    while True:
        # 山的数量不足3,无法形成山谷或山峰
        if len(heights) < 3:
            break
        # 待删除山的索引列表
        to_remove_indices = []
        is_valley_year = (year % 2 == 1)
        # 遍历所有可能的山谷或山峰(排除首尾)
        for i in range(1, len(heights) - 1):
            h_prev = heights[i-1]
            h_curr = heights[i]
            h_next = heights[i+1]
            if is_valley_year:
                # 检查是否为山谷
                if h_curr < h_prev and h_curr < h_next:
                    to_remove_indices.append(i)
            else: # 山峰年
                # 检查是否为山峰
                if h_curr > h_prev and h_curr > h_next:
                    to_remove_indices.append(i)
        # 如果没有山需要移除,当前年份就是答案
        if not to_remove_indices:
            break
        # 为了安全地删除元素,我们从后往前删除,这样不会影响前面元素的索引
        for i in reversed(to_remove_indices):
            del heights[i]
        
        # 另一种方法:通过构建一个新列表来完成删除,对于Python来说通常也足够快
        # survivors = []
        # remove_set = set(to_remove_indices)
        # for i, h in enumerate(heights):
        #     if i not in remove_set:
        #         survivors.append(h)
        # heights = survivors
        year += 1
    # 输出第一个没有山消失的年份
    print(year)
if __name__ == "__main__":
    solve()
Java
import java.util.Scanner;
import java.util.LinkedList;
import java.util.ArrayList;
import java.util.List;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int n = scanner.nextInt();
        // 使用 LinkedList,因为它在删除中间元素时比 ArrayList 更高效
        LinkedList<Integer> mountains = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            mountains.add(scanner.nextInt());
        }
        int year = 1;
        while (true) {
            // 山的数量不足3,无法形成山谷或山峰
            if (mountains.size() < 3) {
                break;
            }
            // 存储待删除山的索引
            List<Integer> toRemoveIndices = new ArrayList<>();
            boolean isValleyYear = (year % 2 == 1);
            
            // 遍历链表,找出所有要删除的山
            // 从索引1开始,到 size-2 结束
            for (int i = 1; i < mountains.size() - 1; i++) {
                int prevHeight = mountains.get(i - 1);
                int currentHeight = mountains.get(i);
                int nextHeight = mountains.get(i + 1);
                if (isValleyYear) {
                    // 检查是否为山谷
                    if (currentHeight < prevHeight && currentHeight < nextHeight) {
                        toRemoveIndices.add(i);
                    }
                } else { // 山峰年
                    // 检查是否为山峰
                    if (currentHeight > prevHeight && currentHeight > nextHeight) {
                        toRemoveIndices.add(i);
                    }
                }
            }
            // 如果没有山需要移除,则当前年份就是答案
            if (toRemoveIndices.isEmpty()) {
                break;
            }
            // 从后往前删除,避免索引变化导致错误
            for (int i = toRemoveIndices.size() - 1; i >= 0; i--) {
                // toRemoveIndices.get(i) 返回的是要删除的原始索引
                int indexToRemove = toRemoveIndices.get(i);
                mountains.remove(indexToRemove);
            }
            year++;
        }
        // 输出第一个没有山消失的年份
        System.out.println(year);
        scanner.close();
    }
}
        题目内容
Z 市以奇山秀林闻名世界。这里一共有 n 座山排成一排,从左向右编号依次为 1,2,…,n ,其中第 i 座山的高度记为 ai ;
每座山的高度互不相同。如果第 i(2≤i≤n−1) 座山的高度满足严格小于左右相邻的两座山,则第 i 座山被称为山谷;
若满足严格大于左右相邻的两座山,则被称为山峰。由于神秘的地壳运动,每年山谷和山峰会轮流全部消失。若某一年所有山谷同时消失,下一年则是所有山峰同时消失。山的消失实际上是和相邻的山融为了一体,第 i 座山消失后,第 i−1 座山与第 i+1 座山视为相邻(如果两座山均存在的话)。
假设今年是第一年,轮到山谷消失,小铭想知道第一次没有山消失是在第几年?
输入描述
输入第一行有一个正整数 n(1≤n≤105) ,表示山的个数;
第二行有 n 个正整数 a1,a2,…,an(1≤ai≤109),表示每座山的高度。
输出描述
输出一个正整数,表示第一个没有山消失的年份。
样例1
输入
6
5 7 1 2 8 3
输出
4
说明
第一年,山谷消失,第三座山是山谷,消失后数组变为 [5 7 2 8 3 ];
第二年,山峰消失,第二座山、第四座山是山峰,消失后数组变为 [5 2 3 ];
第三年,山谷消失,第二座山是山谷,消失后数组变为 [5 3]。
第四年,山峰消失,没有山峰,没有山消失。
注意,即使下一年有山谷消失,答案也应输出 4 ,第一个没有山消失的年份。