pub trait Zero: PartialEq + Sized {
    fn zero() -> Self;
    #[inline]
    fn is_zero(&self) -> bool {
        self == &Self::zero()
    }
}
pub trait One: PartialEq + Sized {
    fn one() -> Self;
    #[inline]
    fn is_one(&self) -> bool {
        self == &Self::one()
    }
}
macro_rules ! zero_one_impls {($ ({$ Trait : ident $ method : ident $ ($ t : ty ) *, $ e : expr } ) * ) => {$ ($ (impl $ Trait for $ t {# [inline ] fn $ method () -> Self {$ e } } ) * ) * } ; }
zero_one_impls ! ({Zero zero u8 u16 u32 u64 usize i8 i16 i32 i64 isize u128 i128 , 0 } {Zero zero f32 f64 , 0. } {One one u8 u16 u32 u64 usize i8 i16 i32 i64 isize u128 i128 , 1 } {One one f32 f64 , 1. } );
pub trait IterScan: Sized {
    type Output;
    fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output>;
}
pub trait MarkedIterScan: Sized {
    type Output;
    fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output>;
}
#[derive(Clone, Debug)]
pub struct Scanner<'a> {
    iter: std::str::SplitAsciiWhitespace<'a>,
}
mod scanner_impls {
    use super::*;
    impl<'a> Scanner<'a> {
        #[inline]
        pub fn new(s: &'a str) -> Self {
            let iter = s.split_ascii_whitespace();
            Self { iter }
        }
        #[inline]
        pub fn scan<T: IterScan>(&mut self) -> <T as IterScan>::Output {
            <T as IterScan>::scan(&mut self.iter).expect("scan error")
        }
        #[inline]
        pub fn mscan<T: MarkedIterScan>(&mut self, marker: T) -> <T as MarkedIterScan>::Output {
            marker.mscan(&mut self.iter).expect("scan error")
        }
        #[inline]
        pub fn scan_vec<T: IterScan>(&mut self, size: usize) -> Vec<<T as IterScan>::Output> {
            (0..size)
                .map(|_| <T as IterScan>::scan(&mut self.iter).expect("scan error"))
                .collect()
        }
        #[inline]
        pub fn iter<'b, T: IterScan>(&'b mut self) -> ScannerIter<'a, 'b, T> {
            ScannerIter {
                inner: self,
                _marker: std::marker::PhantomData,
            }
        }
    }
    macro_rules ! iter_scan_impls {($ ($ t : ty ) * ) => {$ (impl IterScan for $ t {type Output = Self ; # [inline ] fn scan <'a , I : Iterator < Item = &'a str >> (iter : & mut I ) -> Option < Self > {iter . next () ?. parse ::<$ t > () . ok () } } ) * } ; }
    iter_scan_impls ! (char u8 u16 u32 u64 usize i8 i16 i32 i64 isize f32 f64 u128 i128 String );
    macro_rules ! iter_scan_tuple_impl {($ ($ T : ident ) * ) => {impl <$ ($ T : IterScan ) ,*> IterScan for ($ ($ T , ) * ) {type Output = ($ (<$ T as IterScan >:: Output , ) * ) ; # [inline ] fn scan <'a , It : Iterator < Item = &'a str >> (_iter : & mut It ) -> Option < Self :: Output > {Some (($ (<$ T as IterScan >:: scan (_iter ) ?, ) * ) ) } } } ; }
    iter_scan_tuple_impl!();
    iter_scan_tuple_impl!(A);
    iter_scan_tuple_impl ! (A B );
    iter_scan_tuple_impl ! (A B C );
    iter_scan_tuple_impl ! (A B C D );
    iter_scan_tuple_impl ! (A B C D E );
    iter_scan_tuple_impl ! (A B C D E F );
    iter_scan_tuple_impl ! (A B C D E F G );
    iter_scan_tuple_impl ! (A B C D E F G H );
    iter_scan_tuple_impl ! (A B C D E F G H I );
    iter_scan_tuple_impl ! (A B C D E F G H I J );
    iter_scan_tuple_impl ! (A B C D E F G H I J K );
    pub struct ScannerIter<'a, 'b, T> {
        inner: &'b mut Scanner<'a>,
        _marker: std::marker::PhantomData<fn() -> T>,
    }
    impl<'a, 'b, T: IterScan> Iterator for ScannerIter<'a, 'b, T> {
        type Item = <T as IterScan>::Output;
        #[inline]
        fn next(&mut self) -> Option<Self::Item> {
            <T as IterScan>::scan(&mut self.inner.iter)
        }
    }
}
pub mod marker {
    use super::*;
    use std::{iter::FromIterator, marker::PhantomData};
    #[derive(Debug, Copy, Clone)]
    pub struct Usize1;
    impl IterScan for Usize1 {
        type Output = usize;
        #[inline]
        fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output> {
            Some(<usize as IterScan>::scan(iter)?.checked_sub(1)?)
        }
    }
    #[derive(Debug, Copy, Clone)]
    pub struct Chars;
    impl IterScan for Chars {
        type Output = Vec<char>;
        #[inline]
        fn scan<'a, I: Iterator<Item = &'a str>>(iter: &mut I) -> Option<Self::Output> {
            Some(iter.next()?.chars().collect())
        }
    }
    #[derive(Debug, Copy, Clone)]
    pub struct CharsWithBase(pub char);
    impl MarkedIterScan for CharsWithBase {
        type Output = Vec<usize>;
        #[inline]
        fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output> {
            Some(
                iter.next()?
                    .chars()
                    .map(|c| (c as u8 - self.0 as u8) as usize)
                    .collect(),
            )
        }
    }
    #[derive(Debug, Copy, Clone)]
    pub struct Collect<T: IterScan, B: FromIterator<<T as IterScan>::Output>> {
        size: usize,
        _marker: PhantomData<fn() -> (T, B)>,
    }
    impl<T: IterScan, B: FromIterator<<T as IterScan>::Output>> Collect<T, B> {
        pub fn new(size: usize) -> Self {
            Self {
                size,
                _marker: PhantomData,
            }
        }
    }
    impl<T: IterScan, B: FromIterator<<T as IterScan>::Output>> MarkedIterScan for Collect<T, B> {
        type Output = B;
        #[inline]
        fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output> {
            Some(
                (0..self.size)
                    .map(|_| <T as IterScan>::scan(iter).expect("scan error"))
                    .collect::<B>(),
            )
        }
    }
}
#[macro_export]
macro_rules ! min {($ e : expr ) => {$ e } ; ($ e : expr , $ ($ es : expr ) ,+ ) => {std :: cmp :: min ($ e , min ! ($ ($ es ) ,+ ) ) } ; }
#[macro_export]
macro_rules ! chmin {($ dst : expr , $ ($ src : expr ) ,+ ) => {{let x = std :: cmp :: min ($ dst , min ! ($ ($ src ) ,+ ) ) ; $ dst = x ; } } ; }
#[macro_export]
macro_rules ! max {($ e : expr ) => {$ e } ; ($ e : expr , $ ($ es : expr ) ,+ ) => {std :: cmp :: max ($ e , max ! ($ ($ es ) ,+ ) ) } ; }
#[macro_export]
macro_rules ! chmax {($ dst : expr , $ ($ src : expr ) ,+ ) => {{let x = std :: cmp :: max ($ dst , max ! ($ ($ src ) ,+ ) ) ; $ dst = x ; } } ; }
pub use sparse_graph::{
    Adjacency, BidirectionalGraphScanner, BidirectionalSparseGraph, DirectedGraphScanner,
    DirectedSparseGraph, SparseGraph, TreeGraphScanner, UndirectedGraphScanner,
    UndirectedSparseGraph,
};
pub mod sparse_graph {
    use super::*;
    use std::{iter, marker::PhantomData, ops, slice};
    type Marker<T> = PhantomData<fn() -> T>;
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct DirectedEdge;
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct UndirectedEdge;
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct BidirectionalEdge;
    #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd, Hash)]
    pub struct Adjacency {
        pub id: usize,
        pub to: usize,
    }
    impl Adjacency {
        pub fn new(id: usize, to: usize) -> Adjacency {
            Adjacency { id, to }
        }
    }
    /// Static Sparse Graph represented as Compressed Sparse Row.
    #[derive(Debug, Clone)]
    pub struct SparseGraph<D> {
        vsize: usize,
        pub start: Vec<usize>,
        pub elist: Vec<Adjacency>,
        pub edges: Vec<(usize, usize)>,
        _marker: Marker<D>,
    }
    impl<D> SparseGraph<D> {
        /// Return the number of vertices.
        pub fn vertices_size(&self) -> usize {
            self.vsize
        }
        /// Return the number of edges.
        pub fn edges_size(&self) -> usize {
            self.edges.len()
        }
        /// Return an iterator over graph vertices.
        pub fn vertices(&self) -> ops::Range<usize> {
            0..self.vertices_size()
        }
        /// Return a slice of adjacency vertices.
        pub fn adjacencies(&self, v: usize) -> slice::Iter<'_, Adjacency> {
            self.elist[self.start[v]..self.start[v + 1]].iter()
        }
    }
    pub trait SparseGraphConstruction: Sized {
        fn construct_graph(vsize: usize, edges: Vec<(usize, usize)>) -> SparseGraph<Self>;
    }
    impl<D: SparseGraphConstruction> SparseGraph<D> {
        /// Construct graph from edges.
        pub fn from_edges(vsize: usize, edges: Vec<(usize, usize)>) -> Self {
            D::construct_graph(vsize, edges)
        }
    }
    impl SparseGraphConstruction for DirectedEdge {
        fn construct_graph(vsize: usize, edges: Vec<(usize, usize)>) -> SparseGraph<Self> {
            let mut start: Vec<_> = iter::repeat(0).take(vsize + 1).collect();
            let mut elist = Vec::with_capacity(edges.len());
            unsafe { elist.set_len(edges.len()) }
            for (from, _) in edges.iter().cloned() {
                start[from] += 1;
            }
            for i in 1..=vsize {
                start[i] += start[i - 1];
            }
            for (id, (from, to)) in edges.iter().cloned().enumerate() {
                start[from] -= 1;
                elist[start[from]] = Adjacency::new(id, to);
            }
            SparseGraph {
                vsize,
                start,
                elist,
                edges,
                _marker: PhantomData,
            }
        }
    }
    impl SparseGraphConstruction for UndirectedEdge {
        fn construct_graph(vsize: usize, edges: Vec<(usize, usize)>) -> SparseGraph<Self> {
            let mut start: Vec<_> = iter::repeat(0).take(vsize + 1).collect();
            let mut elist = Vec::with_capacity(edges.len() * 2);
            unsafe { elist.set_len(edges.len() * 2) }
            for (from, to) in edges.iter().cloned() {
                start[to] += 1;
                start[from] += 1;
            }
            for i in 1..=vsize {
                start[i] += start[i - 1];
            }
            for (id, (from, to)) in edges.iter().cloned().enumerate() {
                start[from] -= 1;
                elist[start[from]] = Adjacency::new(id, to);
                start[to] -= 1;
                elist[start[to]] = Adjacency::new(id, from);
            }
            SparseGraph {
                vsize,
                start,
                elist,
                edges,
                _marker: PhantomData,
            }
        }
    }
    impl SparseGraphConstruction for BidirectionalEdge {
        fn construct_graph(vsize: usize, edges: Vec<(usize, usize)>) -> SparseGraph<Self> {
            let mut start: Vec<_> = iter::repeat(0).take(vsize + 1).collect();
            let mut elist = Vec::with_capacity(edges.len() * 2);
            unsafe { elist.set_len(edges.len() * 2) }
            for (from, to) in edges.iter().cloned() {
                start[to] += 1;
                start[from] += 1;
            }
            for i in 1..=vsize {
                start[i] += start[i - 1];
            }
            for (id, (from, to)) in edges.iter().cloned().enumerate() {
                start[from] -= 1;
                elist[start[from]] = Adjacency::new(id * 2, to);
                start[to] -= 1;
                elist[start[to]] = Adjacency::new(id * 2 + 1, from);
            }
            SparseGraph {
                vsize,
                start,
                elist,
                edges,
                _marker: PhantomData,
            }
        }
    }
    pub type DirectedSparseGraph = SparseGraph<DirectedEdge>;
    pub type UndirectedSparseGraph = SparseGraph<UndirectedEdge>;
    pub type BidirectionalSparseGraph = SparseGraph<BidirectionalEdge>;
    pub struct SparseGraphScanner<U: IterScan<Output = usize>, T: IterScan, D> {
        vsize: usize,
        esize: usize,
        _marker: Marker<(U, T, D)>,
    }
    impl<U: IterScan<Output = usize>, T: IterScan, D> SparseGraphScanner<U, T, D> {
        pub fn new(vsize: usize, esize: usize) -> Self {
            Self {
                vsize,
                esize,
                _marker: PhantomData,
            }
        }
    }
    impl<U: IterScan<Output = usize>, T: IterScan, D: SparseGraphConstruction> MarkedIterScan
        for SparseGraphScanner<U, T, D>
    {
        type Output = (SparseGraph<D>, Vec<<T as IterScan>::Output>);
        fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output> {
            let mut edges = Vec::with_capacity(self.esize);
            let mut rest = Vec::with_capacity(self.esize);
            for _ in 0..self.esize {
                edges.push((U::scan(iter)?, U::scan(iter)?));
                rest.push(T::scan(iter)?);
            }
            let graph = SparseGraph::from_edges(self.vsize, edges);
            Some((graph, rest))
        }
    }
    pub type DirectedGraphScanner<U, T> = SparseGraphScanner<U, T, DirectedEdge>;
    pub type UndirectedGraphScanner<U, T> = SparseGraphScanner<U, T, UndirectedEdge>;
    pub type BidirectionalGraphScanner<U, T> = SparseGraphScanner<U, T, BidirectionalEdge>;
    pub struct TreeGraphScanner<U: IterScan<Output = usize>, T: IterScan> {
        vsize: usize,
        _marker: Marker<(U, T)>,
    }
    impl<U: IterScan<Output = usize>, T: IterScan> TreeGraphScanner<U, T> {
        pub fn new(vsize: usize) -> Self {
            Self {
                vsize,
                _marker: PhantomData,
            }
        }
    }
    impl<U: IterScan<Output = usize>, T: IterScan> MarkedIterScan for TreeGraphScanner<U, T> {
        type Output = (UndirectedSparseGraph, Vec<<T as IterScan>::Output>);
        fn mscan<'a, I: Iterator<Item = &'a str>>(self, iter: &mut I) -> Option<Self::Output> {
            UndirectedGraphScanner::<U, T>::new(self.vsize, self.vsize - 1).mscan(iter)
        }
    }
}
impl<D> SparseGraph<D> {
    pub fn dijkstra<M>(
        &self,
        start: usize,
        monoid: M,
        weight: impl Fn(usize) -> M::T,
    ) -> Vec<Option<M::T>>
    where
        M: Monoid,
        M::T: Ord,
    {
        use std::cmp::Reverse;
        let mut cost = vec![None; self.vertices_size()];
        let mut heap = std::collections::BinaryHeap::new();
        cost[start] = Some(monoid.unit());
        heap.push((Reverse(monoid.unit()), start));
        while let Some((Reverse(d), u)) = heap.pop() {
            if cost[u].as_ref().unwrap() < &d {
                continue;
            }
            for a in self.adjacencies(u) {
                let nd = monoid.operate(&d, &weight(a.id));
                if cost[a.to].as_ref().map_or(true, |c| c > &nd) {
                    cost[a.to] = Some(nd.clone());
                    heap.push((Reverse(nd), a.to));
                }
            }
        }
        cost
    }
}

/// binary operaion: $T \circ T \to T$
pub trait Magma {
    /// type of operands: $T$
    type T: Clone + PartialEq;
    /// binary operaion: $\circ$
    fn operate(&self, x: &Self::T, y: &Self::T) -> Self::T;
    #[inline]
    fn reverse_operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
        self.operate(y, x)
    }
}
/// $\forall a,\forall b,\forall c \in T, (a \circ b) \circ c = a \circ (b \circ c)$
pub trait Associative {}
/// associative binary operation
pub trait SemiGroup: Magma + Associative {}
impl<S: Magma + Associative> SemiGroup for S {}
/// $\exists e \in T, \forall a \in T, e \circ a = a \circ e = e$
pub trait Unital: Magma {
    /// identity element: $e$
    fn unit(&self) -> Self::T;
}
/// associative binary operation and an identity element
pub trait Monoid: SemiGroup + Unital {
    /// binary exponentiation: $x^n = x\circ\ddots\circ x$
    fn pow(&self, x: Self::T, n: usize) -> Self::T {
        let mut n = n;
        let mut res = self.unit();
        let mut base = x;
        while n > 0 {
            if n & 1 == 1 {
                res = self.operate(&res, &base);
            }
            base = self.operate(&base, &base);
            n >>= 1;
        }
        res
    }
}
impl<M: SemiGroup + Unital> Monoid for M {}
/// $\exists e \in T, \forall a \in T, \exists b,c \in T, b \circ a = a \circ c = e$
pub trait Invertible: Magma {
    /// $a$ where $a \circ x = e$
    fn inverse(&self, x: &Self::T) -> Self::T;
    #[inline]
    fn rinv_operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
        self.operate(x, &self.inverse(y))
    }
}
/// associative binary operation and an identity element and inverse elements
pub trait Group: Monoid + Invertible {}
impl<G: Monoid + Invertible> Group for G {}
/// $\forall a,\forall b \in T, a \circ b = b \circ a$
pub trait Commutative {}
/// commutative monoid
pub trait AbelianMonoid: Monoid + Commutative {}
impl<M: Monoid + Commutative> AbelianMonoid for M {}
/// commutative group
pub trait AbelianGroup: Group + Commutative {}
impl<G: Group + Commutative> AbelianGroup for G {}
/// $\forall a \in T, a \circ a = a$
pub trait Idempotent {}
/// idempotent monoid
pub trait IdempotentMonoid: Monoid + Idempotent {}
impl<M: Monoid + Idempotent> IdempotentMonoid for M {}
/// $+$
#[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct AdditiveOperation<T: Copy + Zero + std::ops::Add<Output = T>> {
    _marker: std::marker::PhantomData<fn() -> T>,
}
mod additive_operation_impl {
    use super::*;
    use std::ops::{Add, Neg, Sub};
    impl<T: Copy + Zero + Add<Output = T>> AdditiveOperation<T> {
        pub fn new() -> Self {
            Self {
                _marker: std::marker::PhantomData,
            }
        }
    }
    impl<T: Copy + Zero + Add<Output = T>> Magma for AdditiveOperation<T> {
        type T = T;
        #[inline]
        fn operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
            *x + *y
        }
    }
    impl<T: Copy + Zero + Add<Output = T>> Unital for AdditiveOperation<T> {
        #[inline]
        fn unit(&self) -> Self::T {
            Zero::zero()
        }
    }
    impl<T: Copy + Zero + Add<Output = T>> Associative for AdditiveOperation<T> {}
    impl<T: Copy + Zero + Add<Output = T>> Commutative for AdditiveOperation<T> {}
    impl<T: Copy + Zero + Add<Output = T> + Sub<Output = T> + Neg<Output = T>> Invertible
        for AdditiveOperation<T>
    {
        #[inline]
        fn inverse(&self, x: &Self::T) -> Self::T {
            -*x
        }
        #[inline]
        fn rinv_operate(&self, x: &Self::T, y: &Self::T) -> Self::T {
            *x - *y
        }
    }
}
fn main() {
    #![allow(unused_imports, unused_macros)]
    use std::io::{stdin, stdout, BufWriter, Read as _, Write as _};
    let mut _in_buf = Vec::new();
    stdin().read_to_end(&mut _in_buf).expect("io error");
    let _in_buf = unsafe { String::from_utf8_unchecked(_in_buf) };
    let mut scanner = Scanner::new(&_in_buf);
    macro_rules ! scan {() => {scan ! (usize ) } ; (($ ($ t : tt ) ,* ) ) => {($ (scan ! ($ t ) ) ,* ) } ; ([$ t : tt ; $ len : expr ] ) => {(0 ..$ len ) . map (| _ | scan ! ($ t ) ) . collect ::< Vec < _ >> () } ; ([$ t : ty ; $ len : expr ] ) => {scanner . scan_vec ::<$ t > ($ len ) } ; ([$ t : ty ] ) => {scanner . iter ::<$ t > () } ; ({$ e : expr } ) => {scanner . mscan ($ e ) } ; ($ t : ty ) => {scanner . scan ::<$ t > () } ; }
    let _out = stdout();
    let mut _out = BufWriter::new(_out.lock());
    macro_rules ! print {($ ($ arg : tt ) * ) => (:: std :: write ! (_out , $ ($ arg ) * ) . expect ("io error" ) ) }
    macro_rules ! println {($ ($ arg : tt ) * ) => (:: std :: writeln ! (_out , $ ($ arg ) * ) . expect ("io error" ) ) }
    macro_rules! echo {
        ($ iter : expr ) => {
            echo!($iter, '\n')
        };
        ($ iter : expr , $ sep : expr ) => {
            let mut iter = $iter.into_iter();
            if let Some(item) = iter.next() {
                print!("{}", item);
            }
            for item in iter {
                print!("{}{}", $sep, item);
            }
            println!();
        };
    }
    let n = scan!();
    let m = scan!();
    let pqc = scan!([(marker::Usize1, marker::Usize1, usize); m]);
    let mut idx = Indexing::default();
    let mut edges = vec![];
    let mut cost = vec![];
    for (p, q, c) in pqc.iter().cloned() {
        let (u, ub) = idx.get((p, c));
        let (v, vb) = idx.get((q, c));
        edges.push((u, v));
        cost.push(0);
        edges.push((v, u));
        cost.push(0);
        if ub {
            let w = idx.get((p, 0)).0;
            edges.push((u, w));
            cost.push(0);
            edges.push((w, u));
            cost.push(1);
        }
        if vb {
            let w = idx.get((q, 0)).0;
            edges.push((v, w));
            cost.push(0);
            edges.push((w, v));
            cost.push(1);
        }
    }
    let s = idx.get((0, 0)).0;
    let t = idx.get((n - 1, 0)).0;
    let g = DirectedSparseGraph::from_edges(idx.map.len(), edges);
    let d = g.dijkstra(s, AdditiveOperation::new(), |i| cost[i]);
    let ans = d[t];
    if let Some(ans) = ans {
        println!("{}", ans);
    } else {
        println!("-1");
    }
}
pub struct Indexing<T: Eq + std::hash::Hash> {
    map: std::collections::HashMap<T, usize>,
}

impl<T: Eq + std::hash::Hash> Default for Indexing<T> {
    fn default() -> Self {
        Self {
            map: Default::default(),
        }
    }
}
impl<T: Eq + std::hash::Hash> Indexing<T> {
    pub fn get(&mut self, t: T) -> (usize, bool) {
        if let Some(&i) = self.map.get(&t) {
            (i, false)
        } else {
            let i = self.map.len();
            self.map.insert(t, i);
            (i, true)
        }
    }
}


Measured memory_bytes: 87404