EPI-String

[TOC]

很久很久以前,有人刷题不做记录,从Array刷到String,刷了几年还是在刷String。

这里记录一下做Elements of Programming Interviews的题目,避免四年之后还是在刷String。

String

String 的基本语法

//
.append("");
.push_back('');
.pop_back();
.insert(begin() + shift, "");
.substr(pos, len);
.compare("");

//

Interconvert strings and integers

虽然这题的意思是要手动转换。。。但是有现成的封装📦为啥不用呢

string IntToString(int x) {
  // TODO - you fill in here.
  return std::to_string(x);
}
int StringToInt(const string& s) {
  // TODO - you fill in here.
  return std::stoi(s);
}

Base conversion

进制间的相互转换,原理上倒是挺简单,写的时候犯浑了,将取余后的结果和字符进行比较。。。

string ConvertBase(const string& num_as_string, int b1, int b2) {
  // TODO - you fill in here.
  int start_idx = num_as_string[0] == '-' ? 1 : 0;
  bool neg = num_as_string[0] == '-';
  int num = 0, len = num_as_string.size() - 1;
  // 转为10进制
  for (; start_idx < num_as_string.size(); ++start_idx) {
      char ch = num_as_string[start_idx];
      int c_val;
      if (ch >= '0' && ch <= '9') {
          c_val = ch - '0';
      }
      else {
          c_val = ch - 'A' + 10;
      }
      num *= b1;
      num += c_val;
  }
  if (num == 0) {
      return "0";
  }
//  string ret = std::to_string(num) + ";";
  //转为b2进制
  string ret;
  while (num) {
      int mod = num % b2;
      if (0 <= mod && mod <= 9) {
          ret.push_back('0'+mod);
      }
      else {
          ret.push_back('A' + mod - 10);
      }
      num /= b2;
  }
  if (neg) {
      ret.push_back('-');
  }
  return {ret.rbegin(), ret.rend()};
}

Compute the spreadsheet column encoding

将表格的列名‘AABEZ’转换为整型数值。等价于进制转换。

int SSDecodeColID(const string& col) {
  // TODO - you fill in here.
  int id = 0;
  for (auto &code : col) {
       id *= 26;
       id += code - 'A' + 1;
  }
  return id;
}

Replace and remove

将字符串中的‘b’删除,将‘a'换成’dd‘。

题目保证给出s的大小满足要求,为了不开辟新空间,第一遍正向将’b‘删除,同时统计’a‘的个数,第二部反向将元素向后移动。

int ReplaceAndRemove(int size, char s[]) {
  // TODO - you fill in here.
  int a_cnt = 0;
  int write_idx = 0, last_idx = 0;
  while(last_idx < size) {
      if (s[last_idx] == 'b') {
          ++last_idx;
          continue;
      }
      if (s[last_idx] == 'a') {
          ++a_cnt;
      }
      s[write_idx++] = s[last_idx++];
  }

  int back_write_idx = write_idx + a_cnt-1, back_idx = write_idx - 1;
  int ret_cnt = back_write_idx+1;
  while (back_write_idx >= 0) {
      if (s[back_idx] == 'a') {
          s[back_write_idx--] = 'd';
          s[back_write_idx--] = 'd';
          --back_idx;
      }
      else {
          s[back_write_idx--] = s[back_idx--];
      }
  }
  return ret_cnt;
}

Test palindromicity

palindromic string:移除非字母数字的字符,不考虑大小写,剩下的左右对称。

bool IsPalindrome(const string& s) {
  // TODO - you fill in here.
  int l_idx = 0, r_idx = s.size() - 1;
  while (l_idx < r_idx) {
      while(l_idx < r_idx && !std::isalnum(s[l_idx])) {
          ++l_idx;
      }
      while (r_idx > l_idx && !std::isalnum(s[r_idx])) {
          --r_idx;
      }
      if (std::tolower(s[l_idx++]) != std::tolower(s[r_idx--])) {
          return false;
      }
  }
  return true;
}

Reverse all the words in a sentence

