Datawhale組隊學習 Task05:字符串(2天)

Task05:字符串(2天)

我們古人沒有電影電視,沒有遊戲網絡,所以文人們就會想出一些文字遊戲來娛樂。比如宋代的李禺寫了這樣一首詩:“枯眼望遙山隔水,往來曾見幾心知?壺空怕酌一杯酒,筆下難成和韻詩。途路阻人離別久,訊音無雁寄回遲。孤燈守夜長寥寂,夫憶妻兮父憶兒。”顯然這是老公想念老婆和兒子的詩句。曾經和妻兒在一起,享受天倫之樂,現在一個人長久沒有回家,也不見書信返回,望着油燈想念親人,能不傷感嗎?

可仔細一讀發現,這首詩竟然可以倒過來讀:“兒憶父兮妻憶夫,寂寥長夜守燈孤。遲迴寄雁無音訊,久別離人阻路途。詩韻和成難下筆,酒杯一酌怕空壺。知心幾見曾來往,水隔山遙望眼枯。”這表達了妻子對丈夫的思念。老公離開好久,路途遙遠,難以相見。寫信不知道寫什麼,獨自喝酒也沒什麼興致。只能和兒子夜夜守在家裏一盞孤燈下,苦等老公的歸來。

這種詩叫做迴文詩。它是一種可以倒讀或反覆迴旋閱讀的詩體。剛纔這首就是正讀是丈夫思念妻子,倒讀是妻子思念丈夫的古詩。是不是感覺很奇妙呢?
在英文單詞中,同樣有神奇的地方。“即使是 lover 也有個 over,即使是 friend 也有個 end,即使是 believe 也有個 lie。”你會發現,本來不相干,甚至對立的兩個詞,卻有某種神奇的聯繫。這可能是創造這幾個單詞的智者們也沒有想到的問題。

今天我們就要來談談這些單詞或句子組成字符串的相關問題。

1. 串的定義與操作

1.1 串的相關定義

  • 串(string)是由零個或多個字符組成的有限序列,又名字符串,記爲S=”a0a1...an”
  • 串中包含字符的個數稱爲串的長度。
  • 長度爲零的串稱爲空串(null string)。直接用雙引號””表示,在C#中也可用string.Empty來表示。

還有一些概念需要解釋:

  • 空白串:由一個或多個空格組成的串。
  • 子串與主串:串中任意連續字符組成的子序列,稱爲該串的子串。相應的包含子串的串稱爲主串,即子串是主串的一部分。
  • 子串在主串中的位置:子串在主串中第一次出現時,子串第一個字符在主串中的序號。
例如:

A=“this is a string”; 
B=“is”;

B在A中的位置爲2。
  • 串相等:長度相等且對應位字符相同。

1.2 串的操作

串的邏輯結構和線性表很相似,不同之處在於串針對的是字符集,也就是串中的元素都是字符。因此,對於串的基本操作與線性表是有很大差別的。線性表更關注的是單個元素的操作,比如查找一個元素,插入或刪除一個元素,但串中更多的是關注它子串的應用問題,如查找子串位置,得到指定位置子串、替換子串等操作。

關於串的基本操作如下:

  • (1)獲取串的長度
  • (2)獲取或設置指定索引處的字符
  • (3)在指定位置插入子串
  • (4)在指定位置移除給定長度的子串
  • (5)在指定位置取子串
  • (6)當前串的拷貝
  • (7)串連接
  • (8)串的匹配

比如C#中,字符串操作還有ToLower轉小寫、ToUpper轉大寫、IndexOf從左查找子串位置、LastIndexOf從右查找子串位置、Trim去除兩邊空格等比較方便的操作,它們其實就是前面這些基本操作的擴展函數。

串接口

2. 串的存儲與實現

串的存儲結構與線性表相同,分爲兩種:

  • 順序存儲:char類型的數組。由於數組是定長的,就存在一個預定義的最大串長度,它規定在串值後面加一個不計入串長度的結束符,比如’\0’來表示串值的終結。
  • 鏈式存儲:SlinkList<char> (浪費存儲空間)

順序串類圖

using System;

namespace LinearStruct
{
    /// <summary>
    /// 串抽象數據類型的實現--順序串
    /// </summary>
    public class SeqString : IString
    {
        /// <summary>
        /// 
        /// </summary>
        protected readonly char[] CStr; //字符串以'\0'結束

        /// <summary>
        /// 初始化SeqString類的新實例
        /// </summary>
        public SeqString()
        {
            CStr = new char[] {'\0'};
        }

