第5章 优化时间和空间效率
5.1 面试官谈效率 5.2 时间效率 面试题29:数组中出现次数超过一半的数字题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组 {1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组
长度的一半,因此输出2.解法一:基于Partition函数的O(n)算法数组的特性:数组中有一个数字出现的次数超过了数组长度的一半,如果把这个数组排序,那么排序之后位于数组中间的数字一定就是那个出现次数超过数组长度一半的数字。也就是统计学上的中位数,即长度为n的数组中第n/2大的数字。我们有成熟的O(n)算法得到数组中任意第k大的数字。int MoreThanHalfNum(int* numbers, int length)
{//判断输入的数组是不是无效的if (CheckInvalidArray(numbers, length)) {return 0;}int middle = length >> 1;int start = 0;int end = length - 1;//Partition 快速排序int index = Partition(numbers, length, start, end);while (index != middle) {if (index > middle) {end = index - 1;index = Partition(numbers, length, start, end);} else {start = index + 1;index = Partition(numbers, length, start, end);}}int result = numbers[middle];//检查出现频率最高的数字,是否超过数组的一半if (!CheckMoreThanHalf(numbers, length, result)) {return 0;}return result;
}bool g_bInputInvalid = false;bool CheckInvalidArray(int* numbers, int length)
{g_bInputInvalid = false;if (numbers == null || length <= 0) g_bInputInvalid = true;return g_bInputInvalid;
}bool CheckMoreThanHalf(int* numbers, int length, int number)
{int times = 0;for (int i = 0; i < length; i++) {if (numbers[i] == number) times++;} bool isMoreThanHalf = true;if (times * 2 <= length) {g_bInputInvalid = true;isMoreThanHalf = false;}return isMoreThanHalf;
}int Partition(int data[], int length, int start, int end)
{if (data == null || length <= 0 || start< 0 || end >=  length) {die('错了');} int index = RandomInRange(start,end);Swap(&data[index], &data[end]);int small = start - 1;for (index = start; index < end; index++) {if (data[index] < data[end]) {small++;if (small != index)Swap(&data[index], &data[small]);}}small++;Swap(&data[small], &data[end]);return small;
}解法二:根据数组特点找出O(n)的算法接下来我们换个思路。数组中有一个数字出现的次数超过数组长度的一半,也就是说它出现的次数比其他所有的数字出现次数的和还要多。因此我们可以考虑在遍历数组的时候保存两个值:一个是数组中的数字,一个是次数。当我们遍历到下一个数字的时候,如果下一个数字和我们之前保存的数组相同,则次数加1;如果下一个数字和我们之前保存的数字不同,则次数减1。如果次数为0,我们需要保存下一个数字,并把次数设置为1.由于我们要找的数字出现的次数比其他所有的数字出现的次数之和还要多,那么要找的数字肯定是最后一次把次数设置为1时对应的数字。int MoreThanHalfNum(int* numbers, int length)
{if (CheckInvalidArray(numbers, length))return 0;int result = numbers[0];int times = 1;for (int i = 1; i < length; i++) {if (times == 0) {result = numbers[i];times = 1;} else if (numbers[i] == result) {times++;} else {time--;}} if (!CheckMoreThanHalf(numbers, length, result)) result = 0;return result;
}面试题30:最小的k个数题目:输入n个整数,找出其中最小的k个数。例如输入 4,5,1,6,2,7,3,8 这8个数字,则最小的4个数字是1,2,3,4.这道题目最简单的思路莫过于把输入的n个整数排序,排序之后位于最前面的k个数字就是最小的k个数。这种思路的时间复杂度是O(nlogn),面试官会提醒我们有更快的算法。解法一:O(n)的算法,只有当我们可以修改输入的数组时可以用。我们同样可以基于 Partition 函数来解决这个问题。如果基于数组的第k个数字来调整,使得比第k个数字小的所有数字都位于数组的左边,比第k个数字大的所有数字都位于数组的右边。这样调整之后,位于数组的左边的k个数字就是小于k个数字(这k个数字不一定是排序的)。void GetLeastNumbers(int* input, int n, int* output, int k)
{if (input == null || output == null || k > n || n <= 0 || k <= 0)return;int start = 0;int end = n - 1;int index = Partition(input, n, start, end);while (index != k - 1) {if (index > k - 1) {end = index - 1;index = Partition(input, n, start, end);} else {start = index + 1;index = Partition(input, n, start, end)}}for (int i = 0; i < k; i++) {output[i] = input[i];}
}缺点:需要修改输入的数组。解法二:O(nlogk)的算法,特别适合处理海量数据我们可以先创建一个大小为k的数据容器来存储最小的k个数字,接下来我们每次从输入的n个整数读入一个数。如果容器中已有的数字少于k个,则直接把这次读入的整数放入容器中;如果容器中已有k个数字,也就是容器满了,此时我们不能再插入新的数字而只能替换已有的数字。找出这k个数字最大的值,然后拿这次插入的新的数字和最大的值比较,如果待插入的值比当前已有的最大值小,则用这个数字替换当前已有的最大值;如果待插入的值比当前已有的最大值还要大,那么这个数不可能是最小的k个整数之一,可以抛弃。因此,当容器满了,我们需要做三件事:一是在k个整数中找到最大数;而是有可能在这个容器中删除最大数;三是有可能要插入一个新的数字。我们可以选择不同的二叉树实现这个容器,由于每次都需要找到k个整数中最大的数字,很容易联想到最大堆。在最大堆中,根结点的值总是大于它的子树中任意结点的值。于是我们每次可以在O(1)得到已有的k个数字的最大值,但需要O(logk)时间完成删除及插入操作。typedef multiset<int, greatere<int>> intSet;
typedef multiset<int, greatere<int>>::iterator setIterator;void GetLeastNumbers(const vector<int>& data, intSet& leastNumbers, int k)
{leastNumbers.clear();if (k < 1 || data.size() < k) return;vector<int>::const_iterator iter = data.begin();for ( ; iter != data.end(); iter++) {if ( (leastNumbers.size()) < k ) {leastNumbers.insert(*iter);} else {setIterator iterGreatest = leastNumbers.begin();if (*iter < *(leastNumbers.begin())) {leastNumbers.erase(iterGreatest);leastNumbers.insert(*iter);}}}
}解法比较:基于函数 Partition 的第一种方法的平均时间复杂度是O(n),比第二种思路要快,但需要修改输入的数组。第二种解法慢一点,但它有两个明显的好处:1.是没有修改输入的数字(data),我们每次只是从data中读入数字,所有的写操作都是在容器 leastNumbers中进行的。2.是该算法特别适合海量数据的输入。假设题目的要求是从海量的数据中找出最小的k个数字,由于内存的大小是有限的,又不可能把这些海量的数据一次性全部载入内存。这个时候我们可以从辅助存储空间(比如硬盘)中每次读入一个数字,根据GetLeastNumbers 的方式判断是不是需要放入容器 leastNumbers即可。面试题31:连续子数组的最大和题目:输入一个整形数组,数组里面有正数也有负数。数组中的一个或者连续的多个数组组成一个子数组。求所有的子数组的和的最大值,要求时间复杂度为O(n)。例如输入的数组为 {1,-2,3,10,-4,7,2,-5},和最大的子数组为{3,10,-4,7,2},因此输入该子数组的和为18.最直观的方法,即枚举出数组的所有子数组并求出它们的和。一个长度为n的数组,总共有 n(n+1)/2 个子数组。计算出所有子数组的和,最快也需要O(n^2)的时间。解法一:举例分析数组的规律bool g_InvalidInput = false;int FindGreatestSumOfSubArray(int *pData, int nLength)
{if ( (pData == null ) || (nLength <= 0) ) {g_InvalidInput = true;return 0;}g_InvalidInput = false;int nCurSum = 0;int nGreatestSum = 0x80000000;for (int i = 0; i < nLength; i++) {if (nCurSum <= 0) {nCurSum = pData[i];} else {nCurSum += pData[i];}if (nCurSum > nGreatestSum) {nGreatestSum = nCurSum;} return nGreatestSum;}
}注意需要考虑无效的输入,还有如果函数返回0,那么我们怎么区分子数组的和是0,还是无效输入。所以需要用个全局变量来定义错误。解法二:应用动态规划法面试题32:从1到n整数中1出现的次数题目:输入一个整数n,求从1到n这n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11和12,1一共出现了5次。解法一:不考虑时间效率的解法,靠它拿offer有点难思路:累加1到n中每个数字出现1的次数。可以每次对10求余数判断整数的个数位是不是1.int NumberOf1Between1AndN(unsigned int n)
{int number = 0;for (unsigned int i = 1; i <= n; i++) {number += NumberOf1(i);}return number;
}int NumberOf1(unsigned int n)
{int number = 0;while (n) {if (n & 10 == 1) {number++;}n = n / 10;}return number;
}缺点:上述思路中,我们需要对每个数字做除法和求余运算。如果输入的数字n,n有O(logN)位,我们需要判断每一位是不是1,那么它的时间复杂度是O(n*logn)。当n比较大的时候,运算效率不高。解法二:从数字规律着手明显提高时间效率的解法,让面试官耳目一新int NumberOf1Between1AndN(int n)
{if (n <= 0) {return 0;}char strN[50];sprintf(strN, "%d", n);return NumberOf1(strN);
}int NumberOf1(const cahr* strN)
{if (!strN || *strN < '0' || *strN > '9' || *strN == '\0')return 0;int first = *strN - '0';unsigned int length = static_cast<unsigned int>(strlen(strN));if (length == 1 && first == 0) {return 0;}if (length == 1 && first > 0) {return 1;}//假设strN是 "21345"//numFirstDigit 是数字 10000~19999 的第一个位中的数目int numFirstDigit = 0;if (first > 1) {numFirstDigit = PowerBase10(length - 1);} else if (first == 1) {//atoi,字符串转换为整数numFirstDigit = atoi(strN + 1) + 1;}//numOtherDigits 是 1346~21345 除了第一位之外的数位中的数目int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);int numRecursive = NumberOf1(strN + 1);return numFirstDigit + numOtherDigits + numRecursive;
}int PowerBase10(unsigned int n)
{int result = 1;for (unsigned int i = 0; i < n; i++) {result *= 10;}return result;
}这种思路是每次去掉最高位做递归,递归的次数和位数相同。一个数组n有 O(logn)位,因此这种思路的时间复杂度是O(logn)。面试题33:把数组排成最小的数题目:输入一个正整数数组,把数组里面所有数字拼接起来排成一个数,打印能拼接处的所有数字中最小的一个。例如输入的数组{3,32,321},则打印出这3个数字能排成的
最小的数字 321323 。分析:这个题目最直接的做法是先求出这个数组中所有数字的全排列,然后把每个排列拼接起来,最后求出拼接起来的数组的最小值。n个数字的总共同有n!个排列。这道题其实是希望我们找到一个排序规则,数组根据这个规则排序之后能排成一个最小的数字。要确定排序规则,就要比较两个数字,也就是给出两个数字m和n,我们需要判断m和n哪个应该排在前面,而不是比较这2个数字的值哪个大。根据题目要求,两个数字m和n能拼接成mn和nm。如果mn<nm,那么我们应该打印出mn,也就是m应该排在n前面,我们定义此时m小于n;反之,n小于m。接下来考虑如何拼接数字,即给出数字m和n,怎么得到数字mn和nm并比较大小。直接用数值计算不难办到,但需要考虑一个潜在的问题,m和n都在int范围内,但把它们拼接起来的数字mn和nm用int表示可能溢出了,这还是一个潜在的大数问题。一个非常直观的解决大数问题的方法就是把数字转换成字符串。另外,由于m和n拼接起来得到mn和nm,它们的位数肯定是相同的,因此比较它们的大小只需要按照字符串大小的比较规则就可以了。const int g_MaxNumberLength = 0;char* g_StrCombine1 = new char[g_MaxNumberLength * 2 + 1];
char* g_StrCombine2 = new char[g_MaxNumberLength * 2 + 1];void PrintMinNumber(int* numbers, int length)
{if (numbers == null || length <= 0)return;char** strNumbers = (char**)(new int[length]);//先把数组中的整数转换成字符串for (int i = 0; i < length; i++) {strNumbers[i] = new char[g_MaxNumberLength + 1];sprintf(strNumbers[i], "%d", numbers[i]);}//在函数 compare 中定义比较规则,并根据该规则用库函数 qsort 排序。qsort(strNumbers, length, sizeof(char*), compare);//最后把排好序的数组中的数字依次打印出来,就是该数组中数字能拼接出来的最小数字。时间复杂度是 O(nlogn)for (int i = 0; i < length; i++) {printf("%s", strNumbers[i]);}printf("\n");for (int i = 0; i < length; i++) {delete[] strNumbers[i];}delete[] strNumbers;
}int compare(const void* strNumber1, const void* strNumber2)
{strcpy(g_StrCombine1, *(const char**)strNumber1);strcat(g_StrCombine1, *(const char**)strNumber2);strcpy(g_StrCombine2, *(const char**)strNumber2);strcat(g_StrCombine1, *(const char**)strNumber1);return strcmp(g_StrCombine1, g_StrCombine2);
}5.3 时间效率与空间效率的平衡 面试题34:丑数题目:我们把只包含因子2,3和5的数叫做丑数。求按从小到大的顺序的第1500个丑数。例如6,8都是丑数,但14不是,因为它包含因子7.习惯上我们把1当做第一个丑数。解法一:逐个判断每个整数是不是丑数的解法,直观订但不够高效所谓一个数m是另外一个数n的因子,是指n能被m整除,也就是 n%m==0。根据丑数的定义,丑数只能被2,3,5整除。也就是说如果一个数能被2整除,我们把它连续除以2;如果能被3整除,就连续除以3;如果能被5整除,就除以连续5。如果最后我们得到的是1,那么这个数就是丑数,否则不是。bool IsUgly(int number)
{while (number % 2 == 0) number /= 2;while (number % 3 == 0)number /= 3;while (number % 5 == 0)number /= 5;return (number == 1) ? true : false;
}int GetUglyNumber(int index)
{if (index <= 0)return 0;int number = 0;int uglyFound = 0;while (uglyFound < index) {++number;if (IsUgly(number)) {++uglyFound;}}return number;
}解法二:创建数组保存已经找到的丑数,用空间换时间的解法前面的算法之所以低效,很大程度上是因为不管一个数是不是丑数,我们都要对它进行计算。接下来我们试着找到一种只要计算丑数的方法,而不在非丑数的整数上花费太多时间。根据丑数的定义,丑数应该是另外一个丑数乘以2,3,5的结果(1除外)。因此我们可以创建一个数组,里面的数字是排序好的丑数,每一个丑数都是前面的丑数乘以2,3或5得到的。这种思路的关键在于怎样确保里面的丑数是排序好的。int GetUglyNumber_Solution2(int index)
{if (index <= 0)return 0;int *pUglyNumbers = new int[index];pUglyNumbers[0] = 1;int nextUglyIndex = 1;int* pMultiply2 = pUglyNumbers;int* pMultiply3 = pUglyNumbers;int* pMultiply5 = pUglyNumbers;while (nextUglyIndex < index) {int min = Min(*pMultiply2 * 2, *pMultiply3 * 3, *pMultiply5 * 5);pUglyNumbers[nextUglyIndex] = min;while (*pMultiply2 * 2 <= pUglyNumbers[nextUglyIndex]);++pMultiply2;while (*pMultiply3 * 3 <= pUglyNumbers[nextUglyIndex]);++pMultiply3;while (*pMultiply5 * 5 <= pUglyNumbers[nextUglyIndex]);++pMultiply5;++nextUglyIndex;}int ugly = pUglyNumbers[nextUglyIndex - 1];delete[] pUglyNumbers;return ugly;
}int Min(int number1, int number2, int number3)
{int min = (number1 < number2) ? number1 : number2;min = (min < number3) ? min : number3;return min;
}面试题35:第一个只出现一次的字符题目:在字符串中找出第一个只出现一次的字符。如输入"abaccdeff",则输出'b'。如果有n个字符,每个字符可能与后面的O(n)个字符相比较,因此这种思路的时间复杂度是O(n^2)。char FirstNotRepeatingChar(char* pString)
{if (pString == null) return '\0';const int tableSize = 256;unsigned int hashTable[tableSize];//初始化hashTablefor (unsigned int i = 0; i < tableSize; i++) {hashTable[i] = 0;}//将值填入char* pHashKey = pString;while ( *(pHashKey) != '\0' ) {hashTable[ *(pHashKey++) ]++;}//找出次数等于1的pHashKey = pString;while (*pHashKey != '\0') {if (hashTable[*pHashKey] == 1) {return *pHashKey;}pHashKey++;}return '\0';
}相关题目:1.定义一个函数,输入两个字符串,从第一个字符串中删除在第二个字符串中出现过的所有字符。例如从第一个字符串"We are students."中删除在第二个字符串"aeiou"中出现过的字符得到的结果是"W r Stdnts."。为了解决这个问题,我们可以创建一个用数组实现的简单哈希表来存储第二个字符串。这样我们从头到尾扫描第一个字符串的每一个字符时,用O(1)时间就能判断出该字符是不是在第二个字符中。如果第一个字符串的长度是n,那么总的时间复杂度是O(n)。2.定义一个函数,删除字符串中所有重复出现的字符。例如输入"google",删除重复的字符之后的结果是"gole"。这个题目和上面的问题比较相似,我们可以创建一个用布尔型数组实现的简单的哈希表。数组中的元素的意义是其下标看做 ASCII 码后对应的字母在字符串中是否已经出现。我们先把数组中所有的元素都设置false。以"google"为例,当扫描到第一个g时,g的ASCII码是103,那么我们把数组中下标为103的元素设置true。当扫描到第二个g时,我们发现数组中下标为103的元素的值是true,就知道g已经在前面出现过了。也就是说,我们用O(1)时间就能判断每个字符在前面是否出现过。如果字符串的长度为n,那么总的时间复杂度是O(n)。3.在英语中,如果两个单词出现的字母相同,并且每个字母出现的次数也相同,那么这个两个单词互为变位词。例如,silent与listen,evil与live等互为变位词。我们可以创建一个用数组实现的简单哈希表,用来统计字符串中每个字符出现的次数。当扫描第一个字符串的每个字符时,为哈希表对应的项的值增加1.接下来扫描第二个字符串,扫到的每一个字符串,为哈希表对应的项减去1。如果扫完第二个字符串后,哈希表中的值都是0,那么这两个字符串就互为变位词。举一反三:如果需要判断多个字符是不是在某个字符串里出现或者统计多个字符在某个字符串中出现的次数,我们可以考虑基于数组创建一个简单的哈希表。这样可以用很小的空间消耗来换取时间效率的提升。面试题36:数组中的逆序对题目:在数组中的两个数字如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。例如,在数组{7,5,6,4}中,一共存在5个逆序对,分别是 (7,6),(7,5),(7,4),(6,4),(5,4)。解法一:顺序扫码数组,用两次循环,时间复杂度为O(n^2)。解法二:归并排序总结出逆序对统计的过程:先把数组分隔成子数组,先统计出子数组内部的逆序对的数目,然后再统计出两个相邻子数组之间的逆序对的数目。在统计逆序对的过程中,还需要对数组进行排序。如果对排序算法很熟悉,就不难发现排序的过程实际上就是归并排序。基于归并排序,写出如下代码。int InverseParis(int* data, int length)
{if (data == null || length < 0)   return 0;int* copy = new int[length];for (int i = 0; i < length; i++) {copy[i] = data[i];}int count = InversePairsCore(data, copy, 0, length - 1);delete[] copy;return count;
}int InversePairsCore(int* data, int* copy, int start, int end)
{if (start == end) {copy[start] = data[start];return 0;}int length = (end - start) / 2;int left = InversePairsCore(copy, data, start, start + length);int right = InversePairsCore(copy, data, start + length + 1, end);//i初始化为前半段最后一个数字的下标int i = start + length;//j初始化为后半段最后一个数字的下标int j = end;int indexCopy = end;int count = 0;while (i >= start && j >= start + length + 1) {//p1 > p2,有逆序对if (data[i] > data[j]) {  copy[indexCopy--] = data[i--];count += j - start - length;} else {copy[indexCopy--] = data[j--];}}for ( ; i >= start; i--) {copy[indexCopy--] = data[i];}for ( ; j >= start + length + 1; j--) {copy[indexCopy--] = data[j];}return left + right + count;
}我们知道归并排序的时间复杂度是O(nlogn),比最直观的O(n^2)要快,但同时需要消耗一个长度为n的辅助数组。面试题37:两个链表的第一个公共结点题目:输入两个链表,找出它们的第一个公共结点。链表节点的定义如下:
struct ListNode {int m_nKey;ListNode* m_pNext;
};思路1:在第一个链表上顺序遍历每个结点,每遍历到一个结点的时候,在第二个链表上顺序遍历每一个结点。如果第二个链表上有一个结点和第一个链表上的结点一样,
说明两个链表在这个结点上重合。于是找到了公共结点。如果第一个链表的长度为m,第二个链表的长度为n,时间复杂度为O(mn)。思路2:用两个辅助栈,分别把两个链表放入栈里,这样两个链表的尾结点就位于两个栈的栈顶,接下来比较两个栈顶是否相同。如果相同,则接着比下一个,直到找到
最后一个相同的结点。思路3:当两个链表的长度不相同的时候,如果我们从头开始遍历到达尾结点的时间就不一致。解决的方法是,首先遍历两个链表的长度,就知道哪个链表长,在较长的
链表上先走若干步,接着再同时遍历,找到的一个相同结点就是它们的第一个公共的结点。ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2)
{//得到两个链表的长度unsigned int nLength1 = GetListLength(pHead1);unsigned int nLength2 = GetListLength(pHead2);int nLengthDif = nLength1 - nLength2;ListNode* pListHeadLong = pHead1;ListNode* pListHeadShort = pHead2;if (nLength2 > nLength1) {pListHeadLong = pHead2;pListHeadShort = pHead1;nLengthDif = nLength2 - nLength1;}//先在长链表上走几步,再同时在两个链表上遍历for (int i = 0; i < nLengthDif; i++) {pListHeadLong = pListHeadLong->m_pNext;}while ( (pListHeadLong != null) && (pListHeadShort != null) && (pListHeadLong != pListHeadShort) ) {pListHeadLong = pListHeadLong->m_pNext;pListHeadShort = pListHeadShort->m_pNext;}//得到第一个公共节点ListNode* pFirstCommonNode = pListHeadLong;return pFirstCommonNode;
}unsigned int GetListLength(ListNode* pHead)
{unsigned int nLength = 0;ListNode* pNode = pHead;while (pNode != null) {++nLength;pNode = pNode->m_pNext;}return nLength;
}

