c++/c算法和数据结构面试整理

文章目录

  • 一.排序:
  • 二.链表
    • 往链表的末尾添加元素
    • 从尾到头打印链表
    • 合并两个有序链表
    • 反转链表
    • 判断链表是否是回文链表
    • 链表相交
    • 链表的环
      • 判断是否有环
      • 计算环的长度
      • 找到环的起始位置
    • 快乐数
  • 二.多数组合并成有序数组
    • 1.如果现在有k个有序数组,.如何合并成一个有序数组
    • 2.为什么归并排序适合处理大数据排序情况
    • 3.如何快速的输出无序数组中前k小的值,请说明你的方法,并分析时间复杂度
    • 4.如何从海量的数据中提取最大的k个?
  • 三.随机抽取
    • 1.假设有一个不知道多长的数字序列, 如何等概率的从里面抽取K个值
      • 储水池抽样
      • 应用场景
    • 2.要求设计一个函数,运行100次, 非顺序的输出1~100内的所有数字, 要求不能使用数组.
      • 链表法
      • 线性求余
    • 3.简述语言中的随机函数原理
  • 四.数字出现的次数
  • 五.找到数组中数量最多的元素或最少的元素
    • 1.已知在一个有n个整数的数组中,数量最多的一个的数字超过n/2个,设计算法找到这个数字.
    • 2.已知一个有n个元素的数组,数量最多的一个数字超过n/k个,请设计算法找到这个数字.
    • 3.在一个数组中,大多数数字都出现3次, 只有一个数字出现次数不足3次, 设法找到这个数字
      • 二进制统计表
      • 状态转换表
  • 六.栈和队列
    • 1.如何设计一个支持min操作的栈, min函数返回当前栈的最小值, 要求min, push, pop操作的时间复杂度都是O(1), 应如何设计?
    • 2.有一个不包含重复元素的数组,如何快速地找到连续的k个位置,使得这k个位置上的数字排序以后在数字上也是连续的,要求算法的时间复杂度为O(n)
    • 3.给定k值,如何找到链表的第k个元素,一个子针怎么做,两个子针怎么做?
      • 一个子针跑两次
      • 两个子针跑一次
  • 七.字符串
    • 替换空格
  • 九.树
    • 1.输入二叉树的前序遍历和中序遍历的结果, 请重建此二叉树, 假设输入的前序遍历和中序遍历的结果中都不含重复的数字.
    • 二叉树的下一个节点
  • 十.队列和栈
    • 用两个栈实现队列
    • 用两个队列实现栈
  • 十一.递归和循环
    • 1.斐波那契序列
  • 2.
  • 十二.找n 个数之和为S的n个数
  • AVL树, 红黑树, 哈弗曼编码,B+树,map和unordered_map
  • 栈与堆
  • 数组
  • 动态规划

一.排序:

排序

二.链表

往链表的末尾添加元素

ListNode *AddToTail(ListNode *pHead, int value) {
    ListNode *pNew = new ListNode();
    pNew->val = value;
    pNew->next = nullptr;
    if (pHead == nullptr) {
        return pNew;
    } else {
        ListNode *p = pHead;
        while(p->next) {
            p = p->next;
        }
        p->next = pNew;
    }
    return pHead;
}

从尾到头打印链表

void  PrintListReversingly(ListNode *head) {
	std::stack s;
	ListNode *p = head;
	while(p != nullptr) {
		s.push(p->val);
		p = p->next;
	}	
	while(!s.empty()) {
		printf("%d\t", s.top());
		s.pop();
	}
}

合并两个有序链表

class Solution {
public:
	ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
		if(l1 == NULL) return l2;
		if(l2 == NULL) return l1;
		if(l1->val < l2->val) {
			l1->next=mergeTwoLists(l1->next,l2);
			return l1;
		} else {
			l2->next=mergeTwoLists(l1,l2->next);
			return l2;
		}
	}
};

反转链表

