剑指offer题解汇总

剑指offer题解汇总

表示数值的字符串

请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串"+100","5e2","-123","3.1416"和"-1E-16"都表示数值。 但是"12e","1a3.14","1.2.3","+-5"和"12e+4.3"都不是。

题目是要判断一个表达式是否满足预设规则。其实就是正则匹配。正则匹配的原理,编译原理课上有讲过,用NFA/DFA来判定即可。


class Solution {
private:
	enum STATUS{ END = 0, START, SIGNED1, INTEGER, POINT, FLOAT, EXPONENT, SIGNED2, SCIENCE };
	STATUS dfa[256][9] = { END };
public:
	Solution(){
		for (int i = 0; i < 256; ++i){
			for (int j = 0; j < 9; ++j){
				dfa[i][j] = END;
			}
		}
		initDFA();
	}
	bool isNumeric(char* string){
		STATUS current = START;
		while (*string && current != END){
			current = DFA(current, *string);
			++string;
		}
		switch (current){
		case INTEGER:
		case FLOAT:
		case SCIENCE:
			return true;
		}
		return false;
	}
private:
	void initDFA(){
		char d = '0';
		// 1. START 变迁
		dfa['+'][START] = SIGNED1;
		dfa['-'][START] = SIGNED1;
		dfa['.'][START] = POINT;
		for (d = '0'; d <= '9'; ++d){
			dfa[d][START] = INTEGER;
		}

		// 2. SIGNED1 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][SIGNED1] = INTEGER;
		}
		dfa['.'][SIGNED1] = POINT;

		// 3. INTEGER 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][INTEGER] = INTEGER;
		}
		dfa['.'][INTEGER] = FLOAT;
		dfa['E'][INTEGER] = EXPONENT;
		dfa['e'][INTEGER] = EXPONENT;

		// 4. POINT 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][POINT] = FLOAT;
		}

		// 5. FLOAT 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][FLOAT] = FLOAT;
		}
		dfa['E'][FLOAT] = EXPONENT;
		dfa['e'][FLOAT] = EXPONENT;

		// 6. EXPONENT 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][EXPONENT] = SCIENCE;
		}
		dfa['+'][EXPONENT] = SIGNED2;
		dfa['-'][EXPONENT] = SIGNED2;

		// 7. SIGNED2 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][SIGNED2] = SCIENCE;
		}

		// 8. SCIENCE 变迁
		for (d = '0'; d <= '9'; ++d){
			dfa[d][SCIENCE] = SCIENCE;
		}

		// 其余情况均变迁到 END
	}

	STATUS DFA(STATUS current, char input){
		STATUS ret = START;
		return dfa[input][current];
	}
};

数组中出现次数超过一半的数字

/*
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
*/

class Solution {
public:
    /*
    最开始的想法:排序后,假如存在元素满足题目条件,那么中间位置的元素就是这样的元素,那么双向增长,判断增长停滞点之间的长度
    缺点是复杂度过高
    int MoreThanHalfNum_Solution(vector<int> numbers) {
    	sort(numbers.begin(), numbers.end());
        if(numbers.size()==0){
            return 0;
        }
        if(numbers.size()==1){
            return numbers[0];
        }
        int mid = (numbers.size()-1)/2;
        int low=mid, high=mid+1;
        while(low>=0 && high<=numbers.size()-1){
            if(numbers[low]==numbers[mid]){
                low--;
            }
            if(numbers[high]==numbers[mid]){
                high++;
            }
        }
        int len = high - low + 1;
        if(len>numbers.size()/2) return numbers[mid];
        return 0;
    }*/
    

        // 讨论帖中的做法,个人理解为:假如有某个数字出现次数超过一半,那么它们每个元素有一口气,我累计收集这些气(遍历):
        // 遇到这个元素,气+1;遇到其他元素,气-1;如果气等于0,则更新气味为新元素的气;
        // 最后验证一下这个气是否为所求:因为留下来的气,对应元素出现次数可以少于一半。
	int MoreThanHalfNum_Solution(vector<int> numbers){
		int n = numbers.size();
		if (n == 0) return 0;
		
		int num = numbers[0], count = 1;
		for (int i = 1; i < n; i++){
			if (numbers[i] == num) {
				count++;
			}
			else{
				count--;
			}
			if (count == 0){
				num = numbers[i];
				count = 1;
			}
		}

		//verification
		count = 0;
		for (int i = 0; i < n; i++){
			if (numbers[i] == num){
				count++;
			}
		}
		if (count * 2 > n){
			return num;
		}
		return 0;
	}
};

斐波那契数列

大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项。
n<=39

这么直接的问fibonacci,显然是迭代计算。递归的问题在于重复计算,而迭代则避免了这一点:递归是自顶向下,会重复产生子问题;而迭代是自底向上,一步一个脚印,没有重复的子问题。

class Solution {
public:
    int Fibonacci(int n) {
		if(n<=1) return n;
        int a = 0; // f(0)
        int b = 1; // f(1)
        for(int i=2; i<=n; i++){
            b = a + b;
            a = b - a;
        }
        return b;
    }
};

和为S的正整数序列

双指针问题。似曾相识。

