Go猜想录
大道至简,悟者天成
真实面试案例分析(上)

摘要

  • 最长回文子串Longest Palindromic Substring
    1. 为什么不需要学 Manacher’s Algorithm
    2. 基于双指针的算法与背向双指针算法简介
    3. 有哪些Bad Coding Style容易踩,面试真实案例分析
    4. 基于动态规划的算法与区间型动态规划简介
  • 面试评分标准
  • 快速提高Coding Quality的十二个技巧

200 · 最长回文子串

求一个字符串中最长的回文子串,回文串定义为: aba, abba 这样的正反都一样的字符串

O(n^3) 的实现方法下比较好的 Coding Quality 是什么样的:

/**
 * @param s: input string
 * @return: a string as the longest palindromic substring
 */
// 暴力枚举
func LongestPalindrome(s string) string {
    // 最长向短遍历
	for length := len(s); length > 0; length-- {
		for start := 0; start+length <= len(s); start++ {
			if isPalindrome(s, start, start+length-1) {
				return s[start : start+length]
			}
		}
	}
	return ""
}

func isPalindrome(s string, left, right int) bool {
	for left < right && s[left] == s[right] {
		left++
		right--
	}
	return left >= right
}

基于中心点枚举法 Enumeration 的最佳实践

/**
 * @param s: input string
 * @return: a string as the longest palindromic substring
 */
func LongestPalindrome(s string) string {
    res := ""
	for i := 0; i < len(s); i++ {
		res = getPalindromeFrom(s, i, i, res)
		res = getPalindromeFrom(s, i, i+1, res)
	}

	return res
}

func getPalindromeFrom(s string, left, right int, res string) string {
	sub := ""
	for left >= 0 && right < len(s) && s[left] == s[right] {
		sub = s[left : right+1]
		left--
		right++
	}
	if len(res) < len(sub) {
		return sub
	}
	return res
}

基于动态规划 Dynamic Programming 的最佳实践

/**
 * @param s: input string
 * @return: a string as the longest palindromic substring
 */
// 动态规划
// 状态转移方程: dp[i][j] = (s[i] == s[j]) && ((j-i < 3) || dp[i+1][j-1])
func LongestPalindrome(s string) string {
	res, dp := "", make([][]bool, len(s))
	for i := 0; i < len(s); i++ {
		dp[i] = make([]bool, len(s))
	}
	for left := len(s) - 1; left >= 0; left-- {
		for right := left; right < len(s); right++ {
			dp[left][right] = (s[left] == s[right]) && ((right-left < 3) || dp[left+1][right-1])
			if dp[left][right] && (res == "" || right-left+1 > len(res)) {
				res = s[left : right+1]
			}
		}
	}
	return res
}

LPS 的面试考点

  • 面试不一定会要求你用最优复杂度的算法来解决问题
    • 因此单纯只刷LC之类的OJ,容易让你产生一定要用最优解来解决这样的误区
  • 代码真的不是写出来就可以过
    • 代码质量(Coding Quality)很重要
    • 好的代码质量包括:
      • Bug Free
      • 好的代码风格(Coding Style),包括变量名命名规范有意义,合理的使用空格,善用空行
      • 容易让人读懂的逻辑。要把复杂的事情用简单的方式,别把简单的事情写复杂了。
      • 没有冗余代码
      • 有边界检测和异常处理

LPS 的时间复杂度

  • Manacher’s Algorithm - O(n) // 学有余力可以阅读全文并背诵后缀数组
  • Suffix Array - O(n) // 完全不用学
  • 动态规划 Dynamic Programming - O(n^2) // 必须掌握
  • 枚举法 Enumeration - O(n^2) // 必须掌握

LPS 的评分体系

  • Strong Hire
  • 使用 O(n) 或者 O(nlogn) 的算法实现出来 (Manacher’s Algorithm or Suffix Array),并且代码质量合格,无 Bug 或者有很小的bug但是能自己发现并解决,无需太多提示
  • Hire
  • 能够分别使用枚举法和动态规划实现时间复杂度 O(n^2) 的算法。并且代码质量优秀,无Bug,无重复代码,无需面试官给提示
  • Weak Hire
  • 只使用了其中一种 O(n^2) 的算法实现出来,代码质量还不错,可以有一些小 Bug,面试官可以给一些小提示
  • No Hire
  • 只能想出一种 O(n^2) 的算法,但是 Bug 太多,或者需要很多提示
  • Strong No Hire
  • 连一种 O(n^2) 的算法都想不到

独孤九剑 —— 总决式

想要做到 Bug Free 最重要的是优化你的 Coding Quality
工程师的代码长什么样比脸长什么样重要

快速提高 Coding Quality 的十二个技巧

  • Coding Style 相关:
    • 二元运算符两边加空格,单元运算符不加空格
    • 花括号和 for, if 之间要加空格(Java),圆括号和 if 之间要加空格
    • 用空行分隔开不同的逻辑块
    • 逗号后面加空格
  • Readability 相关
    • 函数名和变量名用1-2个单词作为名称
    • 确保一个函数内部不超过 3 层缩进(indention)
    • 多用子函数来减少入口函数的代码量
    • 多用 continue 少用 if
  • Bug Free 相关
    • 不管有没有可能出问题,都要对入口函数的参数进行异常检测
    • 访问一个下标的时候,一定要确保这个下标不会越界
    • 访问一个对象的属性或者方法时,一定要确保这个对象不是空
    • 不用全局变量

知识共享许可协议

本作品采用知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议进行许可。