struct ListNode* ReverseList(struct ListNode* pHead ) {
    // write code here
    if(pHead == NULL || pHead->next == NULL) return pHead;
    struct ListNode* p = ReverseList(pHead->next);
    pHead->next->next = pHead;
    pHead->next = NULL;
    return p;
}
class Solution {
public:
    ListNode* ReverseList(ListNode* pHead) {
        ListNode *pre = nullptr, *cur = pHead, *last;
        while(cur) {
            last = cur->next;
            cur->next = pre;
            pre = cur;
            cur = last;
        }
        return pre;
    }
};

判断链表是否是回文链表

	class Solution {
public:
    ListNode *reverse(ListNode *head) {
        ListNode *p = head, *q, *pre = NULL;
        while(p){
            q = p->next;
            p->next = pre;
            pre = p;
            p = q;
        }
        return pre;
    }
    bool isPalindrome(ListNode* head) {
        ListNode *fast = head, *last = head;
        while (fast && fast->next) {
            last = last->next;
            fast = fast->next;
            fast = fast->next;
        }
        if (fast) last = last->next;
        last = reverse(last);
        fast = head;
        while(last && fast) {
            if (last->val != fast->val) return false;
            fast = fast->next;
            last = last->next;
        }
        return true;    
    }
};

链表相交

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    struct ListNode *p = headA, *q = headB;
   while (p != q) {
        p = p ?  p ->next : headB;
        q = q ?  q ->next :  headA;
    } 
    return p;
}

链表的环

c++/c算法和数据结构面试整理_第1张图片快指针p, 慢指针q;
环的起始位置为o, 相遇位置为x, 从起点到环的起始位置长la, 环长lc;1.la < lc
当慢指针q到环的起始位置o时,慢指针q走了la长,快指针p已经走了2*la,此时快指针距离起始位置o还有lb, 得la + lb = lc,现在两者都在环上, 接下来的运动轨迹,可以视为追击问题, 每次运动,快指针p与慢指针q的距离-1,相遇需运动lb次,此时慢指针q走了lb, 在往后走la步可到环的起点(注:la + lb = lc);相遇后让快指针p到起点,此时p距离环的起始位置la,q距离环的起始位置也是la,p, q同时运动,每次移动一个单元距离,相遇的点为环的起始位置.此时慢指针的移动长度为环的长度.

判断是否有环

bool hasCycle(struct ListNode *head) {
    struct ListNode *q = head, *p = head;
    if (p == NULL) return false;
    do {    
        if (q->next == NULL || q->next->next == NULL) return false;
        p = p->next;        
        q = q->next;
        q = q->next;
	} while (p != q);
    return true;
}

计算环的长度

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode  *p = head, *q = head;
    if (p == NULL) return NULL;
    int p_sum = 0;
    do {
        p =p->next;    
        p_sum += 1;
        q =q->next;       
        if (q == NULL || q->next == NULL)  return 0;
        q =q->next;    
    } while (p != q);
    q = head;
    int q_sum = 0;    
    while(p != q) {
    	p_sum += 1;    	
    	q_sum += 1;
        p = p->next;        
        q = q->next;
    }   
    return p_sum - q_sum;

找到环的起始位置

struct ListNode *detectCycle(struct ListNode *head) {
    struct ListNode  *p = head, *q = head;     
    if (p == NULL) return NULL;
    do {        
    	p =p->next;
        q =q->next;        
        if (q == NULL || q->next == NULL)  return false;
        q =q->next;    
    } while (p != q);
    q = head;
    while(p != q) {       
    	p = p->next;
        q = q->next;
    }
    return p;   
}

快乐数

用的是链表的思维

int init(int n) {
    int temp = n, sum = 0;   
     while (temp) {
        sum += (temp % 10)  * (temp % 10);        
        temp /= 10; 
    }    
    return sum;
}
bool isHappy(int n){    
	int p = n, q = n;
    while (p - 1) {       
     	p =	init(p);
        q = init(init(q));        
        if (!(p - q)) break;
    }    
    return (p == 1);  
}

二.多数组合并成有序数组

1.如果现在有k个有序数组,.如何合并成一个有序数组

