翻转图像 图解题解
这道题到底在问什么
- 输入
- image=[[1,1,0],[1,0,1],[0,0,0]]
- 输出
- [[1,0,0],[0,1,0],[1,1,1]]
- 输入
- 一行 [1,1,0]
- 输出
- 先逆序成 [0,1,1],再取反成 [1,0,0]
最优解:一步一步想明白
- 3记牢这把尺子:两端相等就各翻一次,两端不同就不动,正中间单独翻一次。下面每一帧都在套它。
- 4原图共 3 行 3 列,每格非 0 即 1这是原始图片,绿色格是 1、蓝色格是 0。我们一行一行地处理,每行用左右两个指针向中间收。
- 5第 0 行 = [1, 1, 0],左指针 i = 0,右指针 j = 2轮到第 0 行,内容是 [1, 1, 0]。把左指针放在头、右指针放在尾,紫色标出这两端,准备向中间收。
- 6左端值 1,右端值 0 → 不同看这一行的两端:位置 0 是 1,位置 2 是 0。它们不同。互换后再取反正好抵消,所以这两端最终保持原样,不用动。
- 7位置 0 与 2 维持 1 和 0两端不同,什么都不用改,位置 0 还是 1、位置 2 还是 0。两个指针各自向中间挪一格。
- 8左右指针在位置 1 相遇,中点值 1两个指针在位置 1 相遇,这是正中间那一格,值是 1。它水平翻转后位置不变,所以没有「互相抵消」这回事,只需要老老实实取反一次。
- 9中点位置 1 取反为 0中点从 1 取反成 0(红色闪一下)。这一行所有格子都处理完了。
- 10第 0 行最终 = [1, 0, 0]第 0 行收工,结果是 [1, 0, 0]。已完成行数加一,接着处理下一行,套路完全一样。
- 11第 1 行 = [1, 0, 1],左指针 i = 0,右指针 j = 2轮到第 1 行,内容是 [1, 0, 1]。把左指针放在头、右指针放在尾,紫色标出这两端,准备向中间收。
- 12左端值 1,右端值 1 → 相等看这一行的两端:位置 0 是 1,位置 2 是 1。它们相等。水平翻转会把它俩互换,换完仍相等,再各取反,等于两端都要翻一次。
- 13位置 0 与 2 翻转为 0 和 0两端相等,各翻一次:位置 0 变 0、位置 2 变 0(红色闪一下表示刚翻)。这一帧就同时做完了这对格子的翻转和取反。
- 14左右指针在位置 1 相遇,中点值 0两个指针在位置 1 相遇,这是正中间那一格,值是 0。它水平翻转后位置不变,所以没有「互相抵消」这回事,只需要老老实实取反一次。
- 15中点位置 1 取反为 1中点从 0 取反成 1(红色闪一下)。这一行所有格子都处理完了。
- 16第 1 行最终 = [0, 1, 0]第 1 行收工,结果是 [0, 1, 0]。已完成行数加一,接着处理下一行,套路完全一样。
- 17第 2 行 = [0, 0, 0],左指针 i = 0,右指针 j = 2轮到第 2 行,内容是 [0, 0, 0]。把左指针放在头、右指针放在尾,紫色标出这两端,准备向中间收。
- 18左端值 0,右端值 0 → 相等看这一行的两端:位置 0 是 0,位置 2 是 0。它们相等。水平翻转会把它俩互换,换完仍相等,再各取反,等于两端都要翻一次。
- 19位置 0 与 2 翻转为 1 和 1两端相等,各翻一次:位置 0 变 1、位置 2 变 1(红色闪一下表示刚翻)。这一帧就同时做完了这对格子的翻转和取反。
- 20左右指针在位置 1 相遇,中点值 0两个指针在位置 1 相遇,这是正中间那一格,值是 0。它水平翻转后位置不变,所以没有「互相抵消」这回事,只需要老老实实取反一次。
- 21中点位置 1 取反为 1中点从 0 取反成 1(红色闪一下)。这一行所有格子都处理完了。
- 22第 2 行最终 = [1, 1, 1]第 2 行收工,结果是 [1, 1, 1]。三行全部处理完毕,下一帧返回结果。
- 23三行都做完,返回 [[1,0,0],[0,1,0],[1,1,1]]三行全部处理完,矩阵变成 [[1,0,0],[0,1,0],[1,1,1]]。每一行都是「逆序 + 取反」的结果,而我们只用一遍双指针就一次性做完了两步。
- 24先逐行逆序、再整张取反,得到一模一样的矩阵换个老实办法验证一下:把每行先逆序、再把全图 0 和 1 互换,得到的矩阵和上一帧完全相同。可见双指针那套「相等才翻、不同不动、中点单翻」确实等价于规规矩矩的两步法。
⚠️ 容易写错的地方
✗ 错:把两端不同的情况也去翻转
✓ 对:只有两端相等才各翻一次,两端不同保持不动
不同的两端经过「互换 + 取反」正好抵消回原值,强行改反而出错
✗ 错:奇数边长漏掉正中间那一格
✓ 对:循环结束后判断 i 是否等于 j,相等则单独把中点取反一次
中点没有配对的另一端,只在 i 和 j 相遇时处理,忘了它会少翻一格
✗ 错:只背两步顺序,没推清双指针一对位置的净效果
✓ 对:双指针条件要按「一对位置交换 + 各取反」的净效果推导
逐格取反与逐行逆序本就可交换、顺序不影响结果;真正的坑是不去推一对位置的净效果,只死记两步,遇到「相等才翻、不同不动、中点单翻」就说不清为什么
完整代码(Python / C++ / Java)
Python
from __future__ import annotations
from typing import *
from collections import *
from functools import *
from itertools import *
from math import *
from heapq import *
from bisect import *
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def flipAndInvertImage(self, image: List[List[int]]) -> List[List[int]]:
n = len(image)
for row in image:
i, j = 0, n - 1
while i < j:
if row[i] == row[j]:
row[i] ^= 1
row[j] ^= 1
i, j = i + 1, j - 1
if i == j:
row[i] ^= 1
return imageC++
#include <algorithm>
#include <array>
#include <cctype>
#include <climits>
#include <cmath>
#include <deque>
#include <functional>
#include <iostream>
#include <limits>
#include <map>
#include <numeric>
#include <optional>
#include <queue>
#include <set>
#include <sstream>
#include <stack>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
using namespace std;
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};
struct ListNode {
int val;
ListNode *next;
ListNode() : val(0), next(nullptr) {}
ListNode(int x) : val(x), next(nullptr) {}
ListNode(int x, ListNode *next) : val(x), next(next) {}
};
class Solution {
public:
vector<vector<int>> flipAndInvertImage(vector<vector<int>>& image) {
for (auto& row : image) {
int i = 0, j = row.size() - 1;
for (; i < j; ++i, --j) {
if (row[i] == row[j]) {
row[i] ^= 1;
row[j] ^= 1;
}
}
if (i == j) {
row[i] ^= 1;
}
}
return image;
}
};Java
import java.util.*;
class TreeNode { int val; TreeNode left; TreeNode right; TreeNode(){} TreeNode(int val){this.val=val;} TreeNode(int val, TreeNode left, TreeNode right){this.val=val;this.left=left;this.right=right;} }
class ListNode { int val; ListNode next; ListNode(){} ListNode(int val){this.val=val;} ListNode(int val, ListNode next){this.val=val;this.next=next;} }
class Solution {
public int[][] flipAndInvertImage(int[][] image) {
for (int[] row : image) {
int i = 0, j = row.length - 1;
for (; i < j; ++i, --j) {
if (row[i] == row[j]) {
row[i] ^= 1;
row[j] ^= 1;
}
}
if (i == j) {
row[i] ^= 1;
}
}
return image;
}
}复杂度
时间
O(n²)
矩阵共 n×n 个格子,每格恰好被一个指针碰一次,无重复无回头
空间
O(1)
原地修改矩阵,只用 i、j 两个指针变量,不开额外矩阵
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 翻转图像 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么一遍双指针就能把「翻转」和「取反」两步合并?+
因为水平翻转的本质就是把位置 i 和位置 j 的值对调,而取反是对每一格做一次开关。把这两件事落到一对位置上算清楚:相等的一对,对调后不变、再各取反,所以各翻一次;不同的一对,对调再取反恰好抵消,所以不动;中点对调后位置不变,只取反一次。把每一对的净效果直接算出来,就不必真的先生成逆序数组再扫一遍,省掉了一趟遍历和额外空间。
如果边长是偶数,会不会漏处理某些格子?+
不会。偶数边长时,左指针 i 和右指针 j 会一路错身而过、永远不会落在同一格,所有格子都被成对处理,循环自然覆盖全行。只有奇数边长才会出现 i 等于 j 的正中间一格,那时单独补一次取反即可。所以代码里用 i 小于 j 控制成对处理、再用 i 等于 j 兜住中点,奇偶都不漏。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 翻转图像 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。