经典算法实现——字符串(一)

这篇文章主要介绍字符串相关的题目。

处理字符串操作相关问题时,常见的做法是从字符串尾部开始编辑,从后往前逆向操作。这么做的原因是因为字符串的尾部往往有足够空间,可以直接修改而不用担心覆盖字符串前面的数据。

摘自《程序员面试金典》

问题描述:

对于一个给定的源字符串和一个目标字符串,你应该输出在这个源字符串中匹配到的第一个索引。如果源字符串中不存在目标字符串,就返回-1.

例如:

源字符串为“source”和目标字符串为"target",就返回-1;

源字符串为“abcdabcdefg”和目标字符串为"bcd",就返回1;


题解

对于字符串查找问题,可使用双重 for 循环解决,效率更高的则为 KMP 算法。双重 for 循环的使用较有讲究,因为这里需要考虑目标字符串比源字符串短的可能。对目标字符串的循环肯定是必要的,所以可以优化的地方就在于如何访问源字符串了。简单直观的解法是利用源字符串的长度作为 for 循环的截止索引,这种方法需要处理源字符串中剩余长度不足以匹配目标字符串的情况,而更为高效的方案则为仅遍历源字符串中有可能和目标字符串匹配的部分索引。


Python

class Solution:
    def strStr(self, source, target):
        if source is None or target is None:
            return -1

        for i in range(len(source) - len(target) + 1):
            for j in range(len(target)):
                if source[i + j] != target[j]:
                    break
            else:  # no break
                return i
        return -1

C

int strStr(char* haystack, char* needle) {
    if (haystack == NULL || needle == NULL) return -1;

    const int len_h = strlen(haystack);
    const int len_n = strlen(needle);
    for (int i = 0; i < len_h - len_n + 1; i++) {
        int j = 0;
        for (; j < len_n; j++) {
            if (haystack[i+j] != needle[j]) {
                break;
            }
        }
        if (j == len_n) return i;
    }

    return -1;
}

C++

class Solution {
public:
    int strStr(string haystack, string needle) {
        if (haystack.empty() && needle.empty()) return 0;
        if (haystack.empty()) return -1;
        if (needle.empty()) return 0;
        // in case of overflow for negative
        if (haystack.size() < needle.size()) return -1;

        for (int i = 0; i < haystack.size() - needle.size() + 1; i++) {
            string::size_type j = 0;
            for (; j < needle.size(); j++) {
                if (haystack[i + j] != needle[j]) break;
            }
            if (j == needle.size()) return i;
        }
        return -1;
    }
};

Java

public class Solution {
    public int strStr(String haystack, String needle) {
        if (haystack == null && needle == null) return 0;
        if (haystack == null) return -1;
        if (needle == null) return 0;

        for (int i = 0; i < haystack.length() - needle.length() + 1; i++) {
            int j = 0;
            for (; j < needle.length(); j++) {
                if (haystack.charAt(i+j) != needle.charAt(j)) break;
            }
            if (j == needle.length()) return i;
        }

        return -1;
    }
}

源码分析

  1. 边界检查:haystack(source)needle(target)有可能是空串。
  2. 边界检查之下标溢出:注意变量i的循环判断条件,如果用的是i < source.length()则在后面的source.charAt(i + j)时有可能溢出。
  1. 代码风格:
  2. 运算符==两边应加空格
  • 变量名不要起s1``s2这类,要有意义,如target``source
  • Java 代码的大括号一般在同一行右边,C++ 代码的大括号一般另起一行
  • int i, j;`声明前有一行空格,是好的代码风格
  1. 是否在for的条件中声明i,j,这个视情况而定,如果需要在循环外再使用时,则须在外部初始化,否则没有这个必要。
  2. 需要注意的是有些题目要求并不是返回索引,而是返回字符串,此时还需要调用相应语言的substring方法。Python3 中用range替换了xrange,Python2 中使用xrange效率略高一些。 另外需要注意的是 Python 代码中的else接的是for 而不是if, 其含义为no break, 属于比较 Pythonic 的用法。

复杂度分析

双重 for 循环,时间复杂度最坏情况下为 O((nm)m)O((n-m)*m)O((nm)m).


题解1-hashmap 统计字频

判断两个字符串是否互为变位词,若区分大小写,考虑空白字符时,直接来理解可以认为两个字符串的拥有各不同字符的数量相同。对于比较字符数量的问题常用的方法为遍历两个字符串,统计其中各字符出现的频次,若不等则返回false. 有很多简单字符串类面试题都是此题的变形题。

Python

class Solution:
    """
    @param s: The first string
    @param b: The second string
    @return true or false
    """
    def anagram(self, s, t):
        return collections.Counter(s) == collections.Counter(t)

C++
class Solution {
public:
    /**
     * @param s: The first string
     * @param b: The second string
     * @return true or false
     */
    bool anagram(string s, string t) {
        if (s.empty() || t.empty()) {
            return false;
        }
        if (s.size() != t.size()) {
            return false;
        }

        int letterCount[256] = {0};

        for (int i = 0; i != s.size(); ++i) {
            ++letterCount[s[i]];
            --letterCount[t[i]];
        }
        for (int i = 0; i != t.size(); ++i) {
            if (letterCount[t[i]] != 0) {
                return false;
            }
        }

        return true;
    }
};

源码分析
  1. 两个字符串长度不等时必不可能为变位词(需要注意题目条件灵活处理)。
  2. 初始化含有256个字符的计数器数组。
  3. 对字符串 s 自增,字符串 t 递减,再次遍历判断letterCount数组的值,小于0时返回false.
在字符串长度较长(大于所有可能的字符数)时,还可对第二个for循环做进一步优化,即t.size() > 256时,使用256替代t.size(), 使用i替代t[i].
复杂度分析

两次遍历字符串,时间复杂度最坏情况下为 O(n)O(n)O(n) , 使用了额外的数组,空间复杂度 O(1)O(1)O(1) .

题解2-排序字符串
另一直接的解法是对字符串先排序,若排序后的字符串内容相同,则其互为变位词。题解1中使用 hashmap 的方法对于比较两个字符串是否互为变位词十分有效,但是在比较多个字符串时,使用 hashmap 的方法复杂度则较高。

Python
class Solution:
    """
    @param s: The first string
    @param b: The second string
    @return true or false
    """
    def anagram(self, s, t):
        return sorted(s) == sorted(t)

C++
class Solution {
public:
    /**
     * @param s: The first string
     * @param b: The second string
     * @return true or false
     */
    bool anagram(string s, string t) {
        if (s.empty() || t.empty()) {
            return false;
        }
        if (s.size() != t.size()) {
            return false;
        }

        sort(s.begin(), s.end());
        sort(t.begin(), t.end());

        if (s == t) {
            return true;
        } else {
            return false;
        }
    }
};
源码分析

对字符串 s 和 t 分别排序,而后比较是否含相同内容。对字符串排序时可以采用先统计字频再组装成排序后的字符串,效率更高一点。
复杂度
C++的 STL 中 sort 的时间复杂度介于
O(n)O(n)O(n) O(n2)O(n^2)O(n2) 之间,判断 s == t 时间复杂度最坏为 O(n)O(n)O(n) .

你可能感兴趣的:(C++,Python,剑指offer,剑指offer)