数组算法题

1:

 思路:

  1. 首先判断数组长度,如果长度小于 3,直接返回 0,因为等差数列至少需要三个元素。
  2. 初始化一个 dp 数组,dp[i]表示以nums[i]结尾的等差子数组的个数。
  3. 遍历数组,从索引 2 开始,检查当前元素和前两个元素是否构成等差数列。如果nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2],说明以nums[i]结尾的等差子数组个数是dp[i - 1] + 1
  4. 最后遍历 dp 数组,累加所有元素的值,即为所有等差子数组的个数。

代码:

public class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;
        if (n < 3) {
            return 0;
        }
        int[] dp = new int[n];
        for (int i = 2; i < n; i++) {
            if (nums[i] - nums[i - 1] == nums[i - 1] - nums[i - 2]) {
                dp[i] = dp[i - 1] + 1;
            }
        }
        int sum = 0;
        for (int count : dp) {
            sum += count;
        }
        return sum;
    }
}

结果: 

 为什么是dp[i - 1] + 1。

在动态规划的解法中,dp[i] = dp[i - 1] + 1 的核心逻辑在于:当以位置 i 结尾的子数组满足等差数列条件时,它会继承之前所有的等差数列,并新增一个长度为 3 的等差数列。以下是详细解释:

关键理解点

  1. 等差数列的连续性
    题目要求子数组必须是连续的。因此,当位置 i 能与前两个位置 i-1 和 i-2 构成等差数列时,所有以 i-1 结尾的等差数列都可以延伸到 i

  2. 继承性
    如果 nums[i-2], nums[i-1], nums[i] 构成等差数列,那么:

    • 所有以 i-1 结尾的等差数列(共有 dp[i-1] 个),都可以加上 nums[i] 形成更长的等差数列。
    • 新增一个长度为 3 的等差数列:即 nums[i-2], nums[i-1], nums[i] 本身。
  3. 数学归纳
    例如:

    • 当 dp[i-1] = 2 时(表示以 i-1 结尾的等差数列有 2 个),若 i 加入后仍满足等差数列条件,则以 i 结尾的等差数列数量为:
      • 原有的 2 个延伸到 i,新增 1 个长度为 3 的数列,因此 dp[i] = 2 + 1 = 3

示例说明

假设数组为 nums = [1, 2, 3, 4, 5]

  • i=2(元素 3):

    • nums[0]=1nums[1]=2nums[2]=3 构成等差数列,长度为 3,因此 dp[2] = 1
  • i=3(元素 4):

    • nums[1]=2nums[2]=3nums[3]=4 构成等差数列,继承 dp[2]=1,新增 1 个,因此 dp[3] = 1 + 1 = 2
    • 此时以 i=3 结尾的等差数列为 [1,2,3,4] 和 [2,3,4]
  • i=4(元素 5):

    • nums[2]=3nums[3]=4nums[4]=5 构成等差数列,继承 dp[3]=2,新增 1 个,因此 dp[4] = 2 + 1 = 3
    • 此时以 i=4 结尾的等差数列为 [1,2,3,4,5][2,3,4,5][3,4,5]

总结

dp[i-1] + 1 的本质是:

  • dp[i-1]:继承所有以 i-1 结尾的等差数列。
  • +1:新增一个由 i-2, i-1, i 构成的长度为 3 的等差数列。

这种递推关系确保了动态规划能够高效统计所有可能的等差数列子数组。

2:

 思路:

  1. 初始化:首先判断输入矩阵是否有效,然后获取矩阵的行数m和列数n,并初始化两个布尔型二维数组pacificatlantic,用于记录可以流向太平洋和大西洋的单元格。
  2. DFS 函数:递归函数dfs用于从当前单元格出发,探索所有可以到达的更高或等高的单元格,并将这些单元格在对应的布尔数组中标记为true
  3. 太平洋边界处理:从左边界和上边界的每个单元格出发,调用 DFS 函数,将可以到达的单元格在pacific数组中标记为true
  4. 大西洋边界处理:从右边界和下边界的每个单元格出发,调用 DFS 函数,将可以到达的单元格在atlantic数组中标记为true
  5. 求交集:遍历所有单元格,将同时在pacificatlantic数组中被标记为true的单元格添加到结果列表中。

代码:

import java.util.ArrayList;
import java.util.List;

class Solution {
    private int[][] heights;
    private int m, n;
    
