首页 > 技术文章 > 重刷剑指offer

lxy-xf 2019-09-13 11:24 原文

数组中重复的数字

题目描述

在一个长度为 n 的数组里的所有数字都在 0 到 n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字是重
复的,也不知道每个数字重复几次。请找出数组中任意一个重复的数字。

思路:

数组大小为n,数字范围为0~n-1,也就是说如果数组不重复,则排序后每个数组i上的元素就应该是i

为此,我们可以模拟这个排序的过程,如果i位置上的元素不为i,则把它与nums[i]位置的元素进行交换,这相当于把nums[i]放到他该去的地方。如果nums[i]位置的元素已经是i了,则这刚好就是重复的数字;否则将交换到i位置上的数字继续这个过程,如果交换过来的数等于i,则说明这个数放在了他应该放的地方,继续处理i+1上的数,如果仍然不等,则将这个换来的数放到他应该放的地方....最后,对于i位置来说,要么找到了值为i的元素,要么找到了重复的元素,是不可能出现无限循环的,因为无限循环要求换来的数字与i位置的数字相等,但这时候已经判定重复并跳出返回了。

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) {
        if(length<=1)
			return false;
		int p1=0;
		while(p1<length)
		{
			if(numbers[p1]!=p1)
			{
				while(numbers[p1]!=p1)
				{
					int right=numbers[numbers[p1]];
					if(numbers[p1]==right)
					{
						*duplication=right;
						return true;
					}
					swap(numbers[numbers[p1]],numbers[p1]);
				}
			}
			p1++;
		}
		return false;
    }
	void swap(int& a,int& b)
	{
		int tmp=a;
		a=b;
		b=tmp;
	}
};

  

二维数组中的查找

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:
一开始想用两次二分,这实际上是不可行的
比如:
7
[1,2,8,9]
[2,4,9,12]
[4,7,10,13]
[6,8,11,15]
实际上7并不是出现在第四行
问题出现在不能保证比数组头元素大的元素一定出现在这一行,不能保证上一行的某个元素一定会大于这一行的头元素,因为只能保证头元素大于上一行的头元素。对上一行的头元素来说相当于有两个递增方向。只有每行的头都大于上一行的尾,也就是严格单调递增时,可以这样写 
 
实际上只要找到只有一个方向递增,一个方向递减的方向,从这个位置开始搜索,即相当于遍历一个有序数组。这个位置在左下角或右上角。左下角向右递增,向上递减;右上角向左递减,向下递增。
class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        auto& nums=array;
		if(nums.empty()||nums[0].empty())
			return false;
		int row=nums.size()-1;
		int col=0;
		while(row>=0&&col<nums[row].size())
		{
			if(nums[row][col]==target)
				return true;
			else if(nums[row][col]<target)
				col++;
			else
				row--;
		}
		return false;
    }
};

  

替换空格

题目描述

请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为We Are Happy.则经过替换之后的字符串为We%20Are%20Happy。
思路:
最优解当然是不适用空间复杂度
首先计算拓展后的空间大小,然后用2个指针指向2个尾,逐个进行复制。
唯一值得注意的是,由于传进来的是str,因此尾部要取'\0'的位置,因为也要把'\0'也拷进去。
因此2个尾部分别是:

int pLast=len+count*2;
int pCur=len;

而不是

int pLast=len+count*2-1;
int pCur=len-1;

虽然这一种也能实现,但少了个'\0',需要自己在补一个。

class Solution {
public:
	void replaceSpace(char *str,int length) {
		int len=length;
		int count=0;
		for(int i=0;i<len;i++)
		{
			if(str[i]==' ')
				count++;
		}
		int pLast=len+count*2;
		int pCur=len;
		while(pCur>=0&&pLast>pCur)
		{
			if(str[pCur]!=' ')
			{
				str[pLast]=str[pCur];
				pLast--;
			}
			else
			{
				str[pLast--]='0';
				str[pLast--]='2';
				str[pLast--]='%';
			}
			pCur--;
		}
		return ;
	}
};

  

从尾到头打印链表

题目描述

输入一个链表,按链表从尾到头的顺序返回一个ArrayList。
思路:
最简单的就是push进vector后reverse,如果不用这种方式,递归也可以
class Solution {
public:
    vector<int> printListFromTailToHead(ListNode* head) {
        vector<int> ans;
		if(!head)
			return ans;
		Core(ans,head);
		return ans;
    }
	void Core(vector<int>& in,ListNode* head)
	{
		if(!head)
			return;
		Core(in,head->next);
		in.push_back(head->val);
	}
};

  

重建二叉树★★★

思路很清晰的题,但容易出错,多写几次注意下细节,特别是传参长度的处理。

题目描述

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
要唯一的确定一个二叉树,必须知道两种遍历方式,且其中必须要有一个中序遍历,因为前序与后序只能分割出父与子的关系,而无法分割出左右子树的关系。
 
思路:
根据前序的第一个数,找到中序中的那个数的位置,然后将其分割为左右子树,相当于说前序提供父子关系,中序提供左右子树关系。
然后递归这个过程。
值得注意的是Core中的传参,其实只要前序的起点,中序的起点与长度就可以了,因为前序与中序的长度必须一致。如果每次传入前序的起点与终点,中序的起点与终点,则太复杂,也太麻烦。
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
		if(pre.empty()||pre.size()!=vin.size())
			return NULL;
		return reConstructCore(pre,vin,0,0,pre.size());
    }
	TreeNode* reConstructCore(vector<int>& pre,vector<int>& vin,int pstart,int vstart,int len)
	{
		if(len<=0)
			return NULL;
		int tmp=pre[pstart];
		int pos=0;
		for(;pos<len&&vin[pos+vstart]!=tmp;pos++);
		pos+=vstart;
		int left=pos-vstart;
		int right=len-left-1;
		TreeNode* root=new TreeNode(tmp);
		root->left=reConstructCore(pre,vin,pstart+1,vstart,left);
		root->right=reConstructCore(pre,vin,pstart+left+1,pos+1,right);
		return root;
	}
};

  

二叉树的下一个结点★★★

要考虑的情况很多,值得好好看看,体味下二叉树的结构

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
 其实就是分为多种情况:
1).当前节点是父亲节点的左子树时:
此时要遍历该节点的右子树:
如果右子树存在,则找到右子树根节点的最左边的一个节点
如果右子树不存在,以此点为根的树已经遍历完了,此时由于它是父节点的左子树,因此下一个应该遍历父节点
2).当前节点时父亲节点的右子树时:
此时要遍历该节点的右子树:
如果右子树存在,则找到右子树根节点的最左边的一个节点
如果右子树不存在,则不仅以此节点为根的的子树已经遍历完了,由于是父节点的右子树,相当于父节点也遍历完了;如果父节点是祖父节点的右子树,则相当于祖父节点也已经遍历完了。。。。。
因此要找到祖先节点中的某一个节点,该节点要么父亲节点时空,要么父亲节点的左子树的根就是这个节点。
当为选中的祖先节点的父节点为空,相当于已经遍历完了所有节点,返回NULL
如果非空,则相当于已经遍历完了该祖先节点父节点的左子树,此时应该返回该祖先的父亲节点。
 
