算法笔记-lc-面试题 01.08. 零矩阵(简单)

本文介绍了一种算法,当矩阵中的某个元素为0时,将其所在行与列清零。提供了四种实现方法,包括使用标记数组、标记变量及set记录等,并详细分析了每种方法的时间与空间复杂度。

题目

题干

编写一种算法,若M × N矩阵中某个元素为0,则将其所在的行与列清零。

示例

示例 1:

输入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
输出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
示例 2:

输入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
输出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]

题解

方法一:使用标记数组

思路和算法

我们可以用两个标记数组分别记录每一行和每一列是否有零出现。

具体地,我们首先遍历该数组一次,如果某个元素为 0,那么就将该元素所在的行和列所对应标记数组的位置置为 true。最后我们再次遍历该数组,用标记数组更新原数组即可。

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean[] row = new boolean[m];
        boolean[] col = new boolean[n];
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    row[i] = col[j] = true;
                }
            }
        }
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (row[i] || col[j]) {
                    matrix[i][j] = 0;
                }
            }
        }
    }
}

复杂度分析

时间复杂度:O(mn),其中 m 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵两次。

空间复杂度:O(m+n),其中 m 是矩阵的行数,n 是矩阵的列数。我们需要分别记录每一行或每一列是否有零出现。

方法二:使用两个标记变量
思路和算法

我们可以用矩阵的第一行和第一列代替方法一中的两个标记数组,以达到 O(1) 的额外空间。但这样会导致原数组的第一行和第一列被修改,无法记录它们是否原本包含 0。因此我们需要额外使用两个标记变量分别记录第一行和第一列是否原本包含 0。

在实际代码中,我们首先预处理出两个标记变量,接着使用其他行与列去处理第一行与第一列,然后反过来使用第一行与第一列去更新其他行与列,最后使用两个标记变量更新第一行与第一列即可。

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean flagCol0 = false, flagRow0 = false;
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                flagCol0 = true;
            }
        }
        for (int j = 0; j < n; j++) {
            if (matrix[0][j] == 0) {
                flagRow0 = true;
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
        }
        if (flagCol0) {
            for (int i = 0; i < m; i++) {
                matrix[i][0] = 0;
            }
        }
        if (flagRow0) {
            for (int j = 0; j < n; j++) {
                matrix[0][j] = 0;
            }
        }
    }
}

复杂度分析

时间复杂度:O(mn),其中 m 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵两次。

空间复杂度:O(1)。我们只需要常数空间存储若干变量。

方法三:使用一个标记变量

思路和算法

我们可以对方法二进一步优化,只使用一个标记变量记录第一列是否原本存在 0。这样,第一列的第一个元素即可以标记第一行是否出现 0。但为了防止每一列的第一个元素被提前更新,我们需要从最后一行开始,倒序地处理矩阵元素。

class Solution {
    public void setZeroes(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        boolean flagCol0 = false;
        for (int i = 0; i < m; i++) {
            if (matrix[i][0] == 0) {
                flagCol0 = true;
            }
            for (int j = 1; j < n; j++) {
                if (matrix[i][j] == 0) {
                    matrix[i][0] = matrix[0][j] = 0;
                }
            }
        }
        for (int i = m - 1; i >= 0; i--) {
            for (int j = 1; j < n; j++) {
                if (matrix[i][0] == 0 || matrix[0][j] == 0) {
                    matrix[i][j] = 0;
                }
            }
            if (flagCol0) {
                matrix[i][0] = 0;
            }
        }
    }
}

复杂度分析

时间复杂度:O(mn),其中 mm 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵两次。

空间复杂度:O(1)。我们只需要常数空间存储若干变量。

方法四:set记录

class Solution {
    public void setZeroes(int[][] matrix) {
        HashSet<Integer> setx=new HashSet<>();
        HashSet<Integer> sety=new HashSet<>();
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                if(matrix[i][j]==0){
                    setx.add(i);
                    sety.add(j);
                }
            }
        }
        for(Integer i:setx){
            for(int j=0;j<matrix[0].length;j++){
                matrix[i][j]=0;
            }
        }
        for(Integer i:sety){
            for(int j=0;j<matrix.length;j++){
                matrix[j][i]=0;
            }
        }
    }
}

复杂度分析

时间复杂度:O(mn),其中 m 是矩阵的行数,n 是矩阵的列数。我们至多只需要遍历该矩阵三次。

空间复杂度:O(m+n),其中 m 是矩阵的行数,n 是矩阵的列数。我们需要分别记录每一行或每一列是否有零出现。