    public List<List<Integer>> pacificAtlantic(int[][] heights) {
        List<List<Integer>> result = new ArrayList<>();
        if (heights == null || heights.length == 0 || heights[0].length == 0) {
            return result;
        }
        
        this.heights = heights;
        m = heights.length;
        n = heights[0].length;
        
        boolean[][] pacific = new boolean[m][n];
        boolean[][] atlantic = new boolean[m][n];
        
        // 从太平洋边界开始DFS
        for (int r = 0; r < m; r++) {
            dfs(r, 0, pacific, Integer.MIN_VALUE);
        }
        for (int c = 0; c < n; c++) {
            dfs(0, c, pacific, Integer.MIN_VALUE);
        }
        
        // 从大西洋边界开始DFS
        for (int r = 0; r < m; r++) {
            dfs(r, n - 1, atlantic, Integer.MIN_VALUE);
        }
        for (int c = 0; c < n; c++) {
            dfs(m - 1, c, atlantic, Integer.MIN_VALUE);
        }
        
        // 找出同时满足两个条件的坐标
        for (int r = 0; r < m; r++) {
            for (int c = 0; c < n; c++) {
                if (pacific[r][c] && atlantic[r][c]) {
                    result.add(List.of(r, c));
                }
            }
        }
        
        return result;
    }
    
    private void dfs(int r, int c, boolean[][] visited, int prevHeight) {
        if (r < 0 || c < 0 || r >= m || c >= n || visited[r][c] || heights[r][c] < prevHeight) {
            return;
        }
        
        visited[r][c] = true;
        int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
        
        for (int[] dir : directions) {
            dfs(r + dir[0], c + dir[1], visited, heights[r][c]);
        }
    }
}

 结果:

 核心思路

水从高处流向低处,但我们可以反向思考:从海洋边界(太平洋和大西洋)出发,找到所有能从海洋逆流而上到达的单元格。如果某个单元格既可以从太平洋到达,也可以从大西洋到达,那么它就是符合条件的解。

代码结构与关键步骤

1. 初始化与边界检查

if (heights == null || heights.length == 0 || heights[0].length == 0) {
    return result;
}
  • 处理空输入的情况,直接返回空结果。

2. 全局变量与矩阵尺寸

private int[][] heights;
private int m, n;
  • heights:存储输入矩阵。
  • m 和 n:矩阵的行数和列数。

3. 标记数组初始化

boolean[][] pacific = new boolean[m][n];
boolean[][] atlantic = new boolean[m][n];
  • pacific[r][c]:表示坐标 (r, c) 是否能流向太平洋。
  • atlantic[r][c]:表示坐标 (r, c) 是否能流向大西洋。

4. 从边界开始 DFS 探索

太平洋边界(左 + 上)
// 左边界(所有行的第0列)
for (int r = 0; r < m; r++) {
    dfs(r, 0, pacific, Integer.MIN_VALUE);
}
// 上边界(第0行的所有列)
for (int c = 0; c < n; c++) {
    dfs(0, c, pacific, Integer.MIN_VALUE);
}
  • 从左边界(c=0)和上边界(r=0)的所有单元格出发,标记能逆流到达的单元格。
大西洋边界(右 + 下) 
// 右边界(所有行的最后一列)
for (int r = 0; r < m; r++) {
    dfs(r, n - 1, atlantic, Integer.MIN_VALUE);
}
// 下边界(最后一行的所有列)
for (int c = 0; c < n; c++) {
    dfs(m - 1, c, atlantic, Integer.MIN_VALUE);
}

 从右边界(c=n-1)和下边界(r=m-1)的所有单元格出发,标记能逆流到达的单元格

5. DFS 深度优先搜索函数

private void dfs(int r, int c, boolean[][] visited, int prevHeight) {
    if (r < 0 || c < 0 || r >= m || c >= n || visited[r][c] || heights[r][c] < prevHeight) {
        return;
    }
    
    visited[r][c] = true;
    int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
    
    for (int[] dir : directions) {
        dfs(r + dir[0], c + dir[1], visited, heights[r][c]);
    }
}
  • 终止条件
    • 越界(r < 0 或 c < 0 或 r >= m 或 c >= n)。
    • 已访问(visited[r][c] 为 true)。
    • 当前高度小于上一个单元格的高度(heights[r][c] < prevHeight),说明无法从当前单元格流向之前的单元格。
  • 标记当前单元格visited[r][c] = true
  • 递归探索四个方向:上、下、左、右。

6. 结果收集

for (int r = 0; r < m; r++) {
    for (int c = 0; c < n; c++) {
        if (pacific[r][c] && atlantic[r][c]) {
            result.add(List.of(r, c));
        }
    }
}

复杂度分析

  • 时间复杂度:O(m*n),每个单元格最多被访问两次(一次从太平洋边界,一次从大西洋边界)。
  • 空间复杂度:O(m*n),主要用于存储两个标记数组。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值