class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
		if(!pNode)
			return NULL;
		return GetNextCore(pNode);
    }
	TreeLinkNode* GetNextCore(TreeLinkNode* node)
	{
		if(!node->next)
		{
			auto it=node->right;
			if(!it)
				return NULL;
			while(it->left)
				it=it->left;
			return it;
		}
		auto pLast=node->next;
		if(pLast->left==node)
		{
			if(node->right)
			{
				auto right=node->right;
				while(right->left)
					right=right->left;
				return right;
			}
			else
				return pLast;
		}
		else if(pLast->right==node)
		{
			if(node->right)
			{
				auto right=node->right;
				while(right->left)
					right=right->left;
				return right;
			}
			else
			{
				while(pLast->next&&pLast->next->right==pLast)
					pLast=pLast->next;
				if(pLast->next==NULL)
					return NULL;
				else
					return pLast->next;
			}
		}
		else
			return NULL;
	}
};

  这个分类讨论太多了,其实完全可以简化:

1).如果该节点存在右子树,则应该遍历右子树中的最左节点

2).如果不存在,则要找到某个节点,该节点是其父的左子节点(包括目前的这个节点)

这样就简化很多了

class Solution {
public:
    TreeLinkNode* GetNext(TreeLinkNode* pNode)
    {
		if(!pNode)
			return NULL;
		auto right=pNode->right;
		if(right)
		{
			while(right->left)
				right=right->left;
			return right;
		}
		else
		{
			while(pNode->next&&pNode->next->right==pNode)
				pNode=pNode->next;
			if(pNode->next)
				return pNode->next;
			else
				return NULL;
		}
    }
};

用两个栈实现队列

题目描述

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

思路:

栈的特点是先入后出,队列的特点是先入先出

现在给了两个栈,其实可以想象成这样一个情景:

Tail和Head相当于两个水管,水从tail中流入,从Head中流出,这样实际上就相当于实现了队列

我们在Tail中灌水,就相当于push进数,然后pop提出最先灌进去的数,则从Head中读出。

 

 

 

 如果Head现在是空,也就是说没有元素,则把Tail里的数全部转到Head中,当然顺序要变化,这也就相当于上图里用个管子把两个容器相连,然后把Tail底部的水抽到Head中

每次push都push到Tail中。

class Solution
{
public:
    void push(int node) {
		stack2.push(node);
    }
    int pop() {
        if(!stack1.empty())
		{
			auto ans=stack1.top();
			stack1.pop();
			return ans;
		}
		else
		{
			if(stack2.empty())
				return INT_MIN;
			while(!stack2.empty())
			{
				stack1.push(stack2.top());
				stack2.pop();
			}
			auto ans=stack1.top();
			stack1.pop();
			return ans;
		}
    }
private:
    stack<int> stack1;//head
    stack<int> stack2;//tail
};

  

用两个队列实现栈

两个队列:

某个队列pop到只剩一个元素,其他的元素PUSH到另一个队列。

一个队列:

push进后,将新元素前面的元素重新pop并push进队列。

矩形覆盖

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

思路:

矩形可以横着摆与竖着摆,竖着摆时只能组成2x1;而横着摆可以用2个组成一个2X2

因此2*n的矩形可以来自于:2*(n-1)的矩形加一个2X1,或来自于2*(n-2)的矩形加两个横着摆的2x1

class Solution {
public:
    int rectCover(int number) {
		if(number<=0)
			return 0;
		if(number<2)
			return number;
		vector<int> dp(number,0);
		dp[0]=1;
		dp[1]=2;
		for(int i=2;i<dp.size();i++)
			dp[i]=dp[i-1]+dp[i-2];
		return dp.back();
    }
};

  

旋转数组的最小数字★★★★★

题目描述

把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。
输入一个非递减排序的数组的一个旋转,输出旋转数组的最小元素。
思路:
将数组分为两部分,左部分为旋转点右侧的所有元素,右部分为包括旋转点在内的旋转点前的所有元素。
使用二分查找的方法,当mid位于左部分时,l=mid+1;当位于右部分时,h=mid(不减1是因为如果mid刚好指向旋转点,则减1则使h调到了左部分
现在还有一个问题是如何判定mid位于左部分还是右部分,如果nums[mid]>nums[h],则一定位于左部分;如果nums[mid]<nums[h],则一定位于右部分.如果nums[mid]=nums[h],则说不好。
因为如果是11101111,当0左与右都有1则无法判定到底是哪个部分
此时对h--,因为h--相当于缩减l与h的窗口,使重新计算的mid向左部分偏移,这个过程持续到直到nums[mid]>nums[h]或l=h(此时相当于l与h间都是同一种数字)
class Solution {
public:
    int minNumberInRotateArray(vector<int> rotateArray) {
        auto& nums=rotateArray;
		if(nums.size()==0)
			return 0;
		int l=0;int h=nums.size()-1;
		int mid=(l+h)/2;
		while(l<h)
		{
			mid=(l+h)/2;
			if(nums[mid]>nums[h])
				l=mid+1;
			else if(nums[mid]<nums[h])
				h=mid;
			else
				h--;
		}
		return nums[h];
    }
};

  

剪绳子★★★

题目描述
把一根绳子剪成多段,并且使得每段的长度乘积最大。

思路:

切割绳子时,可以将它看做切成两半,然后每半部分再决定切不切

比如10,可以先切成4,6,由于已经切了一次了,因此4和6均可以选择继续切或者不切,这样就有四种情况,取其中最大值即可

class Solution {
public:
    int integerBreak(int n) {
        vector<int> dp(n+1,0);
		dp[0]=1;
		dp[1]=1;
		for(int i=2;i<dp.size();i++)
		{
			for(int j=1;j<=i-j;j++)
				dp[i]=max(dp[i],max(dp[j],j)*max((i-j),dp[i-j]));
		}
		return dp.back();
    }
};

  

二进制中 1 的个数

题目描述

输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
思路:
1).用flag循环左移
由于负数的原因,用n右移会出现问题,因此对1个初值为1的数不断左移相与 
class Solution {
public:
     int  NumberOf1(int n) {
         int num=sizeof(n)*8;
		 int flag=1;
		 int count=0;
		 while(flag)
		 {
			 if((flag&n)==flag)
				 count++;
			 flag=flag<<1;
		 }
		 return count;
     }
};

  

