阅读视图

发现新文章,点击刷新页面。

每日一题-三段式数组 I🟢

给你一个长度为 n 的整数数组 nums

如果存在索引 0 < p < q < n − 1,使得数组满足以下条件,则称其为 三段式数组(trionic)

  • nums[0...p] 严格 递增,
  • nums[p...q] 严格 递减,
  • nums[q...n − 1] 严格 递增。

如果 nums 是三段式数组,返回 true;否则,返回 false

 

示例 1:

输入: nums = [1,3,5,4,2,6]

输出: true

解释:

选择 p = 2, q = 4

  • nums[0...2] = [1, 3, 5] 严格递增 (1 < 3 < 5)。
  • nums[2...4] = [5, 4, 2] 严格递减 (5 > 4 > 2)。
  • nums[4...5] = [2, 6] 严格递增 (2 < 6)。

示例 2:

输入: nums = [2,1,3]

输出: false

解释:

无法选出能使数组满足三段式要求的 pq

 

提示:

  • 3 <= n <= 100
  • -1000 <= nums[i] <= 1000

Vue-常用修饰符

前言

在 Vue 开发中,修饰符(Modifiers)是指令后的一个特殊后缀(以 . 开头),它能以极简的方式帮我们处理事件冒泡、键盘监听以及复杂的双向绑定逻辑。掌握它们,能让你的模板代码既优雅又高效。

一、 事件修饰符:精准控制交互行为

事件修饰符主要用于处理 DOM 事件的细节。

  • .stop:阻止事件冒泡(调用 event.stopPropagation())。
  • .prevent:阻止事件的默认行为(调用 event.preventDefault())。
  • .capture:在捕获模式下触发事件监听器。
  • .self:只有当事件是从触发元素本身触发时才触发回调。
  • .once:事件只触发一次,之后自动移除监听器。
  • .passive:滚动事件的性能优化,告诉浏览器不需要等待 preventDefault

二、 键盘与鼠标修饰符:语义化监听

1. 按键修饰符

在监听键盘事件时,我们经常需要检查特定的按键,例如:<input @keyup.enter="submitForm" type="text" placeholder="按回车提交">

  • .enter:回车键
  • .tab:Tab 键
  • .space:空格键
  • .delete:删除或退格键
  • .up / .down / .left / .right:方向键

2. 鼠标修饰符

用于限制处理程序仅响应特定的鼠标按键。

  • .left:点击鼠标左键触发。
  • .right:点击鼠标右键触发。
  • .middle:点击鼠标中键(滚轮点击)触发。

三、 v-model 修饰符:数据预处理

这些修饰符可以自动处理表单输入的数据格式。

  • .lazy: 将v-model的同步时机设置在change事件之后,一般为在输入框失去焦点时。
  • .number:自动将用户的输入值转为数值类型(内部使用 parseFloat)。
  • .trim:自动过滤用户输入内容的首尾空白字符。

四、 双向绑定修饰符

这是 Vue 2 到 Vue 3 变化最大的部分。

1. Vue 2 时代的 .sync

在 Vue 2 中,.sync 是实现父子组件属性双向绑定的语法糖。

// 使用 .sync 的语法糖
<ChildComponent :title.sync="pageTitle" />
// 在子组件的方法中
this.$emit('update:title', newTitleValue);

2. Vue 3 的统一:v-model:prop

Vue 3 废弃了 .sync,将其功能合并到了 v-model 中。支持在同一个组件上绑定多个 v-model

 // 在父组件中
<ChildComponent v-model:title="pageTitle" />

// 子组件
<script setup>
defineProps(['title']);
const emit = defineEmits(['update:title']);

const updateTitle = (newVal) => {
  emit('update:title', newVal);
};
</script>

3. Vue 3.4+ 的黑科技:defineModel

这是目前 Vue 3 最推荐的写法,极大简化了双向绑定的逻辑代码。

// 父组件
<ChildComponent v-model="inputValue" />