1.基础排序开辟一个可容k个有序数组所有元素的结果空间.让后按传统的排序进行排序.
2.小顶堆开辟一个可存k个数组所有元素的结果空间
因为k 个数组都是升序序列,第一个元素是数组的最小值,将所有数组的第一个元素维护成一个小顶堆,调整后的小顶堆的根节点是最小值,将根节点从其数组弹出,存入到结果空间去.将弹出来的元素所在数组的首元素添加到小顶堆,不断的谈出元素, 在往小顶堆中添加元素, 直到数组中没有元素为止,当数组没有元素时, 往小顶堆中添加一个极大值,这样小顶堆经调整后这个极大值会成为子节点, 不会被弹出.直到结果空间存满为止.

小顶堆:时间复杂度o(nlogk)

2.为什么归并排序适合处理大数据排序情况

通过外部设备多路共同运行的多并发程序,工作效率提高,归并排序可通过采用分治,将数据分成若干份小数据,并存在外存上.
两个数据合并时,只需将首元素存于内存中便可,对内存的需求较小,合并后的结果也是存于外存上.

3.如何快速的输出无序数组中前k小的值,请说明你的方法,并分析时间复杂度

排序名 时间复杂度
大根堆 o(nlogk)
小根堆 o(n + klogn)
BFPRT (快速选择算法) o(n)
快排 最优o(nlogn), 最差o(n2)

大根堆:(前k小)选k个数构造大根堆,经调整后根为最大值,遍历数组,如果小于根的值, 则弹出根,将此元素加到堆中,直到数组遍历完为止.最后得到的堆的根就是第k小的元素

小根堆:
线性建堆法创建小根堆,时间复杂度o(n),弹k次,每次堆的调整的复杂度是o(n),所以总的时间复杂度是n + klog(n),第k次得到就是第k小的元素.

bfprt解法:
O(n)
bfprt解法和常规解法唯一不同的就是在number的选取上,其他地方一模一样,所以我们只讲选取number这一过程。
第一步:我们将数组每5个相邻的数分成一组,后面的数如果不够5个数也分成一组。
第二步:对于每组数,我们找出这5个数的中位数,将所有组的中位数构成一个median数组(中位数数组)。
第三步:我们再求这个中位数数组中的中位数,此时所求出的中位数就是那个number。
第四步:以 number为分界点,把小于number的放在左边,大于number的放在右边;
第五步: 判断主元的位置与 k 的大小,有选择的对左边或右边递归。

4.如何从海量的数据中提取最大的k个?

1.直接全部排序(只适用于内存够的情况)
当数据量较小的情况下,内存中可以容纳所有数据。则最简单也是最容易想到的方法是将数据全部排序,然后取排序后的数据中的前K个。

这种方法对数据量比较敏感,当数据量较大的情况下,内存不能完全容纳全部数据,这种方法便不适应了。即使内存能够满足要求,该方法将全部数据都排序了,而题目只要求找出top K个数据,所以该方法并不十分高效,不建议使用。

2.快速排序的变形 (只使用于内存够的情况)

这是一个基于快速排序的变形,因为第一种方法中说到将所有元素都排序并不十分高效,只需要找出前K个最大的就行。

这种方法类似于快速排序,首先选择一个划分元,将比这个划分元大的元素放到它的前面,比划分元小的元素放到它的后面,此时完成了一趟排序。如果此时这个划分元的序号index刚好等于K,那么这个划分元以及它左边的数,刚好就是前K个最大的元素;如果index > K,那么前K大的数据在index的左边,那么就继续递归的从index-1个数中进行一趟排序;如果index < K,那么再从划分元的右边继续进行排序,直到找到序号index刚好等于K为止。再将前K个数进行排序后,返回Top K个元素。这种方法就避免了对除了Top K个元素以外的数据进行排序所带来的不必要的开销。

3.最小堆法

这是一种局部淘汰法。先读取前K个数,建立一个最小堆。然后将剩余的所有数字依次与最小堆的堆顶进行比较,如果小于或等于堆顶数据,则继续比较下一个;否则,删除堆顶元素,并将新数据插入堆中,重新调整最小堆。当遍历完全部数据后,最小堆中的数据即为最大的K个数。

4.分治法

将全部数据分成N份,前提是每份的数据都可以读到内存中进行处理,找到每份数据中最大的K个数。此时剩下NK个数据,如果内存不能容纳NK个数据,则再继续分治处理,分成M份,找出每份数据中最大的K个数,如果M*K个数仍然不能读到内存中,则继续分治处理。直到剩余的数可以读入内存中,那么可以对这些数使用快速排序的变形或者归并排序进行处理。