        /// <summary>
        /// 初始化SeqString類的新實例
        /// </summary>
        /// <param name="s">初始字符串</param>
        public SeqString(string s)
        {
            if (s == null)
                throw new ArgumentNullException();

            int length = s.Length;
            CStr = new char[length + 1];

            for (int i = 0; i < length; i++)
                CStr[i] = s[i];
            CStr[length] = '\0';
        }

        /// <summary>
        /// 初始化SeqString類的新實例(只能在類的內部使用)
        /// </summary>
        /// <param name="length">串的長度</param>
        protected SeqString(int length)
        {
            if (length < 0)
                throw new ArgumentOutOfRangeException();

            CStr = new char[length + 1];
            CStr[length] = '\0';
        }

        /// <summary>
        /// 右對齊此實例中的字符,在左邊用指定的 Unicode 字符填充以達到指定的總長度。
        /// </summary>
        /// <param name="totalWidth">結果字符串中的字符數,等於原始字符數加上任何其他填充字符。</param>
        /// <param name="paddingChar">Unicode 填充字符。</param>
        /// <returns>
        /// 等效於此實例的一個新 IString,但它是右對齊的,並在左邊用達到 totalWidth 長度所需數目的 paddingChar 字符進行填充。
        /// 如果totalWidth 小於此實例的長度,則爲與此實例相同的新 IString。
        /// </returns>
        /// <remarks>
        /// 異常:
        /// System.ArgumentOutOfRangeException:totalWidth 小於零。
        /// </remarks>
        public IString PadLeft(int totalWidth, char paddingChar)
        {
            if (totalWidth < 0)
                throw new ArgumentOutOfRangeException();
            if (Length >= totalWidth)
                return Clone();

            SeqString result = new SeqString(totalWidth);
            int left = totalWidth - Length;
            for (int i = 0; i < left; i++)
                result.CStr[i] = paddingChar;
            for (int i = 0; i < Length; i++)
                result.CStr[i + left] = CStr[i];

            return result;
        }

        /// <summary>
        /// 獲取串的長度
        /// </summary>
        public int Length
        {
            get
            {
                int i = 0;
                while (CStr[i] != '\0')
                    i++;
                return i;
            }
        }

        /// <summary>
        /// 獲取或設置指定索引處的字符
        /// </summary>
        /// <param name="index">要獲取或設置的字符從零開始的索引</param>
        /// <returns>指定索引處的字符</returns>
        public char this[int index]
        {
            get
            {
                if (index < 0 || index > Length - 1)
                    throw new IndexOutOfRangeException();
                return CStr[index];
            }
            set
            {
                if (index < 0 || index > Length - 1)
                    throw new IndexOutOfRangeException();
                CStr[index] = value;
            }
        }

        /// <summary>
        /// 在指定位置插入子串
        /// </summary>
        /// <param name="startIndex">插入的位置</param>
        /// <param name="s">插入的子串</param>
        /// <returns>插入串後得到的新串</returns>
        public IString Insert(int startIndex, IString s)
        {
            if (s == null)
                throw new ArgumentNullException();

            if (startIndex < 0 || startIndex > Length)
                throw new ArgumentOutOfRangeException();

            SeqString str = new SeqString(s.Length + Length);
            for (int i = 0; i < startIndex; i++)
                str.CStr[i] = CStr[i]; //注意str[i]直接使用是錯誤的
            for (int i = 0, len = s.Length; i < len; i++)
                str.CStr[i + startIndex] = s[i];
            for (int i = startIndex; i < Length; i++)
                str.CStr[i + s.Length] = CStr[i];

            return str;
        }

        /// <summary>
        /// 在指定位置移除子串
        /// </summary>
        /// <param name="startIndex">移除的位置</param>
        /// <param name="count">移除的長度</param>
        /// <returns>移除後得到的新串</returns>
        public IString Remove(int startIndex, int count)
        {
            if (startIndex < 0 || startIndex > Length - 1)
                throw new ArgumentOutOfRangeException();
            if (count < 0)
                throw new ArgumentOutOfRangeException();

            int left = Length - startIndex; //最多移除字符個數
            count = (left < count) ? left : count; //實際移除字符個數
            SeqString str = new SeqString(Length - count);
            for (int i = 0; i < startIndex; i++)
                str.CStr[i] = CStr[i];
            for (int i = startIndex + count; i < Length; i++)
                str.CStr[i - count] = CStr[i];
            return str;
        }