// 子组件
const inputValue = defineModel({
 // inputValue为双向绑定输入框的值
  type: [String],
  // 默认值
  default: ''
})

五、 总结

  1. 交互逻辑优先使用事件修饰符,减少组件内的非业务代码。
  2. 表单处理善用 .trim.number,降低后端校验压力。
  3. 父子通信在 Vue 3 项目中全面拥抱 v-model:prop,如果是新项目(Vue 3.4+),请直接使用 defineModel,它能让你的代码量减少 50% 以上。

3637. 三段式数组 I

解法一

思路和算法

数组 $\textit{nums}$ 的长度是 $n$。最直观的思路是遍历 $0 < p < q < n - 1$ 的所有下标对 $(p, q)$,判断是否同时满足三段式数组的全部条件。

  • 下标范围 $[0, p]$ 严格递增。

  • 下标范围 $[p, q]$ 严格递减。

  • 下标范围 $[q, n - 1]$ 严格递增。

当遇到满足全部条件的下标对 $(p, q)$ 时,返回 $\text{true}$。如果不存在满足全部条件的下标对 $(p, q)$,返回 $\text{false}$。

代码

###Java

class Solution {
    public boolean isTrionic(int[] nums) {
        int n = nums.length;
        for (int p = 1; p < n - 2; p++) {
            if (!isMonotonicInRange(nums, 0, p, 1)) {
                continue;
            }
            for (int q = p + 1; q < n - 1; q++) {
                if (isMonotonicInRange(nums, p, q, -1) && isMonotonicInRange(nums, q, n - 1, 1)) {
                    return true;
                }
            }
        }
        return false;
    }

    public boolean isMonotonicInRange(int[] nums, int start, int end, int direction) {
        for (int i = start; i < end; i++) {
            if ((nums[i + 1] - nums[i]) * direction <= 0) {
                return false;
            }
        }
        return true;
    }
}

###C#

public class Solution {
    public bool IsTrionic(int[] nums) {
        int n = nums.Length;
        for (int p = 1; p < n - 2; p++) {
            if (!IsMonotonicInRange(nums, 0, p, 1)) {
                continue;
            }
            for (int q = p + 1; q < n - 1; q++) {
                if (IsMonotonicInRange(nums, p, q, -1) && IsMonotonicInRange(nums, q, n - 1, 1)) {
                    return true;
                }
            }
        }
        return false;
    }

    public bool IsMonotonicInRange(int[] nums, int start, int end, int direction) {
        for (int i = start; i < end; i++) {
            if ((nums[i + 1] - nums[i]) * direction <= 0) {
                return false;
            }
        }
        return true;
    }
}

复杂度分析

  • 时间复杂度:$O(n^3)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。需要遍历的下标对数量是 $O(n^2)$,每个下标对判断是否符合三段式数组的全部条件的时间是 $O(n)$,因此时间复杂度是 $O(n^3)$。

  • 空间复杂度:$O(1)$。

解法二

思路和算法

可以遍历数组 $\textit{nums}$ 一次,判断是否符合三段式的条件。

三个子数组的单调性必须依次满足严格单调递增、严格单调递减、严格单调递增。首个子数组的起始下标是 $0$,从起始下标向右遍历,遍历过程中执行如下操作。

  • 由于三个子数组都满足严格单调递增或严格单调递减,因此不允许出现相邻元素相等的情况。如果遇到相邻元素相等的情况,则一定不符合三段式的条件。

  • 如果遍历到数组 $\textit{nums}$ 的末尾或者遇到相邻元素的单调性与当前子数组的单调性条件相反的情况(已经排除相邻元素相等的情况),则可以确定当前子数组的结束下标。将当前子数组的结束下标作为下一个子数组的起始下标,继续遍历下一个子数组。

当数组 $\textit{nums}$ 可以恰好分成三个子数组且依次满足严格单调递增、严格单调递减、严格单调递增时,返回 $\text{true}$,否则返回 $\text{false}$。

代码

###Java

