树的深度优先搜索
// 树的深度优先搜索 function Node(value) { this.value = value this.childs = [] } var a = new Node('A') var b = new Node('B') var c = new Node('C') var d = new Node('D') var e = new Node('E') var f = new Node('F') a.childs.push(b) a.childs.push(c) a.childs.push(d) c.childs.push(e) c.childs.push(f) function deepSearch(root, target) { if (root == null) return false if (root.value == target) return true var result = 0 for (var i = 0; i < root.childs.length; i++) { result |= deepSearch(root.childs[i], target) } return result ? true : false } console.log(deepSearch(a, 'C')) //true console.log(deepSearch(a, 'n')) //false <!--树的深度遍历--> function deepSearchBianli(root) { if (root == null) return console.log(root.value) for (var i = 0; i < root.childs.length; i++) { deepSearchBianli(root.childs[i]) } } deepSearchBianli(a) // A B C E F D 深搜 console.log(a) 输出: Node { value: 'A', childs: [ Node { value: 'B', childs: [] }, Node { value: 'C', childs: [Array] }, Node { value: 'D', childs: [] } ] }
树的广度优先搜索
function Node(value) { this.value = value this.childs = [] } var a = new Node('A') var b = new Node('B') var c = new Node('C') var d = new Node('D') var e = new Node('E') var f = new Node('F') a.childs.push(b) a.childs.push(c) a.childs.push(d) c.childs.push(e) c.childs.push(f) function bfc(roots, target) { if (roots == null || roots.length == 0) return false var childs = [] for (var i = 0; i < roots.length; i++) { if (roots[i].value == target) { return true } else { childs = childs.concat(roots[i].childs) } } return bfc(childs, target) } console.log(bfc([a], 'C')) //true console.log(bfc([a], 'n')) //false // 实现广度优先遍历 function bfcBianli(roots) { if (roots == null) return console.log(roots.value) var childs = [] for (var i = 0; i < roots.childs.length; i++) { console.log(roots.childs[i].value) childs = childs.concat(roots.childs[i].childs) } for (var j = 0; j < childs.length; j++) { console.log(childs[j].value) } } bfcBianli(a)// A B C D E F
图的深度优先搜索
function Node(value) { this.value = value this.neighbor = [] } var a = new Node('A') var b = new Node('B') var c = new Node('C') var d = new Node('D') var e = new Node('E') a.neighbor.push(b) a.neighbor.push(c) b.neighbor.push(a) b.neighbor.push(c) b.neighbor.push(d) c.neighbor.push(a) c.neighbor.push(b) c.neighbor.push(d) d.neighbor.push(b) d.neighbor.push(c) d.neighbor.push(e) e.neighbor.push(d) // 图的深度优先搜索 function deepSearch(node, target, path) { if (node == null) return false <!--比较新插入的点是否比较过--> if (path.indexOf(node) > -1) return false if (node.value == target) return true path.push(node) var result = false for (var i = 0; i < node.neighbor.length; i++) { result |= deepSearch(node.neighbor[i], target, path) } return result ? true : false } console.log(deepSearch(a, 'E', []))//true console.log(deepSearch(a, 'n', []))//false
图的广度优先搜索
function Node(value) { this.value = value this.neighbor = [] } var a = new Node('A') var b = new Node('B') var c = new Node('C') var d = new Node('D') var e = new Node('E') a.neighbor.push(b) a.neighbor.push(c) b.neighbor.push(a) b.neighbor.push(c) b.neighbor.push(d) c.neighbor.push(a) c.neighbor.push(b) c.neighbor.push(d) d.neighbor.push(b) d.neighbor.push(c) d.neighbor.push(e) e.neighbor.push(d) // 图的广度优先搜索 function bfs(nodes, target, path) { if (nodes == null || nodes.length == 0) return false var nextNodes = [] for (var i = 0; i < nodes.length; i++) { <!--比较新插入的点是否比较过--> if (path.indexOf(nodes[i]) > -1) continue; path.push(nodes[i]) if (nodes[i].value == target) return true else nextNodes = nextNodes.concat(nodes[i].neighbor) } return bfs(nextNodes, target, path) } console.log(bfs([a], 'A', []))//true console.log(bfs([a], 'n', []))//false
动态规划之斐波那契数列
递归是通过减少问题规模来求子问题的解,得到最小子问题的解后,主问题就能得到答案;动态规划则是一种递推算法,解决最小子问题然后逐一合并,最终变成主问题的解
实现一个计算斐波那契数列的方法: 1,1,2,3,5,8,13...
// 动态规划,笔试遇到动态规划是后面的大题 // 0 , 1 , 1 , 2 , 3 , 5 , 8 , 13... // 给出第n 位,问第n位值是多少? function fibo1(n) { if (n <= 0) return -1 if (n == 1) return 0 if (n == 2) return 1 let first = 0, seconds = 1, third; for (let i = 3; i <= n; i++) { third = first + seconds first = seconds seconds = third } return third } console.log(fibo1(8))//13 // f(n) = f(n - 1) + f(n -2) function fibo2(n) { if (n <= 0) return -1 if (n == 1) return 0 if (n == 2) return 1 return fibo2(n - 1) + fibo2(n - 2) } console.log(fibo2(8))//13
动态规划之青蛙跳台阶问题
一个青蛙,一次只能跳一级台阶,或者跳两级台阶,问,这个青蛙跳上n级台阶有多少种跳法?
分析:
如果这只青蛙,跳上了第n级台阶,那么最后一次跳跃之前,他一定在n - 1级台阶或者 n - 2级台阶上
那么跳上n级台阶有多少种情况就变成了两个子问题
跳上n - 1级台阶的跳法 加上 跳上n - 级台阶的跳法
按照此逻辑递推,跳上n - 1级台阶可以拆解为两个子问题 即:跳上n - 2级台阶的跳法 加上 跳上n - 3级台阶的跳法
. . . 即: f(n) = f(n - 1) + f(n - 2) function jump(n) { if (n <= 0) return -1 if (n == 1) return 1 if (n == 2) return 2 return jump(n - 1) + jump(n - 2) } console.log(jump(5)) //8 // 1 2 2 // 2 2 1 // 2 1 2 // 1 1 1 1 1 // 2 1 1 1 // 1 2 1 1 // 1 1 2 1 // 1 1 1 2 有8种跳法
动态规划之变态青蛙跳台阶问题
一只青蛙,一次可以跳1级台阶,2级台阶、或n级台阶。问: 这只青蛙,跳上n级台阶有多少种方法?
分析:
f(n) = f( n- 1) + f(n -2) + f(n -3) +......+f(2) + f(1) + f(0) function jump(n) { if (n <= 0) return -1; if (n == 1) return 1; if (n == 2) return 2; let result = 0 for (let i = 1; i < n; i++) { result += jump(n - i) } return result + 1; //加上1代表从0级台阶直接跳上去的场景 } console.log(jump(5))//16 // 1 1 1 1 1 // 1 1 1 2 // 1 1 2 1 // 1 2 1 1 // 2 1 1 1 // 1 2 2 // 2 1 2 // 2 2 1 // 1 1 3 // 3 1 1 // 1 3 1 // 2 3 // 3 2 // 1 4 // 4 1 // 5 // 有16种跳法
链表和数组
大家都用过js中的数组,数组其实是一种线性表的顺序存储结构,它的特点是用一组地址连续的存储单元依次存储数据元素。而它的缺点也正是其特点而造成,比如对数组做删除或者插入的时候,可能需要移动大量的元素。
这里大致模拟一下数组的插入操作:
function insert(arr, index, data) { for (let i = arr.length; i >index; i--) { arr[i] = arr[i - 1]; } arr[index] = data; }
从上面的代码可以看出数组的插入以及删除都有可能会是一个O(n)的操作。从而就引出了链表这种数据结构,链表不要求逻辑上相邻的元素在物理位置上也相邻,因此它没有顺序存储结构所具有的缺点,当然它也失去了数组在一块连续空间内随机存取的优点。
单向链表
图片描述
单向链表的特点:
用一组任意的内存空间去存储数据元素(这里的内存空间可以是连续的,也可以是不连续的)
每个节点(node)都由数据本身和一个指向后续节点的指针组成
整个链表的存取必须从头指针开始,头指针指向第一个节点
最后一个节点的指针指向空(NULL)
链表中的几个主要操作
创建节点
插入节点
搜索/遍历节点
删除节点
合并
初始化节点
指针指向空
存储数据
class Node { constructor(key) { this.next = null; this.key = key; } }
初始化单向链表
每个链表都有一个头指针,指向第一个节点,没节点则指向NULL
class List { constructor() { this.head = null; } }
创建节点
static createNode(key) { return new createNode(key); }
这里说明一下,这一块我是向外暴露了一个静态方法来创建节点,而并非直接把它封装进插入操作里去,因为我感觉这样的逻辑会更加正确一些。 从创建一个链表 -> 创建一个节点 -> 将节点插入进链表中。可能你会遇到一些文章介绍的方式是直接将一个数据作为参数去调用insert操作,在insert内部做了一个创建节点。插入节点(插入到头节点之后)
插入操作只需要去调整节点的指针即可,两种情况:
head没有指向任何节点,说明当前插入的节点是第一个head指向新节点新节点的指针指向NULL
head有指向的节点head指向新的节点新节点的指针指向原本head所指向的节点
insert(node) { // 如果head有指向的节点 if(this.head){ node.next = this.head; }else { node.next = null; } this.head = node; }
搜索节点
从head开始查找
找到节点中的key等于想要查找的key的时候,返回该节点
find(key) { let node = this.head; while(node !== null && node.key !== key){ node = node.next; } return node; }
删除节点
这里分三种情况:
所要删除的节点刚好是第一个,也就是head指向的节点将head指向所要删除节点的下一个节点(node.next)
要删除的节点为最后一个节点寻找到所要删除节点的上一个节点(prevNode)将prevNode中的指针指向NULL
在列表中间删除某个节点寻找到所要删除节点的上一个节点(prevNode)将prevNode中的指针指向当前要删除的这个节点的下一个节点
delete(node) { // 第一种情况 if(node === this.head){ this.head = node.next; return; } // 查找所要删除节点的上一个节点 let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } // 第二种情况 if(node.next === null) { prevNode.next = null; } // 第三种情况 if(node.next) { prevNode.next = node.next; } }
单向链表整体的代码
class ListNode { constructor(key) { this.next = null; this.key = key; } } class List { constructor() { this.head = null; this.length = 0; } static createNode(key) { return new ListNode(key); } // 往头部插入数据 insert(node) { // 如果head后面有指向的节点 if (this.head) { node.next = this.head; } else { node.next = null; } this.head = node; this.length++; } find(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { if (this.length === 0) { throw 'node is undefined'; } if (node === this.head) { this.head = node.next; this.length--; return; } let prevNode = this.head; while (prevNode.next !== node) { prevNode = prevNode.next; } if (node.next === null) { prevNode.next = null; } if (node.next) { prevNode.next = node.next; } this.length--; } }
双向链表
如果你把上面介绍的单向列表都看明白了,那么这里介绍的双向列表其实差不多。
从上面的图可以很清楚的看到双向链表和单向链表的区别。双向链表多了一个指向上一个节点的指针。
初始化节点
指向前一个节点的指针
指向后一个节点的指针
节点数据
class ListNode { this.prev = null; this.next = null; this.key = key; }
初始化双向链表
头指针指向NULL
class List { constructor(){ this.head = null; } }
创建节点
static createNode(key){ return new ListNode(key); }
插入节点((插入到头节点之后)
看上图中head后面的第一个节点可以知道,该节点的prev指向NULL节点的next指针指向后一个节点, 也就是当前头指针所指向的那个节点如果head后有节点,那么原本head后的节点的prev指向新插入的这个节点(因为是双向的嘛)最后将head指向新的节点
insert(node) { node.prev = null; node.next = this.head; if(this.head){ this.head.prev = node; } this.head = node; }
搜索节点
这里和单向节点一样,就直接贴代码了
search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; }
删除节点
和之前单向链表一样,分三种情况去看:
删除的是第一个节点
head指向所要删除节点的下一个节点
下一个节点的prev指针指向所要删除节点的上一个节点
删除的是中间的某个节点
所要删除的前一个节点的next指向所要删除的下一个节点
所要删除的下一个节点的prev指向所要删除的前一个节点
删除的是最后一个节点
要删除的节点的上一个节点的next指向null(也就是指向删除节点的next所指的地址)
图片描述
delete(node) { const {prev,next} = node; delete node.prev; delete node.next; if(node === this.head){ this.head = next; } if(next){ next.prev = prev; } if(prev){ prev.next = next; } }
双向链表整体代码
class ListNode { constructor(key) { // 指向前一个节点 this.prev = null; // 指向后一个节点 this.next = null; // 节点的数据(或者用于查找的键) this.key = key; } }
/** * 双向链表 */ class List { constructor() { this.head = null; } static createNode(key) { return new ListNode(key); } insert(node) { node.prev = null; node.next = this.head; if (this.head) { this.head.prev = node; } this.head = node; } search(key) { let node = this.head; while (node !== null && node.key !== key) { node = node.next; } return node; } delete(node) { const { prev, next } = node; delete node.prev; delete node.next; if (node === this.head) { this.head = next; } if (prev) { prev.next = next; } if (next) { next.prev = prev; } } }
1 数组中重复的数字
题目:在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的,但不知道几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组{2,3,1,0,2,5,3}那么对应输出是1或者3
思路:方法有很多,比如说先排序。但是完全可以在时间复杂度为O(N),空间复杂度是O(1)的条件下实现。
如果没有重复的 说明0-N中的每一个数都有,可以把每个数放在下标为该数的位置,比如0放在number[0],1放在number[1].而且每个数所以可以从第一个开始,把每个数和这个数该在的位置替换位置,如果发现那个位置的数和自己相等,则返回。
#include <stdio.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool Duplicate(int numbers[],int length,int *duplicate) { int i = 0; int swap =0; if(numbers == NULL || length<=0) { return false; } for (i = 0;i < length; i++) { if(numbers[i]<0 || numbers[i]>=length) return false; } for(i = 0;i<length ; i++) { while(numbers[i]!=i) { if(numbers[i] == numbers[numbers[i]]) { *duplicate = numbers[i]; return true; } swap = numbers[i]; numbers[i] = numbers[numbers[i]]; numbers[numbers[i]] = swap; } } return none; } int main() { int numbers[6] = {1,0,2,3,4,4}; int length = sizeof(numbers)/4; int duplicate = -1; bool state = Duplicate(numbers,length, &duplicate); printf("%d,%d",state,duplicate); }
2 数组中重复的数字(不能修改数组)
题目:在一个长度为N+1的数组里的所有数字都在1~n范围内,所以数组中至少有一个数字是重复的。请找出数组中任意一个复杂的数字,但不能修改输入的数组。
思路:这道题很简单,因为有很多种解法。但是不同的解法对应不同的时间复杂度和空间复杂度,要根据面试官要求写出他想要的程序。
1.新建一个数组,对该数组进行排序。比如插入排序,时间复杂度是O(N2).排完序找重复的数就很简单了,空间复杂度是O(N)
2.第一种方法时间复杂度可以通过改善排序算法降低,但是空间复杂度无法降低。书中提出了一种可以不额外开辟空间的算法,思路如下:由于数组是有规律的,所有数都在1~n内,有n+1个数。把数字分为两部分(根据数值大小),1 ~ m, m+1 ~ n.一定有一个范围内的数比该区间的大小大1(1 ~m应该有m个数,m+1就有问题 )。一直折半,最后当区间范围的头和尾部相等时,就找到了。
最开始不太好想,可以想象一个盒子里全是红球,有一个黑球,然后把盒子不断分成一半,而你又能判断黑球在哪一半。一直分到最后,自然就出来了。
这种方法有个问题:就是不能找到所有重复的数,{1,1,3,4,5,5}这种只能找到4,因为第一次分半时就和把重复的1当做2的情况一样了,让他安全了。
空间复杂度是O(1),看一下时间复杂度怎么算,最开始范围是n,最后范围大小是1,每次一半,所以2的x次方等于n,x = log2(n),范围二分过程的时间复杂度是log2(n).每次分完要判断整个数组中所有数是否在该范围内,O(n).所以最后总时间复杂度为O(nlog(n))
#include <stdio.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int GetCount(int start,int end,int length,const int *numbers) { int i = 0; int count = 0; for(i = 0;i<length ; i++) { if(numbers[i]>=start && numbers[i]<=end) count ++ ; } return count; } bool Duplicate(const int* numbers,int length,int *deplicate) { int start = 1; int end = length - 1; int middle; int count; int i; if(numbers ==0 || length == 0) return false; for(i=0;i<length;i++) { if(numbers[i]>length-1 || numbers[i]<=0 ) return false; } while(1) { middle = (start + end)/2; count = GetCount(start,middle,length,numbers); /* if(count>(middle - start + 1)) end = middle; else start = middle+1;*/ if(end <= start) { if(count>1) { *deplicate = start; return true; } else return false; } if(count>(middle - start + 1)) end = middle; else start = middle+1; } } int main() { int numbers[] = { 3, 2, 1, 4, 4, 5, 6, 7 }; int length = sizeof(numbers)/4; int duplicate = 0; bool state = Duplicate(numbers,length, &duplicate); printf("%d,%d",state,duplicate); }
3.代码有一个地方要注意:GetCount函数之后必须直接判断end<=start。
{ 3, 2, 1, 4, 4, 5, 6, 7 }为例:
循环几次之后,start = 3,end = 4,middle = 3.GetCount得到1,start变成4,这时候end<=start条件满足,进入判断,但是count=1;出错。
也就是说,在判断之前必须得到的是最新的GetCount。这个BUG很难发现,大多数情况是不会出问题的。
4 二维数组中的查找
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序,请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
思路:将数组看成一个矩阵,从左下角或者右上角开始,以左上角为例,若小于该数则行 ++,如果大于该数则列–。终止条件为找到等于该数或者到了右下角。
注意:
1.二维数组在定义时不能省略列数(后面那个)
2.二维数组传递到子函数中,常用的有两种方法。
一种是直接传入二维数组(本身是二维指针)
int matrix[][4];
bool result = Find(matrix, 4, 4, number);
bool Find(int matrix[][4], int rows, int columns, int number);
这种方法的缺点是子函数必须知道传入的二维数组的列数是多少,如果把4改成5编译会通过,但是实际上的内容就错了。
第二种方法是将二维数据降级为一级指针,然后将二维数组看成一维数组int matrix[][4];
bool result = Find((int*)matrix, 4, 4, number);
bool Find(int* matrix, int rows, int columns, int number);
这种方法的缺点是无法用a[i][j]这种方式查询数组了,要用a[i*colums+j]来查询。
#include <stdio.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool Find(int* matrix, int rows, int columns, int number) { int i = 0; int j = columns-1; while(i<rows && j>=0) { if(matrix[i*columns+j] == number) { return true; } else if(matrix[i*columns+j]>number) j--; else i++; } return false; } int main() { int matrix[][4] = {{1, 2, 8, 9}, {2, 4, 9, 12}, {4, 7, 10, 13}, {6, 8, 11, 15}}; int number = 4; bool result = Find((int*)matrix, 4, 4, number); printf("%d",result); }
4.字符串替换空格
题目:请实现一个函数,把字符串中的每个空格替换成%20.
基本思路:这道题的问题在于要把一个字符的空格变成三个字符的%20,所以要改变字符串。常规思路就是从头开始查找,找到空格后把后面的字符向后移动两位然后把这三个位置替换为%20.
时间复杂度为O(n2).
优化思路:上面思路的时间主要浪费在每次找到空格都要移动后面的字符串。可以先一次性找出空格的数量,这样字符串的新长度就能确定了,由于字符串的后面是空的,所以可以从后向前复制,两个指针,p1指向新长度的最后,p2指向老字符串的最后。然后不断判断老字符串,如果不是空格就复制过去,是空格就把%20写过去。
停止条件:p2 = p1(p2=p1说明前面已经没空格了)
时间复杂度为O(n)
注意:
1.字符串的两种定义,char * str = “csh”;char str[] = “csh”.两种方法是不一样的。C语言会把字符串放到只读存储区,第一种方式会指向该位置,第二种方式会在数据区分配一段区域,然后把字符串复制过去。因为程序要改变字符串,所以必须用char str[]="csh"的方式,否则程序会崩溃。
2.字符串结束的标志是‘\0’,不要忘记。否则会出现乱码。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool ReplaceSpace(char* str,int length) { char* p1,*p2; int originalLength = 0; int newLength = 0; int spaceNum = 0; int i = 0; if(str == 0) return false; while(str[i]!= '\0') { originalLength ++; if(str[i] == ' ') spaceNum++; i++; } newLength = originalLength + spaceNum*2 ; if(newLength > length) return false; p1 = &str[originalLength]; p2 = &str[newLength ]; //while(p1>=&str[0] && p2>p1) while (p2>p1) { if(*p1 == ' ') { *p2-- = '0'; *p2-- = '2'; *p2-- = '%'; } else *p2-- = *p1; p1 --; } return true; } int main() { const int length = 100; char str[100] = " We are happy "; //char *str = " We are happy "; bool state = ReplaceSpace(str, length); if(state) { printf("%s",str); } }
5.链表反向打印
题目:输入一个链表的头结点,从未到头打印出每个节点的值
思路 1.放到栈里 打印栈。2.递归
(C语言需要自己写链表,锻炼以下功底吧)
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; bool NodePush(listNode* L,int data) { listNode* p; listNode* node = (listNode*)malloc(sizeof(listNode)); if(!node)return false; node->data = data; node->next = NULL; p = L; while(p->next!=NULL) p = p->next; p->next = node; return true; } listNode HeadCreat(int data) { listNode head ; head.data = data; head.next = NULL; return head; } void PrintListReverse(listNode* head) { int stack[40]; int i = 0; listNode* p = head; if(head == NULL)return; while(p->next!=NULL) { stack[i++] = p->data; p = p->next; } stack[i] = p->data; for(;i>=0;i--) { printf("%d ",stack[i]); } printf("\n"); } void PrintListReverse1(listNode* head) { if(head == NULL)return; if(head->next!=NULL) PrintListReverse1(head->next); printf("%d ",head->data); } int main() { int i; listNode node = HeadCreat(4); listNode* head = &node; for(i=5;i<20;i++) { NodePush(head ,i); } PrintListReverse(head); PrintListReverse1(head); }
6.重建二叉树
题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如,输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树在这里插入图片描述
思路:二叉树的前序遍历和中序遍历就不讲了,自己看一下。其实编程就是把自己的思路用计算机去实现。所以先用语言把思路写出来:根是前序遍历的第一个,左子树是中序遍历中根的左边,右子树是中序遍历中根的右边。不断递归就好了。
结束条件:没有左子树也没有右子树。
程序写的有点蠢了,非要用指针加来加去搞复杂了,其实看成数组,用数组标会简单点。写了一般的时候发现的也就懒得改了,大家可以试试传入首地址和长度,用下标去做。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct binaryTreeNode { int value; struct binaryTreeNode* left; struct binaryTreeNode* right; }binaryTree; binaryTree* ConstructCore(int *inorderStart,int* inorderEnd,int *preorderStart,int* preorderEnd) { int *p; int *preorderEndLeft; binaryTree* root = (binaryTree*)malloc(sizeof(binaryTree)); if(inorderStart == inorderEnd)//结束条件 { if(*inorderEnd != *preorderEnd)return false; else { root->left = NULL; root->right = NULL; root->value = *preorderStart; return root; } } root->value = *preorderStart++; for(p = inorderStart;p<=inorderEnd && *p!=root->value;p++); if(p>inorderEnd)return false;//中序遍历中没找到根节点 preorderEndLeft = preorderStart+(p-1-inorderStart); root->left = ConstructCore(inorderStart,p-1,preorderStart,preorderEndLeft); root->right = ConstructCore(p+1,inorderEnd,preorderEndLeft+1,preorderEnd); return root; } binaryTree* Construct(int preorder[],int inorder[],int length1,int length2) { if(length1!=length2 || !length1) return false; return ConstructCore(inorder,inorder+length1-1,preorder,preorder+length2-1); } int main() { int preorder[] = {1,2,4,7,3,5,6,8}; int inorder[] = {4,7,2,1,5,3,8,6}; binaryTree* root = Construct(preorder,inorder,sizeof(preorder)/sizeof(int),sizeof(inorder)/sizeof(int)); }
7.二叉树的下一个节点
题目:给定一棵二叉树和其中的一个节点,如何找出中序遍历的下一个节点?树中的节点除了有两个分别指向左右子节点的指针,还有一个指向父节点的指针。
在这里插入图片描述
思路:先用文字理清思路:二叉树中序遍历找下一个节点。上图的中序遍历为dbheiafcg,有三种情况:
1.有右子树,那就去找右子树中最左的子树。b的下一个是h
2.没有右子树,但是是双亲的左子树。那说明它是双亲左子树里面最后一个,所以下一个就是双亲。比如d的下一个是b
3.没有右子树 ,还是双亲的右子树。那说明双亲在他前面,需要一直向上找,找到是双亲的左子树的。这个双亲就是下一个。比如i,找到e,再找到b,再找到a,满足条件了,就是a.
(写完之后发现2和3其实有相关性,都是找有左子树的双亲,2是3的特殊情况)
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct binaryTreeNode { int value; struct binaryTreeNode* left; struct binaryTreeNode* right; struct binaryTreeNode* parent; }binaryTree; binaryTree* GetNext(binaryTree* node) { binaryTree* parent; binaryTree* current; if(node==NULL)return NULL; if(node->right!=NULL) { node = node->right; while(node->left!=NULL) node = node->left; return node; } else if(node->parent!=NULL) { parent = node->parent; current = node; while( parent!=NULL && current!=parent->left)//反过来会出BUG,因为parent是NULL是就访问不到left了 { current = parent; parent = parent->parent; } if(parent!=NULL) return parent; else return NULL; } } /*构建二叉树*/ binaryTree* ContructBinaryTree(int value) { binaryTree* node = (binaryTree*)malloc(sizeof(binaryTree)); node->left = NULL; node->right = NULL; node->parent = NULL; node->value = value; return node; } void ConnectTreeNodes(binaryTree* parent, binaryTree* left, binaryTree* right) { if(parent != NULL) { parent->left = left; parent->right = right; if(left != NULL) left->parent = parent; if(right != NULL) right->parent = parent; } } /*打印二叉树*/ void PrintTree(binaryTree* root) { if(root->left!=NULL) PrintTree(root->left); printf("%d ",root->value); if(root->right!=NULL) PrintTree(root->right); } int main() { binaryTree *node; binaryTree* pNode8 = ContructBinaryTree(8); binaryTree* pNode6 = ContructBinaryTree(6); binaryTree* pNode10 = ContructBinaryTree(10); binaryTree* pNode5 = ContructBinaryTree(5); binaryTree* pNode7 = ContructBinaryTree(7); binaryTree* pNode9 = ContructBinaryTree(9); binaryTree* pNode11 = ContructBinaryTree(11); ConnectTreeNodes(pNode8, pNode6, pNode10); ConnectTreeNodes(pNode6, pNode5, pNode7); ConnectTreeNodes(pNode10, pNode9, pNode11); PrintTree(pNode8); node = GetNext(pNode5); if(node)printf("\n%d",node->value); node = GetNext(pNode6); if(node)printf("\n%d",node->value); node = GetNext(pNode7); if(node)printf("\n%d",node->value); node = GetNext(pNode8); if(node)printf("\n%d",node->value); node = GetNext(pNode9); if(node)printf("\n%d",node->value); node = GetNext(pNode10); if(node)printf("\n%d",node->value); node = GetNext(pNode11); if(node)printf("\n%d",node->value); }
8.用两个栈实现队列
题目:用两个栈实现一个队列。
思路:很简单一道题,栈是先进先出,队列是先进后出,要想让栈有队列的效果,只需要在出栈的时候再建立一个辅助栈,然后:1.先把原始栈出栈到辅助栈里面。2.从辅助栈中出栈。3.将辅助栈的东西再出栈回原栈。4.释放辅助栈
这道题如果放在C里面分分钟就写完了,因为C自带栈函数,但是用C语言就要自己写进栈函数、出栈函数了,代码量还有点大。还有一点就是虽然在子函数中构建栈,子函数返回后会把子函数的栈都释放的,但是栈是要单独申请空间的,不是存在子函数栈中。所以在用完的时候一定要把辅助栈给释放掉。
注意:指针相减得到的是指向类型的个数,不是地址差
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 #define STACK_ININ_SIZE 20//栈初始化长度 #define STACK_INCREMENT 10//每次增长长度 typedef struct Stack { int *base; int *top; int stackSize;//栈长度 }Stack; Stack* InitStack() { Stack* stack = (Stack*)malloc(sizeof(Stack)); stack->base = (int*)malloc(sizeof(int)*STACK_ININ_SIZE); if(stack->base) stack->stackSize = STACK_ININ_SIZE; else return false; stack->top = stack->base; return stack; } bool DestroyStack(Stack* stack) { free(stack->top); free(stack); } bool PopStack(Stack *stack,int elem) { int *a; //if((stack->top - stack->base)/sizeof(int)==stack->stackSize) if((stack->top - stack->base)==stack->stackSize)//等于的时候说明已经存满 指针相减就是指针指向类型的数目 { a = (int*)realloc(stack->base,(stack->stackSize+STACK_INCREMENT)*sizeof(int)); if(!a) return false; else { stack->base = a; stack->top = stack->base + stack->stackSize; stack->stackSize += STACK_INCREMENT; } } *stack->top++ = elem; return true; } int PushStack(Stack* stack) { if(stack->top == stack->base) return false; return *--stack->top; } bool AppendTail(Stack* stack,int elem) { if(PopStack(stack,elem)) return true; else return false; } int DeleteHead(Stack* stack1) { int head; Stack *stack2 = InitStack(); while(stack1->top>stack1->base) { PopStack(stack2,PushStack(stack1)); } head = PushStack(stack2); while(stack2->top>stack2->base) { PopStack(stack1,PushStack(stack2)); } DestroyStack(stack2); return head; } int main() { int i=0; int *p; Stack *stack1 = InitStack(); /*构建栈*/ for(i=1;i<31;i++) { AppendTail(stack1,i); } for(i=0;i<30;i++) { printf("%d ",DeleteHead(stack1)); } }
9.斐波那契数列
题目:写一个函数,输入n,求斐波那契数列的第n项。斐波那契数列的定义如下:
在这里插入图片描述
思路:
1.斐波那契是最经典的递归题目,可以用递归的方法来做。但是递归的时间复杂度和空间复杂度都很低,随着n的增大,计算量是很恐怖的。
2.用递归可以做的题目都可以用循环来做,先根据第0和1项算出第2项,再根据1和2算出第3项。需要三个变量,用来存储前两个斐波那契数。比较简单。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int Fibonacci(unsigned int n) { if(n==0) return 0; if(n==1) return 1; return Fibonacci(n-2)+Fibonacci(n-1); } int Fibonacci1(unsigned int n) { int i; unsigned int fib1,fib2,fibN = 0; if(n==0)return 0; if(n==1)return 1; fib1 = 0; fib2 = 1; for(i=2;i<=n;i++) { fibN = fib1+fib2; fib1 = fib2; fib2 = fibN; } return fibN; } int main() { int i; for(i=0;i<10;i++) printf("%d ",Fibonacci(i)); printf("\n"); for(i=0;i<10;i++) printf("%d ",Fibonacci1(i)); }
10.延伸题目:青蛙上楼梯问题,青蛙一次可以上一个台阶或者上两个台阶,那么问有n个台阶时,青蛙 有多少种上法。
思路:函数f(n),因为青蛙只有两个台阶选择,所以我们可以假定已经跳了一个,那么还有f(n-1)个选择;假定已经跳了两个,那么还有f(n-2)种选择。所以f(n) = f(n-1)+f(n-2).这还是斐波那契数列,只不过f(1) =1,f(2)=2.
11 旋转数组的最小数字
题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。例如,数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,该数组的最小值为1.
思路:常规思路很简单,从头找比第一个小的就行。时间复杂度为O(n).
如何能让时间复杂度更小一点。其实这种题很容易就会想到分治,也就是二分查找。观察数组的顺序,最小的数字其实是打乱递增顺序的分界点,二分查找找到中间位置的数,如果大于等于头,说明没乱序。那么最小的在后半段,应该让中间变成头;如果中间小于头,说明乱序了,最小的在前半段,应该让中间变成尾。
结束条件:和二分查找一样,当尾减头等于一个数据大小的时候,结束。返回头还是尾要想一下。因为判断的过程一直都是保证头大于尾,所以后面肯定是比前面的要小的,所以返回尾。
特殊情况:
1.没旋转,所以在最开始先判断一下头是否小于尾,如果不直接返回第一个(好好考虑等于的问题)。
2.11101,10111,头中间还有尾都相等,这是判断不了的,所以要判断头中间尾三者是否相等。
最后代码的时间复杂度一般是O(log(n)),最差是O(n),最好是O(1)
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int FindMin(int* data,int length) { int indexHead = 0; int indexTail = length -1; int indexMiddle; int i; if(data[indexHead]<data[indexTail]) return data[indexHead]; while((indexTail-indexHead)>1) { indexMiddle = (indexHead+indexTail)/2; /*1 0 1 1 1 头尾中间都相等*/ if(data[indexHead]==data[indexTail] && data[indexHead] == data[indexMiddle]) { for(i=indexHead+1;i<indexTail;i++) { if(data[i]<data[indexHead]) return data[i]; } return false; } /*************************/ if(data[indexMiddle]>=data[indexHead]) indexHead = indexMiddle; else indexTail = indexMiddle; } return data[indexTail]; } int main() { int data[] = {2,3,4,5.6,7,8,1}; printf("%d",FindMin(data,sizeof(data)/sizeof(int))); }
11.矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵的任意一格开始,每一步可以在矩阵中向左右上下移动一格。如果一条路景经过了矩阵的某一格,那么该路径不能再次进入该格子。例如,在下面的3*4的矩阵中包含一条字符串bfce的路径。但矩阵中不包含字符串abfb的路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
在这里插入图片描述
思路:这是很典型的回溯型题。所谓回溯就是满足某个条件了,要继续判断,判断后如果失败就往前回,回到最初继续寻找。
双重循环从头遍历矩阵,如果找到了目标字符串中的第一个字符,就继续寻找他的四周,找到后在继续寻找,直至所有字符串都找到,返回寻找成功,找不到就回溯,回到循环。有一个问题就是用过的字符不能再用了,所以还要加一个辅助矩阵,将用过的字符标记一下。等判定失败后,要释放用过的字符,不要影响接下来的使用。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool HasPathCore(char* martrix,int rows,int cols,int row,int col, char* str,int* pathlength,bool* visit)//判断某位置是否有某字符 { bool hasPath = 0; if(str[*pathlength]=='\0') return true; //需要确定:1.行数在矩阵里 2.列数在矩阵里 3.相等 4.没用过 if(row>=0 && row<rows && col>=0 && col<cols && str[*pathlength]==martrix[row*cols+col] && !visit[row*cols+col]) { (*pathlength)++; visit[row*cols+col]=1; hasPath = (HasPathCore(martrix,rows,cols,row-1,col,str,pathlength,visit) ||HasPathCore(martrix,rows,cols,row+1,col,str,pathlength,visit) ||HasPathCore(martrix,rows,cols,row,col+1,str,pathlength,visit) ||HasPathCore(martrix,rows,cols,row,col-1,str,pathlength,visit)); if(!hasPath) {//没找到就释放visit (*pathlength)--; visit[row*cols+col] = 0; return false; } else { return true; } } return false; } bool HasPath(char* martrix,int rows,int cols,char* str) { int i = 0; int* pathLength; int row = 0; int col = 0; bool* visit; if(martrix == NULL || rows<1 || cols<1 || str == NULL) return false; pathLength = (int *)malloc(sizeof(int)); *pathLength = 0; visit = (bool*)malloc(sizeof(int)*(rows*cols)); for(i=0;i<rows*cols;i++) visit[i]= 0; for(row = 0; row<rows; row++) { for(col = 0;col < cols;col++) { if(HasPathCore(martrix,rows,cols,row,col,str,pathLength,visit)) { free(pathLength); free(visit); return true; } } } free(pathLength); free(visit); return false; } int main() { char martrix[][4] = {{'A','B','T','G'},{'C','F','C','S'},{'J','D','E','H'}}; char *str ="BFCEA"; printf("%d",HasPath((char*)martrix,3,4,str)); str = "BFCE"; printf("%d",HasPath((char*)martrix,3,4,str)); }
12.机器人的运动范围
题目:地上有一个m行n列的方格。一个机器人从坐标(0,0)的各自开始移动,它每次可以向左右上下移动一格,但不能进入行坐标和列坐标的数位之和大于k的格子。例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7=18.但它不能进入方格(35,38),因为3+5+3+8=19.请问该机器人能达到多少个格子。
思路:这道题和上道题基本一样,也是回溯算法。一定要走过之前的格子才能走之后的格子,所以从第一个能走的开始不断的找上下左右,找到一个就把辅助矩阵的该位置置1,然后知道找不到了,一步一步回溯。就能找到能走多少格子了。起初我的想法是回溯之后再遍历一下辅助矩阵,看有多少个1就好了。后来发现书上的解法更简单点,每次返回的时候能返回经过该点能走过的点,所以一直回溯到最后就直接有点数了。
然后在条件上再加一个判断位数就好了
结束条件:
1.行超范围
2.列超范围
3.辅助矩阵已经置一
4.判断位数失败
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int GetDigitSum(int number) { int sum = 0; while(number>0) { sum+=number%10; number/=10; } return sum; } int MovingCountCore(int threshold,int rows,int cols,int row,int col,bool* visit) { int count = 0; if(row>=0 && row<rows && col>=0 && col<cols && GetDigitSum(row)+GetDigitSum(col)<=threshold && !visit[cols*row+col]) { visit[cols*row+col] = 1; count = 1 + MovingCountCore(threshold,rows,cols,row+1,col,visit)+ MovingCountCore(threshold,rows,cols,row-1,col,visit)+ MovingCountCore(threshold,rows,cols,row,col+1,visit)+ MovingCountCore(threshold,rows,cols,row,col-1,visit); } return count; } int MovingCount(int threshold,int rows,int cols) { bool *visit; int count; int i = 0; if(rows<=0 || cols<=0 ||threshold<0) return false; visit = (bool*)malloc(sizeof(bool)*(rows*cols)); for(i=0;i<rows*cols;i++) { visit[i] = 0; } count = MovingCountCore(threshold,rows,cols,0,0,visit); free(visit); return count; } int main() { printf("%d",MovingCount(1,2,2)); }
13.剪绳子
题目:给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m].请问k[0]×k[1]×…×k[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长分别为2、3、3的三段,此时得到的最大乘积是18.
思路:有三种做法,动态规划、贪心、递归,可以通过这道题好好体会一下三种方法的区别。
递归:递归就是从上到下不断分成子问题,比如8,可以是k[1]*k[7],k[2]k[6],一直比较下去取最大的。可能有人会有疑问,这只切两段啊。这里的k[7]是代表长度为7时候的最优解,而不是那一段是7.使用递归的时候就要把递归停止条件整明白,绳子剩1返回1,剩2返回2,剩3返回3.为什么?因为2如果不返回,让它继续递归,那就成11=1了。就错了。
动态规划:动态规划可以看成递归反过来,它是自下向上的。动态规划需要新开辟一个数组去存放每个过程的最优解,而不是通过不断递归去得到最优解。
贪心:递归和动态规划都是局部最优的原则,但是贪心不是,贪心是确定一个整体最优的规则,他不考虑局部性。贪心的关键在于这个规则的制定。这道题的规则就是尽量多剪长为3的绳子;当剩下长度为4的时候剪成两段长为2的。
#include <stdio.h> #include <stdlib.h> #include <math.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int MaxProductCutting1(int length) { int *product; int i,j,max; if(length<2)return 0; if(length==2)return 1; if(length==3)return 2; product = (int*)malloc((length+1)*sizeof(int)); for(i=0;i<length+1;i++) product[i]=0; product[0] = 0; product[1] = 1; product[2] = 2; product[3] = 3; for(i=4;i<length+1;i++) { for(j=1;j<=i/2;j++) { max = product[j]*product[i-j]; if(max>product[i]) product[i] = max; } } max = product[length]; free(product); return max; } int MaxProductCutting2(int length) { int timesOf3 = 0; int timesOf2 = 0; int result; if(length<2)return 0; if(length==2)return 1; if(length==3)return 2; timesOf3 = length/3; if(length%3 == 1) { timesOf3 -=1; timesOf2 = 2; }else if(length%3 == 2) timesOf2 = 1; result = (int)pow(3,timesOf3)*(int)pow(2,timesOf2); return result; } int MaxProductCutting3Core(int length) { int i = 0; int max = 0; int product; if(length<1)return 0; if(length==1)return 1; if(length==2)return 2; if(length==3)return 3;//很重要 for(i=1;i<=length/2;i++) { product = MaxProductCutting3Core(i)*MaxProductCutting3Core(length-i); if(product>max) max = product; } return max; } int MaxProductCutting3(int length) { if(length<2)return 0; if(length==2)return 1; if(length==3)return 2; return MaxProductCutting3Core(length); } int main() { int i; for(i=0;i<15;i++) printf("%d ",MaxProductCutting1(i)); printf("\n"); for(i=0;i<15;i++) printf("%d ",MaxProductCutting2(i)); printf("\n"); for(i=0;i<15;i++) printf("%d ",MaxProductCutting3(i)); }
14.二进制中1的个数
题目:请实现一个函数,输入一个整数,输出该数二进制表示中1的个数。例如,把9表示成二进制是1001,有两位是1。因为如果输入9,该函数输出2.
负数二进制:复习一下,负数在二进制的存储方式是补码,补码是除了最高位,反码加一。例如-1的原码是10000001,反码是11111110,补码是11111111.负数的移位也是可以乘除的,-2补码是11111110,-4补码是11111101.他的移位是补1,不是补0.
思路:这道题很简单,主要是考察对于移位操作的掌握。对与计算机来说移位操作要比乘除法快,所以我们应该多移位少乘除。
没看答案前的想法:通过取余(或&1)判断是否是1,是就把count+1,不是就不加。然后将数除以2(或>>1).这样对于正数没问题,但是负数就 不行了。负数要保证最高位为负,所以移到最后也只会是-1不会是0(除法不会出现这种情况)。虽然负数也可以移位,但是有点复杂,不建议。我们可以左移1,去跟原数不断的与,来判断每一位。
比较一下:原来是将数不断和1与,比完后将数右移。改进还是和1比,比完将1左移。其实还是一样的。
优化:这道题还有一种优化方法,每次和自己减1做与运算,就相当于把最低位的1去掉了,挺神奇的思路。程序好写,不写了。不太好讲。直接上数据吧:10101有三个1。
1.先让10101与10100,等于10100.原来数里面的1少了一个,统计的里面加1;
2.10100与10011,等于10000.原来数里面的1又少了一个,统计的里面加1;
3.10000与01111,等于0,原来数里面的1又少了一个,统计的里面加1。结束。
#include <stdio.h> #include <stdlib.h> #include <math.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int NumberOf1(int n) { int number=0; unsigned int flag =1; while(flag) { if(n & flag)number++; flag = flag<<1; } return number; } int main() { int i; for(i=0;i<20;i++) printf("%d ",NumberOf1(i)); }
16.数值的整数次方
题目:实现函数double Power(double base,int exp),求base的exp次方。不得使用库函数,同时不需要考虑大数问题。
思路:这道题考的不是思路,考的是考虑问题的周全性。先抛开程序,从数学的角度来思考这个问题:
1.指数为正或0时,原数为正负0都没关系。
2.指数为负时,原数不能为0。返回原数的指数的负数次幂的倒数。
3.一旦出错了,要让用户知道错了。可以用一个返回值-1来表示错误,也可以设置一个全局变量表示,C语言没有抛出异常。
用程序表示就是if((base== 0) && (exp<0)),但是由于精度问题double类型不能用==来判断,需要先定义一个精度,然后比较两个数的差的绝对值是否小于该精度:const double eps = 1e-6; if(abs(base-0.0)
考虑完负数和0的情况之后就比较简单了。再想想怎么优化,可以用递归来减少时间复杂度,把O(N)降到O(log(N)):在这里插入图片描述
两种程序都写了
#include <stdio.h> #include <stdlib.h> #include <math.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int errorStatus; double PowerWithUnsigned(double base,unsigned int exp) { /* double result = 1; while(exp) { result*=base; exp--; } return result;*/ double result; if(exp == 0) return 1; if(exp == 1) return base; result = PowerWithUnsigned(base,exp>>1); result*=result; if(exp & 0x1) result*= base; return result; } double Power(double base,int exp) { const double eps = 1e-6; double result = 1; if(abs(base-0.0)<eps && exp<0) { errorStatus = 1; return 0; } if(exp<0) return 1.0/PowerWithUnsigned(base,-exp); else return PowerWithUnsigned(base,exp); } int main() { printf("%f ",Power(2,-3)); }
17.打印从1到最大的n位数
题目:输入数字n,按顺序打印出从1到最大的n位十进制数。比如输入3,则打印出1-999.
思路:难在没有确定n的范围,所以打印的数字完全可能比long long 还大。所以需要借助字符串来做这道题
1.在输入n的时候,说明最多有n位,所以先申请n+1个字符长的空间。然后前n位都变为‘0’,最后一位变为‘\0’(因为字符串的结束标志是‘\0’,如果不用print("%s")、strlen等跟字符串相关的函数),多申请的最后一位也意义不大)
2.存储完之后就每次加1然后打印,判断到最大的时候停止3.如何将字符串按十进制的思维加1是这道题的难点。
3.1 从字符串最后一位开始加1
3.2 如果满10进位标志位置1,不满10结束(只是本函数结束).因为字符串9+1不是10所以需要先将字符转换为整型。
3.3 如果满10的是第一位,返回结束标志位(所有都结束)
4.如何将字符串打印很简单,用prinf("%s")就行,这种做法会把前面的0也打印出来;还不能把后面的0省略掉,所以打印需要判断到第一个不为0的数之后再打印。
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int errorStatus; void PrintNumber(char* number) { bool begin = false; int i = 0; int length = strlen(number); for(i=0;i<length;i++) { if(!begin && number[i]!='0') begin = true; if(begin) printf("%c",number[i]); } printf("\t"); } bool Increment(char* number) { int i; int nSum = 0; int nTakeOver = 0; int isOverflow = 0; int length = strlen(number); for(i=length-1;i>=0;i--) { //只有最低位加一 if(i==length-1) number[i]++; nSum = number[i] - '0' + nTakeOver; if(nSum==10) { nTakeOver = 1; if(i==0) isOverflow = 1; else number[i] = '0'; } else { number[i] = nSum + '0'; break; } } return isOverflow; } void Print(int n) { char* number ; int i=0; if(n<=0) return; number = (char*)malloc(sizeof(char)*(n+1)); for(i=0;i<n;i++) number[i] = '0'; number[i] = '\0'; while(!Increment(number)) { PrintNumber(number); } free(number); } int main() { Print(3); }
思路2:
从另一角度来看这道题,对于输入为n,其实就是从第0位到第n-1位递归,从0写到9.递归的代码要简介一些
结束条件:位数为n
#include <stdio.h> #include <stdlib.h> #include <math.h> #include <string.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 int errorStatus; void PrintNumber(char* number) { bool begin = false; int i = 0; int length = strlen(number); for(i=0;i<length;i++) { if(!begin && number[i]!='0') begin = true; if(begin) printf("%c",number[i]); } printf("\t"); } void Print1ToMaxCore(char* number, int length, int index) { int i = 0; if (index == length) { PrintNumber(number); return; } for (i = 0; i < 10; i++) { number[index] = i + '0'; Print1ToMaxCore(number, length, index + 1); } } void Print1ToMax(int n) { char* number = (char*)malloc(sizeof(char)*(n+1)); int i=0; if (n <= 0) return; number[n] = '\0'; Print1ToMaxCore(number, n, 0); free(number); } int main() { Print1ToMaxOfNDigits_2(3); }
18-1 删除链表的节点
题目:在O(1)时间内删除链表节点,给定单向链表的头指针和一个节点指针,定义一个函数在O(1)时间内删除该节点。
思路:这道题其实有点瓜皮。常规链表只有一个头指针,靠每个节点结构体中的指针寻找下一个节点的位置。所以删除节点的时间复杂度肯定是O(n).这道题是链表中的每个节点都有一个节点指针指向该节点,输入到函数中。所以不用从头节点遍历去找这个节点的位置了。
常规删除节点的时候要做两件事,第一件是找到该节点,第二件事是找到该节点前面的节点,让前面的那个节点指向找到的节点的下一个节点。有指针指向要删除的节点,所以O(1)的时间复杂度就可以。但是找不到该节点前面的节点,但是可以找到后面的节点,把后面节点的数值复制到该节点上。
有两种特殊情况:
1.被删除的节点是尾节点,那就没办法了,只能最基本的方式从头遍历,找到尾节点前面的节点。
2.被删除的节点既是尾节点也是头节点(只有一个节点),
思考:题目里给头节点是二级指针,想想为什么用二级指针。对比一下一级指针和二级指针,一级指针作为形参 传入的的是头节点的地址,可以改变节点结构体的成员,但是不能改一级指针指向的地址。二级指针传入的是一个指向头节点的指针的地址。所以既可以改变头节点,也可以改变头指针。
对于上面的思路,头节点是根本不会动的,所以没必要传入二级指针。但是有一种特殊情况是只有一个节点时,要把头节点指向NULL,所以还是要要传入二级指针。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; if(p==NULL) return; while(p->next!=NULL) { printf("%d",p->data); p = p->next; } printf("%d\n",p->data); } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } //传入结构体指针,只能改结构体,不能改结构体指针。因为头节点可能被改变 void DeleteNode(listNode** head, listNode* deleteNode) { listNode* next; if(*head == NULL || deleteNode == NULL) return; if(deleteNode->next != NULL)//不是尾节点 { next = deleteNode->next; deleteNode->data = next->data; deleteNode->next = next->next; free(next); next = NULL; } else if(deleteNode == *head)//只有一个节点 又头又尾 { free(deleteNode); deleteNode = NULL; *head = NULL; } else { next = *head; while(next->next!=deleteNode) { next = next->next; } free(deleteNode); next->next = NULL; next = NULL; } } void Test(listNode* head,listNode* deleteNode) { PrintList(head); DeleteNode(&head,deleteNode); PrintList(head); } int main() { listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(2); listNode* pNode3 = CreateListNode(3); listNode* pNode4 = CreateListNode(4); listNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); Test(pNode1,pNode1); }
18-2 删除链表中重复的节点
题目:在一个排序的链表中,如何删除重复的节点并只保留一个
思路:这道题不是书上的,是自己理解错题了。原题的意思是只要重复了就全部删除,我理解成了所有重复的留下一个。按我这个想法其实是把这道题变简单了,如果发现后一个节点与该节点相同时,只需要把后面的节点删除了就可以了。头节点不需要被删除,所以也没必要传入二级指针,但是懒得改了。需要注意的就是在node->next的时候一定要保证这个node不是NULL,否则会报错。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; if(p==NULL) return; while(p->next!=NULL) { printf("%d",p->data); p = p->next; } printf("%d\n",p->data); } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } void DeleteDuplication(listNode** head) { listNode* pNode;//当前节点 listNode* nextNode;//下一节点 if(head==NULL || *head == NULL)//head==NULL说明链表指针为空 *head==NULL说明头结点为空 return; pNode = *head; if(pNode->next == NULL)//只有一个节点 return; nextNode = pNode->next; while(pNode->next!=NULL) { if(pNode->data == nextNode->data) { pNode->next = nextNode->next; free(nextNode); if(pNode->next!=NULL) nextNode = pNode->next; else return; } else { pNode = pNode->next; nextNode = pNode->next; } } } void Test(listNode* head,listNode* deleteNode) { PrintList(head); //DeleteNode(&head,deleteNode); DeleteDuplication(&head); PrintList(head); } int main() { listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(1); listNode* pNode3 = CreateListNode(2); listNode* pNode4 = CreateListNode(2); listNode* pNode5 = CreateListNode(3); listNode* pNode6 = CreateListNode(3); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode6); Test(pNode1,pNode1); }
18-3 删除链表中重复的节点
题目:在一个排序的链表中,如何删除重复的节点?(全部删除)
思路:这道题有点难的。当检测到有重复节点时,该节点也需要被删除。这就需要一直记录前一节点。有一种特殊情况就是第一个节点就开始重复,这时候preNode还是NULL,此时就直接把头指针指向下一个就好了。当检测到有重复的节点时,不知道要删除多少,所以设立一个标志位,存储该数值,然后开始循环(最好自己画一画链表)。当待删除的节点不为空且该节点的数值等于记录的数值时,删除,然后指向下一个节点。这时候不用管pNode和pNext,等到跳出while循环时,再把pNode指向第一个不满足循环条件的节点就好了。
这种题最难的就是逻辑,什么时候判断空,什么时候指向NEXT。只有判断完不为空才能指向NEXT.
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; if(p==NULL) return; while(p->next!=NULL) { printf("%d",p->data); p = p->next; } printf("%d\n",p->data); } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } void DeleteDuplication1(listNode** head) { listNode* pNode;//当前节点 listNode* nextNode;//下一节点 listNode* preNode = NULL;//前一节点 listNode* toBeDel; int val; int needDel; if(head==NULL || *head == NULL)//head==NULL说明链表指针为空 *head==NULL说明头结点为空 return; pNode = *head; while(pNode!=NULL) { nextNode = pNode->next;//要保证pNode不为NULL 才能->next if(nextNode!=NULL && nextNode->data == pNode->data) { needDel = 1; toBeDel = pNode; val = pNode->data; } else needDel = 0; if(!needDel) { preNode = pNode; pNode = pNode->next; } else { while(toBeDel!=NULL && toBeDel->data == val) { pNode = toBeDel->next; free(toBeDel); toBeDel = pNode; } if(preNode == NULL) *head = pNode; else preNode->next = pNode; } } } void Test(listNode* head,listNode* deleteNode) { PrintList(head); //DeleteNode(&head,deleteNode); DeleteDuplication1(&head); PrintList(head); } int main() { listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(1); listNode* pNode3 = CreateListNode(1); listNode* pNode4 = CreateListNode(2); listNode* pNode5 = CreateListNode(3); listNode* pNode6 = CreateListNode(3); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode6); Test(pNode1,pNode1);
19 正则表达式匹配
题目:请事先一个函数用来匹配包含‘.’和‘’的正则表达式。模式中的字符‘.’表示任意一个字符,而’'表示他前面的字符可以出现任意次(含0次)。在本题中,匹配是指字符串的所有字符匹配整个模式。例如,字符串"aaa"与模式“a.a”和“ab‘※’ac‘※’a”匹配,但与“aa.a”和“ab‘※’a”均不匹配。
思路:这种题一看应该就想到用递归的方法,一个字符一个字符的比较,比较过后的不管了。需要注意的就是把多种情况都考虑一下:.没什么麻烦的,只要原字符串不是‘\0’就可以,主要就是‘※’.当正则字符串中的第二个字符是‘※’时,而且第一个字符还比较相等后,递归参数有三种可能:
1.str+1,pattern+2.这种情况就是把str的一个与pattern的一个字符和一个‘※’抵消掉,说白了就是过掉一个‘※’,它的作用是之前的字符出现一次;例如:bc,b※c.
2.str+1,pattern 这种情况是‘※’的作用是之前的字符至少出现两次,可以无消耗的把str的一个字符抵消掉;例如:bbc,b※c.
3.str,pattern+2,这种情况是※的作用是之前的字符出现零次。有点难以理解,为什么匹配上了还让他出现零次,因为还要受字符串个数的限制,比如:ab,ab※b.
当正则字符串中的第二个字符是‘※’时,而且第一个字符还比较不等后就只有上面的第三种递归参数这一种了,不相等就必须把他弄掉。
没有※的时候就直接递归str+1和pattern+1就可以了,没啥说的。
注意:注意一下不规则的输入,比如第一个是※(这个书上没说,我觉得还是注意一下),连着两个※。然后还有什么时候返回,当str为空,同时pattern也为空时说明前面比较都没事,可以返回成功;当str不为空但是pattern为空时,肯定时错误的;当pattern不为空,但是str为空时有可能时对的,因为有※的存在。但是不用特殊对待,在递归中都有体现,递归str和pattern+2。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool MatchCore(char* str,char* pattern) { if(*str=='\0'&& *pattern=='\0') return true; if(*str!='\0' && *pattern=='\0') return false; if(*(pattern+1) == '*') { if(*(pattern+2)=='*')return false; if(*str == *pattern || *str!='\0'&& *pattern=='.') { return MatchCore(str+1,pattern)||MatchCore(str+1,pattern+2)||MatchCore(str,pattern+2); } else return MatchCore(str,pattern+2); } else { if(*str == *pattern || *str!='\0'&& *pattern=='.') return MatchCore(str+1,pattern+1); else return false; } } bool Match(char* str,char* pattern) { if(str==NULL || pattern==NULL) return false; if(*pattern=='*') return false; MatchCore(str,pattern); } void main() { char* str = "aaa"; char* pattern = "ab*a*c*a"; bool state = Match(str,pattern); if(state)printf("YES"); else printf("NO"); }
20 表示数值的字符串
题目:请实现一个函数用来判断字符串是否表示数值(包括整数和小数)。例如,字符串+10,5e2,3.14都表示数值,但12e,1a3.14,1.2.3,±5都不是。
思路:整体思路就是设计一种浏览模式,合理就字符串加一,不合理就返回,最后判断是否时‘\0’就可以了,浏览主要是浏览有符号数和无符号数。浏览的一个作用时改变字符串,第二个作用是判断是否检测到了。可以先char* before,记录一下原字符串,然后循环判断后,比较一下改之前的和改之后的地址相不相同就好了。数值字符串有以下几种形式:
1.有符号整数’.‘无符号整数
2.有符号整数e有符号整数
3.’.'有符号整数
总结一下就是如果有‘.’,前面可以没数,后面一定是无符号数,比如 .12。或者前面有有符号数,后面没数,比如12. ;e前面必须有数,后面可以是有符号数,比如5e-3
BUG:
numeric = ScanUnsignedInteger(&str) || numeric;没问题
numeric = numeric || ScanUnsignedInteger(&str) ;就进不去子函数了。&&没事
查了一些资料也没懂,如果有大神看到望指教一下。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool ScanUnsignedInteger(const char **str) { const char* before = *str; while(**str!='\0' && **str>='0' && **str<='9') (*str)++; if(*str!=before) return true; else return false; } bool ScanInteger(const char **str) { if(**str == '+'||**str == '-') (*str)++; return ScanUnsignedInteger(str); } bool IsNumeric(const char* str) { bool numeric; numeric = ScanInteger(&str); if(*str == '.') { str++; numeric = ScanUnsignedInteger(&str) || numeric; } else if(*str == 'e' || *str == 'E') { str++; numeric = numeric && ScanInteger(&str); } return numeric && (*str == '\0'); } void main() { char* str = "+12e-13"; if(IsNumeric(str)) printf("Success"); else printf("Fail"); }
21 调整数组顺序使得奇数在前偶数在后
题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,是的所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。思路:常规的从头遍历的方式就不说了,设置两个指针,一个从头开始一个从尾开始,从头开始的找偶数,从尾开始的找奇数,找完就交换一下。当头和尾相等的时候结束。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 bool func(int a)//是否是奇数 { return a&0x1; } void Reorder(int* number,int length) { int* start = &number[0]; int* end = &number[length-1]; int change; if(number == NULL || length == 0) return; while(start!=end) { while(start!=end && func(*start)) start++; while(start!=end && !func(*end)) end--; if(start!=end) { change = *end; *end = *start; *start = change; } } } void main() { int number[] = {1,2,3,4,5}; int i=0; Reorder(number,sizeof(number)/sizeof(int)); for(i=0;i<sizeof(number)/sizeof(int);i++) printf("%d ",number[i]); }
22 链表中倒数第k个节点
题目:输入一个链表,输出该链表倒数第k个节点。为了符合多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。例如,一个链表有6个节点,从头节点开始,他们的值依次是1 2 3 4 5 6.这个链表的倒数第3个节点是只为4的节点。
思路:由于是单向链表,只有指向下一个的指针,没有指向上一个的指针。第一想法是先遍历链表,查出一共有n个节点,再遍历链表,找到第n-k+1个节点。但是还可以有一次遍历就能结束的更优化的想法:建立两个指针,第一个指针先走k-1步。第k步的时候两个指针一起走,等到第一个指针走到最后时,把第二个指针返回就好了。由于一直保证两个指针之间差k-1个节点,所以等到第二个节点到最后一个节点的时候,第一个节点就是倒数第k个。
注意:写代码要注意鲁棒性,不能因为一些错误的输入就导致程序崩溃。这道题有三种意外情况:
1.输入的头节点指针是NULL
2.k=0
3.k比总长度n要大
别的就没什么了 是难度偏低的一道题
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; while(p!=NULL) { printf("%d ",p->data); p = p->next; } } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } listNode* FindKthToTail(listNode* head,unsigned int k) { listNode* p1 = head; listNode* p2 = NULL; if(head == NULL || k==0) return NULL; while(k!=1) { p1=p1->next; k--; if(p1==NULL) return NULL; } p2 = head; while(p1->next!=NULL) { p1 = p1->next; p2 = p2->next; } return p2; } void main() { int k=1; listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(2); listNode* pNode3 = CreateListNode(3); listNode* pNode4 = CreateListNode(4); listNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); //PrintList(pNode1); if(FindKthToTail(pNode1,k)!=NULL) printf("%d",FindKthToTail(pNode1,k)->data); else printf("Error"); }
23 链表中环的入口节点
题目:如果一个链表中包含环,如何找出环的入口节点?例如在图中所示的链表中,环的入口节点是节点3
在这里插入图片描述思路:要找环的入口节点,首先要判断链表是否包含环。所以要把这道题分成两部分。
第一部分:如何判断该链表是否是环
如果是环一定是在环内不断循环,链表的地址是随机的,所以无法按地址递增来判断;链表中也会有循环的元素,也不可以通过找相同的元素来判断。所以可以用两个步伐不一样的指针,如果有环的话这两个指针总会指到同一个位置上。一个指针每次增1,另一个指针每次增2.当两个指针相同节点的时候说明指到了同一个环内节点上了。
第二部分:判断有环后,如何找到入口节点
在环内是一直循环的,周期是环内节点个数。所以两个指针如果差一个环内节点数,会有一个指针先进环内,第二个节点和这个节点相遇时,就是入口节点。所以第二部分又分两部分:确定环内节点个数、找到入口节点。
确定环内节点个数比较简单,第一部分已经得到一个环内的节点。从该节点不断向后找,再找到next的时候就指到环内节点个数了。找入口节点就按上面说的,设两个指针找就行了。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; while(p!=NULL) { printf("%d ",p->data); p = p->next; } } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } /*找到相遇的节点*/ listNode* MeetingNode(listNode* head) { listNode* pSlow; listNode* pFast; if(head==NULL)return NULL; pSlow = head->next; if(pSlow==NULL)return NULL; pFast = pSlow->next; while( pFast != NULL) { if(pFast == pSlow) return pSlow; pSlow = pSlow->next; pFast = pFast->next; if(pFast == NULL)return NULL; pFast = pFast->next; } return NULL; } listNode* EntryNodeOfLoop(listNode* head) { listNode* meetingNode = MeetingNode(head); listNode* next = NULL; listNode* pNode1 = NULL; listNode* pNode2 = NULL; int loopCount = 0; if(meetingNode == NULL)return NULL; next = meetingNode->next; loopCount++; while(next!= meetingNode) { next = next->next; loopCount++; } pNode2 = head; pNode1 = head; for(;loopCount>0;loopCount--) { pNode1 = pNode1->next; } while(pNode1->data!=pNode2->data) { pNode1 = pNode1->next; pNode2 = pNode2->next; } return pNode1; } void main() { int k=1; listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(2); listNode* pNode3 = CreateListNode(3); listNode* pNode4 = CreateListNode(4); listNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); ConnectListNodes(pNode5, pNode3); if(EntryNodeOfLoop(pNode1)!=NULL) printf("%d",EntryNodeOfLoop(pNode1)->data); else printf("Error"); //PrintList(pNode1); }
24 反转链表
题目:定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
思路:自己写一个链表,抛开程序用语言描述一下该怎么做。
在这里插入图片描述首先需要一个指针pNode指向被改变的节点。由于链表是单向的,所以需要一个preNode记录被改变节点的前一个节点。
从a开始,把a的next变成前面的(因为是头节点,所以是NULL)。这时候应该改b了,但是a和b之间已经没联系了。所以在断连接之前还需要一个nextNode指向a的next。
pNode现在指向b,preNode指向a。1.先把nextNode指向c 2.b(pNode)的next指向a(preNode) 3.preNode指向pNode 4.pNode 指向nextNode.
结束的标志是pNode->next是NULL.这时候说明pNode是原来的尾节点了。这时候跳出循环,把pNode指向preNode,然后把pNode返回就好了。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; while(p!=NULL) { printf("%d ",p->data); p = p->next; } } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } listNode* ReserveList(listNode* head) { listNode* pNode = head; listNode* preNode = NULL; listNode* nextNode = NULL; if(head == NULL)return NULL; while(pNode->next!=NULL) { nextNode = pNode->next; pNode->next = preNode; preNode = pNode; pNode = nextNode; } pNode->next = preNode; return pNode; } void main() { listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(2); listNode* pNode3 = CreateListNode(3); listNode* pNode4 = CreateListNode(4); listNode* pNode5 = CreateListNode(5); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); PrintList(pNode1); printf("\n"); PrintList(ReserveList(pNode1)); }
25 合并两个排序的链表
题目:输入两个递增排序的链表,合并这两个链表并使更新链表中的节点仍然是递增排序的。
思路:这道题可以用递归和非递归两种形式来做,先将非递归。
首先需要两个指针pNode1和pNode2分别指向两个输入链表。还需要一个pNode3和pHead3用来生成新的链表。先判断pNode1和pNode2谁大谁小,选出小的。之后再判断pHead3是不是指向NULL,如果指向NULL,就先将pNode3指向刚才选出的,然后把pHead指向pHead;如果pHead3不指向NULL,就把pNode3指向选出的,然后pNode3 = pNode3->next;
循环条件是pNode1和pNode2都不为空,当跳出循环后说明有一个是空的,这时候说明另一个不为空的链表可以直接接过来。然后把pHead3返回就好了。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct ListNode { int data; struct ListNode* next; }listNode; void PrintList(listNode* head) { listNode* p = head; while(p!=NULL) { printf("%d ",p->data); p = p->next; } } listNode* CreateListNode(int data) { listNode* newListNode = (listNode*)malloc(sizeof(listNode)); if(!newListNode)return false; newListNode->data = data; newListNode->next = NULL; return newListNode; } void ConnectListNodes(listNode* node1,listNode* node2) { node1->next = node2; } listNode* Merge(listNode* pHead1,listNode* pHead2) { listNode* pNode1 = pHead1; listNode* pNode2 = pHead2; listNode* pHead3 = NULL; listNode* pNode3 = NULL; if(pNode1==NULL)return pHead2; if(pNode2==NULL)return pHead1; while(pNode1!=NULL && pNode2!=NULL) { if(pNode1->data<pNode2->data) { if(pHead3 == NULL) { pNode3 = pNode1; pHead3 = pNode3; } else { pNode3->next = pNode1; pNode3 = pNode3->next; } pNode1 = pNode1->next; } else { if(pHead3 == NULL) { pNode3 = pNode2; pHead3 = pNode3; } else { pNode3->next = pNode2; pNode3 = pNode3->next; } pNode2 = pNode2->next; } } if(pNode1 == NULL) pNode3->next = pNode2; else pNode3->next = pNode1; return pHead3; } void main() { listNode* pNode1 = CreateListNode(1); listNode* pNode2 = CreateListNode(3); listNode* pNode3 = CreateListNode(5); listNode* pNode4 = CreateListNode(7); listNode* pNode5 = CreateListNode(9); ConnectListNodes(pNode1, pNode2); ConnectListNodes(pNode2, pNode3); ConnectListNodes(pNode3, pNode4); ConnectListNodes(pNode4, pNode5); PrintList(Merge(pNode1,NULL)); }
思路2:这道题也可以通过递归的方式来实现,代码会更简洁一点。上面的代码要判断一下是否头节点已经指向了某一结点,指向和没指向是两种不同的算法。但是用递归,每次头节点都没指向节点,比较完大小时候,把该节点指向小的那个节点之后,然后该节点指向继续递归除掉那个之后的两个链表的合并。。。说的比较绕,自己写写思路还是挺清晰的。
listNode* Merge(listNode* pHead1,listNode* pHead2) { listNode* pHead3 = NULL; if(pHead1==NULL)return pHead2; if(pHead2==NULL)return pHead1; if(pHead1->data < pHead2->data) { pHead3 = pHead1; pHead1->next = Merge(pHead1->next,pHead2); } else { pHead3 = pHead2; pHead2->next = Merge(pHead1,pHead2->next); } return pHead3; }
26 树的子结构
题目:输入两棵二叉树A和B,判断B是不是A的子结构。
思路:树的结构比链表还要复杂,因为既有左子树又有右子树。所以树相关的问题基本上都用递归来做。
这道题可以分为两部分:1.遍历二叉树A,找到和B根节点相等的节点 2.找到相等的节点后从该节点出发,逐个比较树中的所有节点。直至二叉树的左树和右数都是NULL,证明完全相等。
第一部分:
先判断两个的value相不相等,相等就进入第二部分;不相等就递归A的左孩子和B;递归A的右孩子和B
第二部分:
由于B的任务就是把B比较至NULL,A是NULL说明A不够了,失败。所以先判断B是不是NULL,是就返回成功;再判断A,是NULL就返回失败。如果A和B的value相等,递归A的左孩子和B的左孩子,A的右孩子和B的右孩子,两个最后递归的结果都是true才能返回true,用&&操作。
备注:
1.malloc是在堆中申请内存,用完记得释放掉,free后把指针指向NULL,不然就是野指针了。因为要改变指针指向的位置,所以子函数要传入二级指针。
2.这道题是double类型,所以比较大小不能用==比较,要设置一个比较小的值(比如e-9),判断两个数的差值是不是比这个值小。比较懒,知道意思就行了,没这么做。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct BinaryTreeNode { int value; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BinaryTreeNode; BinaryTreeNode* CreateBinaryTreeNode(double value) { BinaryTreeNode* pNode = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode)); pNode->value = value; pNode->left = NULL; pNode->right = NULL; return pNode; } void ConnectTreeNodes(BinaryTreeNode* parent,BinaryTreeNode* left,BinaryTreeNode* right) { if(parent!=NULL) { parent->left = left; parent->right = right; } } void DestroyTree(BinaryTreeNode** pRoot) { BinaryTreeNode* left = NULL; BinaryTreeNode* right = NULL; if(*pRoot!=NULL) { left = (*pRoot)->left; right = (*pRoot)->right; free(*pRoot); pRoot = NULL; DestroyTree(&left); DestroyTree(&right); } } bool DoesTree1HasTree2(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2) { if(pRoot2 == NULL)return true; if(pRoot1 == NULL)return false; if(pRoot1->value == pRoot2->value) { return DoesTree1HasTree2(pRoot1->left,pRoot2->left)&& DoesTree1HasTree2(pRoot1->right,pRoot2->right); } else return false; } bool HadSubTree(BinaryTreeNode *pRoot1,BinaryTreeNode *pRoot2) { bool result = false; if(pRoot1!=NULL && pRoot2!=NULL) { if(pRoot1->value == pRoot2->value) result = DoesTree1HasTree2(pRoot1,pRoot2); if(!result) result = HadSubTree(pRoot1->left,pRoot2); if(!result) result = HadSubTree(pRoot1->right,pRoot2); } return result; }
27 二叉树的镜像
题目:请完成一个函数,输入一棵二叉树,该函数输入他的镜像。
思路:还是一样,跟二叉树有关的,先考虑递归。所以镜像就是遍历所有二叉树,然后把左孩子和右孩子交换一下即可。很简单的一道题
注意:考虑边界和特殊情况,其实只需要进函数的时候判断一下是不是NULL就可以了。书上还比较一下左孩子和右孩子是不是都为空,其实不加这句话,再递归两次也会返回,所以没什么意义。只需要在->之前判断这个是不是NULL就可以了。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct BinaryTreeNode { int value; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BinaryTreeNode; BinaryTreeNode* CreateBinaryTreeNode(double value) { BinaryTreeNode* pNode = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode)); pNode->value = value; pNode->left = NULL; pNode->right = NULL; return pNode; } void ConnectTreeNodes(BinaryTreeNode* parent,BinaryTreeNode* left,BinaryTreeNode* right) { if(parent!=NULL) { parent->left = left; parent->right = right; } } void DestroyTree(BinaryTreeNode** pRoot) { BinaryTreeNode* left = NULL; BinaryTreeNode* right = NULL; if(*pRoot!=NULL) { left = (*pRoot)->left; right = (*pRoot)->right; free(*pRoot); pRoot = NULL; DestroyTree(&left); DestroyTree(&right); } } void MirrorTree(BinaryTreeNode*pRoot) { BinaryTreeNode* temp; if(pRoot==NULL)return; temp = pRoot->left; pRoot->left = pRoot->right; pRoot->right = temp; MirrorTree(pRoot->left); MirrorTree(pRoot->right); } void main() { /*****************TREE 1 ***********************/ BinaryTreeNode* pNodeA1 = CreateBinaryTreeNode(1); BinaryTreeNode* pNodeA2 = CreateBinaryTreeNode(2); BinaryTreeNode* pNodeA3 = CreateBinaryTreeNode(3); BinaryTreeNode* pNodeA4 = CreateBinaryTreeNode(4); BinaryTreeNode* pNodeA5 = CreateBinaryTreeNode(5); BinaryTreeNode* pNodeA6 = CreateBinaryTreeNode(6); BinaryTreeNode* pNodeA7 = CreateBinaryTreeNode(7); ConnectTreeNodes(pNodeA1, pNodeA2, pNodeA3); ConnectTreeNodes(pNodeA2, pNodeA4, pNodeA5); ConnectTreeNodes(pNodeA3, pNodeA6, NULL); MirrorTree(pNodeA1); DestroyTree(&pNodeA1); }
28 对称的二叉树
题目:请实现一个函数,用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
思路:还是递归,递归函数负责比较输入的两个节点的数值。比较相等之后再递归一个的左节点和另一个的右节点,一个的右节点和另一个的左节点。俩个递归要同时为真才可以。
结束条件:当两个都是NULL就返回TRUE,只有一个NULL返回FALSE。中途发现比较失败时直接返回FALSE。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 typedef struct BinaryTreeNode { int value; struct BinaryTreeNode* left; struct BinaryTreeNode* right; }BinaryTreeNode; BinaryTreeNode* CreateBinaryTreeNode(double value) { BinaryTreeNode* pNode = (BinaryTreeNode*)malloc(sizeof(BinaryTreeNode)); pNode->value = value; pNode->left = NULL; pNode->right = NULL; return pNode; } void ConnectTreeNodes(BinaryTreeNode* parent,BinaryTreeNode* left,BinaryTreeNode* right) { if(parent!=NULL) { parent->left = left; parent->right = right; } } void DestroyTree(BinaryTreeNode** pRoot) { BinaryTreeNode* left = NULL; BinaryTreeNode* right = NULL; if(*pRoot!=NULL) { left = (*pRoot)->left; right = (*pRoot)->right; free(*pRoot); pRoot = NULL; DestroyTree(&left); DestroyTree(&right); } } bool isSymmetrical(BinaryTreeNode* pRoot1,BinaryTreeNode* pRoot2) { if(pRoot1 == NULL && pRoot2 == NULL) return true; if(pRoot1 == NULL || pRoot2 == NULL) return false; if(pRoot1->value == pRoot2->value) { return isSymmetrical(pRoot1->left,pRoot2->right) && isSymmetrical(pRoot1->right,pRoot2->left); } else return false; } void main() { /*****************TREE 1 ***********************/ BinaryTreeNode* pNodeA1 = CreateBinaryTreeNode(1); BinaryTreeNode* pNodeA2 = CreateBinaryTreeNode(2); BinaryTreeNode* pNodeA3 = CreateBinaryTreeNode(3); BinaryTreeNode* pNodeA4 = CreateBinaryTreeNode(4); BinaryTreeNode* pNodeA5 = CreateBinaryTreeNode(5); BinaryTreeNode* pNodeA6 = CreateBinaryTreeNode(6); BinaryTreeNode* pNodeA7 = CreateBinaryTreeNode(7); BinaryTreeNode* pNodeB1 = CreateBinaryTreeNode(1); BinaryTreeNode* pNodeB2 = CreateBinaryTreeNode(2); BinaryTreeNode* pNodeB3 = CreateBinaryTreeNode(3); BinaryTreeNode* pNodeB4 = CreateBinaryTreeNode(4); BinaryTreeNode* pNodeB5 = CreateBinaryTreeNode(5); BinaryTreeNode* pNodeB6 = CreateBinaryTreeNode(6); BinaryTreeNode* pNodeB7 = CreateBinaryTreeNode(7); ConnectTreeNodes(pNodeA1, pNodeA2, pNodeA3); ConnectTreeNodes(pNodeA2, pNodeA4, pNodeA5); ConnectTreeNodes(pNodeA3, pNodeA6, pNodeA7); ConnectTreeNodes(pNodeB1, pNodeB3, pNodeB2); ConnectTreeNodes(pNodeB2, pNodeB5, pNodeB4); ConnectTreeNodes(pNodeB3, pNodeB7, pNodeB6); printf("%d",isSymmetrical(pNodeA1,pNodeB1)); DestroyTree(&pNodeA1); DestroyTree(&pNodeB1); }
29 顺时针打印矩阵
题目:输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
思路:这是不涉及复杂数据结构的一道题,接替的关键就是把题目弄清楚。
顺时针打印,就是从最左上角开始打印一圈,然后横坐标加一纵坐标加一,再打印一圈。直至结束。由于画圈起点的横坐标和纵坐标总相等,所以可以把这个数定义为start.所以第一个问题是:什么时候停止?
这个我拍脑袋没想出来,就在纸上画了画。
1×1的矩阵,当start=0时打印,1时停止;
2×2的矩阵,当start=0时打印,1时停止;
3×3的矩阵,当start=0,1时打印,2时停止;
4×4的矩阵,当start=0,1时打印,2时停止;
找到了规律,start停止是有周期的,周期是2.随便推理一下就可以得到,结束条件是start2>=columns(或rows),这是对于正方形矩阵而言。那么长方形呢?
2×3的矩阵,当start=0时打印,1时停止;
3×2的矩阵,当start=0时打印,1时停止;
又找到了规律,结束条件是 start2>=columns || start*2>=rows
第二个问题:如何打印一圈?
一圈有四条边,分别打印四个边就好了。
1.纵坐标为start不变,横坐标从start开始到columns-1-start结束
2.横坐标为columns-1-start不变,纵坐标从start+1开始,到rows-1-start结束
3.纵坐标为rows-1-start不变,横坐标从columns-1-start-1开始,到start结束
4.横坐标为start不变,纵坐标从rows-2-start开始,到start+1结束
对于常规情况而言,这道题似乎解决了,但是要考虑一下边界条件。
当只有一行的情况下(1234):1打印1234,2条件判定失败,3打印321,4条件判定失败。打印出来是1234321
当只有一列的情况下(1234):1打印了一个1,2打印了234,3条件判定失败,4打印32。打印出来是123432
当只有一行一列的情况下没什么问题。
可以看出,出问题都是在34步出的问题,只有一行是第3步出现问题,只有一列是第4步出现问题。所以在34步前面加一下判断就可以了
备注:C++在二维数组作为二级指针传入子函数之后,还可以用a[][]的形式进行操作,但是C语言会直接报错。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 void PrintClockWisely(int** numbers,int columns,int rows) { int start = 0; int column = start; int row = start; int endX; int endY; if(numbers == NULL || columns<=0 || rows <= 0) return ; while(start*2<columns && start*2<rows) { endX = columns-start-1; endY = rows - 1 - start; for(column = start;column<=endX;column++) { printf("%d ",numbers[columns*start + column]); } for(row = start+1;row<=endY;row++) { printf("%d ",numbers[columns*row + endX]); } if(start < endY) { for(column = endX-1;column>=start;column--) printf("%d ",numbers[columns*endY + column]); } if(start < endX) { for(row = endY-1;row>=start+1;row--) printf("%d ",numbers[columns*row + start]); } start++; } } void main() { int number[][4]= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16}; //int number[][4] = {1,2,3,4}; int columns = sizeof(number[0])/sizeof(int); int rows = sizeof(number)/sizeof(number[0]); PrintClockWisely(number,columns,rows); }
30 包含min函数的栈
题目:定义栈的数据结构,请在该类型中实现一个能得到栈的最小元素的min函数。在该栈中,调用min push pop的时间复杂度都是O(1).思路:读明白题,得到最小值不是出栈最小值,由于栈先进后出的顺序是不能改变的所以不能在原栈上做手脚。而最小的值是随时有可能出栈的,所以也不可以定义一个变量记录当前的最小值。要保证在每个出栈后都更新最小值。所以应该在建立一个辅助栈,在同等位置保证该位置的数是当前的最小值。其实想明白了挺简单的,建立一个辅助栈,在数据栈进数的时候,辅助栈也进,如果进的数比栈顶还小(栈顶一直是当前最小值),就入栈这个数,如果不比栈顶小,就把栈顶再入一次,这就保证了辅助栈的栈顶一直是当前数据栈的最小值。
#include <stdio.h> #include <stdlib.h> #define bool unsigned int #define true 1 #define false 0 #define none 2 #define INITLENGTH 40 #define INCREMENT 20 typedef struct stack { int *top; int *base; int stackSize; }stack; /*栈的辅助代码*/ bool Push(stack* stack1,int data) { int* temp; //栈满 if(stack1->top - stack1->base >=stack1->stackSize) { temp = (int*)realloc(stack1->base,sizeof(int)*(stack1->stackSize+INCREMENT)); if(temp == NULL)return false; stack1->base = temp; stack1->top = stack1->base + stack1->stackSize; stack1->stackSize+=INCREMENT; } *stack1->top++ = data; return true; } int Pop(stack* stack1) { if(stack1->stackSize<=0) return 0; stack1->stackSize-=1; return *--stack1->top; } stack* InitStack() { stack* stack1= (stack*)malloc(sizeof(stack)); stack1->base = (int*)malloc(sizeof(int)*INITLENGTH); if(stack1->base!=NULL) { stack1->top = stack1->base; stack1->stackSize = INITLENGTH; return stack1; } return NULL; } bool PushMin(stack* stack1,stack* minStack,int data) { if(stack1 == NULL || minStack == NULL) return false; Push(stack1,data); if(minStack->top == minStack->base || *(minStack->top-1)>data) Push(minStack,data); else Push(minStack,*(minStack->top-1)); } int PopMin(stack* stack1,stack* minStack) { Pop(minStack); return Pop(stack1); } int MinInStack(stack* stack1,stack* minStack) { if(stack1->top!=stack1->base && minStack->top!=minStack->base) return *(minStack->top-1); return 0; } void main() { int i =0; stack* stack1 = InitStack(); stack* minStack = InitStack(); for(i=0;i<40;i++) { PushMin(stack1,minStack,40-i); } for(i=0;i<40;i++) { printf("%d\n",MinInStack(stack1,minStack)); PopMin(stack1,minStack); } }