2).n&(n-1)会使n最右边一个1变为0

这是因为,当n最右边的数为1时,与n-1相与可以让n的最后一位从1变为0

如果n最右边一位为0,则n-1会使n最优边1个1变为0,而是这个1右侧的0全变为1,再与n相与,则那个1及它右侧全变为0.

这样n&(n-1)将不断使右边的1变为0,直到n==0;

class Solution {
public:
     int  NumberOf1(int n) {
        int count=0;
        while(n)
        {
            count++;
            n&=n-1;
            
        }
         return count;
     }
};

  

数值的整数次方★★★

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

思路:

如果每次只乘base则速度太慢,因此考虑每次进行平方,如果剩余的指数不足以平方,则递归剩余的指数,重复这个过程。

注意exponent为负数的情况,要先判断,最后用1.0去除

class Solution {
public:
    double Power(double base, int exponent) {
		if(!base)
			return 0;
		if(!exponent)
			return 1;
		bool flag=false;
		if(exponent<0)
			flag=true;//判断exponent为负数的情况
		exponent=abs(exponent);
		auto ans=PowerCore(base,exponent,base);
		if(flag)
			ans=1.0/ans;
		return ans;
    }
	double PowerCore(double num,int exponent,double base)
	{
		if(!exponent)
			return 1;
		if(exponent==1)
			return exponent*base;
		int count=2;
		while(count<=exponent)
		{
			num*=num;
			exponent-=count;
			count*=2;
		}
		return num*PowerCore(num,exponent,base);
	}
};

  

打印从 1 到最大的 n 位数

题目描述
输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数即999。

思路:

模拟加法过程,当最低位满足10进1时,通过while一直进位。

pCur用于跟踪最高位。

class Solution
{
public:
	void display(int n)
	{
		if(!n)
			return;
		if(n==1)
		{
			for(int i=0;i<10;i++)
				cout<<i<<endl;
			return;
		}
		string str(n+1,'0');
		int pCur=str.size()-1;
		while(str[0]!='1')
		{
			cout<<str.data()+pCur<<endl;
			if(str.back()=='9')
			{
				int p=str.size()-1;
				while(str[p]=='9')
				{
					str[p]='0';
					p--;
				}
				if(pCur-p==1)
					pCur=p;
				str[p]+=1;
			}
			else
				str.back()+=1;
		}
		return;
	}
};

  

删除链表中重复的结点★★★

题目描述

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表1->2->3->3->4->4->5 处理后为 1->2->5
思路:
pCur指向的是无重复链表的最后一个节点,pre为pCur后一个节点,通过next找到第一个不与pre相等的节点,如果pre与next相邻,则说明无重复,否则令pCur指向next.
注意第一个元素就开始重复的情况,因此创建一个头节点。
class Solution {
public:
    ListNode* deleteDuplication(ListNode* pHead)
    {
		if(!pHead)
			return NULL;
		if(!pHead->next)
			return pHead;
		ListNode* head=new ListNode(100);
		head->next=pHead;
		ListNode* pCur=head;
		ListNode* pre=head->next;
		while(pre)
		{
			ListNode* next=pre->next;
			while(next&&next->val==pre->val)
				next=next->next;
			if(pre->next==next)
			{
				pCur=pre;
				pre=next;
			}
			else
			{
				pCur->next=next;
				pre=next;
			}
		}
		return head->next;
    }
};

  

正则表达式匹配★★★★★

题目描述
请实现一个函数用来匹配包括 '.' 和 '*' 的正则表达式。模式中的字符 '.' 表示任意一个字符,而 '*' 表示它前面的字符可以出现任意次(包含 0 次)。
在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串 "aaa" 与模式 "a.a" 和 "ab*ac*a" 匹配,但是与"aa.a" 和 "ab*a" 均不匹配。

思路:

dp[i][j]表示主串0~i与模式串0~j是否匹配。

状态转移方程为:

1).s[i]==p[j]||[j]=='.':dp[i][j]=dp[i-1][j-1],即只要前i-1与前j-1相等,则匹配

2).s[i]!=p[j]:

a).p[j]!='*':此时p[j]与s[i]完全不等,则dp[i][j]=false;

b).p[j]='*':此时*可以使前面的字母为0个,1个或多个

当p[j-1]!=s[i]时,此时只能使他为0:dp[i][j]=dp[i][j-1];

当p[j-1]==s[i]时,此时可以使前面的字母为0个,1个或多个

为0个时:dp[i][j]=dp[i][j-1]

为1个时:dp[i][j]=dp[i-1][j-1]

为多个时:此时拿出一个前面的字母与s[i]匹配,因此主串还剩i-1个,但由于p[j]='*'且此时是会产生多个前面字母的情况,因此即使已经分出一个了,但还可能分出很多个,因此还是拿s的前i-1个与p的前j个来匹配,因为p[j]='*',相当于他可以无限的拿出来。

举个例子:

s:###bbb

p:###b*

现在*与b不等,我们令*产生多个b的情况,此时先产生一个b与s的b消除:

s:###bb

p:###b*

而此时p串仍然为###b*,这时在令*产生一个b:

s:###b

p:###b*

此时再令*使前面的b为0个:

s:###

p:###

这样就匹配了

相当于说*产生多个p让他分步一步步来,而不是一下生产2个b

因此此时dp[i][j]=dp[i-1][j]

也就是说先拿出一个b与主串的消掉,然后将问题转换为dp[i-1][j],如果剩下的i-1还有b,但这已经是dp[i-1][j]的问题了。

另外注意初始状态:

dp[0][i],当p[i]='*'时,此时应该与dp[0][i-2]一致,也就是隐去i前面的一个

比如

s:""

p".*"

此时*将.隐去,而相当于dp[0][0]=true

class Solution {
public:
    bool match(char* str, char* pattern)
    {

		string str1(str);
		string str2(pattern);
		vector<vector<bool>> dp(str1.size()+1,vector<bool>(str2.size()+1,false));
		dp[0][0]=true;
		if(str2[0]=='*')
			return false;
		for(int i=0;i<str2.size();i++)
		{
			if(str2[i]=='*')
				dp[0][i+1]=dp[0][i-1];
		}
		for(int i=1;i<dp.size();i++)
		{
			for(int j=1;j<dp[i].size();j++)
			{
				if((str1[i-1]==str2[j-1])||(str2[j-1]=='.'))
					dp[i][j]=dp[i-1][j-1];
				else if(str2[j-1]=='*')
				{
					if(j==1)
						return false;
					if(str2[j-2]==str1[i-1]||str2[j-2]=='.')
						dp[i][j]=dp[i][j-2]||dp[i-1][j-1]||dp[i-1][j];
					else
						dp[i][j]=dp[i][j-2];
				}
			}
		}
		return dp.back().back();
    }
};

  

