Google的面试向来以算法为重,尤其是近几年,算法难度更是大大的上了一个台阶。
近几年,在面试题中,很多时候都会考到一些数据处理相关的问题,比如区间查询或是区间染色问题。
一般来说,解决这类问题有一个非常高效有用的方法——线段树。
本来线段树这个知识点事一些信息学竞赛考题的常客,但是这几年来,一些面试里也出现了它的身影,慢慢地吓退了一批又一批的面试者。
线段树是什么
线段树(segment tree)是用来存放给定区间(segment, or interval)内对应信息的一种数据结构。
线段树主要用于高效解决连续区间的动态查询问题,由于二叉结构的特性,它基本能保持每个操作的复杂度为O(logn)。
从数据结构的角度来说,线段树是一种典型的二叉树形结构。它将线段区间组织成树形的结构,并用每个节点来表示一条线段。
线段树和树状数组的区别
在讨论线段树的时候,很多人同时会提到另一个类似的结构——树状数组。
树状数组是一种用于高效处理对一个存储数字的列表进行更新及求前缀和的数据结构。
树状数组和线段树这两个东西,看起来挺像的,因为它们的名字里都带“树”。但其实,这两者之间,还是有一些地方是不同的。
线段树和树状数组的基本功能都是在某一满足结合律的操作(比如加法,乘法,最大值,最小值)下,O(logn)的时间复杂度内修改单个元素并且维护区间信息。
但是两者最大的区别是,树状数组只能维护前缀“操作和”(前缀和,前缀积,前缀最大最小),而线段树可以维护区间操作和。
有懂算法的大佬说过,能用树状数组解决的问题,用线段树都能解决,但是解决的方法和难度都是完全不一样的。
接下来我们一起学习一道难度较大的Google的面试真题:最小合法括号序列关注问题。
题目描述
给出一个非空偶数长度的字符串s
,该字符串包含"("、")"和"?"。对于每一个"?",我们可以使用"("或者")"来代替,但每一次替代都会付出相应的代价。替换成"("的代价数组为a
,其含义为第i
个"?"替换成"("时需要付出的代价为a[i]
。同理,替换为")"的代价数组为b
,其含义为第i
个"?"替换成")"时需要付出的代价为b[i]
。 现在要求替换所有的"?",使得替换后的括号序列是合法的,输出所需要付出的最小代价为多少?如果不能得到一个合法括号序列,则输出-1
。 一个合法的括号序列要求对于每一个"(",其右边均有")"能与其匹配。
- 字符串长度不超过1e6
- 1 <= a[i] <= 1e31<=a[i]<=1e3
- 1 <= b[i] <= 1e31<=b[i]<=1e3
样例
样例1
Input: s = "(??)", a = [1,2], b = [2,8]
Output: 4
Explanation:
Obviously there are two alternatives:
After the replacement, the sequence is "(())" or "()()", and the corresponding cost is 9, 4
Therefore, the minimum cost is 4
样例 2
Input: s = "?()?", a = [1,2], b = [2,8]
Output: 9
Explanation:
There is only one solution, which is "(())" after replacement
The cost is: 1 + 8 = 9
参考答案
考虑将所有的"?"代替为")",此时的代价为∑b[i]随后遍历一遍,如果当前")"的数目已经超过"("的数目,则从前面所有出现过的"?"中选择一个替换代价最小的,将其变为"(",第i个"?"的替换代价为:a[i] - b[i]。该过程可以使用优先队列或者线段树进行维护。 假设字符串长度为nn,则时间复杂度O(nlogn)
/**
* This reference program is provided by @jiuzhang.com
* Copyright is reserved. Please indicate the source for forwarding
*/
public class Solution {
/**
* @param s: The string s
* @param a: The cost array a
* @param b: the cost array b
* @return: Return the minimum cost
*/
public int getAnswer(char[] s, int[] a, int[] b) {
// Write your code here
int n = s.length;
int pos[] = new int[n + 1];
Queue<Integer> q = new PriorityQueue<>();
int ans = 0, tot = 0;
// Replace all "?" with ")"
for (int i = 0; i < n; i++) {
if (s[i] == '?') {
pos[i] = tot++;
ans += b[pos[i]];
}
}
int cnt = 0;
boolean flag = true;
for (int i = 0; i < n; i++) {
if (s[i] == '(') {
cnt++;
} else {
cnt--;
}
// Replace the i-th "?" with "("
if (s[i] == '?') q.add(a[pos[i]] - b[pos[i]]);
if (cnt < 0 && !q.isEmpty()) {
ans += q.poll();
cnt += 2;
}
if (cnt < 0) {
flag = false;
break;
}
}
if (cnt > 0 || !flag) return -1;
return ans;
}
}
此为Java解法,Python,C++解法见Lincode
想要更好地掌握这个知识点,欢迎免费试听《线段树和树状数组》。
这门原价$199的课程,现在:
- 戳我免费试听后,加微信号jiuzhang15,回复「知乎线段树」+试听截图;
- 邀请2位未注册过九章官网的新朋友入群,组成3人学习小组;
3人都可领全额抵价券,0元听课!