class Solution {
    public boolean isTrionic(int[] nums) {
        int n = nums.length;
        int index = 0;
        for (int i = 0, direction = 1; i < 3; i++, direction *= -1) {
            index = findMonotonicRangeEnd(nums, index, direction);
            if (index < 0) {
                return false;
            }
        }
        return index == n - 1;
    }

    public int findMonotonicRangeEnd(int[] nums, int start, int direction) {
        int n = nums.length;
        int end = -1;
        for (int i = start; i < n && end < 0; i++) {
            if (i < n - 1 && nums[i + 1] == nums[i]) {
                return -1;
            }
            if (i == n - 1 || (nums[i + 1] - nums[i]) * direction < 0) {
                end = i;
            }
        }
        return end > start ? end : -1;
    }
}

###C#

public class Solution {
    public bool IsTrionic(int[] nums) {
        int n = nums.Length;
        int index = 0;
        for (int i = 0, direction = 1; i < 3; i++, direction *= -1) {
            index = FindMonotonicRangeEnd(nums, index, direction);
            if (index < 0) {
                return false;
            }
        }
        return index == n - 1;
    }

    public int FindMonotonicRangeEnd(int[] nums, int start, int direction) {
        int n = nums.Length;
        int end = -1;
        for (int i = start; i < n && end < 0; i++) {
            if (i < n - 1 && nums[i + 1] == nums[i]) {
                return -1;
            }
            if (i == n - 1 || (nums[i + 1] - nums[i]) * direction < 0) {
                end = i;
            }
        }
        return end > start ? end : -1;
    }
}

复杂度分析

  • 时间复杂度:$O(n)$,其中 $n$ 是数组 $\textit{nums}$ 的长度。最多需要遍历数组一次。

  • 空间复杂度:$O(1)$。

两种写法,推广到多个拐弯的一般情况(Python/Java/C++/Go)

写法一:三个循环

三段式子数组必须满足「严格递增 - 严格递减 - 严格递增」,一共三段,每一段至少要有两个数

每一段分别用一个循环寻找,具体请看 视频讲解 中的图,欢迎点赞关注~

###py

class Solution:
    def isTrionic(self, nums: List[int]) -> bool:
        n = len(nums)
        # 第一段
        i = 1
        while i < n and nums[i - 1] < nums[i]:
            i += 1
        if i == 1:  # 第一段至少要有两个数
            return False

        # 第二段
        i0 = i
        while i < n and nums[i - 1] > nums[i]:
            i += 1
        if i == i0 or i == n:  # 第二段至少要有两个数,第三段至少要有两个数
            return False

        # 第三段
        while i < n and nums[i - 1] < nums[i]:
            i += 1
        return i == n

###java

class Solution {
    public boolean isTrionic(int[] nums) {
        int n = nums.length;
        // 第一段
        int i = 1;
        while (i < n && nums[i - 1] < nums[i]) {
            i++;
        }
        if (i == 1) { // 第一段至少要有两个数
            return false;
        }

        // 第二段
        int i0 = i;
        while (i < n && nums[i - 1] > nums[i]) {
            i++;
        }
        if (i == i0 || i == n) { // 第二段至少要有两个数,第三段至少要有两个数
            return false;
        }

        // 第三段
        while (i < n && nums[i - 1] < nums[i]) {
            i++;
        }
        return i == n;
    }
}

###cpp

class Solution {
public:
    bool isTrionic(vector<int>& nums) {
        int n = nums.size();
        // 第一段
        int i = 1;
        while (i < n && nums[i - 1] < nums[i]) {
            i++;
        }
        if (i == 1) { // 第一段至少要有两个数
            return false;
        }

        // 第二段
        int i0 = i;
        while (i < n && nums[i - 1] > nums[i]) {
            i++;
        }
        if (i == i0 || i == n) { // 第二段至少要有两个数,第三段至少要有两个数
            return false;
        }

        // 第三段
        while (i < n && nums[i - 1] < nums[i]) {
            i++;
        }
        return i == n;
    }
};

###go

