程序员老鬼

35岁被公司裁了,但拿到了40万赔偿金,失业在家4个月,突然看到丈母娘给媳妇儿的信息,人要崩溃了。网友:丈母娘太坏了!

刚刷到个帖,看得我有点堵。35岁被裁,拿了40万补偿,在家待了4个月,心里早就慌了。

结果更扎心的是,他无意间看到丈母娘给老婆发消息,大概意思就是别把钱让他乱花,工作也没着落,家里以后靠不靠得住都难说,实在不行要给自己留后路。

Image

你说这话站在娘家角度,好像是在替女儿打算,可落到这个男人眼里,那真是当头一棒。

40万看着不少,在中年失业面前也就是个缓冲垫,坐久了照样硌得慌。丈母娘这话吧,坏不坏另说,反正挺冷。

面试题:分割连接字符串

有个题看着像字符串拼接,真写起来最容易漏一刀:分割连接字符串。

给你一组字符串,每个字符串可以选择正着放,也可以反着放。全部连起来之后,再从任意位置切一刀,把后半段挪到前面,问能得到的字典序最大的字符串是什么。

这题我第一眼不太信贪心只扫一遍就能完事。因为“反转”和“切分”两个动作叠在一起,切的位置不同,前缀字符就变了,字典序也跟着变。

比如:

["abc", "xyz"]

abc 可以变成 cba,xyz 也可以变成 zyx。

你最后不是简单拼一个最大串,而是要考虑从哪一个字符开始。

这类题我一般先拆成两层。

第一层,除了当前准备切开的字符串,其他字符串都应该选自己和反转里字典序更大的那个。这个没什么好犹豫的,它们在候选串中是一整段出现,当然越大越好。

第二层,当前字符串要特殊处理。因为切口可能落在它里面,所以它不能只取最大方向,正着和反着都要试。

代码可以这么写:

publicclassSplitJoinString{

public String largestLoop(String[] parts){
int n = parts.length;
        String[] best = new String[n];

for (int i = 0; i < n; i++) {
            String rev = reverse(parts[i]);
            best[i] = parts[i].compareTo(rev) >= 0 ? parts[i] : rev;
        }

        String ans = "";

for (int i = 0; i < n; i++) {
            String leftBlock = join(best, i + 1, n) + join(best, 0, i);

            String raw = parts[i];
            String rev = reverse(raw);

            ans = scanCut(raw, leftBlock, ans);
            ans = scanCut(rev, leftBlock, ans);
        }

return ans;
    }

private String scanCut(String cur, String middle, String ans){
for (int cut = 0; cut < cur.length(); cut++) {
            String candidate = cur.substring(cut) + middle + cur.substring(0, cut);
if (candidate.compareTo(ans) > 0) {
                ans = candidate;
            }
        }
return ans;
    }

private String join(String[] arr, int l, int r){
        StringBuilder sb = new StringBuilder();
for (int i = l; i < r; i++) {
            sb.append(arr[i]);
        }
return sb.toString();
    }

private String reverse(String s){
returnnew StringBuilder(s).reverse().toString();
    }
}

这里最容易写错的是这一句:

ans = scanCut(raw, leftBlock, ans);
ans = scanCut(rev, leftBlock, ans);

当前字符串一定要两个方向都试。有人会先把每个字符串都换成 max(s, reverse(s)),然后只在这个结果上切。这样会漏答案。

原因很简单,字符串整体更大,不代表它从某个位置切开后仍然更大。

比如某个串正着开头小,但中间藏了一个更大的字符;反过来整体看着大,切出来的开头反而不行。这种题,字典序看的就是切完之后第一个不同字符,不能提前替当前串做决定。

整个流程其实就一句话:枚举哪一段被切开,其他段取最优;被切开的这一段,正反两种方向逐字符试。

时间复杂度不算吓人。设总字符数是 L,外层枚举字符串,内层枚举切点,候选串长度也是 L,直接写大概是 O(L^2)。面试题一般够用。