        /// <summary>
        /// 在指定位置取子串
        /// </summary>
        /// <param name="startIndex">取子串的位置</param>
        /// <param name="count">子串的長度</param>
        /// <returns>取得的子串</returns>
        public IString SubString(int startIndex, int count)
        {
            if (startIndex < 0 || startIndex > Length - 1)
                throw new ArgumentOutOfRangeException();
            if (count < 0)
                throw new ArgumentOutOfRangeException();
            int left = Length - startIndex; //取子串最大長度
            count = (left < count) ? left : count; //子串實際長度
            SeqString str = new SeqString(count);
            for (int i = 0; i < count; i++)
                str.CStr[i] = CStr[i + startIndex];
            return str;
        }

        /// <summary>
        /// 當前串的拷貝
        /// </summary>
        /// <returns>當前串的拷貝</returns>
        public IString Clone()
        {
            SeqString str = new SeqString(Length);
            for (int i = 0; i < Length; i++)
                str.CStr[i] = CStr[i];
            return str;
        }

        /// <summary>
        /// 串連接
        /// </summary>
        /// <param name="s">在尾部要連接的串</param>
        /// <returns>連接後得到的新串</returns>
        public IString Concat(IString s)
        {
            if (s == null)
                throw new ArgumentNullException();
            return Insert(Length, s);
        }

        /// <summary>
        /// 串的匹配
        /// </summary>
        /// <param name="s">要匹配的子串</param>
        /// <returns>子串在主串中的位置,不存在返回-1.</returns>
        public int FindParam(IString s)
        {
            if (s == null || s.Length == 0)
                throw new Exception("匹配字符串爲null或空.");
            for (int i = 0; i <= Length - s.Length; i++)
            {
                if (CStr[i] == s[0])
                {
                    int j;
                    for (j = 1; j < s.Length; j++)
                    {
                        if (CStr[j + i] != s[j])
                            break;
                    }
                    if (j == s.Length)
                        return i;
                }
            }
            return -1;
        }

        /// <summary>
        /// 串連接運算符的重載
        /// </summary>
        /// <param name="s1">第一個串</param>
        /// <param name="s2">第二個串</param>
        /// <returns>連接後得到的新串</returns>
        public static SeqString operator +(SeqString s1, SeqString s2)
        {
            if (s1 == null || s2 == null)
                throw new ArgumentNullException();
            return s1.Concat(s2) as SeqString;
        }

        /// <summary>
        /// SeqString類的輸出字符串
        /// </summary>
        /// <returns>SeqString類的輸出字符串</returns>
        public override string ToString()
        {
            string str = string.Empty;
            for (int i = 0; i < Length; i++)
                str += CStr[i];
            return str;
        }

        /// <summary>
        /// 串的匹配
        /// </summary>
        /// <param name="value">要匹配的子串</param>
        /// <returns>子串在主串中的位置,不存在返回-1.</returns>
        public int IndexOf(IString value)
        {
            if (value == null || value.Length == 0)
                throw new Exception("匹配字符串爲null或空.");
            return IndexOf(value, 0);
        }

        /// <summary>
        /// 串的匹配
        /// </summary>
        /// <param name="value">要匹配的子串</param>
        /// <param name="startIndex">匹配的起始位置</param>
        /// <returns>子串在主串中的位置,不存在返回-1.</returns>
        public int IndexOf(IString value, int startIndex)
        {
            if (value == null || value.Length == 0)
                throw new Exception("匹配字符串爲null或空.");
            if (startIndex < 0 || startIndex > value.Length - 1)
                throw new ArgumentOutOfRangeException();

            for (int i = startIndex; i <= Length - value.Length; i++)
            {
                if (CStr[i] == value[0])
                {
                    int j;
                    for (j = 1; j < value.Length; j++)
                    {
                        if (CStr[j + i] != value[j])
                            break;
                    }
                    if (j == value.Length)
                        return i;
                }
            }
            return -1;

        }

        /// <summary>
        /// 將此實例中的指定 IString 的所有匹配項替換爲其他指定的 IString。
        /// </summary>
        /// <param name="oldValue">要替換的 IString。</param>
        /// <param name="newValue">要替換 oldValue 的所有匹配項的 IString。</param>
        /// <returns>等效於此實例,但將 oldValue 的所有實例都替換爲 newValue 的 IString。</returns>
        /// <remarks>
        /// 異常:
        /// System.ArgumentException:oldValue 是空字符串 ("")。
        /// </remarks>
        public IString Replace(IString oldValue, IString newValue)
        {
            if (Length == 0)
                throw new ArgumentException("oldValue是空字符串。");

            string str = string.Empty;
            int i = 0;
            while (i < Length)
            {
                if (CStr[i] == oldValue[0])
                {
                    int j;
                    for (j = 1; j < oldValue.Length; j++)
                    {
                        if (CStr[i + j] != oldValue[j])
                        {
                            break;
                        }
                    }
                    if (j == oldValue.Length)
                    {
                        str += newValue;
                        i += oldValue.Length;
                        continue;
                    }
                }
                str += CStr[i];
                i++;
            }
            return new SeqString(str);
        }

