Z 字形變換
額。。。不知道是不是我瞎,明明是N麼(槓精勿擾,只是說說)
第6題:將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。比如輸入字符串爲 "LEETCODEISHIRING" 行數爲 3 時,排列如下:
L C I R
E T O E S I I G
E D H N
之後,你的輸出需要從左往右逐行讀取,產生出一個新的字符串,比如:"LCIRETOESIIGEDHN"。
請你實現這個將字符串進行指定行數變換的函數:
string convert(string s, int numRows);
示例 1:
輸入: s = "LEETCODEISHIRING", numRows = 3
輸出: "LCIRETOESIIGEDHN"
示例 2:
輸入: s = "LEETCODEISHIRING", numRows = 4
輸出: "LDREOEIIECIHNTSG"
L D R
E O E I I
E C I H N
T S G
02
題目分析
這是我比較推崇的一道“小學題目”,因爲其沒有任何複雜的思維邏輯,只需要按部就班,就可以順利解答。難的是,按部就班的過程裏,卻極其容易出錯。。
因爲最終目的是變換字符串的順序,並且題中也沒有限制說不可用額外空間,所以我們秉承不重複造輪子的原則,想辦法利用某種結構對原字符串做文章。
假若我們採用示例2的數據來進行分析,輸入字符串 s 爲 "LEETCODEISHIRING", numRows 爲 4 ,畫成圖大概長這樣:
重點來了,我們的目標是按行打印,那總得有個東西來存儲每行的數據吧?因爲只需要存儲每行的數據,那是不是用個數組就可以了。(當然,你硬說搞個map來存也沒啥毛病,就是有點閒得蛋疼)
問題來了,那數組設置多大呢?自然是有多少行我們就設置多大唄,換句話說,numRows多大,我們的數組就設置多大。畫成圖大概就是下面這個樣子:
存儲的容器有了,原字符串也列出來是啥樣了,現在嘎哈?自然就是把原字符串放到容器裏咯。怎麼放?根據 numRows 的大小來回進行放置即可(即從0到n-1,再從n-1到0)。啥意思:
(不需要我再繼續畫下去了吧)
上面的圖長得不得了,但是觀察我們能看出來,每 2n-2 即爲一個週期。到了這裏,應該沒有人會質疑這是一道小學題目了吧。。。把所有的字符串放完之後,大概就是下面這個樣子:
最後一步,咱們讓這個數組排排坐,就可以開始吃果果:
如果看不清楚,不如這樣:
根據分析,得出代碼(好久沒翻go的牌子了):
1//GO
2func convert(s string, numRows int) string {
3 if numRows == 1{
4 return s
5 }
6 var b = []rune(s)
7 var res = make([]string, numRows)
8 var length = len(b)
9 var period = numRows * 2 - 2
10 for i := 0 ;i < length; i ++ {
11 var mod = i % period
12 if mod < numRows {
13 res[mod] += string(b[i])
14 } else {
15 res[period - mod] += string(b[i])
16 }
17 }
18 return strings.Join(res, "")
19}
上面的代碼要強調兩點:第一:用了一個rune,這個其實是go裏的用法,用來處理unicode或utf-8字符而已,並沒有什麼特別的。
第二:12-15行的意思是,在週期內,正着走 numRows-1 下,剩餘的反着走。(也就是上面那個長長的圖)
爲了照顧Java的小夥伴,再給出一個Java版本的:
1//java
2class Solution {
3 public static String convert(String s, int numRows) {
4 if (numRows == 1) return s;
5 String[] arr = new String[numRows];
6 Arrays.fill(arr, "");
7 char[] chars = s.toCharArray();
8 int len = chars.length;
9 int period = numRows * 2 - 2;
10 for (int i = 0; i < len; i++) {
11 int mod = i % period;
12 if (mod < numRows) {
13 arr[mod] += chars[i];
14 } else {
15 arr[period - mod] += String.valueOf(chars[i]);
16 }
17 }
18 StringBuilder res = new StringBuilder();
19 for (String ch : arr) {
20 res.append(ch);
21 }
22 return res.toString();
23 }
24}
和Go語言的示例一樣,代碼的關鍵仍然是計算軌跡的過程(10-17),這裏再提供另外一種計算軌跡的方式:
1//java
2class Solution {
3 public String convert(String s, int numRows) {
4 if (numRows == 1) return s;
5 String[] arr = new String[numRows];
6 Arrays.fill(arr, "");
7 int i = 0, flag = -1;
8 for (char c : s.toCharArray()) {
9 arr[i] += c;
10 if (i == 0 || i == numRows - 1) flag = -flag;
11 i += flag;
12 }
13 StringBuilder res = new StringBuilder();
14 for (String ch : arr) {
15 res.append(ch);
16 }
17 return res.toString();
18 }
19}
通過使用一個標誌位,來使軌跡回移。(本質其實是一樣的)
鄭重申明(讀我的文章必看):
- 本系列所有教程都不會用到複雜的語言特性,大家無須擔心沒有學過相關語法,算法思想纔是最重要的!
- 作爲學術文章,雖然風格可以風趣,但嚴謹,我是認真的。本文所有代碼均在leetcode進行過測試運行。
03
結尾
這道題目的意義在於考察coding的能力,本身的思維過程並不複雜。有的同學一看這種題目,就想通過二維數組來進行計算,殊不知已經落入了題目的陷阱(不信你試試,二維數組出錯率一定遠勝一維數組)。當然,本題也是可以不借助額外空間來進行實現的,核心的邏輯完全相同,建議大家下去自己動手練習一下。
最後小編整理了一套技術資料不僅能精準消除技術盲點、累計面試經驗,更可以攻克JVM、Spring、分佈式、微服務等技術難題。
以下文檔獲取方式關注博主後,私信回覆【面試】即可免費獲取所有資料
海量電子書,珍藏版