/*
小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数)。没多久,他就得到另一组连续正数和为100的序列:18,19,20,21,22。现在把问题交给你,你能不能也很快的找出所有和为S的连续正数序列? Good Luck! 
*/
class Solution {
public:
	vector<vector<int> > FindContinuousSequence(int sum) {
		vector<vector<int> > vs;
		int plow = 1, phigh = 2;
		while (plow < phigh){
			int cur_sum = (plow + phigh) * (phigh - plow + 1) / 2;
			if (cur_sum < sum){
				phigh += 1;
			}

			if (cur_sum == sum){
				vector<int> vt;
				for (int i = plow; i <= phigh; i++){
					vt.push_back(i);
				}
				vs.push_back(vt);
				plow += 1;
			}

			if (cur_sum > sum){
				plow += 1;
			}
		}
		return vs;
	}
};

二叉搜索树与双向链表

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

题目的描述不是很习惯。题目的意思是把二叉树从左到右遍历,相当于双向链表的遍历。

其实就是让二叉树在x方向上的投影点,顺序输出。那么其实就是中序遍历。递归版本如下:

struct TreeNode{
	int val;
	struct TreeNode* left;
	struct TreeNode* right;
	TreeNode(int x) :
		val(x), left(NULL), right(NULL){

	}
};

class Solution{
public:
	TreeNode* Convert(TreeNode* root){
		if (root == NULL) return root;
		root = ConvertNode(root);
		while (root->left){
			root = root->left;
		}
		return root;
	}
	TreeNode* ConvertNode(TreeNode* root){
		if (root == NULL) return root;
		if (root->left){
			TreeNode* left = ConvertNode(root->left);  //重要。是递归而不是仅仅取一个节点。
			while (left->right){
				left = left->right;
			}
			left->right = root;
			root->left = left;
		}
		if (root->right){
			TreeNode* right = ConvertNode(root->right);  //重要。是递归而不是仅仅取一个节点。
			while (right->left){
				right = right->left;
			}
			right->left = root;
			root->right = right;
		}
		return root;
	}
};

旋转数组的最小数字

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
例如数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1。
NOTE:给出的所有元素都大于0,若数组大小为0,请返回0。

考察二分查找。虽然是有序数组进行了旋转的,但实际上依然可以用二分的做法来找。

	int minNumberInRotateArray(vector<int> rotateArray){
		int low = 0;
		int high = rotateArray.size() - 1;
		while (low < high){
			int mid = low + (high - low) / 2;
			if (rotateArray[mid] > rotateArray[high]){
				low = mid + 1;
			}
			else if (rotateArray[mid] == rotateArray[high]){
				high = high - 1;;
			}
			else{
				high = mid;
			}
		}
		return array[low];
	}

数组中只出现一次的数字


/*
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。

思路:
如果是只有一个数字出现一次,那么所有数字做异或就得到结果;
现在有两个数字x,y分别出现一次,其他数字出现两次,那么所有数字异或的结果是result = x^y
x^y肯定不等于0,那么找其二进制表示中不等于0的一个位,比如从右到左第一个位置好了,那么用这个位置能区分开来这两个数字,以及其他的数字,每两个一样的数字都处于同一边。
*/
class Solution {
public:
	void FindNumsAppearOnce(vector<int> data, int* num1, int *num2) {
		int res = data[0];
		for (int i = 1; i < data.size(); i++){
			res = res ^ data[i];
		}
		int cnt = 0;
		while (res % 2 != 1){
			res = res >> 1;
			cnt = cnt + 1;
		}
		*num1 = *num2 = 0;
		for (int i = 0; i < data.size(); i++){
			if ((data[i] >> cnt) & 1){
				*num1 ^= data[i];
			}
			else{
				*num2 ^= data[i];
			}
		}
	}
};

合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

class Solution{
public:
	ListNode* Merge(ListNode* pHead1, ListNode* pHead2){
		ListNode* result = NULL;
		ListNode* current = NULL;

		if (pHead1 == NULL){
			return pHead2;
		}
		if (pHead2 == NULL){
			return pHead1;
		}

		while (pHead1 && pHead2){
			if (pHead1->val <= pHead2->val){
				if (result == NULL){
					current = result = pHead1;
				}
				else{
					current->next = pHead1;
					current = current->next;
				}
				pHead1 = pHead1->next;
			}
			else{
				if (result == NULL){
					current = result = pHead2;
				}
				else{
					current->next = pHead2;
					current = current->next;
				}
				pHead2 = pHead2->next;
			}
		}

		if (pHead1){
			current->next = pHead1;
		}
		if (pHead2){
			current->next = pHead2;
		}

		return result;
	}
};


栈的压入、弹出序列

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

思路是用一个栈来重新一一压入“压入序列”中的元素,每次压入后都和要验证的弹出序列的开头若干元素一一比较,如果一致,那么这个临时栈就弹出元素,否则进入下一轮压入新元素和对比验证。
最后,看看临时栈是否为空,如果为空则说明要验证的弹出序列是valid,否则不valid。

