对“求数组中所有和为某固定数的所有数对”的算法的简单思考
一、题目描述
有一个数组a[1000],里面存放了1000个整数,请找出该数组中所有和为M的数对。例如数组为-1,2,4,6,5,3,4,2,9,0,8,3,那么和为8的数对有(-1,9),(2,6),(4,4),(5,3),(5,3),(0,8)。
二、最普通的算法
在不可率复杂度的情况下,对于这个问题的最简单的算法如下:
private static List<int[]> UseNormalWay(int[] arr, int sumTotal) { List<int[]> result = new List<int[]>(); for (int i = 0; i < arr.Length; i++) { int expectNum = sumTotal - arr[i]; for (int j = i + 1; j < arr.Length; j++) { if (arr[j] == expectNum) { result.Add(new int[]{arr[i], expectNum}); } } } return result; }
利用两层循环查找所有和为固定数sumTotal的所有数对,将找到的数对存入List中。但是这个算法复杂度有点高,实际上在遍历的时候做了一些重复的工作:
1) 后续循环的时候没有必要对前面已经配对的数列进行处理。
2)查找与某个数和为sumTotal的数时,应该可以有一些可以相互利用的过程。
基于这两点,可以引用一些01背包或动态规划的思想(或许引用得不恰当,我对这两种思想和理解很肤浅)来对这个算法进行改进,利用递归来操作。
二、采用递归算法
采用递归算法的代码如下:
private static List<int[]> UseRecursion(int[] arr, int length, int sumTotal) { List<int[]> result = new List<int[]>(); int[] nextArr = new int[length]; int nextLength = 0; int expectNum = sumTotal - arr[0]; int findStartNumCount = 0; int findEndNumCount = 0; for (int i = 1; i < length; i++) { if (arr[i] == expectNum) { int circleCount = arr[0] == expectNum ? findEndNumCount : findStartNumCount; circleCount += 1; for (int j = 0; j < circleCount; j++) { result.Add(new int[] { arr[0], expectNum }); } findEndNumCount++; } else if (arr[i] == arr[0]) { for (int j = 0; j < findEndNumCount; j++) { result.Add(new int[] { expectNum, arr[0] }); } findStartNumCount++; } else { nextArr[nextLength++] = arr[i]; } } if (nextLength > 0) { List<int[]> nextResult = UseRecursion(nextArr, nextLength, sumTotal); foreach (int[] item in nextResult) { result.Add(item); } } return result; }
每递归一次后,将所有没有检查过匹配的数字再组成一个新的数组以便在下一次递归中来处理,但这么做浪费了巨大的空间,特别是数组很大的情况下会消耗很多内存。为了不每次递归创建新的数组,可以在原来的数组上进行处理,将已经匹配的数字从数组中剔出,将后续数字前移替补。
三、数组动态改变算法
这种算法的代码如下:
private static List<int[]> UseAdvancedWay(int[] arr, int sumTotal) { List<int[]> result = new List<int[]>(); int startIndex = 0, endIndex = arr.Length - 1; while (startIndex < endIndex) { int expectNum = sumTotal - arr[startIndex]; int findStartNumCount = 0; int findEndNumCount = 0; for (int i = startIndex + 1; i <= endIndex; i++) { if (findStartNumCount > 0 || findEndNumCount > 0) { arr[i] = arr[i + findStartNumCount + findEndNumCount]; } if (arr[i] == expectNum) { int circleCount = arr[startIndex] == expectNum ? findEndNumCount : findStartNumCount; circleCount += 1; for (int iStart = 0; iStart < circleCount; iStart++) { result.Add(new int[] { arr[startIndex], arr[i] }); } findEndNumCount++; endIndex--; i--; } else if (arr[i] == arr[startIndex] && arr[startIndex] != expectNum) { for (int iEnd = 0; iEnd < findEndNumCount; iEnd++) { result.Add(new int[] { expectNum, arr[i] }); } findStartNumCount++; endIndex--; i--; } } startIndex++; } return result; }
这种算法会破坏原有数组的数据的。
四、三种算法时间的比较
虽然后两种算法的初衷是为了减少时间复杂度,但在具体测试中并没有比第一种算法优越多少,特别是递归的算法比第一种算法所用的时间还明显加长。或许我的想法从基础上就有问题,也或许是这个例子过于简单使得移动数据占用的时间明显凸现出来了。
一直未对算法有过多研究,希望高手们能指点一二,我相信有肯定有更完美的解决方案。
五、所有码
static void Main(string[] args) { const int NUM_COUNT = 8000; const int MIN_NUM = -4; const int MAX_NUM = 12; const int TOTAL = 8; int[] arr = GenerateArray(NUM_COUNT, MIN_NUM, MAX_NUM); //Console.WriteLine("The numbers generated are:\n-------------------------------------"); //PrintNumArray(arr); // Normal way TimeSpan normalTimeStart = Process.GetCurrentProcess().TotalProcessorTime; Stopwatch normalStw = new Stopwatch(); normalStw.Start(); List<int[]> resultUseNormalWay = UseNormalWay(arr, TOTAL); double normalCupTime = Process.GetCurrentProcess().TotalProcessorTime.Subtract(normalTimeStart).TotalMilliseconds; normalStw.Stop(); double normalRealTime = normalStw.Elapsed.TotalMilliseconds; // Print Normal Result //Console.WriteLine("The result number pairs using normal way are:\n----------------------------------"); //PrintNumPairs(resultUseNormalWay); // Recursion way TimeSpan recuTimeStart = Process.GetCurrentProcess().TotalProcessorTime; Stopwatch recuStw = new Stopwatch(); recuStw.Start(); List<int[]> resultUseRecursionWay = UseRecursion(arr, arr.Length, TOTAL); double recuCupTime = Process.GetCurrentProcess().TotalProcessorTime.Subtract(recuTimeStart).TotalMilliseconds; recuStw.Stop(); double recuRealTime = recuStw.Elapsed.TotalMilliseconds; // Print Recursion Result //Console.WriteLine("The result number pairs using recusion way are:\n----------------------------------"); //PrintNumPairs(resultUseRecursionWay); // Advanced way TimeSpan advTimeStart = Process.GetCurrentProcess().TotalProcessorTime; Stopwatch advStw = new Stopwatch(); advStw.Start(); List<int[]> resultUseAdvancedWay = UseAdvancedWay(arr, TOTAL); double advCupTime = Process.GetCurrentProcess().TotalProcessorTime.Subtract(advTimeStart).TotalMilliseconds; advStw.Stop(); double advRealTime = advStw.Elapsed.TotalMilliseconds; // Print Advanced Result //Console.WriteLine("The result number pairs using advanced way are:\n----------------------------------"); //PrintNumPairs(resultUseAdvancedWay); Console.WriteLine("\n================================\nThe time used:\n-----------------------------------"); Console.WriteLine(String.Format("Normal : count - {0} Cpu Time - {1} Real Time - {2}", resultUseNormalWay.Count, normalCupTime, normalRealTime)); Console.WriteLine(String.Format("Recursion: count - {0} Cpu Time - {1} Real Time - {2}", resultUseRecursionWay.Count, recuCupTime, recuRealTime)); Console.WriteLine(String.Format("Advanced : count - {0} Cpu Time - {1} Real Time - {2}", resultUseAdvancedWay.Count, advCupTime, advRealTime)); Console.Read(); } private static int[] GenerateArray(int numCount, int minValue, int maxValue) { int[] arr = new int[numCount]; Random rdm = new Random((int)DateTime.Now.Ticks); for (int i = 0; i < arr.Length; i++) { arr[i] = rdm.Next(minValue, maxValue); } return arr; } private static void PrintNumArray(int[] arr) { for (int i = 0; i < arr.Length; i++) { if (i > 0 && i % 20 == 0) { Console.Write("\n"); } Console.Write(String.Format("{0,2} ", arr[i])); } Console.Write("\n"); } private static void PrintNumPairs(List<int[]> numList) { for (int i = 0; i < numList.Count; i++) { if (i > 0 && i % 10 == 0) { Console.Write("\n"); } Console.Write(string.Format("({0,2},{1,2}) ", numList[i][0], numList[i][1])); } Console.Write("\n"); } private static List<int[]> UseNormalWay(int[] arr, int sumTotal) { List<int[]> result = new List<int[]>(); for (int i = 0; i < arr.Length; i++) { int expectNum = sumTotal - arr[i]; for (int j = i + 1; j < arr.Length; j++) { if (arr[j] == expectNum) { result.Add(new int[]{arr[i], expectNum}); } } } return result; } private static List<int[]> UseRecursion(int[] arr, int length, int sumTotal) { List<int[]> result = new List<int[]>(); int[] nextArr = new int[length]; int nextLength = 0; int expectNum = sumTotal - arr[0]; int findStartNumCount = 0; int findEndNumCount = 0; for (int i = 1; i < length; i++) { if (arr[i] == expectNum) { int circleCount = arr[0] == expectNum ? findEndNumCount : findStartNumCount; circleCount += 1; for (int j = 0; j < circleCount; j++) { result.Add(new int[] { arr[0], expectNum }); } findEndNumCount++; } else if (arr[i] == arr[0]) { for (int j = 0; j < findEndNumCount; j++) { result.Add(new int[] { expectNum, arr[0] }); } findStartNumCount++; } else { nextArr[nextLength++] = arr[i]; } } if (nextLength > 0) { List<int[]> nextResult = UseRecursion(nextArr, nextLength, sumTotal); foreach (int[] item in nextResult) { result.Add(item); } } return result; } private static List<int[]> UseAdvancedWay(int[] arr, int sumTotal) { List<int[]> result = new List<int[]>(); int startIndex = 0, endIndex = arr.Length - 1; while (startIndex < endIndex) { int expectNum = sumTotal - arr[startIndex]; int findStartNumCount = 0; int findEndNumCount = 0; for (int i = startIndex + 1; i <= endIndex; i++) { if (findStartNumCount > 0 || findEndNumCount > 0) { arr[i] = arr[i + findStartNumCount + findEndNumCount]; } if (arr[i] == expectNum) { int circleCount = arr[startIndex] == expectNum ? findEndNumCount : findStartNumCount; circleCount += 1; for (int iStart = 0; iStart < circleCount; iStart++) { result.Add(new int[] { arr[startIndex], arr[i] }); } findEndNumCount++; endIndex--; i--; } else if (arr[i] == arr[startIndex] && arr[startIndex] != expectNum) { for (int iEnd = 0; iEnd < findEndNumCount; iEnd++) { result.Add(new int[] { expectNum, arr[i] }); } findStartNumCount++; endIndex--; i--; } } startIndex++; } return result; }
=======================================================================
野文(Jasson Qian)
------------------------------------------------------
博客园:http://qguohog.cnblogs.com
CSDN:http://blog.csdn.net/sallay