5.Hash法

如果这些数据中有很多重复的数据,可以先通过hash法,把重复的数去掉。这样如果重复率很高的话,会减少很大的内存用量,从而缩小运算空间。处理后的数据如果能够读入内存,则可以直接排序;否则可以使用分治法或者最小堆法来处理数据。

三.随机抽取

1.假设有一个不知道多长的数字序列, 如何等概率的从里面抽取K个值

储水池抽样

1.如果接收的数据量小于k,则依次放入蓄水池。2.当接收到第i个数据时,i > k,在[0, i]范围内取以随机数d,若d的落在[0, k]范围内,则用接收到的第i个数据替换蓄水池中的第d个数据, 如果落在[k + 1, i]范围内则继续接受数据。

证:
假设当有i - 1个元素时,每个元素被选中的概率为 k i − 1 \frac{k}{i - 1} i1k,当第i个元素出现后,第i 个元素想被放入蓄水池内,就必须从蓄水池内拿出一个来(蓄水池有k个元素).因此第i 个元素能放入蓄水池的概率为 k i \frac{k}{i} ik,而前i - 1个元素此时能在蓄水池内的概率是多少呢?
此时前i - 1个元素想要在蓄水池内,有两个条件要满足:一是该元素在第i 个元素出现之前就在蓄水池内,概率为 k i − 1 \frac{k}{i - 1} i1k;二是在第i个元素出现后,该元素不被替换(也就是不被取出来).假设该元素的下标为l(l <= k).不被替换又分两种情况:1.随机选中的元素下标在[k + 1, i],不用从蓄水池内替换元素,继续接受数据,概率为 i − k i \frac{i - k}{i} iik; 2.随机选中的元素的下标d在[1, k],但不是该元素(下标l的元素)的下标(l != d),因此将第i个元素会将下标为d的元素替换掉,概率为 k − 1 i \frac{k-1}{i} ik1.
因此可得 p l = k i − 1 × ( k − 1 i + i − k i ) = k i p_l=\frac{k}{i-1}\times(\frac{k-1}{i}+\frac{i - k}{i})=\frac{k}{i} pl=i1k×(ik1+iik)=ik.综上可知每个元素被选中的概率是等概的.

应用场景

大数据场景下的采样问题数据流采样问题

2.要求设计一个函数,运行100次, 非顺序的输出1~100内的所有数字, 要求不能使用数组.

链表法

1.创建一个一百位的链表,;2.将1~100插入链表;
3.在链表的下标中随机生成一个下标;4.输出该下标对应的数字,;
5.删除该点,6.重复3~5步,直到链表的值全部输出.

线性求余

x j + 1 = ( a x j + b ) x_j+1= (ax_j+b) % m xj+1=(axj+b)
例:4k % 7
k = 1, 4 % 7 = 4;k = 2, 42 % 7 = 2;
k = 3, 43 % 7 = 1;k = 4, 44 % 7 = 4;
k = 5, 45 % 7 = 2;…
我们会得到一个{4, 2, 1}的剩余系
要得到1~100的剩余系,需用公式3k %101;

#include
#include
#include
#include
#include
#include
using namespace std;
 int my_rand() {
    static int seed = 1;    
    seed = seed * 3  % 101;
    return seed;
}


int main() {    
	for (int i = 1; i <= 100; i++) cout << my_rand() << endl;
    return 0;
}

3.简述语言中的随机函数原理

srand:获取随机种子rand:获取随机值

#include
#include
#include
#include
#include
#include
using namespace std;
int main() {
    //起点随时间的变化而变化    srand(time(0));
    for (int i = 0; i < 10; i++) {        
    	cout << rand() % 10 << endl;
    }
    cout << endl;    //起点为srand设置的固定起点
    srand(4);
    for (int i = 0; i < 10; i++) {        
    	cout << rand() << endl;
    }    
    cout << endl;    //起点为系统的默认起点
    for (int i = 0; i < 10; i++) {       
     	cout << rand() << endl; 
    }    
    return 0;
}