class Solution{
public:
	bool IsPopOrder(vector<int> pushV, vector<int> popV){
		stack<int> s;
		int posV = 0;
		for (int i = 0; i < pushV.size(); i++){
			s.push(pushV[i]);
			while (!s.empty() && s.top() == popV[posV]){
				s.pop();
				posV += 1;
			}
		}
		if (s.empty()){
			return true;
		}
		return false;
	}
};

数据流中的中位数

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

最开始的思路就是用map或者set存储。习惯写python就想直接用median的key去访问median,但是C++ STL的map或者set没有key这个东西,如果用迭代器那么访问元素复杂度是O(n)

看到很多解法是用两个堆来做,一个最大堆,一个最小堆,一开始不理解。后来发现这样的好处是把数据总体切分为两部分,一部分(最大堆)所有元素都比另一部分(最小堆)小。然后当有新元素需要insert的时候,根据现有元素总数奇偶,决定先压入哪个堆,然后弹出一个元素,弹出元素放入另一个堆。

最后的答案处理,根据元素总数奇偶,决定从两个堆分别取还是从特定的那个取。

class Solution{
public:
	void Insert(int num){
		if (maxS.size() == minS.size()){
			maxS.insert(num);
			minS.insert(*maxS.begin());
			maxS.erase(maxS.begin());
		}
		else{
			minS.insert(num);
			maxS.insert(*minS.begin());
			minS.erase(minS.begin());
		}
	}

	double GetMedian(){
		int num = maxS.size() + minS.size();
		
		double median;
		if ((num&1)==1){
			median = *minS.begin();
		}
		else{
			median = (*maxS.begin() + *minS.begin()) / 2.0;
		}
		return median;
	}
private:
	multiset<int, greater<int> > maxS;
	multiset<int, less<int> > minS;
};

反转链表

输入一个链表,反转链表后,输出链表的所有元素。

题目考察链表反转,但是挖坑不是反转本身,而是题目的描述再次不清晰:什么叫“反转链表后输出链表所有元素”?给的代码框架只有一个函数ReverseList,返回值类型是ListNode*,输出不输出和我有什么关系?

class Solution{
public:
	ListNode* ReverseList(ListNode* pHead){
		if (pHead == NULL){
			return NULL;
		}
		if (pHead->next == NULL) {
			return pHead;
		}

		ListNode* pBefore = pHead;
		ListNode* p = pHead->next;
		ListNode* pAfter = p->next;

		while (pAfter != NULL){
			p->next = pBefore;
			pBefore = p;
			p = pAfter;
			pAfter = pAfter->next;
		}
		p->next = pBefore;
		pHead->next = NULL;   //这句一定要加上,因为逆序后再遍历,需要判断出链表结束,也就是节点的next等于NULL
		return p;
	}
};

左旋转字符串

汇编语言中有一种移位指令叫做循环左移(ROL),现在有个简单的任务,就是用字符串模拟这个指令的运算结果。对于一个给定的字符序列S,请你把其循环左移K位后的序列输出。例如,字符序列S=”abcXYZdef”,要求输出循环左移3位后的结果,即“XYZdefabc”。是不是很简单?OK,搞定它!

基本的字符串子串和拼接。C++里的string的substr(int start_index, int count) 起始索引和数量。

这种题目就喜欢在细节上挖坑,比如字符串长度为0,你怎么搞?要能够应对这种情况。过分专注细节,这样的任务应当交给机器去做。

class Solution {
public:
	string LeftRotateString(string str, int n){
		int len = str.length();
		if (len == 0){
			return "";
		}
		n = n%len;
		string result = str.substr(n, len - n) + str.substr(0, n);
		return result;
	}
};

数字在排序数组中出现的次数

统计一个数字在排序数组中出现的次数。

首先吐槽下出题人的用词,啥叫排序数组?“排序”是个动词好么,“有序”作为一个形容词表示状态,修饰“数组”,才是合适的。

题目考察二分查找,首先找到指定数字最先出现的位置,然后找到最后出现的位置,他们的距离+1就是个数。

class Solution14{
public:
	int GetNumberOfK(vector<int> data, int k){
		if (data.empty()){
			return 0;
		}
		int first = GetFirstIndex(data, k, 0, data.size() - 1);
		int last = GetLastIndex(data, k, 0, data.size() - 1);
		if (first > -1 && last > -1){
			return last - first + 1;
		}
		return 0;
	}
	int GetFirstIndex(vector<int>& data, int k, int start, int end){
		if (start > end) return -1;
		int mid = start + (end - start) / 2;
		if (data[mid] == k){
			if (mid == start || data[mid-1]!=k){
				return mid;
			}
			else{
				end = mid - 1;
			}
		}
		else{
			if (data[mid]>k){
				end = mid - 1;
			}
			else{
				start = mid + 1;
			}
		}
		return GetFirstIndex(data, k, start, end);
	}
	int GetLastIndex(vector<int>& data, int k, int start, int end){
		if (start > end) return -1;
		int mid = start + (end - start) / 2;
		if (data[mid] == k){
			if (mid == end || data[mid + 1] != k){
				return mid;
			}
			else{
				start = mid + 1;
			}
		}
		else{
			if (data[mid]>k){
				end = mid - 1;
			}
			else{
				start = mid + 1;
			}
		}
		return GetLastIndex(data, k, start, end);
	}
};

