好久没有水刷算法题的博客了,也不是因为一直没写,而是单个题目总感觉记录下来差点意思。正好今天又碰到一个可以用双指针来解决的问题,顺便记录一下。
在我看来,双指针并不是一种“算法”,而是一种解决算法题目的技巧,如果运用得当其实可以方便的解决某一种类型的问题。
写到这里想起来前几天和基友吹水,关于链表的题目,问就是快慢指针。为什么呢?链表相关的题目有一些比较基础又比较简单的都很适合用快慢指针来解决。比如单向链表是否有环,就可以利用快慢指针是否相遇来判断。再者如秋链表的中间项,可以利用快指针步长为2,慢指针步长为1,保证快指针先走来得出结果。(当快指针走到链表尾部,奇数个项时,慢指针就是中间点;偶数个时,慢指针指向的就是中间两个项的左边一项。)又或是求倒数第n个结点,就可以“快”指针先走n项,慢指针再走等等。所以看来多总结相似的解法还是有些好处的。
废话不扯了,来看几道题目吧。题目来自于LeetCode,传送门。
11. 盛最多水的容器
给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0)。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。
说明:你不能倾斜容器,且 n 的值至少为 2。
图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。
示例:
输入:[1,8,6,2,5,4,8,3,7]
输出:49
如果你和我一样,一开始没想起使用双指针来解决这个问题的话,可能会感觉比较困扰。因为按照题目的要求,我们实际上是求得最大的面积。而这个面积又受两个变量影响,一是高度二是长度。而当我们确定一个面积之后,如果要去判断下一个面积是不是更大就需要移动“水桶的一边”,这可能会导致长度和高度都发生变化。进而与暴力求解法没什么差别了。
当然如果再仔细一点其实会发现,关于高度,最大面积只受两边高度中最矮的那个影响,传说中的木桶效应嘛~~。这样我们不妨就拿数组的两个边界作为木桶的两边,来计算一个初始的面积。这样做的好处是“长度”已经是最大值了,接下来如果我们要移动某一条边,发现移动后的高度还不高于移动前的高度,我们可以直接说它的面积是会比移动前更小的。并且,由于“木桶效应”,我们也可以推断每次都应该移动最矮的那条边。(如果移动的是高的边,那么木桶的长度一定比之前小,高度一定不大于之前的高度,故面积一定比之前小)。
看看Code吧。
1 public class Solution { 2 public int MaxArea(int[] height) { 3 int begin = 0; 4 int end = height.Length - 1; 5 6 int result = -1; 7 8 while(begin < end) 9 { 10 int temp; 11 if(height[begin] > height[end]) 12 { 13 temp = (end-begin) * height[end]; 14 end --; 15 } 16 else 17 { 18 temp = (end-begin) * height[begin]; 19 begin ++; 20 } 21 22 result = temp > result ? temp : result; 23 } 24 25 return result; 26 } 27 }
官方解法有关于应该移动哪条边更详细的证明,传送门。
15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
对于原始的数组,看起来似乎只能使用暴力求解。但如果它是有序的呢,毕竟要求三个数字和为0,如果是有序数组应该可以省去一些边界值的情况,减少比较的次数。
假设我们按索引的顺序选取a,b,c。如果数组是有序的,那么可以推断如果a>0或a+b>0,那么一定不存在这样的结果。所以我们可以从索引为0的位置让a开始遍历,然后第二个数字b选取a的下一位,c选取数组的末尾。这样根据a+b+c与0的大小比较,就可以知道我们当前的值应该进行怎样的调整。如果较大,我们就移动c,让和变小。如果较小。我们就移动b,让和变大。
Show code:
1 public class Solution { 2 public IListint>> ThreeSum(int[] nums) 3 { 4 List int>> result = new List int>>(); 5 6 if (nums.Length < 3) 7 { 8 return result; 9 } 10 11 Array.Sort(nums); 12 13 for(int i = 0; i < nums.Length - 2; i++) 14 { 15 int k = i; 16 int m = i + 1; 17 int n = nums.Length - 1; 18 19 while (m < n) 20 { 21 if (nums[k] > 0) 22 break; 23 24 if (nums[k] + nums[m] + nums[n] == 0) 25 { 26 var temp = new List<int>(); 27 temp.Add(nums[k]); 28 temp.Add(nums[m]); 29 temp.Add(nums[n]); 30 31 result.Add(temp); 32 33 do 34 { 35 n--; 36 } 37 while (n > m && nums[n] == nums[n + 1]); 38 39 do 40 { 41 m++; 42 } 43 while (m < n && nums[m] == nums[m - 1]); 44 } 45 else if (nums[k] + nums[m] + nums[n] > 0) 46 { 47 do 48 { 49 n--; 50 } 51 while (n > m && nums[n] == nums[n + 1]); 52 } 53 else 54 { 55 do 56 { 57 m++; 58 } 59 while (m < n && nums[m] == nums[m - 1]); 60 } 61 } 62 63 do 64 { 65 i++; 66 } 67 while (i < nums.Length && nums[i] == nums[i - 1]); 68 69 i--; 70 } 71 72 return result; 73 } 74 }
感兴趣的小伙伴也可以去搜索一些这道题目下别的解法。只能说大神实在是太厉害了,哈哈。
16. 最接近的三数之和
给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
例如,给定数组 nums = [-1,2,1,-4], 和 target = 1.
与 target 最接近的三个数的和为 2. (-1 + 2 + 1 = 2).
这道题和前面一道有一点点区别,不是相等,而是最接近。因此需要利用绝对值来进行判断。主要需要改变的地方是需要记录一下当前绝对值,如果不是需要更新,来帮助我们找到最接近的值。
上代码:
1 public class Solution { 2 public int ThreeSumClosest(int[] nums, int target) 3 { 4 int ans = nums[0] + nums[1] + nums[2]; 5 6 var sortNums = nums.OrderBy(x => x).ToArray(); 7 8 for (int i = 0; i < sortNums.Count() - 2; i++) 9 { 10 int start = i + 1; 11 int end = sortNums.Count() - 1; 12 13 while (start < end) 14 { 15 int temp = sortNums[i] + sortNums[start] + sortNums[end]; 16 if (Math.Abs(temp - target) < Math.Abs(ans - target)) 17 { 18 ans = temp; 19 } 20 21 if (temp > target) 22 { 23 end--; 24 } 25 else if (temp < target) 26 { 27 start++; 28 } 29 else 30 { 31 return target; 32 } 33 } 34 35 } 36 37 return ans; 38 } 39 }
嗯,算法还是要多多练习呀。