四.数字出现的次数

数字出现的次数

五.找到数组中数量最多的元素或最少的元素

1.已知在一个有n个整数的数组中,数量最多的一个的数字超过n/2个,设计算法找到这个数字.

思路:删除抵消: 遍历数组,将两个数值不同的数字相抵消,最后剩下的那个数字便是要求的数字.

代码实现

#include
#include
#include
#include
#include
#include
using namespace std;
#define MAX_N 10000int num[MAX_N + 5];
int n;
void read() {    
	cin >> n;
    for (int i = 0; i < n; i++) {        
    	cin >> num[i];
    }        
    return ;
}
bool checkMoreThanHalf(int res) {    
int times = 0;
    for (int i = 0; i < n; i++) {        
    	if (num[i] == res) times += 1;
    }    
    if (times * 2 <= n) return false;
    return true;
}

int solve() {    
	int res = -1, ans = 0;
    if (num == NULL || n <= 0) return res;    
    for (int i = 0; i < n; i++) {
        if (ans == 0) {            
        	res = num[i];
            ans = 1;        
        } else if (res != num[i]) {
            ans -= 1;        
        } else {
            ans += 1;        
        }
    }
    if (!checkMoreThanHalf(res)) res = -1;    
    return res;
}

int main() {    
	read();
    cout << solve() << endl;    
    return 0;
}	

2.已知一个有n个元素的数组,数量最多的一个数字超过n/k个,请设计算法找到这个数字.

**思路分析:**摩尔投票法:
维护k个元素,遍历数组, 当元素储存的储存数量为0时,将该元素储存进去, 当该元素储存的数量不为0时,则将该元素的储存数量加1, 则当储存k个不同的元素时,k个元素相抵消,即k个元素都减1,不断地储存和抵消, 最后遍历储存的元素,找到数量最多的元素.
代码实现

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include
#include 
using namespace std;
#define MAX_N 1000000
int n, k;int nums[MAX_N + 5];

void read() {    
cin >> n >> k;
    int temp;    
    for (int i = 0; i < n; i++) {
        cin >> temp;        
        nums[i] = temp;
    }    
    return ;
}
int slove(){    
	map m;
    int ans = 0;    
    for(int i = 0; i < n; i++) {
        if (m.find(nums[i]) == m.end() || m[nums[i] == 0]) ans += 1;        
        m[nums[i]] += 1;
        if (ans != k) continue;        
        for (auto j = m.begin(); j != m.end(); j++) {    
            if (j->second) j->second -= 1;            
            if (j->second == 0) ans -= 1;
        }    
    }
    int res = 0, mmax = INT_MIN;    
    for (auto i = m.begin(); 
    i != m.end(); i++) {
        if (i->second == 0) continue;        
        if (i->second > mmax) {
            mmax = i->second;           
            res = i->first;
        }    
    }
    return res;
}

int main() {    
	read();
    cout << slove() << endl;    
    return 0;
}

3.在一个数组中,大多数数字都出现3次, 只有一个数字出现次数不足3次, 设法找到这个数字

二进制统计表

转换成二进制, 找到每个位的和不为3的位, 和不为3的位便是要找的数的二进制位为1的位.

状态转换表

代码演示

#include 
using namespace std;
int main() {    
int n, c, a = 0, b = 0;
    cin >> n;    
    while (n--) {
        cin >> c;        
        int a1 = (a & ~b & ~c) | (~a & b & c);
        int b1 = (~a & b & ~c) | (~a & ~b & c);        
        a = a1;
        b = b1;    
    }
    cout << (a | b) << endl;    
    return 0;
}

六.栈和队列

1.如何设计一个支持min操作的栈, min函数返回当前栈的最小值, 要求min, push, pop操作的时间复杂度都是O(1), 应如何设计?

思路:1.在栈NewStack里添加一个用于储存最小值, 次小值的栈m_stack.
2.当往栈NewStack里插入元素时, 先将元素的值和压入的时间压入栈NewStack,如果栈m_stack是空的,或者该元素比栈m_stack的栈顶元素的值小时, 将该元素压入到my_stack中存放起来,此时m_stack的栈顶元素的值就是栈NewStack的最小值.3.当NewStack弹栈时,比较NewStack 弹出元素的时间和辅助栈m_stack栈顶元素的时间是否一致, 一致证明是同一元素,需将该元素弹出辅助栈.
4.NewStack的最小值就辅助栈m_stack的栈顶元素.

