云小杰

相对于绝对成功的汲汲渴求,越无杂质的奔赴,越是动人。

要一个黄昏, 满是风, 和正在落下的夕阳。如此, 足够我爱这破碎泥泞的人间。


Download the theme

等差数组

等差数组

413. 等差数列划分

题目描述

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

  • 例如,[1,3,5,7,9][7,7,7,7][3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列。

示例 1:

输入nums = [1,2,3,4]
输出3
解释nums 中有三个子等差数组[1, 2, 3][2, 3, 4]  [1,2,3,4] 自身

示例 2:

输入nums = [1]
输出0

提示:

  • 1 <= nums.length <= 5000
  • -1000 <= nums[i] <= 1000

方法:差分+计数

如果我们已经求出了 $\textit{nums}[i - 1]$ 以及 $\textit{nums}[i]$ 作为等差数列的最后两项时,答案增加的次数 $t_i$ ,那么能否快速地求出 $t_{i+1}$ 呢?

答案是可以的:

  • 如果 $\textit{nums}[i] - \textit{nums}[i + 1] = d$,那么在这一轮遍历中,$j$ 会遍历到与上一轮相同的位置,答案增加的次数相同,并且额外多出了 $\textit{nums}[i-1]$, $\textit{nums}[i]$, $\textit{nums}[i+1]$ 这一个等差数列,因此有: \(t_{i+1} = t_i + 1\)

  • 如果 $\textit{nums}[i] - \textit{num}[i + 1] \neq d$,那么 $j$ 从初始值 $i-1$ 开始就会直接退出遍历,答案不会增加,因此有: \(t_{i+1} = 0\)

    /**
     * 413. 等差数列划分
     *
     * @param nums
     * @return
     */
    public int numberOfArithmeticSlices(int[] nums) {
        int length = nums.length;
        if (length == 1 || length == 2)
            return 0;
        int sub = nums[0] - nums[1], t = 0, ans = 0; // sub表示数组相邻元素差值
        for (int i = 2; i < length; i++) {
            if (nums[i - 1] - nums[i] == sub) {
                ++t; // t可以看作以num[i]结尾的等差数组的数目
            } else {
                sub = nums[i - 1] - nums[i];
                t = 0;
            }
            ans += t;
        }
        return ans;
    }

446. 等差数列划分 II - 子序列

题目描述

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。

如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列

  • 例如,[1, 3, 5, 7, 9][7, 7, 7, 7][3, -1, -5, -9] 都是等差序列。
  • 再例如,[1, 1, 2, 5, 7] 不是等差序列。

数组中的子序列是从数组中删除一些元素(也可能不删除)得到的一个序列。

  • 例如,[2,5,10][1,2,1,*2*,4,1,*5*,*10*] 的一个子序列。

题目数据保证答案是一个 32-bit 整数。

实例1

输入nums = [2,4,6,8,10]
输出7
解释所有的等差子序列为
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]

实例2

示例 2

输入nums = [7,7,7,7,7]
输出16
解释数组中的任意子序列都是等差子序列

提示:

  • 1 <= nums.length <= 1000
  • -2^{31} <= nums[i] <= 2^{31} - 1

方法:哈希表+动态规划

  • 「以 nums[i] 结尾」这件事情肯定要定义在状态中;
  • 题目不要求连续,因此在求每一个状态的时候,就需要 考虑它之前的所有的元素
  • 能不能接上去,看「公差」,因此记录状态的时候,除了要求以 nums[i] 结尾以外,还要记录「公差」,两个整数的差可以有很多很多,因此需要用哈希表记录下来。

到这里为止,每一个 nums[i] 的状态,其实是一张哈希表(键值对),「键」 是 nums[i] 与它前面的每一个元素的「差」,那「值」是什么呢?「值」是以 nums[i] 结尾组成的、公差为某个值的 长度大于等于 2 的等差子序列的个数(就是官方题解中提到的弱等差数列的个数)