        /// <summary>
        /// 移除所有前導空白字符和尾部空白字符。
        /// </summary>
        /// <returns>從當前對象的開始和末尾移除所有空白字符後保留的字符串。</returns>
        public IString Trim()
        {
            //返回移除所有前導空白字符和尾部空白字符後保留的字符串.
            int left;
            int right;
            for (left = 0; left < CStr.Length - 1; left++)
            {
                if (CStr[left] != ' ')
                    break;
            }
            if (left == CStr.Length - 1)
                return new SeqString();

            for (right = CStr.Length - 2; right >= 0; right--)
            {
                if (CStr[right] != ' ')
                    break;
            }
            return SubString(left, right - left + 1);
        }
    }
}

3. 練習參考答案

1. 無重複字符的最長子串

以下爲python的實現

class Solution:
    """
    這道題主要用到思路是:滑動窗口
    什麼是滑動窗口?
    其實就是一個隊列,比如例題中的 abcabcbb,進入這個隊列(窗口)爲 abc 滿足題目要求,
    當再進入 a,隊列變成了 abca,這時候不滿足要求。所以,我們要移動這個隊列!
    如何移動?
    我們只要把隊列的左邊的元素移出就行了,直到滿足題目要求!
    一直維持這樣的隊列,找出隊列出現最長的長度時候,求出解!
    """
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s:
            return 0
        left = 0 # 窗口的左側
        lookup = set()  # 窗口裏的字符,初始爲空,因爲左右都爲空
        n = len(s)  # 字符串總長度
        max_len = 0  # 無重複最長子串的長度
        cur_len = 0  # 當前窗口的長度
        for i in range(n):  # 窗口右側移動
            cur_len += 1  # 移動一次,窗口長度加一
            # 條件判定開始
            while s[i] in lookup:  # s[i]表示本次移動時進入窗口的值
                # 如果右側新加入的字符在窗口裏面有相同的值,則將窗口左側向右邊移動,以下是該動作需要完成的幾處更新
                lookup.remove(s[left])  # 移除窗口裏的值
                left += 1  # 左側下標加一
                cur_len -= 1  # 窗口長度減一
            # 條件判定結束
            if cur_len > max_len:  # 如果當前窗口長度大於記錄的最大窗口長度,則更新該最大窗口長度
                max_len = cur_len
            lookup.add(s[i])  # 在條件判定完成之後,繼續窗口右側移動的更新操作
        return max_len


if __name__ == '__main__':
    solution = Solution()
    max_length = solution.lengthOfLongestSubstring("abcddsd")
    print(max_length)

2. 串聯所有單詞的子串

以下爲python的實現

class Solution:
    def findSubstring(self, s, words):
        from collections import Counter
        if not s or not words:
            return []
        one_word = len(words[0])
        all_len = len(words) * one_word  # words裏面所有字符組成的字符串長度
        n = len(s)
        words = Counter(words)
        res = []
        for i in range(0, n - all_len + 1):  # 這裏是左側窗口,左側窗口有個限制
            tmp = s[i:i + all_len]  # 窗口所包含的字符串 i+all_len就是窗口右側
            # 條件判定
            c_tmp = []
            for j in range(0, all_len, one_word): 
                c_tmp.append(tmp[j:j + one_word])
            if Counter(c_tmp) == words:
                # 判定通過則加一個下標
                res.append(i)
        return res


if __name__ == '__main__':
    solution = Solution()
    s = "barfoothefoobarman"
    words = ["foo", "bar"]
    out = solution.findSubstring(s, words)
    print(out)

3. 替換子串得到平衡字符串

以下爲python的實現

class Solution(object):
    def balancedString(self, s):
        """
        python版本
        :type s: str
        :rtype: int
        """
        cnt = collections.Counter(s)
        res = n = len(s)
        i, avg = 0, n//4
        for j, c in enumerate(s):
            cnt[c] -= 1
            while i < n and all(avg >= cnt[x] for x in 'QWER'):
                res = min(res, j - i + 1)
                cnt[s[i]] += 1
                i += 1
        return res
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章