代码实现

include
#include
#include
#include
#include
#include 
#include
using namespace std;
typedef pair PII;
struct NewStack : stack {
public:
    NewStack() {        
    this->t == 0;
    }    
    void push(int val);
    void pop();    
    int min();
private:    
	int t;
    stack m_stack;
};

void NewStack::push(int val) {    
	PII data(val, t++);
    this->stack ::push(data);    
    if (m_stack.empty() || val < m_stack.top().first) {
        m_stack .push(data);      
    }
    return ;
}

void NewStack::pop() {    
	if (this->empty()) return ;
    if (this->top().second == m_stack.top().second) {        		
    	m_stack.pop();
    }    
    this->stack::pop();
    return ;
}

int NewStack::min() {    
	if (this->empty()) return 0;
    return m_stack.top().first;
}


int main() {    
	NewStack s;
    int op, val;    
    while (cin >> op) {
        switch (op) {            
        	case 0: {
                cin >> val;                
                s.push(val); 
            }break;            
            case 1:{
                cin >> val;                
                s.pop(); 
            }break;            
            case 2: 
            	cout << s.min() << endl; 
            break;      
        }    
    }
    return 0;
}

2.有一个不包含重复元素的数组,如何快速地找到连续的k个位置,使得这k个位置上的数字排序以后在数字上也是连续的,要求算法的时间复杂度为O(n)

思路用滑动窗口来维护两个单调队列,维护获取每k个位置的最大值max和最小值min, 如果max - min == k - 1; 那么这k个位置便是要找的连续k个位置.

代码实现

#include
#include
#include
#include
#include
#include
using namespace std;
#define MAX_N 300000int max_q[MAX_N + 5], min_q[MAX_N + 5];
int max_head = 0, max_tail = 0, min_head = 0, min_tail = 0;
int val[MAX_N + 5];

int main() {    
	int n,k;
    cin >> n >> k;    
    for (int i = 1; i <= n; i++) {
        cin >> val[i];    
    }
    int min_loc, max_loc;    
    for (int i = 1; i <= n; i++) {
        while(min_tail - min_head && val[min_q[min_tail - 1]] > val[i]) min_tail--;        
        while(max_tail - max_head && val[max_q[max_tail - 1]] < val[i]) max_tail--; 
        min_q[min_tail++] = i;        
        max_q[max_tail++] = i;
        if (min_q[min_head] <= i - k) min_head++;        
        if (max_q[max_head] <= i - k) max_head++;
        if (i >= k) {            
        	if (val[max_q[max_head]] - val[min_q[min_head]] == k - 1)  {
                min_loc = min_q[min_head];                
                max_loc = max_q[max_head];
                break;            
            }
        }    
    }
    int s = min(min_loc, max_loc);    
    cout << "下标为: " << s << " ~ " << s + k - 1 << endl;
    return 0;
}

3.给定k值,如何找到链表的第k个元素,一个子针怎么做,两个子针怎么做?

一个子针跑两次

class Solution {
public:    
	ListNode* getKthFromEnd(ListNode* head, int k) {
        if (head == nullptr || k == 0) return nullptr;        ListNode *q = head;
        int n = 0;        
        while(q) {
            q = q->next;            
            n++;
        }        
        if (k > n) return nullptr;
        int l = n - k;        
        q = head;
        while(l--) {           
        	q = q->next;
        }        
        return q;
    }
 
};

两个子针跑一次

class Solution {
public:   
	ListNode* getKthFromEnd(ListNode* head, int k) {
        if (head == nullptr || k == 0) return nullptr;        
        ListNode *q = head, *p = head;
        while(--k) {            
        	q = q->next;
            if (q == nullptr) return nullptr;         
        }
        while(q->next) {            
        	q = q->next;
            p = p->next;        
        }
        return p;    
   }
};

七.字符串

替换空格

请实现一个函数, 把字符串中的每个空格替换成"%20".