表示数值的字符串

题目描述

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

 思路:

以e/E为分界线将整个数分为两部分:

左部分的数字部分可以为整数或者小数,+-只能出现在0,只能有一个'.'

右部分的数字部分只能是整数,+-只能出现在e后面一个位置,不能有'.'

class Solution {
public:
    bool isNumeric(char* string)
    {
		char * str=string;
		int p1=0;
		while(p1<strlen(str)&&str[p1]!='e'&&str[p1]!='E')
			p1++;
		return isLeft(str,p1-1)&&isRight(str,p1+1);
    }
	bool isLeft(char* str,int end)
	{
		if(end<0)
			return false;
		int pointnum=0;
		for(int i=0;i<=end;i++)
		{
			if((str[i]=='+'||str[i]=='-'))
			{
				if(i!=0)
					return false;
				continue;
			}
			if(str[i]=='.')
			{
				pointnum++;
				if(pointnum==2)
					return false;
				continue;
			}
			if(str[i]<'0'||str[i]>'9')
				return false;
		}
		return true;
	}
	bool isRight(char* str,int start)
	{
		if(start==strlen(str))
			return false;
		if(start>strlen(str))
			return true;
		int len=strlen(str);
		for(int i=start;i<len;i++)
		{
			if((str[i]=='+'||str[i]=='-'))
			{
				if(i!=start)
					return false;
				continue;
			}
			if(str[i]<'0'||str[i]>'9')
				return false;
		}
		return true;
	}
};

  

调整数组顺序使奇数位于偶数前面★★★

题目描述

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
思路:
每当找到一个奇数,就使用类似于冒泡的思想,将它放到前面。
while来找到一个位置j,这个位置的前面j-1是奇数,此时这个j就是插入位置.
class Solution {
public:
    void reOrderArray(vector<int> &array) {
        if(array.empty())
			return;
		vector<int>& nums=array;
		for(int i=0;i<nums.size();i++)
		{
			int target=nums[i];
			if(target&0x1)
			{
				int j=i;
				while(j>0&&!(array[j-1]&0x1))//当j==0或j前面是奇数时停止
				{
					array[j]=array[j-1];
					j--;
				}
				array[j]=target;
			}
		}
		return;
    }
};

  

链表中倒数第k个结点

题目描述

输入一个链表,输出该链表中倒数第k个结点
思路:
倒数第k个节点,比如倒数第一个节点,也就是最后一个节点,其领先NULL一个身位;倒数第二个领先2个身位。。。因此实际上只要先找到一个节点,其领先起点k个身位,则当这个节点走到NULL时,起点的那个指针也走到了倒数第k个节点的位置
class Solution {
public:
    ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
		ListNode* pNext=pListHead;
		if(k<=0||!pListHead)
			return NULL;
		while(pNext&&k)
		{
			pNext=pNext->next;
			k--;
		}
		if(k!=0)
			return NULL;
		ListNode* pCur=pListHead;
		while(pNext)
		{
			pCur=pCur->next;
			pNext=pNext->next;
		}
		return pCur;

    }
};

  

链表中环的入口结点

题目描述

给一个链表,若其中包含环,请找出该链表的环的入口结点,否则,输出null。
思路:
首先找到一个环内的节点,如果找不到,return NULL;
其次如果找到环内的节点了:假设这个链表由两部分组成,一部分为环,另一部分为一个链。
设环的周长为r,链的长为l,由于环会回到起点,也就是有l=l+r。因此如果让一个节点先走r的长度,则当他们相遇时,先走的节点走了l+r,而后走的节点走了l,这个l刚好就是环的入口
此外注意下slow-fast指针的写法。
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead)
    {
		if(!pHead)
			return NULL;
		ListNode* p1=pHead;
		ListNode* p2=pHead;
		while(p2&&p2->next)
		{
			p1=p1->next;
			p2=p2->next->next;
			if(p1==p2)
				break;
		}
		if(!p2||!p2->next)
			return NULL;
		p1=pHead;
		auto tmp=p2;
		do{
			tmp=tmp->next;
			p1=p1->next;
		}while(tmp!=p2);
		auto pCur=pHead;
		while(pCur!=p1)
		{
			pCur=pCur->next;
			p1=p1->next;
		}
		return pCur;
    }
};

  

反转链表

题目描述

输入一个链表,反转链表后,输出新链表的表头。
思路:
两种方法:
最简单的:
头插法,不多说了。
其次:
使用三个指针pre,pCur,pNext;pre保存上一个节点,pCur保存当前节点,pNext保存下一个节点。
每次循环让pCur的next指向pre,然后更新pre为pCur,更新pCur为pNext;
头插法:
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        if(!pHead)
            return NULL;
        ListNode* pCur=new ListNode(250);
        while(pHead)
        {
            auto tmp=pHead->next;
            pHead->next=pCur->next;
            pCur->next=pHead;
            pHead=tmp;
        }
        return pCur->next;
    }
};
三指针:
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
		if(!pHead)
			return NULL;
		if(!pHead->next)
			return pHead;
		ListNode* pre=NULL;
		ListNode* pCur=pHead;
		ListNode* pNext=pHead->next;
		while(pNext)
		{
			pNext=pCur->next;
			pCur->next=pre;
			pre=pCur;
			pCur=pNext;
		}
		return pre;
    }
};

  

合并两个排序的链表

题目描述

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。
思路:
小的加入新链表。
最后如果有链表还有剩则补进新链表
class Solution {
public:
    ListNode* Merge(ListNode* pHead1, ListNode* pHead2)
    {
		if(!pHead1)
			return pHead2;
		if(!pHead2)
			return pHead1;
        ListNode* pHead=new ListNode(-1);
		ListNode* pCur=pHead;
		while(pHead1&&pHead2)
		{
			if(pHead1->val<pHead2->val)
			{
				auto tmp=pHead1->next;
				pCur->next=pHead1;
				pHead1->next=NULL;
				pHead1=tmp;
			}
			else
			{
				auto tmp=pHead2->next;
				pCur->next=pHead2;
				pHead2->next=NULL;
				pHead2=tmp;
			}
			pCur=pCur->next;
		}
		if(pHead1)
			pCur->next=pHead1;
		if(pHead2)
			pCur->next=pHead2;
		return pHead->next;
    }
};

  

树的子结构★★★