5.剑指Offer --- 优化时间和空间效率相关推荐

  1. java计算整数出现的次数_[剑指offer题解][Java]1到n整数中1出现的次数

    前言 众所周知,<剑指offer>是一本"好书". 如果你是个算法菜鸡(和我一样),那么最推荐的是先把剑指offer的题目搞明白. 对于剑指offer题解这个系列,我的 ...

  2. 剑指Offer名企面试官精讲典型编程题pdf

    下载地址:网盘下载 <剑指Offer:名企面试官精讲典型编程题(第2版)>剖析了80个典型的编程面试题,系统整理基础知识.代码质量.解题思路.优化效率和综合能力这5个面试要点.<剑指 ...

  3. 《剑指offer》面试题的Python实现

    所属网站分类: 面试经典 > python 作者:gg 链接: http://www.pythonheidong.com/blog/article/464/ 来源:python黑洞网 www.p ...

  4. 面试经典算法题集锦——《剑指 offer》小结

    本文转载自: http://www.cnblogs.com/fanling999/p/7810558.html 作者:fanling999 转载请注明该声明. 从今年 3 月份开始准备找实习,到现在校 ...

  5. 剑指Offer第二版Java代码实现

    剑指Offer第二版Java代码实现 A.单例模式 面试题 2:实现Singleton模式 B.面试需要的基础知识 面试题 3:数组中重复的数字 面试题 4:二维数组的查找 面试题 5:替换空格 面试 ...

  6. 剑指offer读书笔记

    剑指offer读书笔记 一.面试的流程 1.浅谈面试的重点 2.面试的形式 3.面试的环节 二.面试的基础知识 1.基础知识 2.数据结构 3.算法和数据操作 三.高质量代码 1.代码的规范性 2.代 ...

  7. 【读书笔记】剑指offer

    导语 所有的编程练习都在牛客网OJ提交,链接: https://www.nowcoder.com/ta/coding-interviews 九章算法的 lintcode 也有这本书的题目.https: ...

  8. 《剑指offer》序——面试流程及面试须知

    通常,面试是从电话面试开始的.接下来可能有一两轮共享桌面远程面试,面试官通过桌面共享软件远程考查应聘者的编程和调试能力.如果应聘者的表现足够优秀,那么公司将邀请他到公司去接收现场面试. 通常,面试官会 ...

  9. 剑指offer:java版

    作者:CyC2018 链接:https 文章目录 一.基础 3.数组中的重复的数字 4. 二维数组中的查找 5. 替换空格 6. 从尾到头打印链表 7. 重建二叉树 8. 二叉树的下一个结点 9. 用 ...

  10. 【剑指offer】解题思路汇总

    文章目录 总结备忘 第二章 面试需要的基础知识 2.3 数据结构 面试题3:数组中重复的数字 题目一:找出数组中重复的数字 题目二:不修改数组找出重复的数字 面试题4:二维数组中的查找 面试题5:替换 ...