func isTrionic(nums []int) bool {
n := len(nums)
// 第一段
i := 1
for i < n && nums[i-1] < nums[i] {
i++
}
if i == 1 { // 第一段至少要有两个数
return false
}

// 第二段
i0 := i
for i < n && nums[i-1] > nums[i] {
i++
}
if i == i0 || i == n { // 第二段至少要有两个数,第三段至少要有两个数
return false
}

// 第三段
for i < n && nums[i-1] < nums[i] {
i++
}
return i == n
}

写法二:一个循环

如果题目改成「增减增减增」,难道要写五个循环吗?

改成统计拐弯的次数,可以推广到更一般的情况。

###py

class Solution:
    def isTrionic(self, nums: List[int]) -> bool:
        if nums[0] >= nums[1]:  # 一开始必须是递增的
            return False
        cnt = 1
        for i in range(2, len(nums)):
            if nums[i - 1] == nums[i]:
                return False
            if (nums[i - 2] < nums[i - 1]) != (nums[i - 1] < nums[i]):
                cnt += 1
        return cnt == 3  # 一定是增减增

###java

class Solution {
    public boolean isTrionic(int[] nums) {
        if (nums[0] >= nums[1]) { // 一开始必须是递增的
            return false;
        }
        int cnt = 1;
        for (int i = 2; i < nums.length; i++) {
            if (nums[i - 1] == nums[i]) {
                return false;
            }
            if ((nums[i - 2] < nums[i - 1]) != (nums[i - 1] < nums[i])) {
                cnt++;
            }
        }
        return cnt == 3; // 一定是增减增
    }
}

###cpp

class Solution {
public:
    bool isTrionic(vector<int>& nums) {
        if (nums[0] >= nums[1]) { // 一开始必须是递增的
            return false;
        }
        int cnt = 1;
        for (int i = 2; i < nums.size(); i++) {
            if (nums[i - 1] == nums[i]) {
                return false;
            }
            if ((nums[i - 2] < nums[i - 1]) != (nums[i - 1] < nums[i])) {
                cnt++;
            }
        }
        return cnt == 3; // 一定是增减增
    }
};

###go

func isTrionic(nums []int) bool {
if nums[0] >= nums[1] { // 一开始必须是递增的
return false
}
cnt := 1
for i := 2; i < len(nums); i++ {
if nums[i-1] == nums[i] {
return false
}
if (nums[i-2] < nums[i-1]) != (nums[i-1] < nums[i]) {
cnt++
}
}
return cnt == 3 // 一定是增减增
}

复杂度分析

  • 时间复杂度:$\mathcal{O}(n)$,其中 $n$ 是 $\textit{nums}$ 的长度。
  • 空间复杂度:$\mathcal{O}(1)$。

专题训练

见下面滑动窗口与双指针题单的「六、分组循环」。

分类题单

如何科学刷题?

  1. 滑动窗口与双指针(定长/不定长/单序列/双序列/三指针/分组循环)
  2. 二分算法(二分答案/最小化最大值/最大化最小值/第K小)
  3. 单调栈(基础/矩形面积/贡献法/最小字典序)
  4. 网格图(DFS/BFS/综合应用)
  5. 位运算(基础/性质/拆位/试填/恒等式/思维)
  6. 图论算法(DFS/BFS/拓扑排序/基环树/最短路/最小生成树/网络流)
  7. 动态规划(入门/背包/划分/状态机/区间/状压/数位/数据结构优化/树形/博弈/概率期望)
  8. 常用数据结构(前缀和/差分/栈/队列/堆/字典树/并查集/树状数组/线段树)
  9. 数学算法(数论/组合/概率期望/博弈/计算几何/随机算法)
  10. 贪心与思维(基本贪心策略/反悔/区间/字典序/数学/思维/脑筋急转弯/构造)
  11. 链表、二叉树与回溯(前后指针/快慢指针/DFS/BFS/直径/LCA/一般树)
  12. 字符串(KMP/Z函数/Manacher/字符串哈希/AC自动机/后缀数组/子序列自动机)

我的题解精选(已分类)

❌