最近接着刷题,剑指offer里面这些道题还不错,整理下来。
2018/08/02更新: 66道题的代码终于整合好了! ! ! ! ! 已放至git: 代码链接
用两个栈实现一个队列
栈和队列的区别是先进后出(LIFO)和先进先出(FIFO)的区别。
用两个栈实现一个队列思想是,出栈的时候,把栈里面所有元素倒入另一个栈里面,相当于对这个栈 的所有元素出栈一次,然后倒入的栈出栈就是队列一样。
注意出栈之后还要把元素倒回来原来的栈。
Stack<Integer> stack1 = new Stack<Integer>();
Stack<Integer> stack2 = new Stack<Integer>();
public void push(int node) {
stack1.push(node);
}
//用两个栈实现一个队列 出栈的时候,把一个栈里面的元素全部倒入另外一个栈里面去
public int pop() {
while(!stack1.isEmpty()) {
stack2.push(stack1.pop()); //把一个栈的元素出栈到另一个栈里面去
}
stack1.clear();
int result = stack2.pop();
while(!stack2.isEmpty()) { //再把元素倒回来
stack1.push(stack2.pop());
}
return result;
}
二进制中1的个数
输入一个整数,输出该数二进制表示中1的个数。其中负数用补码表示。
分负数和非负数处理,负数补码的形式其中二进制1的个数是32减去对应的相反数加一的二进制1的个数。64位同理。
非负数逐个位移比较最低位和1相与,是1的话记录个数,直到该数字为0.
int NumberOf1(int n) {
if(n < 0) {
return 32 - NumberOf1(-1 * n - 1);
}else {
int count = 0;
for(; n!= 0; n = n >> 1) {
count += n & 1 ? 1 : 0;
}
return count;
}
}
数值的整次方
直接调库pow函数一行代码也可以通过,但这样毫无编程思想可言,普通的一个数num的k次方可以循环乘k次,但是这样的循环效率太低。可以这样子做快速幂,那么循环次数就是logK向上取整,每次对指数进行奇偶性的判断。就是学习一下快速幂算法。
指数为负的话,还是递归同样计算,对结果除1。
double Power(double base, int exponent) {
if(exponent >= 0) {
double ans = 1.0;
while(exponent) {
// 判断是偶数还是奇数 和1与运算 为1则为奇数
if(exponent & 1) {
ans = ans * base;
}
base = base *base;
exponent = exponent >> 1;
}
return ans;
}else {
return 1.0 / Power(base, -1 * exponent);//指数为负数的情况 结果是一个分数
}
}
求1到n的和不准使用循环和判断语句
原题:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。
题目规定的是不得使用循环判断这些语句,第一想到的就是递归完成循环,思考递归的终止条件是什么,去掉这些判断语句能起到判断作用就是C语言里面的短路运算了,A && B,当A为假的时候,不会执行后面的语句。所以就通过递归和短路运算完成,如果这道题不是从1加到n,那短路运算的判断,前面的ans也应该做细微变化 ans减去起始值。依旧可以起到作用。
代码不多 但是遇到题目的时候不容易想到
int add(int n) {
int ans = n;
ans && (ans += (add(n - 1)));
return ans;
}
链表中倒数第K个结点
输入一个链表,输出该链表中倒数第k个结点。
这肯定是一个单链表,所以要想得到倒数第k个节点,笨办法是先求出链表长度n,再去访问第n-k+1个结点就是倒数第k个结点。这样子虽然可以达到目的,但是效率不够。
ListNode* FindKthToTail(ListNode* pListHead, unsigned int k) {
ListNode *fast = pListHead;
ListNode *slow = pListHead;
if(pListHead == NULL || k == 0) {
return NULL;
}
while(k-- > 0) {
if(fast == NULL) {
return NULL;
}
fast = fast->next;
}
while(fast) {
fast = fast->next;
slow = slow->next;
}
}
判断树的子结构
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
就是判断A的所有左右子树是不是和B树一样,只是每个结点的值相同。
先需要一个判断两个二叉树是否相同的函数, 判断这两个二叉树的左右分支是否都相同,递归即可,从左子树向右子树判断。
然后分解A树,对A树的所有左右子树和B判断,用上面说的函数去判断。
bool isSubtree(TreeNode* pRootA, TreeNode* pRootB) {
if (pRootB == NULL) return true;
if (pRootA == NULL) return false;
if (pRootB->val == pRootA->val) {
return isSubtree(pRootA->left, pRootB->left) && isSubtree(pRootA->right, pRootB->right);
} else return false;
}
bool HasSubtree(TreeNode* pRootA, TreeNode* pRootB)
{
if (pRootA == NULL || pRootB == NULL) return false;
//整个算法的难点在于这里的递归HasSubtree(pRootA->left, pRootB) || HasSubtree(pRootA->right, pRootB);
//递归的查 A树的所有左右子树是否和B树相同
return isSubtree(pRootA, pRootB) || HasSubtree(pRootA->left, pRootB) || HasSubtree(pRootA->right, pRootB);
}
二叉树的镜像
操作给定的二叉树,将其变换为源二叉树的镜像。
这里交换的是节点地址,并不是节点的值,所以只需要递归遍历树,然后交换节点就可以了,这里的交换是从最左子树开始交换的。从二叉树的下面向上交换。
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL) {
return;
}
Mirror(pRoot->left);
Mirror(pRoot->right);
TreeNode *temp = pRoot->left;
pRoot->left = pRoot->right;
pRoot->right = temp;
}
栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
这种题以前在数据结构的选择题中常遇到,选择题做的时候都是去验证每个答案是否可以满足压栈弹栈。这里实际编码解决刚开始没有什么思路。
通过分析错的出栈序列,4,3,5,1,2中错误的地方在1,2这是错误的关键,1的出栈这里不行的 1此时是栈底的元素 上面还有元素2.
压栈的时候2在1的后面压栈,此时2都还没有出栈,1就要出栈显然是不合理。
可以借助另一个栈来判断完成,遍历压栈顺序,逐个把压栈顺序压入到栈中,压栈一个判断一个是否需要出栈,如果此时栈顶元素等于出栈元素的第一个就出栈,最后只需要栈是否为空,是否出栈完了。如下图。
public boolean IsPopOrder(int [] pushA,int [] popA) {
Stack<Integer> temp = new Stack<>();
int index = 0;
for(int i = 0; i < pushA.length; i++) {
temp.push(pushA[i]);
while(temp.size() > 0 && temp.peek() == popA[index]) {
temp.pop();
index++;
}
}
return temp.isEmpty();
}
连续子数组的最大和
这道题在寒假的时候做过,但是没有在网上测试,昨天做的时候在牛客网一跑报了问题,仔细看了半天,是自己逻辑上的一个漏洞,求和0并不是最小的 忽略掉了数组中全是负数的情况。
原题:例如:{6,-3,-2,7,-15,1,2,2},连续子向量的最大和为8(从第0个开始,到第3个为止)。(子向量的长度至少是1)
应该再加上一个要求 只允许一遍遍历数组。
思路:在一遍遍历数组的时候需要一个记录求和sum,一个记录当前和最大max,遍历数组的时候每走一步,求和变量是如何变化:一开始求和和最大都让它是数组第一个元素,加上当前数组元素大于自己了的话就更新求和,不大于的话就保持不变,加上你之后还不如没加你的时候大,那我就不更新求和变量。而当前和最大整个过程也很简单,就是找到这一趟下来和的最大 如果找一个数组的最大值一样 出现了更大的和就更新max,没有更大的就保持max不变。(寒假时候写的是默认都是0,这是不对的。)
int FindGreatestSumOfSubArray(vector<int> array) {
int sum = array[0];
int max = array[0];
for(int i = 1; i < array.size(); i++) {
sum = (sum + array[i]) > array[i] ? (sum + array[i]) : array[i];
max = sum > max ? sum : max;
}
return max;
}
求一个序列最小的k个数
以前有看到过这种类似的题,考察的还是数据结构的基本功,比如求一个无序序列的第k大的元素,求前k大个元素等等。
这道题的原型是:输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4,。
直接上排序排好了输出的做法就不说了,捞的嘛就不谈了。这类题考察的是对堆的用法,一开始做的时候知道只建立k个元素堆,然后维护堆,最后堆里面的所有元素就是所求。但是烦的错误就是究竟是建立大根堆还是小根堆,插入元素在堆尾还是堆顶。
解决思路:要得到一个序列的最小的k个数需要建立的是大根堆,然后将剩下的元素和堆顶比较,堆顶是这个堆最大的元素,小于堆顶的话,那么这个元素有可能是最小的k个数之一,所以若小于堆顶元素,那么就与堆顶元素交换,并维护堆,不小于的话就过。直到遍历完这个序列,得到的堆就是最小的k个数,但可能并不是升序的。那么同理要得到一个序列的最大的k个数,那么就是建立一个最小堆,然后将剩下的元素和堆顶比较,若大于堆顶则交换并维护堆,直至遍历序列结束,刚好是反过来的。
还要注意的是如果k大于了数组的长度 返回的应是空
void swapData(int *x, int *y) {
*x = *x ^ *y;
*y = *x ^ *y;
*x = *x ^ *y;
}
void adjustHeap(int *array, int count, int root) {
int leftChild;
int rightChild;
int whichChild;
while(root <= count/2 - 1) {
leftChild = 2 * root + 1;
rightChild = ((leftChild + 1) >= count) ? -1 : (leftChild + 1);
whichChild = (rightChild == -1) ? leftChild : (array[leftChild] > array[rightChild] ? leftChild : rightChild);
whichChild = array[root] > array[whichChild] ? -1 : whichChild;
if(-1 == whichChild) {
return;
}
swapData(&array[root], &array[whichChild]);
root = whichChild;
}
}
int* GetLeastNumbers_Solution(int *array, int array_length, int k) {
int root;
int i;
int *result;
//初始化大根堆
for(root = k / 2 - 1; root >= 0; root--) {
adjustHeap(array, k, root);
}
//逐个比较后面的元素 如果小于根顶的元素 交换 并维护大根堆
for(i = k; i < array_length; i++) {
if(array[i] < array[0]) {
swapData(&array[i], &array[0]);
adjustHeap(array, k, 0);
}
}
result = (int *)calloc(sizeof(int), k);
for(i = 0; i < k; i++) {
result[i] = array[i];
}
return reuslt;
}
当时在牛客的那个编译环境c跑着太难受了 这是java版
public static ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> result = new ArrayList<>();
int root;
if(k > input.length) {
return result;
}
for(root = k / 2 - 1; root >= 0; root--) {
adjustHeap(input, k, root);
}
for(int i = k; i < input.length; i++) {
if(input[i] < input[0]) {
swap(input, i, 0);
adjustHeap(input, k, 0);
}
}
for(int i = 0; i < k; i++) {
result.add(input[i]);
}
return result;
}
public static void adjustHeap(int[] array, int count, int root) {
int leftChild;
int rightChild;
int whichChild;
while(root <= count/2 - 1) {
leftChild = 2 * root + 1;
rightChild = ((leftChild + 1) >= count) ? -1 : (leftChild + 1);
whichChild = (rightChild == -1) ? leftChild : (array[leftChild] > array[rightChild] ? leftChild : rightChild);
whichChild = array[root] > array[whichChild] ? -1 : whichChild;
if(-1 == whichChild) {
return;
}
swap(array, root, whichChild);
root = whichChild;
}
}
复杂链表的复制
题目:输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。
按照题目要求要做到复制一个链表,新的链表应是新申请的内存空间,并且新链表的信息也要保持一致,也就是出了内存地址不一样,复制的链表和原链表是一样的,这道题的难点就在于链表节点多了一个指向链表任意节点的指针,如何复制这个指针是难点所在,你没法确定这个随机指针指向的哪一个节点,这个随机指针意思就是说指向了链表中某个节点,要保持复制后的链表里的随机指针指向位置和原链表的随机指针指向位置一致,这是主要解决的问题。
我们先抛开随机指针,先将链表的数据复制一遍。复制完了再去考虑该如何复制这个随机指针。
这样子的思路去拷贝倒也是可以达到要求,但是太过于复杂。
思路:对于原链表的每个节点复制一下,粘贴在其后,也就是插入在原节点之后,只是对节点的数据复制,有一个和它相同数据的就节点插入在其后。这是第一步。
第二步:拷贝随机指针,可以通过原节点找到随机指针指向的那个节点,不妨叫做randomNode,而这个randomNode的next节点就是镜像节点的随机指针的指向啊。完成指向关系即可达到拷贝随机指针。
第三步:拆开原节点和镜像节点,这个不难,通过改变这个长链表里各节点的next指针指向即可完成。
RandomListNode* Clone(RandomListNode* pHead)
{
// 第一步先给原链表的每个节点后面复制一个镜像 实际上是复制了节点的值 随机指针没法复制
if(pHead == NULL) {
return NULL;
}
RandomListNode *pMove = pHead;
// 复制链表中的每一个节点 插入在其后
while(pMove != NULL) {
RandomListNode *copyNode = (RandomListNode *)malloc(sizeof(RandomListNode));
copyNode->label = pMove->label;
copyNode->next = pMove->next;
pMove->next = copyNode;
pMove = copyNode->next;
}
// 第二步是复制指向随机指针 可以利用链表中原节点的指向随机节点 后一个镜像节点完成复制
pMove = pHead;
while(pMove != NULL) {
RandomListNode *copyNode = pMove->next;
if(pMove->random != NULL) {
copyNode->random = pMove->random->next;
}
pMove = copyNode->next;
}
// 拆分镜像节点和原链表节点 主要是改变链表中各个节点指向关系
RandomListNode *resultHead = pHead->next;
pMove = pHead;
RandomListNode *temp;
// 链表中第一个节点的链接关系就是原链表 代表原链表的头结点
// 第二个节点代表复制的链表的头结点
while(pMove->next != NULL) {
temp = pMove->next;
pMove->next = temp->next;
pMove = temp;
}
return resultHead;
}
二叉搜索树和链表
题目:输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
将一个二叉搜索树转化为有序的链表,搜索树的中序遍历是升序。二叉树本身就是一个二叉链表,将一个二叉树转化为链表主要难点在如何改变孩子和根节点的指向关系,并且将整个树串起来。
这个二叉搜索树最右边的孩子一定是最大值,最左的孩子一定是最小的孩子,这里的算法链表的时候是从大往小链接的。
进行递归,递归到最右边的的孩子,这个节点就是链表的第一个节点,递归往上返回,链接的时候最后更新链表头结点。反正递归看图比较直观一点 差不多这样
TreeNode *resultList = NULL;
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(!pRootOfTree) {
return NULL;
}
Convert(pRootOfTree->right);
if(!resultList) {
resultList = pRootOfTree;
}else {
// 这里才是真正的改变二叉树里指针指向关系的部分 使其成为一个链表
resultList->left = pRootOfTree;
pRootOfTree->right = resultList;
resultList = pRootOfTree;
}
Convert(pRootOfTree->left);
return resultList;
}
数组中出现次数超过一半的元素
数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。
同样在LeetCode里面遇到过,不需要统计的每个元素出现次数的做法,需要一个count表示次数,初始化为0,开始设这个出现次数超过一半元素的数字就是数组第一个,然后开始遍历数组,然后是从数组第二个也就是下标为1开始
如果数组当前元素和当前主要元素不同,那么count就需要自减1,相同就自加。
如果count到0,就需要更换了主要元素为数组当前元素。
这样的一遍遍历完了之后,如果count大于0,说明存在主要元素,再遍历一遍数组统计一下这个主要元素的次数,是否真的超过了一半,是只统计上一步得到的这个主要元素的出现次数。
int MoreThanHalfNum_Solution(vector<int> array) {
int array_length = array.size();
int i;
int majority = array[0];
int count = 1;
int counter;
for(i = 1 ; i < array_length; i++) {
if(count == 0) {
majority = array[i];
count++;
}else {
count += (majority == array[i]) ? 1 : -1;
}
}
//对已经得到的元素进行进一步的判断
//如果给出的数组元素没有存在主要元素 就返回0
if(count <= 0) {
return 0;
}else {
for(i = 0, counter = 0; i < array_length; i++) {
if(majority == array[i]) {
counter++;
}
}
}
return (counter > (array_length / 2)) ? majority : 0;
}
把数组排成最小的数
输入一个正整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。例如输入数组{3,32,321},则打印出这三个数字能排成的最小数字为321323。
开始没什么思路,看了下评论区的思路,就是先对序列进行排序,自定义的排序规则,排序后的序列顺序起来就是最小的数。
主要是这个自定义的排序方法compare,方法很简单,就是两个数组合,看怎样组合出来的结果最小。局部最小的思想,差不多就是贪心的思想,排序的时候,保证最终的结果顺序起来是最小的,从局部出发,保证局部组成的数字是最小的。
public String PrintMinNumber(int [] numbers) {
Comparator compare = new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
String s1 = "" + o1 + o2;
String s2 = "" + o2 + o1;
return s1.compareTo(s2);
}
};
Integer[] ar_result = new Integer[numbers.length];
for(int i = 0; i < numbers.length; i++) {
ar_result[i] = numbers[i];
}
Arrays.sort(ar_result, compare);
String result = "";
for(int num : ar_result) {
result += num;
}
return result;
}
丑数
把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
这个对丑数的定义不是很清晰,其实丑数就是拿这个数字分别连续除以2,3,5,直到除不尽了,看最后剩下的数字是不是1,是1 就是丑数。比如10,除以2,为5了,3没法除,除以5等于1,这个数就是丑数。
如果直接求第n个丑数,逐个从1判断n个丑数,这样子会超市,每次都要判断这个数字是不是丑数。
丑数从1开始分别是:1,2,3,4,5,6,8,9,10,.........所有的丑数都可以通过2,3,4的倍数来实现,1除外,那么可以直接去生成第n个丑数,要得到第n个丑数,申请一个长度为n的数组,去生成这个数组的每个元素,数组第一个元素就是1,第二个元素在1*2,1*3,1*5中诞生取最小,依次可以生成整个数组。
int min3Num(int a, int b, int c) {
return (a <b ? a : b) < c ? (a <b ? a : b) : c;
}
int GetUglyNumber_Solution(int index) {
int *array;
int count2 = 0;
int count3 = 0;
int count5 = 0;
int i;
array = (int *)calloc(sizeof(int), index);
array[0] = 1; //规定第一个丑数是1
for(i = 1; i < index; i++) {
array[i] = min3Num(array[count2] * 2, array[count3] * 3, array[count5] * 5);
if(array[i] == array[count2] * 2) {
count2++;
}
if(array[i] == array[count3] * 3) {
count3++;
}
if(array[i] == array[count5] * 5) {
count5++;
}
}
i = array[index - 1];
free(array);
array = NULL;
return i;
}
第一个只出现一次的字符
在一个字符串(1<=字符串长度<=10000,全部由字母组成)中找到第一个只出现一次的字符,并返回它的位置
直接简单粗暴的统计一下字符串中各个字符出现的次数,统计各个字符出现次数 就用一个厂度256的数组统计,拿字符当做下标,hash的思想。这是第一遍遍历字符串。
第二遍遍历字符串,找到第一个出现次数为1的字符,返回下标即可。
int FirstNotRepeatingChar(string str) {
int chCount[256] = {0};
int i;
for(i = 0; str[i]; i++) {
chCount[str[i]]++;
}
for(i = 0; str[i]; i++){
if(chCount[str[i]] == 1) {
return i;
}
}
return -1;
}
数组中的逆序对
在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数P。并将P对1000000007取模的结果输出。 即输出P%1000000007
这道题用 普通的做法 双层循环 遍历 可以统计出来次数,但是会超时,O(n^2)的时间复杂度显然不能满足需要。
归并排序的思路:把这个序列不断划分,合并的时候比较两个子序列的逆序对,合并的时候 那些子序列一定都是有序的,比较逆序对起来就容易的多了。
int InversePairs(vector<int> data) {
int n = data.size();
if(n <= 0) {
return 0;
}
vector<int> copy;
for(int i = 0; i < n; i++) {
copy.push_back(data[i]);
}
long long count;
count = merge(data, copy, 0, n - 1);
return count % 1000000007;
}
long long merge(vector<int> &data, vector<int> ©, int start, int end) {
if(start == end) {
copy[start] = data[start];
return 0;
}
int length = (end - start)/ 2;
long long left = merge(copy, data, start, start + length);
long long right = merge(copy, data, start + length + 1, end);
int i = start + length;
int j = end;
int indexcopy = end;
long long count = 0;
while(i >= start && j >= start + length + 1) {
if(data[i] > data[j]) {
copy[indexcopy--] = data[i--];
count = count + j -start - length;
}else{
copy[indexcopy--] = data[j--];
}
}
while(i >= start) {
copy[indexcopy--] = data[i--];
}
while(j >= start + length + 1){
copy[indexcopy--] = data[j--];
}
return left + right + count;
}
写的时候难免有大意的地方,若发现文中错误之处, 欢迎指出更正!把所有题已经刷完了, 看时间吧 再继续更新