#include <iostream>
#include <cstdio>
#include <queue>
#include <cstring>
#include <algorithm>
using namespace std;
#define int long long
#define CCF 0
#define FUCK return
struct ba//邻接表边结构体
{
int from;
int to;
int nex;
int lon;
bool use;
}bian[100001];
int f[100001][21]={};//倍增,f[i][j]表示点i的2^j辈祖先
int fa[100001]={};//父节点
int g[100001][21][2]={};//最后一维0表示i到f[i][j]的最小生成树的最大边,1表示次大边
struct hd
{
int fir;
int las;
int num;
}head[100001];//邻接表结构体
bool bmp(ba a,ba b)
{
return a.lon<b.lon;
}//kruskal找最小边
int p=0;//邻接表连边的条数
void lian(int a,int b,int c)//邻接表连边
{
p++;
if(head[a].num)
{
bian[head[a].las].nex=p;
head[a].num++;
head[a].las=p;
}
else
{
head[a].fir=p;
head[a].las=p;
head[a].num=1;
}
}
int fin(int x)//并查集
{
if(fa[x]==x)
return x;
return fa[x]=fin(fa[x]);
}
int dep[100001]={};//节点深度
void dfs(int a,int fat)//dfs求深度和父节点
{
fa[a]=fat;
dep[a]=dep[fat]+1;
for(int i=head[a].fir;i;i=bian[i].nex)
{
if(bian[i].to!=fat&&bian[i].use==1)
{
dfs(bian[i].to,a);
g[a][0][0]=bian[i].lon;//只有一条边,最大即边长,次大-99999999(后面初始化)
}
}
return;
}
void zuida(int a,int b,int &ans1,int &ans2)//找a到b的最长和次长边,ans1,ans2保存(LCA)
{
if(dep[a]<dep[b])
swap(a,b);
for(int i=20;i>=0;i--)
{
if(a==b)
return;
if(dep[a]==dep[b])
break;
if(dep[f[a][i]]>=dep[b])
{
ans1=max(g[a][i][0],ans1);
ans2=max(g[a][i][1],ans2);
a=f[a][i];
}
}//统一深度
for(int i=20;i>=0;i--)
{
if(a==b)
return;
if(f[a][i]!=f[b][i])
{
ans1=max(g[a][i][0],ans1);
ans1=max(g[b][i][0],ans1);
ans2=max(g[a][i][1],ans2);
ans2=max(g[b][i][1],ans2);
a=f[a][i];
b=f[b][i];
}
}
ans1=max(g[a][0][0],ans1);
ans1=max(g[b][0][0],ans1);
ans2=max(g[a][0][1],ans2);
ans2=max(g[b][0][1],ans2);//求最大与次大
}
#undef int
int main()
{
#define int long long
int n,m;
scanf("%lld %lld",&n,&m);
for(int i=1;i<=m;i++)//初始化,因为i条边,编号2*i-1,2*i
{
int a,b,c;
scanf("%lld %lld %lld",&a,&b,&c);
bian[(i<<1)-1].from=a;
bian[(i<<1)-1].to=b;
bian[(i<<1)-1].lon=c;
bian[(i<<1)].from=b;
bian[i<<1].to=a;
bian[i<<1].lon=c;
}
/*for(int i=1;i<=2*m;i++)
{
}*/
sort(bian+1,bian+1+m+m,bmp);//排序
for(int i=1;i<=m;i++)//邻接表存边
{
lian(bian[i<<1-1].from,bian[i<<1-1].to,bian[i<<1-1].lon);
lian(bian[i<<1].from,bian[i<<1].to,bian[i<<1].lon);
}
for(int i=1;i<=n;i++)//并查集初始化
{
fa[i]=i;
}
int temp=0;//kruskal求最小生成树边数
int ans=0;//最小生成树大小
for(int i=1;i<=m&&temp<=n-1;i++)
{
if(fin(bian[i<<1].from)!=fin(bian[i<<1].to))//如果祖先不同
{
temp++;
ans+=bian[i<<1].lon;
fa[fin(bian[i<<1].to)]=bian[i<<1].from;
bian[i<<1].use=bian[(i<<1)-1].use=1;//表示这条边和反向边都被用过
}
}
dfs(1,0);//求父节点
int ans2=21000000000000000;
for(int i=1;i<=n;i++)//初始化
{
f[i][0]=fa[i];//2^0辈肯定是父节点啦
g[i][0][1]=-100000000000;
}
for(int i=1;i<=20;i++)//求f,g
{
for(int j=1;j<=n;j++)
{
f[j][i]=f[f[j][i-1]][i-1];
g[j][i][0]=max(g[j][i-1][0],g[f[j][i-1]][i-1][0]);
if(g[j][i-1][0]==g[f[j][i-1]][i-1][0])
g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][1]);
if(g[j][i-1][0]<g[f[j][i-1]][i-1][0])
g[i][j][1]=max(g[i][j-1][0],g[f[i][j-1]][j-1][1]);
if(g[j][i-1][0]>g[f[j][i-1]][i-1][0])
g[i][j][1]=max(g[i][j-1][1],g[f[i][j-1]][j-1][0]);
}
}
for(int i=1;i<=m;i++)
{
if(!bian[i<<2].use)//求次小生成树
{
int ansss;
int anssss;
zuida(bian[i<<2].from,bian[i<<2].to,ansss,anssss);
if(ansss==bian[i<<2].lon)//如果最大边和最小生成树的最大边一样,用anssss替换
{
ans2=min(ans2,ans+bian[i<<2].lon-anssss);
}
else//否则用ansss替换
{
ans2=min(ans2,ans+bian[i<<2].lon-ansss);
}
}
}
printf("%lld",ans2);//输出
FUCK CCF;//和别人学的
}