#include <bits/stdc++.h>
using namespace std;
template <typename T, typename T2>
ostream &operator<<(ostream &s, const pair<T, T2> &p) {
  return s << p.first << ' ' << p.second << ' ';
}
template <typename T, typename T2>
istream &operator>>(istream &s, pair<T, T2> &p) {
  return s >> p.first >> p.second;
}
template <typename T>
ostream &operator<<(ostream &s, const vector<T> &v) {
  for (auto it : v) s << it << ' ';
  return s;
}
template <typename T>
istream &operator>>(istream &s, vector<T> &v) {
  for (auto it = (v).begin(), it_ = (v).end(); it != it_; it++) s >> *it;
  return s;
}
template <typename T>
void read_range(T beg, T end) {
  while (beg != end) cin >> *beg++;
}
template <typename T>
void print_range(T beg, T end) {
  while (beg != end) cout << *beg++ << ' ';
}
struct reader {
  template <typename T>
  reader &operator,(T &v) {
    cin >> v;
    return *this;
  }
} rdr;
struct debugger {
  template <typename T>
  debugger &operator,(const T &v) {
    cerr << v << ", ";
    return *this;
  }
} dbg;
int dp[(int)(3e5 + 5)];
pair<int, pair<int, int> > edge[(int)(3e5 + 5)];
int main() {
  ios_base::sync_with_stdio(false);
  int n, m;
  rdr, n, m;
  ;
  for (int i = 0; i < m; i++)
    cin >> edge[i].second.first >> edge[i].second.second >> edge[i].first;
  sort(edge, edge + m);
  int i = 0;
  while (i < m) {
    queue<pair<int, int> > add;
    while (true) {
      if (dp[edge[i].second.first] + 1 > dp[edge[i].second.second])
        add.push(
            make_pair(edge[i].second.second, dp[edge[i].second.first] + 1));
      if (i + 1 < m and edge[i + 1].first == edge[i].first)
        i++;
      else
        break;
    }
    while (not add.empty()) {
      dp[add.front().first] = max(dp[add.front().first], add.front().second);
      add.pop();
    }
    i++;
  }
  int max_p = 0;
  for (int i = 1; i <= n; i++) max_p = max(max_p, dp[i]);
  cout << max_p;
  return 0;
}
