题目描述:给定一个01字符串S(字符串中只包含0、1两种字符),求两个区间s1、s2,要求这两个区间的0、1字符的个数相等,且区间最长,两区间可重叠但不重合。
例子:S=“11011”,s1=[0,3],s2=[1,4]时,满足要求,且区间最长为4。
解法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
| namespace DataStructure { internal class Program { static void Main(string[] args) { string text = string.Empty; while (!IsZeroOneString(text)) text = Console.ReadLine(); Console.WriteLine(text + ":"); Console.WriteLine(SolveProblem(text)); Console.ReadKey(); }
static bool IsZeroOneString(string text) { return !string.IsNullOrWhiteSpace(text) && !text.Split('0', '1').Where(t => t != "").Any(); }
static (int, int, int, int) SolveProblem(string text) { int N = text.Length; int x1 = -1, y1 = -1, x2 = -1, y2 = -1;
var prefixSums = new int[N + 1]; for (int i = 0; i < N; i++) prefixSums[i + 1] = prefixSums[i] + (text[i] == '1' ? 1 : 0);
var dict = new Dictionary<int, List<int>>(); for (int l = N - 1; l > 0; l--) { dict.Clear(); for (int start = 0; start < N - l + 1; start++) { int end = N + start - 2; int oneNum = prefixSums[end + 1] - prefixSums[start]; if (!dict.ContainsKey(oneNum)) { dict[oneNum] = new List<int>(); } dict[oneNum].Add(start); if (dict[oneNum].Count == 2) { x1 = dict[oneNum][0]; y1 = l + x1 - 1; x2 = dict[oneNum][1]; y2 = l + x2 - 1; return (x1, y1, x2, y2); } } } return (x1, y1, x2, y2); } } }
|
思路:
- 累积和数组
prefixSums
:时间复杂度是O(n),有了这个数据后,可以在O(1)事件内求任一区间中0和1的个数,[x,y]区间1的个数是prefixSums[y+1]-prefixSums[x]
。
- 从区间长度为n-1开始遍历,以(区间长度,区间中1个数)为key,将区间起点记录在字典
dict
中,字典值是一个数组,记录着相同区间长度、相同1个数的所有区间起点。由于只需要找到两个区间,当这个数组长度到2时,就已经找到了。
- 从区间长度为n-1开始遍历,找到即可终止算法,因为已经是尽可能长的区间了。
- 不用在字典中同时记录所以种类长度的区间信息,搜索每一种长度之前可清空字典。
总结
前缀和+字典,两者组合可以很好解决这类问题,注意朝这个方向思考。