最新文章

  1. Web前端开发必备:《Jquery实战》第3版 介绍
  2. chapter1:The way of the program
  3. 将CRgn rgn对象以图片形式输出便于查看
  4. Struts2 拦截器: 拦截器与过滤器区别
  5. seo自动工具_爱站SEO工具包详细介绍
  6. android线程优先级大小,android 设置线程优先级 两种方式
  7. 【操作系统】系统调用
  8. [单刷APUE系列]第十一章——线程[1]
  9. python replace函数后面的数字的含义
  10. <读书笔记> Thinking in python (Python 设计模式) 3. Proxy and State模式
  11. OFFICE技术讲座:JDK绘制旋转斜体字体的效果
  12. 监控sqlserver 数据变化并记录_携程机票数据仓库11年技术栈的演进
  13. ppt矩形里面的图片怎么放大缩小_PPT5题目要求-矩形放大缩小
  14. 错误No Feign Client for loadBalancing defined. Did you forget to include spring-cloud-starter-loadbala
  15. 树莓派编译安装完整版本ROS
  16. 产品经理可以考哪些证书提升自己?一篇文章回答你
  17. 记一次作业:完成企业网络安全运营建设方案
  18. 南昌地铁信息化服务器,南昌地铁1号线信息化建设
  19. 日期选择器:jquery datepicker的使用
  20. java节点是啥_节点什么意思?什么叫节点?

热门文章

  1. 小技巧:方便开机打开多个程序
  2. 你一点要认真了解 C++中的模式匹配介绍
  3. Handsontable 自定义菜单 自定义命令存放位置
  4. static使用之静态变量
  5. 第二阶段团队站立会议08
  6. SQL多表连接查询时间最新的
  7. 类似于Function.bind的dojo.hitch --dojo Quick Start/dojo入门手册4
  8. 历史上的第一位程序员(一)
  9. 精选|2018年12月R新包推荐
  10. R爬虫可视化第五季-图解欧洲足球五大联赛