孩子们的游戏(圆圈中最后剩下的数)

每年六一儿童节,牛客都会准备一些小礼物去看望孤儿院的小朋友,今年亦是如此。HF作为牛客的资深元老,自然也准备了一些小游戏。其中,有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数....这样下去....直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的“名侦探柯南”典藏版(名额有限哦!!_)。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0到n-1)

坑爹的题目描述,细节都不讲清楚。当输入n=0,m=0的时候,要输出什么?题目没有给,测试用例显示为-1。

思路是用递归,将第n次的问题转化为第n-1次的问题的结果,再做一个编号的变换。比如第一次执行后,编号m%n-1的出局,下一次从编号m%n的开始,这个编号又可以当作子问题“n-1个人玩这个游戏,参数为m”的编号为0的元素。那么n-1规模问题的解加入知道了为x,则对应到规模为n的问题上她的编号就是x'=(x+m)%n。

递归出来的表达式就是:f(1)=0, f(i)=(f(i-1)+m)%i
然后加上坑爹的f(0)=-1

递归写法:

class Solution{
public:
	int LastRemaining_Solution(int n, int m){
		if (n == 1){
			return 0;
		}
		int t = LastRemaining_Solution(n - 1, m);
		int result = (t + m) % n;
		return result;
	}
};

迭代写法:

class Solution{
public:
	int LastRemaining_Solution(int n, int m){
        if(n==0){
            return -1;
        }
		int s=0;
       for(int i=2;i<=n;i++){
           s=(s+m)%i;
       }
       return s;
	}
    

};

包含min函数的栈

定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。

class Solution{
public:
	void push(int value){
		v.push_back(value);
	}
	void pop(){
		v.pop_back();
	}
	int top(){
		return v[v.size()-1];
	}
	int min(){
		int m = v[0];
		for (int i = 1; i < v.size(); i++){
			if (v[i] < m){
				m = v[i];
			}
		}
		return m;
	}
private:
	vector<int> v;
};

递增数组中找到和为S的(最小)两个元素

题目描述
输入一个递增排序的数组和一个数字S,在数组中查找两个数,是的他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。
输出描述:
对应每个测试案例,输出两个数,小的先输出。

首先一个坑是,返回值的问题。题目描述说返回两个数字,并且小的在前面,大的在后面。
C++的返回值明明只有一个啊?那就要放到数组或者其他容器里面,或者作为一个对象返回。。本题中使用vector容器进行存储。

其次容易想到两边夹的方法,直接写就好了

class Solution {
public:
    vector<int> FindNumbersWithSum(vector<int> a,int sum) {
        int i=0, j=a.size()-1;
        vector<int>res;
        while(i<j){
            int t = a[i] + a[j];
            if(t==sum){
                res.push_back(a[i]);
                res.push_back(a[j]);
                break;
            }
            if(t<sum){
                i+=1;
            }else{
                j-=1;
            }
        }
        return res;
    }
};

链表找环入口

题目描述
一个链表中包含环,请找出该链表的环的入口结点。

初步想法是每个节点做几个标记,表示是否被访问过,那么遍历链表的时候就知道哪个被访问到了。但是不会实现。

另一个直觉是判断链表有环的算法中出现过的策略,分别按1x和2x速度遍历,总会相遇。假设环长为n。
容易知道,当1x的指针p1和2x的指针p2相遇时,p1走了x步,p2走了2x步,而p2比p1多走的,有两部分:(1)环内部,p1还没有走过的;(2)换内部,p1和p2重合的
这两部分加起来就是整个环。那么其实p2比p1多走的就是这么一个环的距离,也就是说:

2x-x = n  ==> n=x

即:p1当前走了一个环长度的距离。

下图表示了p1和p2相遇在B点的情况,其中A点开始顺时针方向回到A的轨迹就是环形,长度为n。

不妨假设链表总长为L,那么:
CA+A环=CA+n=L ==>L-n=CA
CB=x=n=A环
==> BA弧=L-CB=L-n=CA

也即:BA弧=CA,那么指针p1从B继续走,而指针p2从链表起点C从新开始(但是步长这次取1),则当p2到达A点时,p1也到达A点。
对应到算法中,当1x和2x套圈时,p1和p2按照这个策略继续做while循环,直到再次相遇,就找到了环的入口。

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
        if(pHead==NULL){
            return NULL;
        }
        if(pHead->next==NULL){
            return NULL;
        }
        ListNode* p1=pHead;
        ListNode* p2=pHead;
        while(p2!=NULL && p2->next!=NULL){
            p1=p1->next;
            p2=p2->next->next;
            if(p1==p2){
                p1 = pHead;
                while(p1!=p2){
                    p1=p1->next;
                    p2=p2->next;
                }
                if(p1==p2){
                    return p1;
                }
            }
        }
        return NULL;
    }
};

中序遍历下一个节点

题目描述
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