题目描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
思路:
递归
class Solution {
public:
    bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
    {
		if(!pRoot2||!pRoot1)
			return false;
		return HasSubtreeCore(pRoot1,pRoot2);
    }
	bool HasSubtreeCore(TreeNode* p1,TreeNode* p2)
	{
		if(!p1&&p2)
			return false;
		if(!p2)
			return true;
		if(p1->val==p2->val
			&&HasSubtreeCore(p1->left,p2->left)//根相等且左右子树都是子结构
			&&HasSubtreeCore(p1->right,p2->right))
			return true;
		return HasSubtreeCore(p1->left,p2)||HasSubtreeCore(p1->right,p2);
	}
};

  

二叉树的镜像

题目描述

操作给定的二叉树,将其变换为源二叉树的镜像。
思路:
。。。。
class Solution {
public:
    void Mirror(TreeNode *pRoot) {
		if(!pRoot)
			return;
		MirrorCore(pRoot);
    }
	void MirrorCore(TreeNode* root)
	{
		if(!root)
			return;
		MirrorCore(root->left);
		MirrorCore(root->right);
		auto tmp=root->right;
		root->right=root->left;
		root->left=tmp;
		return;
	}
};

  

对称的二叉树★★★

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
思路:
isSymCore用来判断两个树是否对称相等,我们用这个来递归判断根的左子树与右子树。
递归中首先判断根是否相等,如果相等则判断这两个树的左与右和右与左的对称情况
class Solution {
public:
    bool isSymmetrical(TreeNode* pRoot)
    {
		if(!pRoot)
			return true;
		return isSymCore(pRoot->left,pRoot->right);
    }
	bool isSymCore(TreeNode* p1,TreeNode* p2)
	{
		if(!p1&&!p2)
			return true;
		if(p1&&!p2||p2&&!p1)
			return false;
		if(p1->val!=p2->val)
			return false;
		return isSymCore(p1->left,p2->right)&&isSymCore(p1->right,p2->left);
	}
};

  

顺时针打印矩阵★★★★★

题目描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字
思路:
一开始想的太贪,想用一下模拟所有情况,实际上只剩一行或只剩一列的情况是要特殊考虑的。
r_num为当前轮次的行数,c_num为当前轮次的列数
class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
		if(matrix.empty()||matrix[0].empty())
			return vector<int> ();
		int circle=0;
		int r_num=matrix.size();
		int c_num=matrix[0].size();
		vector<int> ans;
		while(r_num>0&&c_num>0)
		{
			if(r_num==1)
			{
				for(int i=0;i<c_num;i++)
					ans.push_back(matrix[circle][circle+i]);
			}
			else if(c_num==1)
			{
				for(int i=0;i<r_num;i++)
					ans.push_back(matrix[circle+i][circle]);
			}
			else
			{
				for(int i=0;i<c_num-1;i++)
					ans.push_back(matrix[circle][circle+i]);
				for(int i=0;i<r_num-1;i++)
					ans.push_back(matrix[circle+i][circle+c_num-1]);
				for(int i=0;i<c_num-1;i++)
					ans.push_back(matrix[circle+r_num-1][circle+c_num-1-i]);
				for(int i=0;i<r_num-1;i++)
					ans.push_back(matrix[circle+r_num-1-i][circle]);
			}
			circle++;
			r_num-=2;
			c_num-=2;
		}
		return ans;
    }
};

  

包含min函数的栈★★★

题目描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
思路:
由于栈会push与pop,这都会导致栈内元素出现变化:push可能push进一个更小的数,pop可能pop出最小的数。
因此不能只存当前最小的数,因为他可能被pop出去。
准备一个栈aux用于存放可能成为最小的数的数,当当前push进来的数小于aux栈顶元素时,这个元素将成为最小元素,因此要将他存进aux;而原来的栈顶元素,由于对新元素pop后原来的栈顶元素又将成为最小,因此要留下。
如果大于栈顶元素,则它不可能成为当前栈内的最小元素,且即使pop出去也不会影响最小元素,因此不入aux.
由于aux与min栈不是完全相等的,因此如果要判断min pop出去的元素是不是aux的栈顶元素,只能在aux中存放索引而不是值。
class Solution {
public:
    void push(int value) {
		nums.push_back(value);
		if(nums.size()==1)
		{
			aux.push(0);
			return;
		}
		auto tmp=aux.top();
		if(value<nums[tmp])
			aux.push(nums.size()-1);
    }
    void pop() {
        if(nums.empty())
			return;
		auto idx=nums.size()-1;
		nums.pop_back();
		if(idx==aux.top())
			aux.pop();
    }
    int top() {
        if(nums.empty())
			return INT_MIN;
		return nums.back();

    }
    int min() {
        if(nums.empty())
			return INT_MIN;
		return nums[aux.top()];
    }
private:
	vector<int> nums;
	stack<int> aux;
};

  

栈的压入、弹出序列★★

 题目描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。
思路:
模拟压入与弹出的过程,用一个栈aux,push到栈顶等于popV中的某个元素。如果无法达到则return false。
然后aux.pop;p2++。相当于找下一个出栈的元素。
如果最后栈内空,说明可以实行,return true.
class Solution {
public:
    bool IsPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size()!=popV.size())
			return false;
		if(pushV.empty())
			return true;
		int p1=0;int p2=0;
		stack<int> aux;
		while(p2<popV.size())
		{
			while(p1<popV.size()&&(aux.empty()||aux.top()!=popV[p2]))//1 2 3 4 5 ;4 5 3 2 1
			{
				aux.push(pushV[p1]);
				p1++;
			}
			if(p1>=pushV.size()&&aux.top()!=popV[p2])
			{
				return false;
			}
			aux.pop();
			p2++;
		}
		if(aux.empty())
			return true;
		else
			return false;
    }
};

  

从上往下打印二叉树

题目描述

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

思路:

层次遍历。。。

class Solution {
public:
    vector<int> PrintFromTopToBottom(TreeNode* root) {
		if(!root)
			return vector<int>();
		queue<TreeNode*> aux;
		aux.push(root);
		vector<int> ans;
		while(!aux.empty())
		{
			ans.push_back(aux.front()->val);
			auto tmp=aux.front();
			aux.pop();
			if(tmp->left)
				aux.push(tmp->left);
			if(tmp->right)
				aux.push(tmp->right);
		}
		return ans;
    }
};

  

把二叉树打印成多行

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
思路:
 count记录每一层的节点个数。tmp记录下一层的节点个数,当这一层的pop完毕后,count=tmp,即进入下一层。
class Solution {
public:
        vector<vector<int> > Print(TreeNode* pRoot) {
			if(!pRoot)
				return vector<vector<int>>();
			queue<TreeNode*> aux;
			int count=1;
			aux.push(pRoot);
			vector<vector<int>> ans;
			while(!aux.empty())
			{
				int tmp=0;
				vector<int> in;
				while(count)
				{
					in.push_back(aux.front()->val);
					if(aux.front()->left)
					{
						aux.push(aux.front()->left);
						tmp++;
					}
					if(aux.front()->right)
					{
						aux.push(aux.front()->right);
						tmp++;
					}
					aux.pop();
					count--;
				}
				count=tmp;
				ans.push_back(in);
			}
			return ans;
        }
};

  

