PAT题解汇总

剑指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;
};
posted @ 2017-11-09 12:26  ChrisZZ  阅读(1466)  评论(0编辑  收藏  举报