/*
struct TreeLinkNode {
    int val;
    struct TreeLinkNode *left;
    struct TreeLinkNode *right;
    struct TreeLinkNode *next;
    TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
        
    }
};
*/
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
        if(pNode == NULL){
            return NULL;
        }
    	if(pNode->right==NULL){
            while(pNode->next!=NULL){
                TreeLinkNode* pRoot = pNode->next;
                if(pRoot->left==pNode){
                    return pRoot;
                }
                pNode = pNode->next;
            }
            return NULL;
        }else{
            pNode = pNode->right;
            while(pNode->left != NULL){
                pNode = pNode->left;
            }
            return pNode;
        } 
    }
};

数值的整数次方

题目描述
给定一个double类型的浮点数base和int类型的整数exponent。求base的exponent次方。

上次面试让写过这个...想要1A的话还是需要熟练些才行。这次写依然没考虑全边缘情况,不过比上次好一些。

class Solution {
public:
    double Power(double base, int exponent) {
        if(exponent==0){
            return 1;
        }
    	if(exponent==1){
            return base;
        }
        if(exponent<0){
            return Power(1/base, -exponent);
        }
        if(exponent%2==1){
            return base*Power(base, exponent-1);
        }

        double t = Power(base, exponent/2);
        return t*t;

    }
};

判断对称二叉树

题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

思路上还是广度优先搜索(BFS)来做的。BFS是依托于STL的queue作为容器作为存储空间的,并且树的每一层节点的处理都放到一个内部的while循环中;
使用一个vector存储层内节点元素值,然后判断这个vector是否为对称的。如果对称,还要考虑元素个数,除了第一层之外都应该是偶数个元素。这样的话没有考虑到null节点的处理。

如果节点为null则存储一个0(假定样例节点值都是大于0的),这样就不用考虑vector元素个数的问题了。

代码如下:

class Solution {
public:
	bool isSymmetrical(TreeNode* pRoot)
	{
		if (pRoot == NULL){
			return true;
		}

		queue<TreeNode*> Q;
		Q.push(pRoot);

		bool first_layer = true;

		while (!Q.empty()){
			int lo = 0, hi = Q.size();
			vector<int> v;
			while (lo++ < hi){
				TreeNode* t = Q.front();
				Q.pop();
				if (t == NULL){
					v.push_back(0);
					continue;
				}
				else{
					v.push_back(t->val);
				}
				if (t->left){
					Q.push(t->left);
				}
				else{
					Q.push(0);
				}
				if (t->right){
					Q.push(t->right);
				}
				else{
					Q.push(0);
				}
			}
			for (int i = 0; i < v.size() / 2; i++){
				if (v[i] != v[v.size() - 1 - i]){
					return false;
				}
			}
		}
		return true;
	}
};

找到数组中重复的数字

题目描述
在一个长度为n的数组里的所有数字都在0到n-1的范围内。 数组中某些数字是重复的,但不知道有几个数字是重复的。也不知道每个数字重复几次。请找出数组中任意一个重复的数字。 例如,如果输入长度为7的数组{2,3,1,0,2,5,3},那么对应的输出是重复的数字2或者3。

用STL的map果然就是很爽。想到大一时候期末考试就用的map,哈哈。

class Solution {
public:
    // Parameters:
    //        numbers:     an array of integers
    //        length:      the length of array numbers
    //        duplication: (Output) the duplicated number in the array number
    // Return value:       true if the input is valid, and there are some duplications in the array number
    //                     otherwise false
    bool duplicate(int numbers[], int length, int* duplication) {
        map<int,int> mapper;
        for(int i=0; i<length; i++){
            mapper[numbers[i]] += 1;
            if(mapper[numbers[i]]>1){
                *duplication = numbers[i];
                return true;
            }
        }
        return false;
    }
};

找到字符串中第一个只出现一个的字符

题目描述
请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
输出描述:
如果当前字符流没有存在出现一次的字符,返回#字符。

class Solution
{
private:
	vector<char> vec;
    map<char, int> mapper;
public:
  //Insert one char from stringstream
    void Insert(char ch)
    {
        vec.push_back(ch);
        mapper[ch] ++;
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
        for(int i=0; i<vec.size(); i++){
            if(mapper[vec[i]]==1){
                return vec[i];
            }
        }
        
        return '#';
    }

};

逐层打印二叉树

题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

乍一看就是一个BFS,但是因为太久没刷题都忘记了要使用queue来作为空间存储容器了。

先参考milolip的代码,写出这样的solution:


class Solution {
public:
	vector<vector<int> > Print(TreeNode* pRoot) {
		vector<vector<int> > res;
        if(pRoot==NULL){
            return res;
        }
        
		queue<TreeNode*> Q;
		Q.push(pRoot);
		Q.push(NULL);
		vector<int> v;
		v.push_back(pRoot->val);
		res.push_back(v);
		v.clear();
		while (!Q.empty()){
			TreeNode* node = Q.front();
			Q.pop();
			if (node != NULL){
				//v.push_back(node->val);
				//cout << node->val << ends;
				if (node->left){
					Q.push(node->left);
					v.push_back(node->left->val);
				}
				if (node->right){
					Q.push(node->right);
					v.push_back(node->right->val);
				}
			}
			else if (!Q.empty()){
				//cout << "test " << endl;
				Q.push(NULL);
				res.push_back(v);
				v.clear();
				//cout << endl;
			}
		}
		return res;
	}
};