<think> 我们面对的是RK3568的ISP功能日志中的两个主要问题: 1. 'can't request region for resource [mem 0xfdff0000-0xfdffffff]':这通常表示内存映射冲突,即该段物理内存已经被其他设备占用或未在设备树中正确保留。 2. 'Entity type for entity... was not initialized!':这通常表示在媒体控制器框架中,实体(如摄像头传感器、ISP等)的类型未正确初始化,可能是设备树节点缺失或驱动加载顺序问题。 结合用户提供的引用和之前的回答,我们逐步解决: 引用[1]显示的是RK3568的启动日志,其中有一些错误(如dfs参数错误),但主要关注点在于BL31和OP-TEE的初始化。这些错误可能与ISP无关。 引用[2]是本地回环接口的信息,与ISP无关。 引用[3]提供了内核编译的环境设置方法,说明用户可能正在编译内核。 因此,我们重点从设备树配置和内核驱动加载入手。 解决步骤: 1. **内存区域冲突解决** 错误信息`can't request region for resource [mem 0xfdff0000-0xfdffffff]`说明ISP驱动在尝试申请一段内存区域时失败。这段内存区域(0xfdff0000-0xfdffffff)大小为64KB(0x10000字节),位于0xfdff0000开始。 可能原因: - 该内存区域已被其他设备占用(在设备树中已被其他节点声明) - 设备树中ISP节点的内存区域定义错误 在RK3568上,ISP设备对应的内存区域定义在设备树中。根据RK3568的TRM,ISP的寄存器基地址为0xFF910000。但是错误信息中的地址0xfdff0000并不在ISP的寄存器范围内,因此可能是设备树中某个节点的地址设置错误。 检查设备树中与ISP相关的节点,特别是CIF(视频输入捕获)和ISP节点: ```dts // 检查rkcif节点 rkcif: rkcif@ff960000 { reg = <0x0 0xff960000 0x0 0x8000>; ... }; // 检查isp节点 isp0: isp@ff910000 { reg = <0x0 0xff910000 0x0 0x8000>; ... }; ``` 同时,需要检查整个设备树中是否有其他节点使用了0xfdff0000-0xfdffffff这段内存。如果有,则需要调整冲突节点的地址或内存范围。 另外,可能是保留内存(reserved memory)冲突。在设备树中,保留内存节点可能如下: ```dts reserved-memory { #address-cells = <2>; #size-cells = <2>; ranges; /* 检查是否有保留内存区域与0xfdff0000重叠 */ example_reserved: example@fdff0000 { reg = <0x0 0xfdff0000 0x0 0x10000>; no-map; }; }; ``` 如果确实有冲突,需要移除或修改这个保留区域。 2. **实体类型未初始化问题** 错误信息`Entity type for entity... was not initialized!`通常发生在媒体控制器框架(media controller framework)中。在RK3568的ISP驱动中,每个实体(如传感器、CSI接收器、ISP等)都需要在初始化时设置其类型(如MEDIA_ENT_T_V4L2_SUBDEV_SENSOR)。 可能原因: - 设备树中缺少必要的节点或属性,导致实体无法正确初始化。 - 驱动加载顺序问题,导致实体注册时依赖的其他模块尚未加载。 检查设备树中摄像头传感器节点是否正确,例如: ```dts // 摄像头传感器节点 &i2c1 { status = "okay"; ov5695: ov5695@36 { compatible = "ovti,ov5695"; reg = <0x36>; // 必须包含以下属性以初始化实体类型 port { ov5695_out: endpoint { remote-endpoint = <&csi_in>; }; }; }; }; ``` 同时,检查CSI和ISP节点之间的连接: ```dts &csi_dphy { ports { port@0 { csi_in: endpoint { remote-endpoint = <&ov5695_out>; }; }; port@1 { csi_out: endpoint { remote-endpoint = <&isp_in>; }; }; }; }; &isp { ports { port@0 { isp_in: endpoint { remote-endpoint = <&csi_out>; }; }; }; }; ``` 确保这些连接正确无误。 3. **综合解决方案** 步骤一:修改设备树,解决内存冲突 - 查找设备树中占用0xfdff0000-0xfdffffff区域的节点,并将其移除或修改地址。 - 如果该区域是系统保留内存且确实需要,则考虑在ISP驱动中避开这段内存(但通常ISP驱动使用固定的寄存器地址,所以需要确认设备树中ISP节点地址的正确性)。 步骤二:确保设备树中摄像头相关节点完整 - 检查摄像头传感器、CSI、ISP等节点的完整性和正确性。 - 确保每个实体(尤其是传感器)都有正确的端口定义。 步骤三:重新编译设备树并更新内核 使用引用[3]中提供的方法编译设备树,并更新到开发板。 步骤四:检查驱动加载顺序 在内核启动参数中,可以尝试添加模块加载顺序,确保摄像头传感器驱动在ISP驱动之前加载。这可以通过在/etc/modules文件中调整顺序实现(如果使用模块化驱动)。 4. **调试方法** 如果问题仍然存在,可以进一步调试: - 在内核启动参数中添加`ignore_loglevel`和`debug`,查看详细日志。 - 通过`cat /proc/iomem`查看内存映射情况,确认0xfdff0000-0xfdffffff区域的归属。 - 使用`dmesg | grep media`查看媒体设备注册情况。 5. **参考修复案例** 对于内存区域冲突,有报告显示某些保留内存区域(如用于安全操作的内存)会与设备驱动冲突。例如,在RK平台上,op-tee保留的内存区域可能与设备驱动冲突。在设备树中,调整保留内存区域: ```dts reserved-memory { // 将原来的区域注释掉 /* tee_reserved: tee@8400000 { reg = <0x0 0x8400000 0x0 0x1e00000>; no-map; }; */ // 或者修改为不冲突的地址 tee_reserved: tee@fe000000 { reg = <0x0 0xfe000000 0x0 0x2000000>; no-map; }; }; ``` 注意:修改保留内存需要同时修改op-tee的配置,确保一致性。 6. **相关问题扩展** 针对用户可能进一步的问题,生成以下相关提问: §§相关问题§§ 1. 如何调整RK3568设备树中的保留内存区域? 2. RK3568的媒体控制器框架中实体初始化失败如何调试? 3. 如何解决RK3568设备树节点之间的地址冲突? </think> ### 解决RK3568 ISP日志内存映射与实体初始化错误 #### 问题1:内存区域请求失败 `can't request region for resource [mem 0xfdff0000-0xfdffffff]` **根本原因**: - 物理内存区域 `0xfdff0000-0xfdffffff` 被其他驱动占用或未在设备树中保留 - 与安全子系统(如OP-TEE)保留的内存区域冲突(参考启动日志中的`tee_reserved`区域)[^1] **解决方案**: 1. **检查内存映射冲突**: ```bash # 查看物理内存分配情况 cat /proc/iomem | grep -A 5 "fdff" ``` - 若输出显示该区域被`optee`或其他驱动占用,需调整保留内存 2. **修改设备树**: 在设备树中显式保留ISP所需内存(需与内核源码中`drivers/media/platform/rockchip/isp/hw.h`的地址一致): ```dts reserved-memory { isp_reserved: region@fdff0000 { compatible = "shared-dma-pool"; reg = <0x0 0xfdff0000 0x0 0x100000>; // 1MB空间 no-map; }; }; ``` 3. **更新ISP节点引用**: ```dts &isp { memory-region = <&isp_reserved>; status = "okay"; }; ``` 4. **重新编译并验证**: ```bash make ARCH=arm64 dtbs # 重新编译设备树 cp arch/arm64/boot/dts/rockchip/rk3568-evb.dtb /boot/ reboot dmesg | grep -i isp # 检查错误是否消失 ``` --- #### 问题2:实体未初始化 `Entity type for entity... was not initialized!` **根本原因**: - 媒体控制器框架(media controller)未正确链接摄像头传感器→CSI→ISP通路 - 设备树中缺少或错误的端点(endpoint)连接定义 **解决方案**: 1. **验证设备树连接关系**: 确保完整的传感器→CSI→ISP链路(关键节点示例): ```dts // 传感器节点(以OV5695为例) &i2c1 { ov5695: sensor@36 { compatible = "ovti,ov5695"; reg = <0x36>; port { ov5695_out: endpoint { remote-endpoint = <&csi_in>; // 指向CSI输入 }; }; }; }; // CSI接收器 &csi_dphy { ports { port@0 { csi_in: endpoint { remote-endpoint = <&ov5695_out>; // 连接传感器 }; }; port@1 { csi_out: endpoint { remote-endpoint = <&isp_in>; // 指向ISP输入 }; }; }; }; // ISP节点 &isp { ports { port@0 { isp_in: endpoint { remote-endpoint = <&csi_out>; // 连接CSI输出 }; }; }; }; ``` 2. **检查驱动加载顺序**: ```bash lsmod | grep -E "v4l2|vimc|rockchip" ``` - 确保加载顺序:传感器驱动 → V4L2框架 → ISP驱动 - 若使用模块,在`/etc/modules`中调整顺序: ``` ov5695 v4l2_fwnode rkisp ``` 3. **使用media-ctl调试链路**: ```bash media-ctl -p -d /dev/media0 ``` - 正常输出应显示完整链路: ``` Entity: rkisp-isp-subdev (type: V4L2 subdev) Entity: ov5695 1-0036 (type: V4L2 subdev) Pad0: Source -> [ "rockchip-csi2-dphy":0 ] ``` --- #### 完整验证流程 1. **编译更新设备树**(参考引用[3]方法): ```bash export ARCH=arm64 export CROSS_COMPILE=aarch64-linux-gnu- make rockchip/rk3568-evb.dtb ``` 2. **启动后检查内核日志**: ```bash dmesg | grep -E "isp|cif|v4l2" ``` - 成功标志: ``` rkisp ff910000.isp: ISP registered as /dev/video0 rockchip-csi2-dphy ff970000.csi2-dphy: Linked as ov5695 to rkisp ``` 3. **实时捕获测试**: ```bash v4l2-ctl -d /dev/video0 --stream-count=10 \ --stream-to=test.raw --stream-mmap ``` > ⚠️ **若问题仍存**: > 1. 检查时钟配置:`cat /sys/kernel/debug/clk/clk_summary | grep isp` > 2. 确认供电状态:测量传感器AVDD/DOVDD电压(典型值:2.8V) > 3. 验证I²C通信:`i2cdetect -y 1`(检测传感器地址0x36响应)
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值