状态的「值」是什么?

  • 计算「差」,至少需要两个元素;
  • 等差数列最开始形成的时候,即:只有两个元素的时候,对结果没有贡献,因为题目要求等差数列的长度至少为 3;
  • (这里是重点)如果发现公差相等,才可以找到若干个长度大于等于 3 的等差数列
  • (这里是重点)「弱等差子序列」后面再加上一个数,形成长度更长的等差数列,就是题目中定义的长度大于等于 3 的等差数列,因此在下一次「公差匹配」的时候,记录结果。

[2,4,6,8,10] 为例:

img

  • 2 的前面没有元素,哈希表为空;
  • 4 的前面只有一个元素 2 ,此时记录键值对 {2:1},这里 2 是「公差」,14 前面的元素的个数;
  • 6 的前面有两个元素 42
    • 6 - 4 = 24 的键值对里看一下,有 2,说明 6 可以接在 4 的后面形成长度更长的等差数列 [2, 4, 6],此时记录记录键值对 {2:2}同时找到了一个长度为 3 的等差数列
    • 6 - 2 = 42 的键值对里看一下(看第 1 条,哈希表为空),没有 4,此时记录 {4:1}
  • 8 的前面有三个元素 642
    • 8 - 6 = 26 的键值对里看一下,有 2,说明 8 可以接在 6 的后面形成长度更长的等差数列,此时记录键值对 {2:3}同时找到了两个长度大于等于 3 的等差数列 [4, 6, 8][2, 4, 6, 8]
    • 8 - 4 = 44 的键值对里看一下,没有 4,记录 {4:1}
    • 8 - 2 = 62 的键值对里看一下,没有 6,记录 {6:1}
  • 10 的前面有四个元素 8642
    • 10 - 8 = 28 的键值对里看一下,有 2,说明 10 可以接在 8 的后面形成长度更长的等差数列,此时记录记录键值对 {2:4}同时找到了两个长度大于等于 3 的等差数列[6, 8, 10][4, 6, 8, 10][2, 4, 6, 8, 10](这里的 3 个是基于 8 的状态值得到的;
    • 10 - 6 = 46 的键值对里看一下,有 4,说明 10 可以接在 6 的后面形成长度更长的等差数列,此时记录记录键值对 {4:2},同时找到了一个长度大于等于 33 的等差数列 [2, 6, 10]
    • 10 - 4 = 64 的键值对里看一下,没有 6,记录 {6:1}
    • 10 - 2 = 82 的键值对里看一下,没有 8,记录 {8:1}
class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int length = nums.length;
        int ans = 0;
        if (length < 3)
            return ans;
        Map<Long, Integer>[] dp = new HashMap[length];
        for (int i = 0; i < length; i++)
            dp[i] = new HashMap<>();
        for (int i = 1; i < length; i++) {
            for (int j = 0; j < i; j++) {
                long d = 1L * nums[i] - nums[j];
                Integer integer = dp[j].getOrDefault(d, 0);
                ans += integer;
                dp[i].put(d, dp[i].getOrDefault(d, 0) + integer + 1);
            }
        }
        return ans;
    }
}
最近的文章

Java random方法

Java Random类1. 用处​ Random类常用来创建一些随机数。​ Random类中实现的随机算法是伪随机,也就是有规则的随机。在进行随机时,随机算法的起源数字称为种子数(seed),在种子数的基础上进行一定的变换,从而产生需要的随机数字。​ 相同种子数的Random对象,相同次数生成的随机数字是完全相同的。也就是说,两个种子数相同的Random对象,第一次生成的随机数字完全相同,第二次生成的随机数字也完全相同。这点在生成多个随机数字时需要特别注意。2. 构造...…

继续阅读
更早的文章

Learnswing

Swing1. GUI图形用户界面(Graphical User Interface,简称 GUI,又称图形用户接口)是指采用图形方式显示的计算机操作用户界面。Java提供了三个主要包做GUI开发: java.awt 包 – 主要提供字体/布局管理器 javax.swing 包[商业开发常用] – 主要提供各种组件(窗口/按钮/文本框) java.awt.event 包 – 事件处理,后台功能的实现。2. Swing组件如图所示:swing组件主要可分为三个部分: 顶层容器::常用...…

继续阅读