滴滴AI Lab面试题

求最长回文子串的长度

Leetcode: https://leetcode.com/problems/longest-palindromic-substring/
ACWing: https://www.acwing.com/problem/content/141/
解法:

  • 暴力搜索+判断 O(n^3) — 显然不好
  • O(n^2) 中心拓展,动态规划
  • O(n) kmp算法

比较容易想的是中心扩展的方法,思路是:依次枚举N个点为回文子串的中心,然后从中心往两边拓展,找到以当前点为中心能得到的最大回文子串长度,最后取最大。
这个思路还需要解决一个奇偶问题(eg: aba, abba),常见的策略是在每两个字符中间插入一个没出现过的字符,比如‘#’,这样就可以把左右情况都转换为奇数的情况,减小代码实现难度。这也是解决奇偶问题常见的技巧。
这题还有个常见的错误解法:把当前串reverse之后去两个串的最长公共子串。反例:abcdba, abdcba。原数组最大回文子串的长度应该为1,按照上述方法求出来结果是2.

#include 
#include 
#include 

using namespace std;

int get_longest_palindromic(string s){
    int len = s.size();
    s.resize(2 * len + 1); // 重置string的长度
    for (int i = 2 * len; i >= 0; i--){
        if (i % 2 == 0) s[i] = '#';
        else s[i] = s[i / 2];
    }
    len = s.size();
    int mv = 1;
    for (int i = 0; i < len; i++){
        int l = i - 1, r = i + 1;
        int cnt = 1;
        while(l >= 0 && r <= len){
            if (s[l--] == s[r++]) cnt += 2;
            else break;
        }
        mv = max(mv, cnt);
    }
    return mv >> 1; // 要除2
}

int main(){
    string s;
    string end = "END";
    int len = s.size();
    int num = 1;
    while(cin >> s && s.compare(end)){ 
        cout << "Case " << num++ << ": " << get_longest_palindromic(s) << endl;
    }
    return 0;
}

以上代码在测试样例很长时间,有超时风险。

常用代码小结

  1. 重置长度
将长度为N的序列每两个字符间插入‘#’,首尾也插入,查完之后长度为2 * N + 1.
** vector数组 **
for (int i = len - 1; i >= 0; i--){
            chars.insert(chars.begin() + i + 1, '#');  //一定要加1嗷~
        }
chars.insert(chars.begin(), '#');

** char[]数组 **  初始化一旦定义了长度不能再更改,因此需要在初始化时将大小开成 2*N + 1

** char* **  不好操作~~

** string **
s.resize(2 * len + 1); // 重置string的长度
for (int i = 2 * len; i >= 0; i--){
    if (i % 2 == 0) s[i] = '#';
    else s[i] = s[i / 2];
}
  1. 字符串比较
**比较两个字符串(string)是否相等不能用==, ==比较的是两个字符串地址是否相等**

string str1; 
string str2;
str1.compare(str2);  // 相等时返回0

char* str1;
char* str2;
int res = strcmp(str1, str2); //相等时返回0

/*
int strcmp(const char* s1,const char* s2)
当s1s2时,返回正数。
*/

折叠(旋转)数组中查找某个数

ACWing: https://www.acwing.com/problem/content/20/
解法:

  • 循环找到折叠(旋转)位置,然后二分查找指定数
    面试官提示:循环查找效率低,改成二分查找折叠(旋转位置),即二分查找数组中第一个最小的数(或者最后一个最大的数),剑指offer原题哎~~ (黑体部分是快速解题的关键,面试的时候是通过判断当前数比它后一个数大则返回,否者递归查找两边)
	int get_fold_point(vector nums){
        int len = nums.size();
        if (!len) return -1;
        // 删除末尾和头结点相同的数,以便二分
        int n = len - 1;
        while(n >= 0 && nums[n] == nums[0]) n--;
        if (nums[n] > nums[0]) return 0; //边界
        int l = 0, r = n;
        //这里不能直接调用half_check,对于边界的判断正好相反
        // 一个是二分找到最小值,一个是二分找到某个值,判断条件不一样
        while(l < r){
            int mid = (l + r) >> 1;
            if (nums[mid] < nums[0]) r = mid;
            else l = mid + 1;
        }
        return l;
    }
    
    int half_check(vectornums, int l, int r, int target){
        while(l < r){
            int mid = (l + r + 1) >> 1;
            if (nums[mid] > target) r = mid -1;
            else l = mid;
            // 效果相同
            // int mid = (l + r) >> 1;
            // if (nums[mid] >= target) r = mid;
            // else l = mid + 1;
        }
        return l;
    }
    
    int findMin(vector& nums) {
        int len = nums.size();
        int target = nums[len / 3];
        int index = get_fold_point(nums);
        // 二分查找target
        if (index == 0)  return half_check(nums, 0, len - 1, target) ;
        else if (target < nums[0])  return half_check(nums, index, len - 1, target);
        else return half_check(nums, 0, index - 1, target);
    }

以上代码有几个需要注意的点:

  • 二分查找对折点时,要保证后面一段是单调递增才能用二分,因此需要将末尾和收割元素相等的元素都删掉。
  • 删掉之后也有边界条件需要判断。边界条件就是删完之后末尾没有比第一个元素小的元素了,这时应该要返回数组中首个元素,否则会wa。
  • 关于二分查找模板(https://www.acwing.com/blog/content/31/), 计算mid要不要加1,是根据判断条件最终写出来的样子判断的,判断条件用if (a < b) 和if(a <= b)最终的效果是一样的,这一点验证过了。
  • 二分模板不能随便调用,要判断当前二分条件和单独的二分函数所假设的条件是否一样,若不一样,是不能直接调用的,如get_fold_point函数中的二分查找就和half_check中的判断条件不一样,因此不能直接调用half_check函数。

你可能感兴趣的:(实习面试)