按之字形顺序打印二叉树★★★★

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
思路:
分解成多个问题:
1.层次遍历
2.要记录每一层的个数
3.要求之字形打印
首先层次遍历想到用队列,其次要分层打印,因此还要一个count记录当前层数量。
最后由于要之字型打印:
也就是说如果这一层从左到右,则下一层则要从右到左,此时可以考虑,在push子节点时,先push进一个栈,这样再从栈里push进队列时,就是从右到左了
当要求下一层从右到左时,应该先push左,在push右,这样栈里才会是右->左
而当要求下一层从左到右时,应该先push右,在push左,这样栈里才会是左->右
因此还要加入一个flag,用于不同情况下的左右的顺序
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        vector<vector<int>> ans;
		if(!pRoot)
			return ans;
		int count=1;
		queue<TreeNode*> aux;
		stack<TreeNode*> carry;
		aux.push(pRoot);
		int flag=1;
		while(!aux.empty())
		{
			int tmp=0;
			vector<int> val;
			while(count)
			{
				val.push_back(aux.front()->val);
				auto first=(flag==1)?aux.front()->left:aux.front()->right;
				auto second=(flag==1)?aux.front()->right:aux.front()->left;
				if(first)
				{
					carry.push(first);
					tmp++;
				}
				if(second)
				{
					carry.push(second);
					tmp++;
				}
				count--;
				aux.pop();
			}
			while(!carry.empty())
			{
				aux.push(carry.top());
				carry.pop();
			}
			flag=-flag;
			count=tmp;
			ans.push_back(val);
		}
		return ans;
    }  
};

  

二叉搜索树的后序遍历序列★★★★

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
思路:
首先,后序遍历代表根节点在尾部。
其次,二叉搜索树代表左右子树可以根据根节点的大小分开。
要确定是否为后序遍历,有两个地方:
1.两个子树也要是二叉搜索树
2.尾部的节点要比左子树中所有树大,比右子树中所有树小。
因此:
首先找到尾部节点,根据尾部节点的值把左子树分割出来,然后看看剩余的数里有没有比根节点小的,如果有则说明不是二叉搜索树。
然后递归左子树与右子树,如果都是二叉搜索树的后序遍历,则return true.
class Solution {
public:
    bool VerifySquenceOfBST(vector<int> sequence) {
		if(sequence.empty())
			return false;
		return VerifyBSTCore(sequence,0,sequence.size());
    }
	bool VerifyBSTCore(vector<int>& nums,int start,int len)
	{
		if(len<=1)
			return true;
		int val=nums[start+len-1];
		int i=0;
		for(;i<len&&nums[start+i]<val;i++);
		int left=i;
		for(;i<len&&nums[start+i]>=val;i++);
		if(i!=len)
			return false;
		return VerifyBSTCore(nums,start,left)&&VerifyBSTCore(nums,left,len-left-1);
	}
};

  

二叉树中和为某一值的路径

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
思路:
递归回溯来解
对于叶子节点,如果当前expectNumber-node->val==0,则说明此路径可用,将这条路径加入ans。注意返回时要回退pop_back
对于非叶子节点,首先将当前节点加入路径,然后递归左与右,最后一定要回退pop_back,因为使用的是引用传递,因此当此节点结束时一定要回退。
class Solution {
public:
    vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
		vector<vector<int>> ans;
		if(!root)
			return ans;
		vector<int> tmp;
		FindPathCore(ans,tmp,root,expectNumber);
		sort(ans.begin(),ans.end(),cmp);
		return ans;
    }
private:
	static bool cmp(vector<int>& pre,vector<int>& next)//类中使用自定义排序cmp要使用静态方法
	{
		return pre.size()>next.size();
	}
	void FindPathCore(vector<vector<int>>& ans,vector<int>& tmp,TreeNode* node,int num)
	{
		if(!node->left&&!node->right)
		{
			if(!(num-node->val))
			{
				tmp.push_back(node->val);
				ans.push_back(tmp);
				tmp.pop_back();
			}
			return;
		}
		tmp.push_back(node->val);
		if(node->left)
			FindPathCore(ans,tmp,node->left,num-node->val);
		if(node->right)
			FindPathCore(ans,tmp,node->right,num-node->val);
		tmp.pop_back();
		return;
	}
};

  

复杂链表的复制★★★

题目描述

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)
思路:
1).两边遍历链表,第一遍构建next,同时用map构建原链表节点与新建链表节点的映射;第二遍遍历链表,用映射来链接random
class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
        if(!pHead)
            return NULL;
        //firsst
        RandomListNode* pCur=pHead;
        RandomListNode* head=new RandomListNode(1);
        RandomListNode* p=head;
        unordered_map<RandomListNode*,RandomListNode*> dict;
        while(pCur)
        {
            RandomListNode* node=new RandomListNode(pCur->label);
            dict[pCur]=node;
            p->next=node;
            p=node;
            pCur=pCur->next;
        }
        pCur=pHead;
        p=head->next;
        while(pCur)
        {
            if(pCur->random)
                p->random=dict[pCur->random];
            else
                p->random=0;
            pCur=pCur->next;
            p=p->next;
        }
        return head->next;
    }
};

 2).剑指offer方法:将新建节点插入到原节点右边,这样node->next->random=node->random->next

但这样需要遍历三次链表,第一次将节点插入,第二次构建random,第三次分开链表。

class Solution {
public:
    RandomListNode* Clone(RandomListNode* pHead)
    {
		if(!pHead)
			return NULL;
		RandomListNode* pCur=pHead;
		while(pCur)
		{
			RandomListNode* node=new RandomListNode(pCur->label);
			auto tmp=pCur->next;
			pCur->next=node;
			node->next=tmp;
			pCur=tmp;
		}
		pCur=pHead;
		auto ans=pCur->next;
		while(pCur&&pCur->next)
		{
			if(pCur->random)
				pCur->next->random=pCur->random->next;
			else if(pCur->next)
				pCur->next->random=0;
			pCur=pCur->next->next;
		}
		pCur=pHead;
		RandomListNode* pLast=NULL;
		while(pCur)
		{
			auto node=pCur->next;
			if(pLast)
				pLast->next=node;
			pLast=node;
			pCur->next=node->next;
			pCur=pCur->next;
		}
		return ans;
    }
};

  