class Solution {
public:
    string replaceSpace(string s) {
        int oldlength = s.size();
        if (oldlength == 0) return s;
        int black_num = 0;
        for (int i = 0; i < oldlength; i++) {
            if (s[i] == ' ') black_num += 1;
        }
        int newlength = oldlength + black_num * 2;
        s.resize(newlength);
        for (int j = newlength - 1, i = oldlength - 1; j > i && i >= 0; i--) {
            if (s[i] == ' ') {
                s[j--] = '0';
                s[j--] = '2';
                s[j--] = '%';
            } else {
                s[j--] = s[i];    
            }
        }
        return s;
    }
};

有额外空间

class Solution {
public:
    void  replaceSpace(char s[], int oldlength) {
        if (s == nullptr || oldlength <= 0) return ;
        int black_num = 0;
        for (int i = 0; i < oldlength; i++) {
            if (s[i] == ' ') black_num += 1;
        }
        int newlength = oldlength + black_num * 2;
       	if (newlength > oldlength) return ;
        for (int j = newlength - 1, i = oldlength - 1; j > i && i >= 0; i--) {
            if (s[i] == ' ') {
                s[j--] = '0';
                s[j--] = '2';
                s[j--] = '%';
            } else {
                s[j--] = s[i];    
            }
        }
        return ;
    }
};

九.树

1.输入二叉树的前序遍历和中序遍历的结果, 请重建此二叉树, 假设输入的前序遍历和中序遍历的结果中都不含重复的数字.

思路
1.我们可以从前序遍历中, 找到根节点, 即前序遍历的第一个数字的值就是根节点的值.
2.知道根节点的值后, 在后序遍历中遍历, 由于数字都是不重复的, 因此我们能很容易地找到根节点在后续遍历的位置.
3.找到根节点在后续遍历的位置ind后, 在后续遍历中0~ind是左子树, ind ~ n(n为后续遍历的长度)为右子树.同时我们可知左右子树的节点个数.以及左右字数后序遍历.
4. 在前序遍历中, 根节点后面的ind个节点是左字数,这样我们也可知道了左子树和右子树前序遍历.
5.既然我们已经得知了左右子树的前序遍历和后序遍历, 通过同样的方法去分别构建左右子树.

代码实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    unordered_map loc;
    TreeNode* buildTree(vector& preorder, vector& inorder) {
        int n = preorder.size(), m = inorder.size();
        if (n <= 0 || m <= 0 || n != m) return nullptr;
        for (int i = 0; i < m; i++) {
            loc[inorder[i]] = i;
        }
        return buildTree_func(preorder, 0, 0, m - 1);
    }
    TreeNode *buildTree_func(vector& preorder, int preRootLoc, int l, int r) {
        if (l > r) return nullptr; 
        TreeNode *root = new TreeNode();
        root->val = preorder[preRootLoc];
        int ind = loc[root->val];
        int l_num = ind - l;
        root->left = buildTree_func(preorder, preRootLoc + 1, l, ind - 1);
        root->right = buildTree_func(preorder, preRootLoc + l_num + 1, ind + 1, r);
        return root;
    }
};

二叉树的下一个节点

思路
1.如果一个节点有右节点那么它的下一个节点就是它的右子树的最左子节点.
2.如果一个节点没有右节点, 但是有父亲节点(即是子节点不是根节点), 且它为它的父亲节点的左子节点, 那么它的下一个节点是它的父亲节点
3.如果一个节点没有右节点, 但是有父亲节点(即是子节点不是根节点), 且它为它的父亲节点的右子节点, 那么我们需要沿着指向父节点的指针一直向上
遍历, 直到找到一个是它父节点的左子节点的节点.如果这样的节点存在, 那么这个节点的父节点就是我们要找的下一个节点.
代码实现


typedef struct TreeNode{
    int val;
    struct TreeNode *left, *right, *parent;
} TreeNode;