上面的代码并不太简洁的样子。

另一种写法是从评论区copy来的,又简洁,又非常直观清晰。两层while的嵌套,正好对应到数的层次遍历以及层内逐点遍历。而这种双层嵌套的循环其实并没有增加复杂度,和原来的复杂度是一样的。

class Solution_11 {
public:
	vector<vector<int> > Print(TreeNode* pRoot) {
		vector<vector<int> > res;

		if (pRoot == NULL){
			return res;
		}
		
		queue<TreeNode*> q;
		q.push(pRoot);

		while (!q.empty()){
			int lo = 0, hi = q.size();
			vector<int> v;
			while (lo++ < hi){
				TreeNode *t = q.front();
				q.pop();
				v.push_back(t->val);
				if (t->left){
					q.push(t->left);
				}
				if (t->right){
					q.push(t->right);
				}
			}
			res.push_back(v);
		}
		return res;
	}
};

测试代码;

void main_solution_11(){
	Solution_11 s = Solution_11();
	TreeNode* a = new TreeNode(8);
	TreeNode* b1 = new TreeNode(6);
	TreeNode* b2 = new TreeNode(10);
	TreeNode* c1 = new TreeNode(5);
	TreeNode* c2 = new TreeNode(7);
	TreeNode* c3 = new TreeNode(9);
	TreeNode* c4 = new TreeNode(1);

	a->left = b1;
	a->right = b2;

	b1->left = c1;
	b1->right = c2;
	b2->left = c3;
	b2->right = c4;

	vector<vector<int> > q = s.Print(a);
	for (int i = 0; i < q.size(); i++){
		for (int j = 0; j < q[i].size(); j++){
			if (j > 0){
				cout << " ";
			}
			cout << q[i][j];
		}
		cout << endl;
	}
}

int main(){
	main_solution_11();
	return 0;
}

连续子数组的最大和

题目描述
HZ偶尔会拿些专业问题来忽悠那些非计算机专业的同学。今天测试组开完会后,他又发话了:在古老的一维模式识别中,常常需要计算连续子向量的最大和,当向量全为正数的时候,问题很好解决。但是,如果向量中包含负数,是否应该包含某个负数,并期望旁边的正数会弥补它呢?例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。你会不会被他忽悠住?(子向量的长度至少是1)

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
        vector<int> B(array.size());
        int pre_max = 0;
        for(int i=0; i<B.size(); i++){
            B[i] = pre_max + array[i];
            if(B[i]<0){
                pre_max = 0;
            }else{
                pre_max = B[i];
            }
        }
        int the_max = array[0];
        for(int i=1; i<B.size(); i++){
            if(B[i]>the_max){
                the_max = B[i];
            }
        }
        
    	return the_max;
    }
};

整数中1出现的次数

题目描述
求出113的整数中1出现的次数,并算出1001300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数。

直接暴力可以过。但是不优美。
尝试推导公式,思路是递归求解,发现假如n都是999,99999这种全9的数字会很好处理:f(n)=g(t)*f(h(n)), 其中t表示n的第一个位,h(n)表示n去掉第一位,g(t)要特别考虑1的情况。

但是n很可能连一个9都没有。没关系,那就把n切分成两部分,一部分是能用99999这种处理的,另一部分是再单独计算的。

而其实这两个部分是可以合并的,99999的情况是后者的特例而已。

利用数字特点和规律,计算每一位上1出现的次数:
例如百位上1出现次数,数值n在百位上的值是curNum则:

if(curNum==0)
1出现的次数等于比百位更高位数100。例如n=1023,高位数就是1,百位上出现1的次数是1100;

if(curNum==1)
1出现的次数等于比百位更高位数100,再加上低位上的数,再加1。例如n=1123,高位数就是1,低位数是23,百位上出现1的次数是1100+23+1;

if(curNum>1)
1出现的次数等于比百位更(高位数+1)100,例如n=1223,高位数就是1,次数百位上出现1的次数是(1+1)100;

而其实这种策略是适用于各个位的,不仅仅是在百位上。那么直接上码吧:

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
    	int count=0;
        int factor=1;
        while(n/factor!=0){
            int curNum = (n/factor)%10;
            int lowNum = n%factor;
            int highNum = n/(factor*10);
            
            if(curNum==0){
                count += factor*highNum;
            }
            else if(curNum==1){
                count += factor*highNum + lowNum + 1;
            }
            else{
                count += factor*(highNum + 1);
            }
            factor *= 10;
        }
        return count;
    }
};