二叉搜索树与双向链表

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:
用递归来解
对当前节点来说,首先处理它的左子树,并返回左子树最后一个节点,然后连接起来。
然后处理右子树,并返回右子树的第一个节点,然后连接起来。
返回时,根据pLast与pCur的关系来返回,如果pLast->left==pCur,代表当前节点是左子节点,因此应该返回链表的最后一个节点
如果pLast->right==pCur,代表当前节点是右子节点,应该返回右子树链表的第一个节点
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree)
    {
		if(!pRootOfTree)
			return NULL;
		return ConvertCore(pRootOfTree,NULL);
    }
private:
	TreeNode* ConvertCore(TreeNode* pCur,TreeNode* pLast)
	{
		if(!pCur)
			return NULL;
		auto l=ConvertCore(pCur->left,pCur);
		auto r=ConvertCore(pCur->right,pCur);
		if(l)
			l->right=pCur;
		pCur->left=l;
		if(r)
			r->left=pCur;
		pCur->right=r;
		if(!pLast)
		{
			while(pCur->left)
				pCur=pCur->left;
			return pCur;
		}
		if(pCur==pLast->left)
		{
			while(pCur->right)
				pCur=pCur->right;
			return pCur;
		}
		else
		{
			while(pCur->left)
				pCur=pCur->left;
			return pCur;
		}
	}
};

  

序列化二叉树★★★

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树
思路:
序列化:任选一种遍历方式即可,通过','来分割不同节点,对于空子节点使用#
反序列化:同理
另外注意参数,使用*&,这样才能使指针在函数中发生变化
class Solution {
public:
    char* Serialize(TreeNode *root) {    
        if(!root)
			return NULL;
		char* buf=new char[1000];
		char* ans=buf;
		SerializeCore(root,ans);
		return buf;
    }
    TreeNode* Deserialize(char *str) {
		if(!str)
			return NULL;
		return DeserializeCore(str);
    }
private:
	TreeNode* DeserializeCore(char*& str)
	{
		if(*str==0)
			return NULL;
		while(*str==',')
			str++;
		if(*str=='#')
		{
			str++;
			return NULL;
		}
		int num;
		char* buf=new char[100];
		char* tmp=buf;
		while(*str!=',')
			*(buf++)=*(str++);
		*buf=0;
		sscanf(tmp,"%d",&num);
		TreeNode* node=new TreeNode(num);
		node->left=DeserializeCore(str);
		node->right=DeserializeCore(str);
		return node;
	}
	void SerializeCore(TreeNode* node,char*& str)
	{
		if(!node)
		{
			*(str++)=',';
			*(str++)='#';
			*str=0;
			return;
		}
		char* buf=new char[100];
		sprintf(buf,"%d",node->val);
		char* pCur=buf;
		*(str++)=',';
		while(*pCur)
			*(str++)=*(pCur++);
		*str=0;
		delete[] buf;
		SerializeCore(node->left,str);
		SerializeCore(node->right,str);
		return;
	}
};

  

字符串的排列★★★

题目描述

输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba。
思路:
以abc为例子
abc的全排,首先将a与a(原位置交换),交换后加上a以后元素的全排,这就是第一个位置为a的全排
然后将a与b交换,交换后在对b以后的元素全排,这就是第一个位置为b的全排
由于有重复元素,因此交换时,如果相等则不交换(第一个位置除外)
class Solution {
public:
    vector<string> Permutation(string str) {
        if(str.empty())
			return vector<string>();
		vector<string> ans;
		PermutationCore(str,0,ans);
		sort(ans.begin(),ans.end());
		return ans;
    }
private:
	void PermutationCore(string& str,int pos,vector<string>& ans)
	{
		if(pos>=str.size())
		{
			ans.push_back(str);
			return;
		}
		for(int i=pos;i<str.size();i++)
		{
			if(i==pos||str[i]!=str[pos])
			{
				swap(str[i],str[pos]);
				PermutationCore(str,pos+1,ans);
				swap(str[i],str[pos]);
			}
		}
		return;
	}
};

  

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

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
思路:
出现次数超过一半的数字一定出现在中位数的位置。因此首先通过TopK找到位于中位数位置的数,然后遍历数组,看看这个数是否出现超过一半
class Solution {
public:
    int MoreThanHalfNum_Solution(vector<int> numbers) {
		if(numbers.empty())
			return 0;
		if(numbers.size()==1)
			return numbers.back();
		int pos=Partition(numbers,0,numbers.size()-1);
		int target=numbers.size()/2+1;
		while(pos!=target)
		{
			if(pos<target)
				pos=Partition(numbers,pos+1,numbers.size()-1);
			else
				pos=Partition(numbers,0,pos-1);
		}
		int ans=0;
		for(int i=0;i<numbers.size();i++)
		{
			if(numbers[i]==numbers[target])
				ans++;
		}
		if(ans<target)
			return 0;
		else
			return numbers[target];

    }
private:
	int Partition(vector<int>& nums,int start,int end)
	{
		if(start>=end)
			return start;
		int target=nums[start];
		while(start<end)
		{
			while(start<end&&target<=nums[end])
				end--;
			swap(nums[start],nums[end]);
			while(start<end&&target>=nums[end])
				start++;
			swap(nums[start],nums[end]);
		}
		return start;
	}
};

  

40. 最小的 K 个数

题目描述

输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
 
class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if(k<=0)
			return vector<int>();
		if(k>=input.size())
			return input;
		int pos=Partition(input,0,input.size()-1);
		k--;
		while(pos!=k)
		{
			if(pos<k)
				pos=Partition(input,pos+1,input.size()-1);
			else
				pos=Partition(input,0,pos-1);
		}
		vector<int> ans;
		for(int i=0;i<=k;i++)
			ans.push_back(input[i]);
		return ans;

    }
private:
	int Partition(vector<int>& nums,int start,int end)
	{
		if(start>=end)
			return start;
		auto target=nums[start];
		while(start<end)
		{
			while(start<end&&nums[end]>=target)
				end--;
			swap(nums[start],nums[end]);
			while(start<end&&nums[start]<=target)
				start++;
			swap(nums[start],nums[end]);
		}
		return start;
	}
};

 堆排:

class Solution {
public:
    vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
        if(k<=0||k>input.size())
			return vector<int>();
		for(int i=k/2-1;i>=0;i--)
			HeapAdjust(input,i,k-1);
		for(int i=k;i<input.size();i++)
		{
			if(input[i]<input[0])
			{
				swap(input[i],input[0]);
				HeapAdjust(input,0,k-1);
			}
		}
		vector<int> ans;
		for(int i=0;i<k;i++)
			ans.push_back(input[i]);
		return ans;
    }
