public static Diff diff(List<String> lines1, List<String> lines2, boolean ignoreWhitespace) {
        int m = lines1.size();
        int n = lines2.size();
        List<String> orig1=lines1, orig2=lines2;
        if (ignoreWhitespace) {
            lines1 = trimLines(lines1);
            lines2 = trimLines(lines2);
        }
        Line[] l2s = new Line[n + 1];
        // In l2s we have sorted lines of the second file <1, n>
        for (int i = 1; i <= n; i++) {
            l2s[i] = new Line(i, lines2.get(i - 1));
        }
        Arrays.sort(l2s, 1, n+1);
        
        int[] equvalenceLines = new int[n+1];
        boolean[] equivalence = new boolean[n+1];
        for (int i = 1; i <= n; i++) {
            Line l = l2s[i];
            equvalenceLines[i] = l.lineNo;
            equivalence[i] = (i == n) || !l.line.equals(l2s[i+1].line);//((Line) l2s.get(i)).line);
        }
        equvalenceLines[0] = 0;
        equivalence[0] = true;
        int[] equivalenceAssoc = new int[m + 1];
        for (int i = 1; i <= m; i++) {
            equivalenceAssoc[i] = findAssoc(lines1.get(i - 1), l2s, equivalence);
        }
        
        l2s = null;
        Candidate[] K = new Candidate[Math.min(m, n) + 2];
        K[0] = new Candidate(0, 0, null);
        K[1] = new Candidate(m + 1, n + 1, null);
        int k = 0;
        for (int i = 1; i <= m; i++) {
            if (equivalenceAssoc[i] != 0) {
                k = merge(K, k, i, equvalenceLines, equivalence, equivalenceAssoc[i]);
            }
        }
        int[] J = new int[m+2]; // Initialized with zeros
        
        Candidate c = K[k];
        while (c != null) {
            J[c.a] = c.b;
            c = c.c;
        }
        
        Diff differences = getDifferences(J, orig1, orig2);
        cleanup(differences);
        return differences;
    }