lc82
引入flag,标记重复_跳过
return前处理:newhead->next=nullptr; //截断尾部可能的残留
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head)
{
if(!head || !head->next)
return head;
ListNode* newhead=new ListNode(0);
ListNode* ret=newhead;
int flag=-1000;
while(head)
{
if(head->next && head->val==head->next->val)
flag=head->val;
++if(head->val!=flag)++
{
newhead->next=head;
newhead=newhead->next;
}
head=head->next;
}
++newhead->next=nullptr; //截断尾部可能的残留++
return ret->next;
}
};
lc52
dfs 尝试逐行处理
class Solution {
vector<bool> cols,diag1,diag2;
int n=0,ret=0;
public:
int totalNQueens(int n)
{
this->n=n;
cols.resize(n,false);
diag1.resize(n+n,false);
diag2.resize(n+n,false);
dfs(0);
return ret;
}
void dfs(int j)
{
if(j==n)
{
ret++;
return;
}
for(int i=0;i<n;i++)
{
if(!cols[i] && !diag1[i+j] && !diag2[i-j+n])
{
cols[i]=diag1[i+j]=diag2[i-j+n]=true;
dfs(j+1);
cols[i]=diag1[i+j]=diag2[i-j+n]=false;
}
}
}
};
lc1652
拼接2*n数组之后,前缀和
class Solution {
public:
vector<int> decrypt(vector<int>& code, int k) {
int n=code.size();
vector<int> t=code;
t.insert(t.end(),code.begin(),code.end());
vector<int> pre(2*n);
pre[0]=code[0];
for(int i=1;i<2*n;i++)
{
pre[i]=pre[i-1]+t[i];
}
for(int i=0;i<n;i++)
{
if(k>=0)
code[i]=pre[i+k]-pre[i];
else
code[i]=pre[i+n-1]-pre[i+n+k-1];
}
return code;
}
};
lc1089 复写0
双指针
预统计,找到右端点+反向填充

class Solution {
public:
void duplicateZeros(vector<int>& arr) {
int n = arr.size(), i, j = n-1, k = n-1;
for(i = 0; i < j; ++i)
if(arr[i] == 0)
--j;
// 此处特判需要多加思考
if(i == j && arr[i] == 0) arr[k--] = arr[j--];
while(j >= 0)
{
if(arr[j] == 0)
arr[k--] = 0;
arr[k--] = arr[j--];
}
}
};
lc498
按对角线(行&列 和相同)分组矩阵元素
偶数索引对角线reverse
依次拼接所有元素,得到对角线遍历结果。
class Solution {
public:
vector<int> findDiagonalOrder(vector<vector<int>>& mat)
{
int m=mat.size(),n=mat[0].size();
vector<vector<int>> diag(m+n-1);
for(int i=0;i<m;i++)
{
for(int j=0;j<n;j++)
{
//对角线 和等
diag[i+j].push_back(mat[i][j]);
}
}
int i=0;
vector<int> ret;
for(auto& d:diag)
{
if(i%2==0)
//偶数列 从下往上
reverse(d.begin(),d.end());
i++;
for(auto& a:d)
ret.push_back(a);
}
return ret;
}
};
lc2316
int DFS找出图中所有连通块的大小
然后计算不同连通块之间的节点对数量总和(乘法原理)
class Solution {
public:
long long countPairs(int n, vector<vector<int>> &edges) {
vector<vector<int>> g(n);
for (auto &e: edges) {
int x = e[0], y = e[1];
g[x].push_back(y);
g[y].push_back(x); // 建图
}
vector<int> vis(n);
function<int(int)> dfs = [&](int x) -> int
{
vis[x] = true; // 避免重复访问同一个点
++int size = 1;
for (int y: g[x]) {
if (!vis[y]) {
size += dfs(y);++
}
}
return size;
};
long long ans = 0;
for (int i = 0, total = 0; i < n; i++)
{
++if (!vis[i]) { // 未访问的点:说明找到了一个新的连通块
int size = dfs(i);
ans += (long) size * total;
total += size;++
}
}
return ans;
}
};
神经网络
等变线性变换(对应代码里的 e3nn.o3.Linear ) 和 等变非线性变换(对应代码里的 e3nn.nn.Gate ) 拆开讲,它们是干啥的、有啥用:
一、先理解「等变」是啥
可以把++「等变」简单想成:输入变了、输出跟着变,++ 但变化的"规律"和输入的"对称性"能对应上。
举个生活例子:
你给一张对称的蝴蝶图片,先旋转 90 度,再用神经网络处理;
和你先处理原图,再旋转 90 度 ------ 这两种操作后,结果应该能对应上(蝴++蝶该对称还对称、该在哪还在哪)++。
这种"++输入变、输出也按相同规律变++"的性质,就是「等变」的核心。
二、等变线性变换(e3nn.o3.Linear)
把它想成「保持对称性的"线性接力"」。
比如你有一组数据,本身带着 3D 空间的对称规律(像球、立方体这些对称物体的数据),经过这个变换后,数据能"按对称规则"被线性拉伸、组合,但原本的对称性质不会乱套。
类比生活:你给一个对称的魔方拍张照,用这个变换处理照片++,魔方的"对称块"会按规律移动/组合,但魔方整体的对称结构还能看出来,++不会变成乱糟糟的图案。

实现逻辑(不用深究数学,理解思路):
基于「不可约表示(Irreps)」这些数学工具(代码里也提到了)
++保证变换时,不同"对称分量"各自线性变换,互不干扰破坏整体对称。++
就像给魔方每个面的颜色做"线性调整",但面与面的对称关系不变。
三、等变非线性变换(e3nn.nn.Gate)
通俗理解:
在线性变换基础上,给对称数据"加一把智能的'小锁'"。
它会++挑出数据里简单的、基础的对称特征(标量 Irreps),用这些特征去"控制"复杂的、高阶的对称特征(高阶 Irreps)的激活。++
类比生活:
你教机器人识别对称的汽车,"车门对称"是简单标量特征(好提取),++"整个车身的 3D 对称"是复杂高阶特征。++
++Gate 就像让"车门对称"这个小锁,去决定是否激活"车身 3D 对称"的判断++ ,让识别更高效。
实现逻辑(简单说):
先用简单的标量(比如某个对称方向的数值)当"开关",符合条件了,才让复杂的高阶对称特征参与运算。数学上还是靠 Irreps 这些工具,保证"开关"和"被控制的特征"都遵循 3D 对称规律。
四、应用场景 & 现实意义
应用场景:
-
3D 图形/机器人领域:识别 3D 物体(像对称的零件、建筑结构)、让机器人理解空间对称规律(抓对称的工具更顺手)。
-
材料科学:研究晶体(天生带对称结构)的性质,用等变网络分析原子排列的对称规律,预测材料性能。
-
自动驾驶:识别道路上的对称标志(比如对称的车道线、交通标识),辅助判断路况。
现实意义:
让 AI 真正"理解" 3D 空间的对称规律,不再是"死记硬背图片/数据"。
++处理 3D 对称相关问题时,效率更高、结果更准,能解决传统神经网络"一遇到旋转/对称变化就失效"的痛点。++
五、总结
-
- 等变线性:让数据按 3D 对称规律"规矩地线性变换",对称性质不变形。
-
- 等变非线性:给对称数据加"智能开关",用简单特征控制复杂特征的激活,让模型更聪明。
它们就像++给 AI 装上"3D 对称透视眼",处理 3D 对称相关任务时,又稳又高效++ ,能帮我们在识别 3D 物体、研究材料、自动驾驶这些事儿上,做得更出色~