private:
	void HeapAdjust(vector<int>& nums,int pos,int end)
	{
		auto target=nums[pos];
		int i=2*pos+1;
		for(;i<=end;i=2*pos+1)
		{
			if(i<end&&nums[i+1]>nums[i])
				i++;
			if(nums[i]<target)
				break;
			nums[pos]=nums[i];
			pos=i;
		}
		nums[pos]=target;
		return;
	}
};

  

41 数据流中的中位数★★★★★

题目描述

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:
中位数的特征是位于数组中间,且比左边的数大,比右边的数小。
因此实际上可以用left和right将数组分为两部分
left代表数组左侧的数,right代表数组右侧的数
left组成一个大顶堆,right组成一个小顶堆
插入始终保持right.size()>=left.size(),因为求一个数组的中位数时,当数组为奇数,size/2刚好位于中位数位置。而当数组为偶数时,size/2刚好位于中间两个数的第一个数的位置,因此我们保持right.size()>=left.size(),也就是说当数组奇数时,right.size()-left.size()=1;当数组为偶数时,right.size()=left.size()
这样数组为奇数,我们直接取小顶堆的对顶元素就是中位数;数组为偶数时,取大顶堆的堆顶和小顶堆的堆顶就是中间的两个数
插入时,当left.size==right.size,此时应该在右侧插入,但由于插入元素可能非常小,因此先要与大顶堆的堆顶对比,如果小于堆顶元素,则先弹出堆顶top,将num先插入left,然后将top插入到右侧。
当left.size()<right.size(),此时应该在左侧插入,但由于插入元素可能非常大,因此先与小顶堆的堆顶对比,如果大于堆顶元素,则先弹出堆顶,将num插入right,然后将top插入到左侧
class Solution {
public:
    void Insert(int num)
    {
		if(left.size()==right.size())
		{
			if(right.empty())
			{
				right.push_back(num);
				return;
			}
			if(num>=left[0])
			{
				right.push_back(num);
				push_heap(right.begin(),right.end(),greater<int>());
				return;
			}
			pop_heap(left.begin(),left.end(),less<int>());
			auto top=left.back();
			left.pop_back();
			left.push_back(num);
			push_heap(left.begin(),left.end(),less<int>());
			right.push_back(top);
			push_heap(right.begin(),right.end(),greater<int>());
			return;
		}
		else if(left.size()<right.size())
		{
			if(num<=right[0])
			{
				left.push_back(num);
				push_heap(left.begin(),left.end(),less<int>());
				return;
			}
			else
			{
				auto top=right[0];
				pop_heap(right.begin(),right.end(),greater<int>());
				right.pop_back();
				right.push_back(num);
				push_heap(right.begin(),right.end(),greater<int>());
				left.push_back(top);
				push_heap(left.begin(),left.end(),less<int>());
				return;
			}
		}
    }
    double GetMedian()
    { 
		int size=left.size()+right.size();
		if(!size)
			return 0;
		if(size&0x1==1)
			return right[0];
		else
			return (left[0]+right[0])/2.0;
    }
private:
	vector<int> left;
	vector<int> right;
};

  

字符流中第一个不重复的字符★★★

题目描述

请实现一个函数用来找出字符流中第一个只出现一次的字符。例如,当从字符流中只读出前两个字符"go"时,第一个只出现一次的字符是"g"。当从该字符流中读出前六个字符“google"时,第一个只出现一次的字符是"l"。
思路:
哈希表存储位置,如果重复出现则设置为-1
找第一个不重复的过程就是遍历哈希表,过程中记录位置,位置最小的输出
class Solution
{
public:
  //Insert one char from stringstream
	Solution()
	{
		count=0;
	}
    void Insert(char ch)
    {
         if(dict.find(ch)!=dict.end())
			 dict[ch]=-1;
		 else
			 dict[ch]=(count++);
    }
  //return the first appearence once char in current stringstream
    char FirstAppearingOnce()
    {
		char ans='#';
		int pos=INT_MAX;
		for(auto it=dict.begin();it!=dict.end();it++)
		{
			if(it->second!=-1&&it->second<pos)
			{
				ans=it->first;
				pos=it->second;
			}
		}
		return ans;
    }
private:
	int count;
	unordered_map<char,int> dict;
};

  

连续子数组的最大和

题目描述

给一个数组,返回它的最大连续子序列的和,你会不会被他忽悠住?(子向量的长度至少是1)

思路:

dp[i]表示以i结尾的连续子数组(并不一定是以0为首)的和的最大值,当dp[i]+dp[i-1]>0时,此时第i个数可与前面的数组成连续子数组;当小于等于0时,dp[i]=0。

class Solution {
public:
    int FindGreatestSumOfSubArray(vector<int> array) {
		vector<int>& nums=array;
		if(nums.empty())
			return 0;
		vector<int> dp(nums.size()+1,0);
		int resi=INT_MIN;
		for(int i=1;i<dp.size();i++)
		{
			int idx=i-1;
			if(nums[idx]>resi)
				resi=nums[idx];
			if(dp[idx]+nums[idx]<=0)
				dp[i]=0;
			else
				dp[i]=dp[idx]+nums[idx];
		}
		int max=INT_MIN;
		for(int i=1;i<dp.size();i++)
		{
			if(dp[i]>max)
				max=dp[i];
		}
		if(resi<0)
			return resi;
		else
			return max;
    }
};

  

整数中1出现的次数(从1到n整数中1出现的次数)

题目描述

从1 到 n 中1出现的次数

思路:

以21345为例:

分为1~1345和1346~21345

1346~21345中:

万位上的1从10000~19999共出现了10000次

然后统计其他四位上1出现的次数。

继续将1346~21345分为:

1346~11345和11346~21345

对于1346~11345来说

首先1出现在千位:

此时右边三位任选0~9(当出现其他三位均为0时,1000是不合法的,因为1000不在1346~11345间,但对于11000是合法的,也就是说当小于下界时,可以填加一个万位使其合法):共1000种

其次1出现在百位,其他三位任选0~9(0100也是不合法的,但当补上万位后就合法了),这时也有1000种

同理:1346~11345中共有4*1000=4000种

而在11346~21345中也一样

则最后1346~21345共有8000种

则对于1346~21345共有18000种

而1~1345可以递归这个过程

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n)
    {
		char* buf=new char[50];
		sprintf(buf,"%d",n);
		return NumberCore(buf);
    }
private:
	int NumberCore(char* n)
	{
		if(!n||*n<='0'||*n>'9'||*n==0)
			return 0;
		int len=strlen(n);
		if(len==1)
			return 1;
		int first=0;
		if(*n>'1')
			first=pow((double)10,(double)(len-1));
		else
			first=atoi(n+1)+1;
		int second=0;
		second=(*n-'0')*(len-1)*pow((double)10,(double)(len-2));
		return first+second+NumberCore(n+1);
	}
};

  

推荐阅读