参考:[https://troywu0.gitbooks.io/interview/content/整数中出现1的次数(从1到n整数中1出现的次数).html]

链表的第一个公共节点

题目描述
输入两个链表,找出它们的第一个公共结点。

这题目是指针相关的题目。初步要判断出来,有公共节点的两个指针,应当是链表后半部分相同。这样的话,当遇到第一个相同节点(不是node的val相同,而是node完全相同),则找到了结果。

网上找到一种很简介的写法。稍微分析了下,其实就是将两个链表L1和L2进行了拼接,得到L1+L2和L2+L1两个拼接结果。这两个长度相同,那么one node by one node做判断就好了。

struct ListNode{
	int val;
	struct ListNode* next;
	ListNode(int x) :val(x), next(NULL){
	}
};

class Solution_8{
public:
	ListNode* FindFirstCommonNode(ListNode* pHead1, ListNode* pHead2){
		ListNode* p1 = pHead1;
		ListNode* p2 = pHead2;
		while (p1 != p2){
			
			if (p1 == NULL){
				cout << "p1 is NULL";
			}
			else{
				cout << "p1 is " << p1->val;
			}
			cout << ", ";
			if (p2 == NULL){
				cout << "p2 is NULL";
			}
			else{
				cout << "p2 is " << p2->val;
			}
			cout << endl;

			p1 = (p1 == NULL ? pHead2 : p1->next);
			p2 = (p2 == NULL ? pHead1 : p2->next);
		}
		return p1;
	}
};

第一眼看到这么短的代码不敢相信答案是对的。然后手动试着模拟运行了几个样例,发现是没有问题的。

第一种情况:有相同节点
L1:1 2 3 4 5
L2:3 4 5
因为两个链表每次都是移动一个步长,尽管两个链表长度往往不一样,但是L1拼接L2、L2拼接L1,这两种拼接后的链表长度是一致的,就很容易发现相同节点了。

第二种情况:没有相同节点
那么上一种情况的做法,会使得最终找到的节点是NULL,也就是分别到了“拼接后的链表尾部”。

测试一下吧:

int main_Solution_8_1(){
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);
	ListNode* n5 = new ListNode(5);
	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = n5;

	Solution_8 s = Solution_8();
	ListNode* res = s.FindFirstCommonNode(n1, n2);
	cout << res->val << endl;
	return 0;
}

int main_Solution_8_2(){
	ListNode* n1 = new ListNode(1);
	ListNode* n2 = new ListNode(2);
	ListNode* n3 = new ListNode(3);
	ListNode* n4 = new ListNode(4);
	ListNode* n5 = new ListNode(5);
	ListNode* n6 = new ListNode(6);
	ListNode* n7 = new ListNode(7);
	n1->next = n2;
	n2->next = n3;

	n4->next = n5;
	n5->next = n6;
	n6->next = n7;

	Solution_8 s = Solution_8();
	ListNode* res = s.FindFirstCommonNode(n1, n4);
	if (res != NULL){
		cout << res->val << endl;
	}
	else{
		cout << "res is NULL " << endl;
	}
	return 0;
}

int main(){
	main_Solution_8_2();
	return 0;
}

铺地砖方案数

我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?

又是斐波那契...稍微变形一下。

class Solution {
public:
    int rectCover(int number) {
        if(number==0 || number==1 || number==2){
            return number;
        }
        return rectCover(number-1) + rectCover(number-2);  
    }
};

青蛙跳台阶

题目描述
一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

很裸的斐波那契数列。

class Solution {
public:
    int jumpFloor(int number) {
        if(number<=0 || number==1){
            return 1;
        }
        return jumpFloor(number-1) + jumpFloor(number-2);
    }
};

判断平衡二叉树

题目描述
输入一棵二叉树,判断该二叉树是否是平衡二叉树。

考察平衡树的概念和递归的使用。平衡树是指,树中的每个节点的左右子树的高度差小于等于1。

class Solution {
public:
    bool IsBalanced_Solution(TreeNode* pRoot) {
		if(pRoot == NULL){
            return true;
        }
        if(IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right)){
            int left_depth = getDepth(pRoot->left);
            int right_depth = getDepth(pRoot->right);
            if(abs(left_depth-right_depth)<=1){
                return true;
            }
            return false;
        }
        return false;
    }
    int getDepth(TreeNode* pRoot){
        if(pRoot == NULL){
            return 0;
        }
        return 1+max(getDepth(pRoot->left), getDepth(pRoot->right));
    }
};

前n项和不准用通解和各种判断

题目描述
求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

这题目简直没事找事...为啥这么说,因为没有限制的话用等差数列求和公式直接算出结果,有限制的话是希望用递归的思想来做。但是明明有通式了还用递归做,又傻又费内存。

换个思路,先取log再e回去,这样避免了乘法。除法的话因为只需要除以2,那就用移位操作。

提交发现有小数的坑,需要+0.5再取整。

class Solution {
public:
	int Sum_Solution(int n) {
		//return n*(n+1)/2;
		return multi(n, n + 1) >> 1;
	}
	int multi(int a, int b){
		// we can guarantee that both a and b is positive integers
		int res = int(pow(10, log10(a) + log10(b))+0.5);
		return res;
	}
};

统计整数二进制表示中1的个数

题目描述
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。

直观思路就是把二进制表示从右往左统计1的个数。直接想到移位操作来迭代处理。坑点在于负数的移位操作会填充1。有人贴出了逻辑移位操作,但还是麻烦。直接按照int的位数,32或64,做这么多次移位操作就好了,每次移位操作累计是否为1。int的位数是32还是64,可以写一个函数来做到,而不是硬编码。