将句子中的单词逆序排列,这里是将每个单词提取出来,逆序后填回原字符串, 空间复杂度为O(n)。

EPI给出了一个空间复杂度O(1)的解法:先将字符串翻转,再对每个单词进行翻转。

//EPI solution
void ReverseWords(string* s) {
    std::reverse(s->begin(), s->end());
    size_t start = 0, end;
    while ((end = s->find(' ', start)) != string::npos) {
        std::reverse(s->begin()+start, s->begin()+end);
        start = end + 1;
    }
    std::reverse(s->begin() + start, s->end());
    return;
}

void ReverseWords(string* s) {
  // TODO - you fill in here.
  string &ss = *s;
  int s_len = (*s).size();
  std::vector<string> words;
  int word_st_idx = 0, word_end_idx = 0;
  while (word_end_idx < s_len) {
      // find next word
      while (word_end_idx < s_len && ss[word_end_idx] != ' ') {
          ++word_end_idx;
      }
      words.push_back(ss.substr(word_st_idx, word_end_idx-word_st_idx));
      // deal with space
      if (word_end_idx < s_len && ss[word_end_idx] == ' ') {
          words.push_back(" ");
      }
      // move idx to next word
      ++word_end_idx;
      word_st_idx = word_end_idx;
  }
  // fill words reversely
  int s_idx = 0;
  for (int w_idx = words.size() - 1; w_idx >= 0; --w_idx) {
      for (int idx_in_word = 0; idx_in_word < words[w_idx].size(); ++idx_in_word) {
          ss[s_idx++] = words[w_idx][idx_in_word];
      }
  }
  return;
}

The look-and-say problem

look-and-say序列:<1, 11, 21, 1211,...>

对前一个序列值,读出其有x个y值,写作xy。

模拟即可。

std::to_string 的参数类型均为数值类型

string LookAndSay(int n) {
  // TODO - you fill in here.
  string start_str = "1";
  for (int i = 1; i < n; ++i) {
      int iter_idx = 0;
      string c_str;
      while (iter_idx < start_str.size()) {
          char c_val = start_str[iter_idx];
          int cnt = 1;
          while (iter_idx < start_str.size() - 1 && start_str[iter_idx] == start_str[iter_idx+1]) {
              ++iter_idx;
              ++cnt;
          }
          ++iter_idx;
          c_str += std::to_string(cnt) + std::to_string(c_val-'0');
      }
      start_str = c_str;
  }
  return start_str;
}

Convert from Roman to decimal

将罗马数字转换为10进制,难度可能在理解规则上。

const std::map<char, int> mp = {
        {'I', 1},
        {'V', 5},
        {'X', 10},
        {'L', 50},
        {'C', 100},
        {'D', 500},
        {'M',1000}
};
int RomanToInteger(const string& s) {
  // TODO - you fill in here.
  int cnt = 0;
  for (int i = 0; i < s.size(); ++i) {
      char ch = s[i];
      if (i > 0) {
            // 处理exception
          if ((ch == 'V' || ch == 'X') && s[i-1] == 'I') {
              cnt -= 1 * 2;
          }
          if ((ch == 'L' || ch == 'C') && s[i-1] == 'X') {
              cnt -= 10 * 2;
          }
          if ((ch == 'D' || ch == 'M') && s[i-1] == 'C') {
              cnt -= 100 * 2;
          }
      }
      cnt += mp.at(ch);
  }
  return cnt;
}

Compute all valid IP addresses

计算一个无分隔符的ip字符串的所有可能形式。

遍历3个分隔符的位置,判断每个区间的字串是否合法。 需要处理边界情况,如整个串都是不合法的。

// 判断ip区间十分合规
inline bool isValid(const string& s){
    if (s.size() > 3){
        return false;
    }
    if (s.size() > 1 && s[0] == '0') {
        return false;
    }
    int val = std::stoi(s);
    return val <= 255;
}

