*ps:*每天刷刷面试题目,新的感悟就通过写博客发出来了,就不需要用一些云笔记去整理了哈哈,希望大佬们不要介意~ 博客目录在右侧->
文章目录
BM1 反转链表-递归
这应该是一个比较经典的链表例题,每个算法网站上都会有:下面给出递归的写法,当然有很多大佬也有别的方法去巧妙的解决,我会多多留意然后更新~
C Version:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
*
* @param pHead ListNode类
* @return ListNode类
*/
struct ListNode* ReverseList(struct ListNode* pHead ) {
if(pHead->next==NULL||pHead==NULL){
return pHead;
}
struct ListNode* newpHead=ReverseList(pHead->next);
pHead->next->next=pHead;//目的是2要放在3的后面
pHead->next=NULL;//同时把2后面的指向弄成NULL
return newpHead;
}
BM2 链表内指定区间反转
思路比较多,因为没有规定必须要修改节点,所以你可以直接去改对应节点的值,但是这的确是个馊主意,你也可以利用先进后出的栈来做…
-C语言版本
但是正常的话,还是给自己应该提升难度来做,通过修改节点之间的关系:
-C++版本
C version:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param head ListNode类
* @param m int整型
* @param n int整型
* @return ListNode类
*/
struct ListNode* reverseBetween(struct ListNode* head, int m, int n ) {
// write code here
struct ListNode *p1,*p2;
int num,clock=0,i=0;
p1=p2=head;
if(head==NULL||head->next==NULL){
return head;
}
for(int i=1;i<m;i++){
p1=p1->next;
}
p2=p1;
for(int p=0;p<n-m;p++){
p2=p2->next;
}
num=p1->val;
p1->val=p2->val;
p2->val=num;
return head;
}
C++ version:
//1.将原链表分为三部分,m之前,m到n直接,n之后,并切断其联系
//2.将m,n之间链表反转
//3.将三部分重新连接
class Solution {
public:
ListNode *reverseBetween(ListNode *head, int m, int n) {
ListNode* dummy = new ListNode(-1);
dummy->next = head;
ListNode* pNode = dummy;
ListNode* ftail = pNode,*rhead = NULL, *rtail = NULL, *res = NULL;
for (int i = 0; i < m - 1; i++)
ftail = ftail->next;
rhead = ftail->next;
ftail->next = NULL;
ListNode* prev = NULL, *pcur = rhead, *pnext = rhead->next;
for (int i = m; i <= n;i++) {
pcur->next = prev;
prev = pcur;
pcur = pnext;
pnext = pnext->next;
}
res = pcur;
ftail->next = prev;
rhead->next = res;
ListNode* phead = dummy->next;
dummy->next = NULL;
delete dummy;
return phead;
}
};
BM3 链表中的节点每k个一组翻转
考虑用栈的特性来做,当时想用链式栈来做,这样出栈的适合就不用去建新的链表了,但是写了半天发现用C过不了…搬运了一下别的大佬的C++版本来学习一下把…栈使用的是顺序存储的结构,顺序压栈,然后出栈建新链表来完成的.具体可以看一下注释那里的链接哦~
C++ version:
/*
用栈的操作:入栈然后出栈,然后新建链表
https://www.nowcoder.com/practice/b49c3dc907814e9bbfa8437c251b028e?tpId=295&tqId=722&ru=/exam/oj&qru=/ta/format-top101/question-ranking&sourceUrl=%2Fexam%2Foj
*/
class Solution{
public:
ListNode* reverseKGroup(ListNode* head, int k) {
if(head==NULL)return head;
ListNode* head2=new ListNode(0);//新建链表
ListNode* p=head2;//p是头指针
stack<int>s,s2;
while(head){//遍历旧链表
s.push(head->val);//权值压入栈
if(s.size()==k){//当栈满k个元素,就将栈内元素全部出栈用以新建链表
while(!s.empty()){
p->next=new ListNode(s.top());s.pop();//新建链表
p=p->next;//指针后移
}
}
head=head->next;//指针后移
}
while(!s.empty()){//当栈s还有剩余,即链表结点不能被k整除时,将剩下的元素入两次栈,即可实现原顺序的结点权值
s2.push(s.top());s.pop();
}
while(!s2.empty()){
p->next=new ListNode(s2.top());s2.pop();//新建链表
p=p->next;//指针后移
}
return head2->next;//返回结果链表
}
};
BM4 合并两个排序的链表
这个也比较经典,但是注意,新的链表也是要符合升序的排列,题目好像没有说的太清楚,只说明了给出的链表是升序的,其实最终的链表也需要升序,我第一次就中招了,没理解清楚:
C Version:
struct ListNode* Merge(struct ListNode* pHead1, struct ListNode* pHead2 )
{
struct ListNode* vhead = (struct ListNode*)malloc(sizeof(struct ListNode)); //新建一个头结点
vhead->val = -1;
struct ListNode *p = vhead; //用一个指针指向该头结点
while (pHead1 && pHead2) //两个链表都未比较完
{
if (pHead1->val <= pHead2->val) //表1的值更小
{
p->next = pHead1; //先将结点连接到新表,再让原表指针后移一位,二者顺序不可换
pHead1 = pHead1->next;
}
else {
p->next = pHead2;
pHead2 = pHead2->next;
}
p = p->next; //加入一个新结点后,新表指针也后移一位
}
p->next = pHead1 ? pHead1 : pHead2; //都比较完后,哪个表非空则直接加入到新表中
return vhead->next; //返回第一个结点
}
BM6 判断链表中是否有环 -快慢指针
今天的题目比较简单,但是对运行时间有点要求:
#include <stdbool.h>
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
*
* @param head ListNode类
* @return bool布尔型
*/
/*
bool hasCycle(struct ListNode* head ) {
// write code here
if(head==NULL||head->next==NULL){
return false;
}
struct ListNode *p1=head->next;
while(p1->next!=head->next){
if(p1->next==NULL){
return false;
}
p1=p1->next;
}
return true;
//我的本意是直接去用一个指针去遍历,如果有循环圈圈的话,一定会p1->next==第二个节点的地址,但是这个方法好像超时了
}
*/
//这里我用参考大神的方法去写,这里我使用快慢指针的方法,慢指针每次都执行一次next,而快指针每次都执行两次的next,如果有循环的话,两个地址一定会遇见
bool hasCycle(struct ListNode* head ) {
if(head==NULL||head->next==NULL){
return false;
}
struct ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return true;
}
return false;
}
BM7 链表中环的入口结点
解法一:
这个题目看起来就是BM6的升级版,需要我们进一步的去确定交点的位置:那么我首先想到的就是哈希表的原理,把链表的值存储到哈希表中,然后遍历的过程中,如果有重复的节点地址,那么改地址就是我们要得到的节点的位置:
用C实现哈希表比较繁琐,C++的STL给我们分享了一个unordered_set:
关于这个unordered_set:它的特点简单的说是能够天然去重,里面存储的值是不相同的,所以我们用来存储地址是不错的选择,而且合理的用好它的find()和end方法:
成员方法 | 功能 |
---|---|
find (key) | 查找值为key的元素,如果找到,则返回一个指向该元素的正向迭代器;如果没找到,则返回一个与end()方法相同的迭代器 |
end | 返回指向容器中最后一个元素之后位置的迭代器 |
具体的功能和例子可以参考这个链接:unordered_set中end()与find()的使用
解法二: 参考的大神的解法,我是没想到这一层,实际上看成一个数学问题:
快慢指针思想:
初始化:快指针fast指向头结点, 慢指针slow指向头结点
让fast一次走两步, slow一次走一步,第一次相遇在C处,停止
然后让fast指向头结点,slow原地不动,让后fast,slow每次走一步,当再次相遇,就是入口结点。
C++ version:
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead) {
unordered_set<ListNode *> ret;//这个容器容纳的是节点的地址
ListNode *p;
p=pHead;
while(p){
if(ret.find(p)==ret.end()){//find方法,如果能从容器当中找不到与p相同的地址,则find与end相同
ret.insert(p);
p=p->next;
}
else{
return p;
}
}
return NULL;
}
};
附解法二:
class Solution {
public:
ListNode* EntryNodeOfLoop(ListNode* pHead)
{
ListNode *fast = pHead;
ListNode *slow = pHead;
while (fast && fast->next) {
fast = fast->next->next;
slow = slow->next;
if (fast == slow) break;
}
if (!fast || !fast->next) return nullptr;
fast = pHead;
while (fast != slow) {
fast = fast->next;
slow = slow->next;
}
return fast;
}
};
BM8 链表中倒数最后k个结点
1.头插法:
先用vector获取一下ListNode* 的所有节点,然后对vector反转一下,最后从下标0遍历到k结束,期间使用头插法来构建链表,就可以得到倒数最后k个节点
2.双指针:
双指针,定于两个指针fast和slow,让fast先跑k步,然后slow和fast同时开始跑,当fast为null时,slow的值就是第k个节点,输出slow即可。这个过程画个图就很清晰明了了。
这一过程大多数人都会做,主要是边界条件不要忘了判断。
<1>当链表为空时,当k <= 0时,都要输出为空
<2>当链表长度小于k时,输出为空。如果k大于链表长度,根据前面写好的程序可以得知,此时的fast已经为null了,而i却还小于k,所以直接在最后面加个判断语句就行了。
C++:
头插法:
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* ListNode(int x) : val(x), next(nullptr) {}
* };
*/
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* FindKthToTail(ListNode* pHead, int k) {
// write code here
vector<ListNode *> ret_int;
ListNode * p;
if(!pHead||k==0){
return NULL;
}
while(pHead){
ret_int.push_back(pHead);
pHead=pHead->next;
}
if(ret_int.size()<k){
return NULL;
}
else{
reverse(ret_int.begin(), ret_int.end());
p=ret_int[0];
for(int i=1;i<k;i++){
ListNode *p1=(ListNode *)malloc(sizeof(ListNode));
p1=ret_int[i];
p1->next=p;//头插法
p=p1;
}
}
return p;
}
};
双指针:
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param pHead ListNode类
* @param k int整型
* @return ListNode类
*/
ListNode* FindKthToTail(ListNode* pHead, int k) {
if(nullptr == pHead || k <= 0)
return nullptr;
ListNode *p = pHead, *q = pHead;
while(nullptr != p && k)
{
--k;
p = p->next;
}
if(k)
return nullptr;
while(p)
{
p = p->next;
q = q->next;
}
return q;
}
};
BM9 删除链表的倒数第n个节点
本题是要删除倒数的第n个节点:
1.可以参考之前的方式,把节点存入vector数组当中,然后反转一下,然后删除第n个节点,最后使用头插法进行构建新链表返回即可;
2.使用快慢指针的方式,快指针和慢指针之间要相差n个节点,当快指针到了节点末尾的时候,慢指针会正好处于第n-1个节点的位置上,这个时候要有两种情况:
如果n=1,意思是删除最后一个节点,那么直接给慢指针->next=NULL
如果n>1,那么就慢指针->next=慢指针->next->next;意思是越过第n个节点,直接把第n+1个节点交给慢指针的next;
C++ version:快慢指针
/**
* struct ListNode {
* int val;
* struct ListNode *next;
* };
*/
class Solution {
public:
/**
*
* @param head ListNode类
* @param n int整型
* @return ListNode类
*/
ListNode* removeNthFromEnd(ListNode* head, int n) {
// write code here
vector<ListNode *>res_vec;
ListNode *p1=head;
ListNode *p2=head;
int num=0;
if(n==2){
return head->next;
}
if(!head){
return NULL;
}
for(int i=0;i<n-1;i++){
p1=p1->next;
}
while(p1){
p2=p2->next;
p1=p1->next;
}
p2->next=p1;
return head;
}
};
BM17 二分查找-I
二分查找:记住while条件
class Solution {
public:
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param nums int整型vector
* @param target int整型
* @return int整型
*/
int search(vector<int>& nums, int target) {
// write code here
vector<int>::iterator it = find(nums.begin(),nums.end(),target);;
if(it !=nums.end()){
return it-nums.begin();
}
else
return -1;
}
};
BM18 二维数组中的查找
考察的其实就是二分法,把每一列都看作一个有序的数列,使用二分法进行判断target是否在里面;
如果不在,就换第i列继续二分查找:
注意每次进入新的一列的二分查找的时候,要将left=0,right=size-1;
二分查找的过程中,一般会出现两种情况:
1.要么就是mid,找到了和target的值,此时只需要return 返回即可;
2.要么就是没有找到target的值,并且left>right;
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int left;
int right;
int mid;
if(!array.size())
return false;
if(!array[0].size())
return false;
for(int i=0;i<array.size();i++){
left=0;
right=array.size()-1;
while(left<=right){
mid=(left+right)/2;
if(target==array[i][mid]){
return true;
}
if(target>array[i][mid])
left=mid+1;
else
right=mid-1;
}
}
return false;
}
};
BM21 二叉树的前序遍历#c/c++递归的写法
C Version:
递归先序遍历二叉树
BM24 二叉树的中序遍历
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型一维数组
* @return int* returnSize 返回数组行数
*/
//中序遍历:顺序左->根->右
static num;
void inorder(struct TreeNode *root,int *ret){
if(!root){
return ;
}
inorder(root->left, ret);
ret[num++]=root->val;
inorder(root->right, ret);
}
int* inorderTraversal(struct TreeNode* root, int* returnSize ) {
// write code here
if(!root){
return NULL;
}
int *ret=(int *)malloc(sizeof(int)*10000);
inorder(root, ret);
*returnSize=num;
return ret;
}
BM25 二叉树的后序遍历
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*
* C语言声明定义全局变量请加上static,防止重复定义
*/
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param root TreeNode类
* @return int整型一维数组
* @return int* returnSize 返回数组行数
*/
static num;
//后续遍历的顺序:left->right->mid
void postorder(struct TreeNode *root,int *ret){
if(!root){
return ;
}
postorder(root->left, ret);//left
postorder(root->right, ret);//right
ret[num++]=root->val;//mid
}
int* postorderTraversal(struct TreeNode* root, int* returnSize ) {
// write code here
int *ret=(int *)malloc(sizeof(int)*1000);
postorder(root, ret);
*returnSize=num;
return ret;
}
BM26 求二叉树的层序遍历
前面三个属于深度遍历,一般采用递归比较方便理解;
层次遍历的话,一般采用的是使用队列的方法,因为层次遍历的思路:
至于为什么要用队列的方式来实现,这里有个大神的博客可以参考一下:
树的层次遍历以及使用队列遍历过程图
这里涉及到STL的使用方法:queue 队列 vector容器,他们都有对应的方法实现进队出队和赋值:
一些我在注释当中标明:
C++ version:
/**
* struct TreeNode {
* int val;
* struct TreeNode *left;
* struct TreeNode *right;
* };
*/
class Solution {
public:
/**
*
* @param root TreeNode类
* @return int整型vector<vector<>>
*/
vector<vector<int> > levelOrder(TreeNode* root) {
// write code here
int size;
vector<vector<int>>ret;//相当于C语言的二维数组
if(!root){
return ret;
}
queue<TreeNode *>ret_queue;//STL的队列声明
vector<int> ret_int;//声明一维数组
ret_queue.push(root);//根节点进队列
while(!ret_queue.empty()){
//empty():如果 queue 中没有元素的话,返回 true。
size=ret_queue.size();
ret_int.clear();//记得把数组的元素清0,不然的话上一个根节点会进入到第二行当中
while(size--){//size监视队列的长度
TreeNode *num=ret_queue.front();
ret_queue.pop();//出队列
ret_int.push_back(num->val);//把根节点的值拿过来存入数组当中
if(num->left) ret_queue.push(num->left);
if(num->right) ret_queue.push(num->right);
}
if(ret_int.size()>0) ret.push_back(ret_int);//添加每行的一维数组
}
return ret;
}
};
BM27 按之字形顺序打印二叉树
可以理解为BM26的进阶版本,无疑就是之前按照层次遍历的顺序,现在要求是奇数层次层次遍历自左向右,偶数层次要求自右向左,这个问题并不难:
1.你可以设置一个变量,代表层数,参照上面BM26的模板。当层数为偶数的时候,你可以先让根节点的右子树进队列再根节点的左子树进队列;
2.参考BM26的模板,当层数为偶数的时候,你可以去将一维数组,反转一下,这样就是自右向左啦,期间用到的函数是vector的reverse:
关于Vector的详细用法:参考博客哦:
Vector的使用方法:
C++ Version:
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
int num,res=0;
vector<vector<int>> ret_Twoint;
if(!pRoot){
return ret_Twoint ;
}
vector<int> ret_int;
queue<TreeNode *>ret_queue;
TreeNode * p;
ret_queue.push(pRoot);//队列存储根节点
while(!ret_queue.empty()){
num=ret_queue.size();//获取队列长度
ret_int.clear();//每次清除掉前面的size
res++;
while(num--){
p=ret_queue.front();//获取第一个元素
ret_queue.pop();//出队列
ret_int.push_back(p->val);
if(p->left){
ret_queue.push(p->left);
}
if(p->right){
ret_queue.push(p->right);
}
}
if(ret_int.size()){
if(res%2==0){
reverse(ret_int.begin(), ret_int.end());//反转一下数组
}
ret_Twoint.push_back(ret_int);
}
}
return ret_Twoint;
}
};
NC37 合并区间
首选需要对区间进行一个排序,排序的规则是按照区间的start的高低来排序:
这里需要重写一个sort排序,来实现vector的排序;
c++ version:
/**
* Definition for an interval.
* struct Interval {
* int start;
* int end;
* Interval() : start(0), end(0) {}
* Interval(int s, int e) : start(s), end(e) {}
* };
*/
class Solution {
public:
static bool cmp_1(const Interval &a,const Interval &b) { //重载比较
if(a.start < b.start)
return true;
else
return false;
}
vector<Interval> merge(vector<Interval> &intervals) {
vector<Interval> res;
if(intervals.size() == 0) //去除特殊情况
return res;
sort(intervals.begin(), intervals.end(),cmp_1); //按照区间首排序
res.push_back(intervals[0]); //放入第一个区间
for(int i = 1; i < intervals.size(); i++){ //遍历后续区间,查看是否与末尾有重叠
if(intervals[i].start <= res.back().end) //区间有重叠,更新结尾
res.back().end = max(res.back().end, intervals[i].end);
else //区间没有重叠,直接加入
res.push_back(intervals[i]);
}
return res;
}
};
BM42 用两个栈实现队列
题目比较简单,栈的特点是先进后出,队列的特点是先进先出,队列的实现,只需要将stack1压栈,然后再出栈给stack2,最后把stack2出栈,达到了负负得正的效果,从而就得到了队列的先进先出的效果:
注意考虑,当push1 push2 pop push3 pop pop的时候:
C++ version:
class Solution
{
public:
void push(int node) {
vector<int> ret;
stack1.push(node);//进栈
}
int pop() {
int num_2,num_3;
if(!stack2.empty()){
num_3=stack2.top();
stack2.pop();
return num_3;
}
while(!stack1.empty()){
int num=stack1.top();
stack1.pop();
stack2.push(num);
}
num_2=stack2.top();
stack2.pop();
return num_2;
}
private:
stack<int> stack1;
stack<int> stack2;
};
BM43 包含min函数的栈
功能就是在出栈入栈的基础上,添加一个能返回min最小值的功能:
可以在每次入栈的时候,二次重复压栈,每次栈顶元素是最小值:
例如入栈-1 2 然后取min:
C++ version:
class Solution {
public:
public:
void push(int value) {
int ret_push;
if(stack_1.empty()){
stack_1.push(value);//栈中无元素就只进一个
stack_1.push(value);
}
else{
ret_push=stack_1.top();//出栈一个元素
stack_1.push(value);
if(ret_push<value){
stack_1.push(ret_push);//永远把小的放在栈顶
}
else{
stack_1.push(value);//永远把小的放在栈顶
}
}
}
void pop() {
stack_1.pop();
stack_1.pop();
}
int top() {//注意获取top的时候,获取完毕一次,记得把上面的最小值在还回来。
int num,num_2;
if(stack_1.empty()){
return 0;
}
num_2=min();
stack_1.pop();
num=stack_1.top();
stack_1.push(num_2);
return num;
}
int min() {
int num;
if(stack_1.empty()){
return 0;
}
num=stack_1.top();
//二次获取的时候:
return num;
}
private:
stack<int> stack_1;
};
BM50 两数之和
1.暴力求解:直接两个for循环遍历,然后找一下有没有和是target的情况;
2.使用哈希表,用Target-number[i] ,然后搜查一下Target-number[i] 在number中有没有,有的话就返回下标res。注意判断排除一下下标是否和i相同的情况。
C++ version:
哈希表:
class Solution {
public:
/**
*
* @param numbers int整型vector
* @param target int整型
* @return int整型vector
*/
vector<int> twoSum(vector<int>& numbers, int target) {
vector<int>res;//保存结果
map<int,int>mp;//定义一个哈希表存储numbers[i]和对应的下标
for (int i = 0; i < numbers.size(); i ++) {//进行标记
mp[numbers[i]] = i;
}
for (int i = 0; i < numbers.size(); i++) {
//每遍历一个numbers[i]就去对应的mp里找有没有target - numbers[i]
//如果有就返回结果
//如果没有就找下一个
if(mp.find(target - numbers[i]) != mp.end() && i != mp[target - numbers[i]]){
res.push_back(i + 1);
res.push_back(mp[target - numbers[i]] + 1);
return res;
}
}
return res;
}
};
本博客使用到的友情链接:
Vector的使用方法:
Stack的使用方法
Queue的使用方法
树的层次遍历以及使用队列遍历过程图
unordered_set中end()与find()的使用