class Solution {
public:
	int  NumberOf1(int n) {
		int cnt = 0;
		int i = 0;
		int int_bit_length = get_int_bit_length();
		while (i<int_bit_length){
			cnt += (n & 1);
			n = (n >> 1);
			i = i + 1;
		}
		return cnt;
	}
	int get_int_bit_length(){
		int a = 1;
		int bit_cnt = 0;
		while (a != 0){
			a = (a<<1);
			bit_cnt += 1;
		}
		return bit_cnt;
	}
};

双栈实现队列

题目描述
用两个栈来实现一个队列,完成队列的Push和Pop操作。 队列中的元素为int类型。

很基本的STL容器操作了,应该可以1A的,但是忘记返回值的时候,clang的报错感觉并不友好啊。。

class Solution
{
public:
    void push(int node) {
        while(!stack2.empty()){
            int val = stack2.top();
            stack2.pop();
            stack1.push(val);
        }
        stack2.push(node);
        while(!stack1.empty()){
            int val = stack1.top();
            stack1.pop();
            stack2.push(val);
        }
    }

    int pop() {
        int val = stack2.top();
        stack2.pop();
        return val;
    }

private:
    stack<int> stack1;
    stack<int> stack2;
};

数组乘积,不使用除法

题目描述
给定一个数组A[0,1,...,n-1],请构建一个数组B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...A[i-1]A[i+1]...A[n-1]。不能使用除法。

这题不准用除法,那么就转化。首先想到的是用“先取log再e回去”的做法,但是因为坑比较多,比如A[i]等于0的情况,log处理0不好弄。

但是这种思路并非一无是处。当发现log 0做不下去,就容易想到直接元素A[i]左右两侧各个元素的乘积left和right,它俩的乘积left x right就是最终的结果啊。

那么结果数组的每个元素B[i]就分解成左右两部分的乘积。代码就是网上找到的通用版本那个了。复杂度O(n^2)

class Solution {
public:
	vector<int> multiply(const vector<int>& A) {
		vector<int> B(A.size());
		
		vector<int> left(A.size());
		left[0] = 1;

		cout << "left:" << endl;
		for (int i = 1; i<A.size(); i++){
			left[i] = left[i - 1] * A[i-1];
		}
		for (int i = 0; i < A.size(); i++){
			cout << left[i] << " ";
		}
		cout << endl;

		cout << "right:" << endl;
		vector<int> right(A.size());
		right[A.size()-1] = 1;
		for (int i = A.size()-2; i>=0; i--){
			right[i] = right[i + 1] * A[i+1];
			//cout << right[i] << " ";
		}
		for (int i = 0; i < A.size(); i++){
			cout << right[i] << " ";
		}
		cout << endl;

		for (int i = 0; i<B.size(); i++){
			B[i] = left[i] * right[i];
		}
		return B;
	}
};

青蛙跳台阶II

一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。

其实题目很水...就是一个等比数列通项公式嘛
f(0)=1
f(1)=1
f(n)=f(0)+f(1)+...+f(n-1)

==>
f(n)=2*f(n-1) (when n>=2)

==>
f(n)=2^(n-1)

class Solution {
public:
    int jumpFloorII(int number){
        /*
        暴力写法
        if(number==0){
            return 1;
        }    
        if(number==1){
            return 1;
        }
        
	    int tot=0;
        for(int i=1; i<=number; i++){
           tot = tot + jumpFloorII(number-i);
        }
        return tot;
        */
        /*
        稍微写一下,发现递推式可以化简
        if(number==0 || number==1){
            return 1;
        }
        return 2*jumpFloorII(number-1);
        */
        //再精简一点,这不就是一个等比数列嘛
        return int(pow(2,number-1));
    }
};

二叉树镜像

剑指offer简单题,但是能一下写对也需要小心考虑细节。

题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
输入描述:
二叉树的镜像定义:源二叉树
8
/
6 10
/ \ /
5 7 9 11
镜像二叉树
8
/
10 6
/ \ /
11 9 7 5

/*
struct TreeNode {
	int val;
	struct TreeNode *left;
	struct TreeNode *right;
	TreeNode(int x) :
			val(x), left(NULL), right(NULL) {
	}
};*/
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
        if (pRoot == NULL){
            return;
        }
        Mirror(pRoot->left);
        Mirror(pRoot->right);
	TreeNode* t_node = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = t_node;
    }
};

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

弱菜刷题还是刷中文题好了,没必要和英文过不去,现在的重点是基本代码能力的恢复。

【题目】
剑指offer

写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号。

【思路】
直觉想到用二进制的位运算。最后写出来是一个迭代的过程。
每次迭代先计算x和y的和但不处理进位,那么相当于做异或,得到res1
然后处理进位问题,相当于计算与运算,得到res2
那么res2左移1位,再加到res1上,则整个运算的最终结果转化为res1+(res2<<1)
因为res2做左移,总会减小到0,那时候的res1就是最终结果

class Solution{
public:
	int Add(int x, int y){
		int sum;
		int carry;

		while (true){
			sum = x^y;
			carry = (x&y) << 1;
			y = carry;
			x = sum;
			if (y == 0){
				break;
			}
		}
		return sum;
	}
};
posted @ 2017-04-03 23:22  ChrisZZ  阅读(405)  评论(0编辑  收藏  举报