vector<string> GetValidIpAddress(const string& s) {
  // TODO - you fill in here.
  if (s.size() < 4) {
      return {};
  }
  vector<string> ret;
  // 遍历3个点的位置
  for (int first_dot = 0; first_dot < s.size()-3; ++first_dot) {
      string f_part = s.substr(0, first_dot+1);
      if (!isValid(f_part)) continue;
      for (int sec_dot = first_dot+1; sec_dot < s.size() - 2; ++sec_dot) {
          string s_part = s.substr(first_dot+1, sec_dot-first_dot);
          if (!isValid(s_part)) continue;
          for (int th_dot = sec_dot + 1; th_dot < s.size() - 1; ++th_dot) {
              string t_part = s.substr(sec_dot+1, th_dot-sec_dot);
              if (!isValid(t_part)) continue;
              string fo_part = s.substr(th_dot+1, s.size() - th_dot);
              if (!isValid(fo_part)) continue;

              ret.emplace_back(f_part + "." + s_part + "." + t_part + "." + fo_part);
          }
      }
  }
  return ret;
}

Write a string sinusoidally

输出字符串的蛇皮走位。 将字符串展示成sin函数状,然后按行输出。

模拟的做法是申请3个数组,依次往数组内填充元素。这样的做法太浪费空间,sin函数形状也是有规律的,直接按照下标拼接结果即可。

string SnakeString(const string& s) {
  // TODO - you fill in here.
  string ret;
  for (int i = 1; i < s.size(); i+=4) {
      ret.push_back(s[i]);
  }
  for (int i = 0; i < s.size(); i+=2) {
      ret.push_back(s[i]);
  }
  for (int i = 3; i < s.size(); i+=4) {
      ret.push_back(s[i]);
  }
  return ret;
}

Implement run-length encoding

RLE编码在kaggle比赛中也见过,即将bbbccd表示为3b2c1d。类似与之前的lookup-and-say。

有个需要注意的地方就是计数会是大于1位数的,在编码和解码的时候都需要注意这个。

string Decoding(const string &s) {
  // TODO - you fill in here.
  string ret;
  for (size_t i = 0; i < s.size();) {
      size_t ed = i;
      // ed 为字符位置
      while (std::isdigit(s[ed])) {
          ++ed;
      }
      int cnt = std::stoi(s.substr(i, ed-i));
      char ch = s[ed];
      while(cnt--) {
          ret.push_back(ch);
      }
      i = ed+1;
  }
  return ret;
}
string Encoding(const string &s) {
  // TODO - you fill in here.
  string ret;
  size_t start = 0;
  while (start < s.size()) {
      int cnt = 1;
      char ch = s[start];
      while (start < s.size() - 1 && s[start] == s[start+1]) {
          ++cnt;
          ++start;
      }
      ret += std::to_string(cnt);
      ret.push_back(ch);
      ++start;
  }
  return ret;
}

Find the first occurrence of a substring

子串查找,上KMP。可惜忘光了。。。

有3种线性时间的搜索算法:KMP、BM、RK。其中RK是最简单,最容易实现的。

RK算法基于滚动的hash,当s与t的子串hash值相同时,他们有可能是匹配的,进一步验证其是否真正匹配。滚动的hash匹配避免了匹配失败时的多次回溯。

int RabinKarp(const string &t, const string &s) {
  // TODO - you fill in here.
  if (t.size() < s.size()) {
      return -1;
  }
  const int kBase = 26;
  int t_hash = 0, s_hash = 0;
  int power_s = 1; // kBase ^|s|
  for (int i = 0; i < s.size(); ++i) {
      power_s = i ? power_s * kBase : 1;
      t_hash = t_hash * kBase + t[i] - '0';
      s_hash = s_hash * kBase + s[i] - '0';
  }

  for (int i = s.size(); i < t.size(); ++i) {
      // 匹配成功
      if (t_hash == s_hash && t.compare(i - s.size(), s.size(), s) == 0) {
          return i - s.size();
      }

      // 滚动更新hash
      t_hash -= (t[i-s.size()] - '0') * power_s;
      t_hash = t_hash * kBase + t[i] - '0';
  }
    if (t_hash == s_hash && t.compare(t.size() - s.size(), s.size(), s) == 0) {
        return t.size() - s.size();
    }
  return -1;
}

Comments !