TreeNode *getNext(TreeNode *Node) {
    if (Node == NULL || (Node->parent == NULL && Node->right == NULL)) return NULL;
    TreeNode *nextNode = NULL;
    if (Node->right != NULL) {
        TreeNode *nodeRight = Node->right;
        while(nodeRight->left != NULL) {
            nodeRight = nodeRight->left;
        }
        nextNode = nodeRight;
    } else {
        TreeNode *curNode = Node;
        TreeNode *parent = Node->parent;
        while((parent != NULL) && (curNode == parent->right)) {
            curNode = parent;
            parent = parent->parent;
        }
        nextNode = parent;
    }
    return nextNode;
}

十.队列和栈

用两个栈实现队列

思路
1.创建两个栈, 分别为stack1, stack2.
2.当队列要插入元素时, 往stack1添加元素.
3.当队列要删除一个元素时,按照队列的先进先出的原则, 如果栈stack2中有元素时那么就将stack2栈顶的元素弹出,如果栈stack2中没有元素时, 而栈stack1中有元素时, 我们将stack1的元素依次弹到stack2中, 这样stack1为空,而stack2的栈顶元素正好时我们要弹的元素,将此值弹出.如果两个栈都为空, 说明队列没有元素.

代码实现

class CQueue {
public:
    CQueue() {
        
    }
    
    void appendTail(int value) {
        this->stack1.push(value);
    }
    
    int deleteHead() {
        if (this->stack2.empty()) {
            while(!this->stack1.empty()) {
                this->stack2.push(this->stack1.top());
                this->stack1.pop();
            }
        }
        if (this->stack2.empty()) return -1;
        int head = this->stack2.top();
        this->stack2.pop();
        return head;
    }
private:
    stack stack1;
    stack stack2;

};

用两个队列实现栈

解题思路
根据栈的先入后出的原则,最后被压入栈的元素先被弹出,我们可以设置两个队列q1, q2, 每次将元素压栈时, 将元素添加到q1中, 弹出时将除了q1的最后一个元素的其他元素依次从q1中弹入到q2中去, 然后q1中的剩下的那个元素就是栈顶元素,弹出即可, 然后交换q1和q2.q1的队尾元素就是栈顶元素.判断栈是否为空, 因q2是空的, 只需判断q1即可.

class MyStack {
public:
    MyStack() {

    }  
    void push(int x) {
        que1.push(x);
    }
    int pop() {
        if(empty()) return -1;
        while(que1.size() > 1) {
            que2.push(que1.front());
            que1.pop();
        }
        int top = que1.front();
        que1.pop();
        swap(que1, que2);
        return top;
    }
    
    /** Get the top element. */
    int top() {
        return que1.back();
    }
    
    /** Returns whether the stack is empty. */
    bool empty() {
        return que1.empty();
    }
private:
    queue que1;
    queue que2;
};

十一.递归和循环

1.斐波那契序列

时间复杂度 O ( n ) O(n) O(n)

long long Fibonaqie(int n) {
    if (n < 2) return n;
    long long fibOne = 0;
    long long fibTwo = 1;
    long long fibN = 1;
    
    for (int i = 2; i <= n; i++) {
        fibN = fibOne + fibTwo;
        fibOne = fibTwo;
        fibTwo = fibN;
    }
    return fibN;
}

青蛙跳台阶问题

一只青蛙一次可以跳1级台阶, 也可以跳上2级台阶, 求该青蛙跳上一个n级的台阶共有多少种跳法.
同样是斐波那契数列f(n ) = f(n -1) + f(n -2);

如果把条件改成:一只青蛙跳可以上一级台阶, 也可以跳两级台阶…它也可以跳n级台阶, 则此时该青蛙跳上一个n级的台阶共有多少种跳法 ?
f ( n ) f(n) f(n) = 2n-1.

2.

十二.找n 个数之和为S的n个数

找n 个数之和为S的n个数

AVL树, 红黑树, 哈弗曼编码,B+树,map和unordered_map

map unordered_map
底层实现 红黑树 哈希表
复杂度 O ( l o g n ) O(logn) O(logn) O ( c ) O(c) O(c)
有序性 有序 无序
稳定性 稳定 不稳定
效率 查询,插入, 删除慢 查找, 删除, 插入快

栈与堆

栈溢出;
堆与栈的区别;
两栈实一队列;
堆,大根堆,小根堆

数组

数组和链表的区别

动态规划

你可能感兴趣的:(面试,算法,面试)