【leetcode】1163. Last Substring in Lexicographical Order

题目的描述:

Given a string s, return the last substring of s in lexicographical order.

 

Example 1:

Input: "abab"
Output: "bab"
Explanation: The substrings are ["a", "ab", "aba", "abab", "b", "ba", "bab"]. 
The lexicographically maximum substring is "bab".
Example 2:

Input: "leetcode"
Output: "tcode"
 

Note:

1 <= s.length <= 10^5
s contains only lowercase English letters.

理解题目的意思,花了大半天的时间,即使是看了答案,也没有立即清除题目的意思,这个时候,就体现出来英语的重要了。

the last substring of s in lexicographical order

如果一个字符串s,可以给出来这个字符串s包含的所有的子串,代码如下:

    public static void main(String[] args) {
		System.out.print(allSubString("eetcode"));
	}

	private static List<String> allSubString(String string) {
		List<String> resultList = new ArrayList<String>();
		for (int i = 0; i < string.length(); i++) {
			for (int j = i+1; j <= string.length(); j++) {
				resultList.add(string.substring(i, j));
			}
		}
		return resultList;
	}

例如上面的测试用例中的:eetcode,所有的子串就是:

[e, ee, eet, eetc, eetco, eetcod, eetcode,
e, et, etc, etco, etcod, etcode, t, tc, tco,
tcod, tcode, c, co, cod, code, o, od, ode, d, de, e]

这些子串,按照字典排序,那么最后的那个子串就是:tcode

这个问题,就是给出来,一个字符串s,然后给出来这个字符串s,按照字典排序的那个最后的子串。

然后,就是各种的分析,其中首先找到最迟的那个字符,标志着字典序的确认进入了一大步,然后我们就可以直接的从选中的那个字符开始到字符转结束,就是那个”最后的字符串”了。具体的代码为:


	public static void main(String[] args) {
		System.out.println(allSubString("eetcode"));
		
		System.out.println(lastSubstring("eetcode"));
	}

	public static String lastSubstring(String str) {
		String mx = "";
		char cur = 'a';
		for (int i = 0; i < str.length(); i++) {
			if (str.charAt(i) >= cur && mx.compareTo(str.substring(i)) <= 0) { // the first letter of substring matters,
				cur = str.charAt(i);
				mx = str.substring(i); // for example, "tabc" is lexicographically larger than "tab"
			}
			while (i != str.length() - 1 && str.charAt(i) == str.charAt(i + 1))
				i++; // get rid of continuous repeating letters
		}
		return mx;
	}

仔细分析,这个算法,因为有一个一个substring的操作,所以这个时间的复杂度应该为O(n2),最坏的情况,就是:ababab…….

这道题目的关键在于i的移动,我们每次比较的时候,动用了 mx.compareTo(str.substring(i)) <= 0 而且我们这样的比较导致了一个O(n),能够按照坐标,上一次开始,到这次的比较的点,进行比较,这样就不需要O(n),分析到这里,就和一个机制很像了:滑动窗口

具体的代码:

	public static String lastSubstring(String s) {
        int n = s.length();
        // We have two pointers each pointing at the start of a
        // possible candidate substring. The pointer on the left
        // is the start of the final answer. Move the pointer with
        // smaller ASCII value to the right.
        // 两个类似指针的标志位,i和j, i 表示最后字段子串的起始位,j为滑动窗口的位置,curLen为窗口的大小
        int i = 0, j = 1, curLen = 0;
        while (i < n && j < n && curLen < n) {
            // 如果窗口的越过了n则直接的结束
            if (i + curLen >= n || j + curLen >= n) {
                break;
            }
            // 窗口的开始大小为0,所以扩展的前提是:i和j的下一个字符是一样的,可以直接则增加窗口
            if (s.charAt(i + curLen) == s.charAt(j + curLen)) {
                curLen++;
            } else {
                // If two strings have different priority, move the pointer that
                // is pointing to the start of the string with lower priority.
                // We can directly do i += curLen + 1 instead of i += 1 because
                // the substring from i to i + curLen has already been covered
                // (it is impossible that any substring in this range will have a 
                // higher priority than the one that the other pointer is pointing 
                // to right now). Thus, we can skip them directly
                // E.g. d  a  b  d  a  b  x
                //      i        j           --> curLen = 3 (dab = dab)
                // When curLen = 4, d < x, so we need to move i to the right
                // We can move i to index 4 (i.e. the second a) directly. If some 
                // character between index 1 (the first a) and pointer j has higher
                // priority than the character pointed to by j, then i will have left
                // i long ago, which is clearly a contradiction.
                // 如果位置i的字符的字典序,小于j字符的字典序,i直接的递增curLen + 1
                if (s.charAt(i + curLen) < s.charAt(j + curLen)) {
                    i += curLen + 1;
                } else {
                    j += curLen + 1;
                }

                if (i == j) {
                    j++;
                }
                
                curLen = 0;
            }
        }
        return s.substring(Math.min(i, j));
    }

i和j的移动,就是第一版的优化而来的,并不是特别的依赖某种的算法过来的。

Powered by andiHappy and Theme by AndiHappy