程序员面试金典(一)||20题

目录

 

#16.06 最小差

#面试题 08.08. 有重复字符串的排列组合

面试题 16.10. 生存人数

面试题 02.06. 回文链表

面试题 02.01. 移除重复节点

面试题 02.07. 链表相交

面试题 02.08. 环路检测

面试题 01.04. 回文排列

面试题 01.05. 一次编辑

面试题 02.04. 分割链表

面试题 02.05. 链表求和

面试题 03.01. 三合一

面试题 04.12. 求和路径

面试题 04.06. 后继者

面试题 05.04. 下一个数

面试题 05.06. 整数转换

面试题 05.07. 配对交换

面试题 05.08. 绘制直线

面试题 08.02. 迷路的机器人

面试题 08.03. 魔术索引


#16.06 最小差

        给定两个整数数组ab,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该数值的差。

思路:1. 双指针:对两个数组排序->用双指针分别指向两个数组a[i]、b[j]->哪个更小就哪个走,走到更大时就停下,另一个走。记录最小差。

          代码:sort(a.begin(),a.end());

    sort(b.begin(),b.end());

    int i=0,j=0;

    while(i

        Min=min(Min,abs(a[i]-b[j]));

        if(a[i]==b[j]) return 0;

else if(a[i]

else j++;

    }

    return Min;

          2. 二分法: b进行二分和a的每一项比较。

sort(b.begin(),b.end());

long res=INT_MAX;

for(int i=0;i

Int left=0,right=m-1;

While(left

Int mid=(left+mid)/2;

If(b[mid]

Else if(b[mid]>a[i]) right=mid-1;

Else return 0;

}

res=min(res,abs((long)(a[i]-b[left])));

 if(left-1>=0) res=min(res,abs((long)(a[i]-b[left-1])));

 if(left+1

}

return res;

#面试题 08.08. 有重复字符串的排列组合

有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。

      回溯法:

      每个字符都和第一个交换,得到的新字符串再让之后的每个字符和第二个交换,……以此类推。

void dfs(string &s,int begin){

     if(begin==s.size()) {

        res.push_back(s);

        return;

    }

    for(int i=begin;i

        if(illegal(string,begin,i) continue;//保证没有重复的

        swap(s[begin],s[i]);

        dfs(s,begin+1);

        swap(s[begin],s[i]);//回溯

    }
}

bool ilegal(string s,int a,int b){

for(int i=a;i

       

if(s[i]==s[b]) return true;

return false;

}

面试题 16.10. 生存人数

给定N个人的出生年份和死亡年份,第i个人的出生年份为birth[i],死亡年份为death[i],实现一个方法以计算生存人数最多的年份。

你可以假设所有人都出生于1900年至2000年(含1900和2000)之间。如果一个人在某一年的任意时期都处于生存状态,那么他们应该被纳入那一年的统计中。例如,生于1908年、死于1909年的人应当被列入1908年和1909年的计数。

如果有多个年份生存人数相同且均为最大值,输出其中最小的年份。来自

 

1.暴力法,哈希表,把每年出生的人和死亡的人相见,存入哈希表中。

vector hash(101,0);

for(int i=0;i

for(int j=birth[i];j<=death[i];j++){

  hash[j-1900]++;

}

}

//统计最大值

for(int i=0;i<101;i++){

            if(hash[i]>Max){

                Max=hash[i];

                index=i;

            }

        }

return indexx+1900;

2.前缀和法,出生时间和死亡时间和某个人没有关系。就像上下公交车一样,只要知道上了几个人,下了几个人就行。

vectorlive(102,0);

vector gone(102,0);

for(int i=0;i

  live[birth[i]-1900]++;

  gone[death[i]-1900+1]++;

}

for(int i=1;i<102;i++){//每年存在的人数

live[i]+=live[i-1];

live[i]-=gone[i];

}

for(int i=0;i<110;i++){//统计最多的年份

    if(live[i]>Max) {

       Max=live[i];

       flag=i;

    }

}

return flag+1900;

面试题 02.06. 回文链表

编写一个函数,检查输入的链表是否是回文的。

进阶:

你能否用 O(n) 时间复杂度和 O(1) 空间复杂度解决此题?

 

1.借助数组,或者栈把数值保存下来然后对比。

if(head==nullptr||head->next==nullptr) return true;

vector vals;

ListNode* p=head;

while(p){

   vals.push_back(p->val);

   p=p->next;

}

int left=0,right=vals.size()-1;

while(left

  if(vals[left++]==vals[right--]) continue;

   else return false;

 }

 return true;

2.反转链表(要注意需要深复制,不然没办法对比)

         ListNode* p=head;

        ListNode* pre=nullptr;ListNode* cur=new ListNode(p->val);

        while(cur){

            ListNode * node=new ListNode(0);

            if(p->next) node->val=(p->next->val);

            else node=nullptr;

        cur->next=pre;

        pre=cur;

        cur=node;

            p=p->next;

        }//反转链表。pre是头结点

        while(head!=nullptr&&pre!=nullptr){

            if(head->val!=pre->val) return false;

            else{

                head=head->next;

                pre=pre->next;

            }

        }

        return true;

3.快慢指针,反转后半部分之后和前半部分对比(可以避免深复制)

ListNode* slow=head,*fast=head;

while(fast&&fast->next){

slow=slow->next;

fast=fast->next->next;

}

ListNode* pre=nullptr,*cur=slow;

while(cur){

ListNode* t=cur->next;

cur->next=pre;

pre=cur;

cur=t;

}//翻转后半段,头结点为pre

while(pre){

if(pre!=head) return false;

else{

pre=pre->next;

head=head->next;

}

}

return true;

面试题 02.01. 移除重复节点

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点。

进阶:

如果不得使用临时缓冲区,该怎么解决?

1.借助哈希法,深度复制,把重复的跳过。

map hash;

ListNode* pres=new ListNode(0);

ListNode* res=new ListNode(head->val);

pres->next=res;

hash[head->val]=1;head=head->next;//跳过头结点

while(head){

if(hash[head->val]) head=head->next;

else{

hash[head->val]=1;

res->next=new ListNode(head->val);

head=head->next;

res=res->next;

}

}

return pres;

2.直接next的指向来跳过,不需要深赋值,但注意要在最后指向nullptr

bool hash[20001]={0};

ListNode* pres=new ListNode(0);

ListNode* p=head;

pres->next=p;

hash[head->val]=1;head=head->next;

while(head){

if(hash[head->val]==0){

hash[head->val]=1;

p->next=head;

p=p->next;

head=head->next;

}

else{

head=head->next;

if(!head)//到最后了,p要指向nullptr;

p->next=nullptr;

}

}

return pres->next;

面试题 02.07. 链表相交

给定两个(单向)链表,判定它们是否相交并返回交点。请注意相交的定义基于节点的引用,而不是基于节点的值。换句话说,如果一个链表的第k个节点与另一个链表的第j个节点是同一节点(引用完全相同),则这两个链表相交。

来自

1.使链表相连(终止条件是相连之后的两个链表都走到了nullptr也就是走了n1+n2个节点)

p1=headA,p2=headB;

while(p1!=p2){

p1=p1->next;p2=p2->next;

if(p1!=p2){//循环终止条件

if(p1=nullptr) p1=headB;

if(p2=nullptr) p2=headA;

}

}

return p1;

2.哈希表 先存入A再检测B

3.先让长的先走长度差,然后一起走。

4.存入栈s1,s2;然后pop直到相同。//因为相遇之后后面都一样。

面试题 02.08. 环路检测

给定一个有环链表,实现一个算法返回环路的开头节点。

有环链表的定义:在链表中某个节点的next元素指向在它前面出现过的节点,则表明该链表存在环路

1.借助map

 

2.通过数学推导

while(fast&&fast->next){

fast=fast->next->next;

slow=slow->next;

if(fast==slow) break;

}

if(fast!=slow) //如果不相等那么上面就不是break跳出的,而是while结束了,说明不存在环。

return nullptr;

fast=head;//让fast从头,再次相遇。

while(fast!=slow){

fast=fast->next;

slow=slow->next;

}

return fast;

面试题 01.04. 回文排列

给定一个字符串,编写一个函数判定其是否为某个回文串的排列之一。

回文串是指正反两个方向都一样的单词或短语。排列是指字母的重新排列。

回文串不一定是字典当中的单词。

       //利用哈希表可以实现

        //用bitset这种特殊的数据结构

        bitset<128> b;//这种默认为0 ,

        b.set();  //则全部置为1;

        b.reset();//全部置为0

        for(char c:s){

            b.flip(c);

        }

        if(b.none()||b.count()==1) return true;

        else return false;

面试题 01.05. 一次编辑

字符串有三种编辑操作:插入一个字符、删除一个字符或者替换一个字符。 给定两个字符串,编写一个函数判定它们是否只需要一次(或者零次)编辑。

1.考虑所有的情况即可: 编辑首位,编辑中间,编辑尾位。

{

int a=first,size();int b=second.size();//让size赋值给int ,因为size()方法的返回是size_t没有负数,加减或者比较会出错

if(a-b>1||a-b<-1) return false;

if(a>=b) return cmp(first,second);

else return  cmp(second,first);

}

 

bool cmp(string &first,string &second){

int i=0,j=0;

while(i

i++;j++;

}

if(j==second.side()) return true;//全等或删除最后一位

if(i==first.size()-1&&j==second.size()-1) return true;//替换最后一位

if(first.substr(i+1)==second.substr(j)||first.substr(i+1)==second.substr(j))//删除或替换中间

return true;

else return false;

 

2.既然只有一位不一样,那么从两边夹击:

               int a=first.size();int b=second.size();

        if(a-b>1||a-b<-1) return false;

        int begin=0,end1=a-1,end2=b-1;

        while(begin

            begin++;

        while(end1>=0&&end2>=0&&first[end1]==second[end2]){

            end1--;end2--;

        }

        if(end1==-1&&end2==-1) return true;//全等

        return end1-begin<1&&end2-begin<1;

 

面试题 02.04. 分割链表

编写程序以 x 为基准分割链表,使得所有小于 x 的节点排在大于或等于 x 的节点之前。如果链表中包含 x,x 只需出现在小于 x 的元素之后(如下所示)。分割元素 x 只需处于“右半部分”即可,其不需要被置于左右两部分之间。

1.建立两个链表small和big分别存储小的和大的部分。最后链接起来。这样可以保持原有的顺序。

2.头插法  凡是遇到小于x的就插入到头结点 那么剩下的肯定是大于等于x的

 

if(!head||!head->next) return head;

ListNode* dummy=new ListNode(0);

dummy->next=head;

ListNode* pre=head,*cur=head->next;

while(cur){

if(cur->val

pre->next=cur->next;

cur->next=dummy->next;

dummy->next=cur;

cur=pre->next;

}

else{

pre=pre->next;

cur=cur->next;

}

}

return dummy->next;

面试题 02.05. 链表求和

给定两个用链表表示的整数,每个节点包含一个数位。这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

大数的相加

                     只需考虑每个位数的值为多少,加上是否进位

                    //另外在对应位相加的时候,考虑长度不一样,就一个一个加 if(l1) m+=l1->val;if(l2) m+=l2->val;

ListNode* addTwoNumbers(ListNode* l1, ListNode* l2){

ListNode* dummy=new ListNode(0);

ListNode* p=dummy;

int m=0;

bool flag=false;

while(l1||l2){

m=(int)flag;

if(l1) m+=l1->val;

if(l2) m+=l2->val;

 

if(m>=10){

flag=true;

m-=10;

}

else flag=false;

 

p=>next=new ListNode(m)

p=p->next;

 

if(l1) l1=l1->next;

if(l2) l2=l2->next;

}

reurn dummy->next;

}

面试题 03.01. 三合一

描述如何只用一个数组来实现三个栈。

你应该实现push(stackNum, value)、pop(stackNum)、isEmpty(stackNum)、peek(stackNum)方法。stackNum表示栈下标,value表示压入的值。

构造函数会传入一个stackSize参数,代表每个栈的大小。

当栈为空时`pop, peek`返回-1,当栈满时`push`不压入元素。

 

思路:把数组分成三段分别用index0,index1,index2,指向三个数组。->可以用pointer[3]来表示更方便。

 

class TripleInOne {

private:

    int pointer[3];//int index0,index1,index2;

    vector s;//表示三个栈

    int stackSize;//表示栈大小

public:

    TripleInOne(int stackSize) {

        this->stackSize=stackSize;

        s.resize(3*stackSize);

        //index0=-1;index1=stackSize-1;index2=2*stackSize-1;

pointer[0]=-1;pointer[1]=stackSize-1;pointer[2]=2*stackSize-1;

    }

   

    void push(int stackNum, int value) {

if(pointer[stackNum]<(stackNum+1)*stackSize-1)

s[++pointer[stackNum]]=value;

else return;

    }

   

    int pop(int stackNum) {

if(pointer[stackNum]>stackNum*stackSize-1){

pointer[stackNum]--;

return s[pointer[stackNum]+1];

}

else return -1;

    }

   

    int peek(int stackNum) {

       if(pointer[stackNum]>stackNum*stackSize-1)

return s[pointer[stackNum]];

    else return -1;

       }

   

    bool isEmpty(int stackNum) {

if((pointer[stackNum]==stackNum*stackSize-1)

return true;

else return false;       

    }

};

面试题 04.12. 求和路径

给定一棵二叉树,其中每个节点都含有一个整数数值(该值或正或负)。设计一个算法,打印节点数值总和等于某个给定值的所有路径的数量。注意,路径不一定非得从二叉树的根节点或叶节点开始或结束,但是其方向必须向下(只能从父节点指向子节点方向)。

int cnt=0;

int pathSum(TreeNode* root, int sum) {

if(root==nullptr) return 0;

dfs(root,sum,cnt);

if(root->left) pathSum(root->left,sum-root->val);//有返回值,但不一定要返回 只是用于递归

if(root->right) pathSum(root->right,sum-root->val);

return cnt;

}

void dfs(TreeNode* root, int sum,int &cnt){

if(root->val==sum) cnt++;//不需要return,有可能子树还有

if(root->left) dfs(root->left,sum-root->val,cnt);

if(root->right) dfs(root->right,sum-root->val,cnt);

}

面试题 04.06. 后继者

设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。

如果指定节点没有对应的“下一个”节点,则返回null。

class Solution {

    vector inorder;

public:

    TreeNode* inorderSuccessor(TreeNode* root, TreeNode* p) {

        //1.中序遍历

        /*if(!root) return nullptr;

        dfs(root);

        auto it=find(inorder.begin(),inorder.end(),p);

        if(it==inorder.end()||it==--inorder.end()) return nullptr;

        else return *++it;*/

        //2.利用二叉树的特点(不太好理解)

        if(!root) return root;

        if(root->val<=p->val) return inorderSuccessor(root->right,p);//大于或等于root说明在right。等于一定要在right,这样才是后继。

        else{

            TreeNode* t=inorderSuccessor(root->left,p);//左边如果找不到就是root,找到了就返回该值。

            return t?t:root;

        }

    }

    void dfs(TreeNode* root){

        if(!root) return;

        dfs(root->left);

        inorder.push_back(root);

        dfs(root->right);

    }

};

面试题 05.04. 下一个数

下一个数。给定一个正整数,找出与其二进制表达式中1的个数相同且大小最接近的那两个数(一个略大,一个略小)。

  1. num的范围在[1, 2147483647]之间;
  2. 如果找不到前一个或者后一个满足条件的正数,那么输出 -1。

1.解题思路

比 num 大的数:从右往左,找到第一个 01 位置,然后把 01 转为 10,右侧剩下的 1 移到右侧的低位,右侧剩下的位清0。

比 num 小的数:从右往左,找到第一个 10 位置,然后把 10 转为 01,右侧剩下的 1 移到右侧的高位,右侧剩下的位置0。

 

class Solution {

public:

    vector findClosedNumbers(int num) {

        int k=0;

        vector res(2,-1);

        if(num==1) return {2,-1};

        if(num==INT_MAX) return {-1,-1};//处理特殊情况

        for(k=0;k<=31;k++){//01变成10

            if(((num>>k)&1)==1&&((num>>(k+1))&1)==0){

                res[0]=(num+(1<<(k+1)))-(1<

                break;

            }

        }

        //K右边的1全部移到最右边。包括k

        int cnt=0;

        for(int j=k;j>=0;j--){

            if((res[0]>>j)&1) {

                cnt++;

                res[0]=res[0]-(1<

            }

        }

        for(int i=0;i

            res[0]+=(1<

        }

          

        for(k=0;k<=31;k++){//10变成01

            if(((num>>k)&1)==0&&((num>>(k+1))&1)==1){

                res[1]=(num+(1<

                break;

            }

        }

        //K右边的1全部移到最左边。

        cnt=0;

        for(int j=k-1;j>=0;j--){

            if((res[1]>>j)&1) {

                cnt++;

                res[1]=res[1]-(1<

            }

        }

        for(int i=k-1;i>k-1-cnt;i--){

            res[1]+=(1<

        }

        return res;

    }

};

法2:用bitset<>

vector findClosedNumbers(int num) {

        bitset<32> small(num);//二进制初始化bitset

        bitset<32> bigger(num);

 

        int s = -1;

        // small, 10 转 01,1移到左侧

        for (int i = 1; i < 32; i++) {

            if (small[i] == 1 && small[i - 1] == 0) {

                small.flip(i);

                small.flip(i - 1);

                for (int left = 0, right = i - 2; left < right;) {

                    while (left < right && small[left] == 0) left++;

                    while (left < right && small[right] == 1) right--;

                    small.flip(left);

                    small.flip(right);

                }

                s = (int)small.to_ulong();//函数作用二进制转化为无符号十进制。

                break;

            }

        }

 

        // bigger, 01转10,1移到最右侧

        int b = -1;

        for (int i = 1; i < 32; i++) {

            if (bigger[i] == 0 && bigger[i - 1] == 1) {

                bigger.flip(i);

                bigger.flip(i - 1);

 

                for (int left = 0, right = i - 2; left < right;) {

                    while (left < right && bigger[left] == 1) left++;

                    while (left < right && bigger[right] == 0) right--;

                    bigger.flip(left);

                    bigger.flip(right);

                }

                b = (int)bigger.to_ulong();

                break;

            }

        }

 

        return {b, s};

    }

面试题 05.06. 整数转换

整数转换。编写一个函数,确定需要改变几个位才能将整数A转成整数B。

class Solution {

public:

    int convertInteger(int A, int B) {

        //1.用bitset

        /*int cnt=0;

        bitset<32> a(A);

        bitset<32> b(B);

        for(int i=31;i>=0;i--){

            if(a[i]!=b[i]) cnt++;

        }

        return cnt;*/

        //2.先用A^B弄出不同的,再计算1的个数;而且要用unsigned

        unsigned a=A,b=B;

        unsigned int n=a^b;

        int cnt=0;

        while(n){

            cnt+=n&1;n>>=1;

        }

        return cnt;

    }

};

 

面试题 05.07. 配对交换

配对交换。编写程序,交换某个整数的奇数位和偶数位,尽量使用较少的指令(也就是说,位0与位1交换,位2与位3交换,以此类推)。

 

public:

    int exchangeBits(int num) {

//1.模拟交换过程。

        /*int res=num;

        for(int i=0;i<=30;i+=2){//做位运算的时候一定要多加括号

            int t1=num&1<

            res=res-(num&(1<<(i+1)))+(t1<<1);

            int t2=num&(1<<(i+1));

            res=res-(num&(1<>1);

        }

        return res;*/

2.//分别取出数字二进制奇数位和偶数位

  //将奇数位的二进制放在偶数位(即右移)

  //将偶数位的二进制放在奇数位(即左移)

        //0x55555555 = 0b0101_0101_0101_0101_0101_0101_0101_0101

        //0xaaaaaaaa = 0b1010_1010_1010_1010_1010_1010_1010_1010

        int odd = (num & 0xaaaaaaaa) >> 1;//

        int even = (num & 0x55555555) << 1;

        return even | odd;

    }

};

 

面试题 05.08. 绘制直线

绘制直线。有个单色屏幕存储在一个一维数组中,使得32个连续像素可以存放在一个 int 里。屏幕宽度为w,且w可被32整除(即一个 int 不会分布在两行上),屏幕高度可由数组长度及屏幕宽度推算得出。请实现一个函数,绘制从点(x1, y)到点(x2, y)的水平线。

 

给出数组的长度 length,宽度 w(以比特为单位)、直线开始位置 x1(比特为单位)、直线结束位置 x2(比特为单位)、直线所在行数 y。返回绘制过后的数组。

 

vector drawLine(int length, int w, int x1, int x2, int y) {

        // 高度y<(length*32)/w

         if(length<=0||y>=(length*32)/w||x1>=w||x2>=w) return {};//非法输入

        vector res(length, 0);

        for( int i=x1; i<=x2; ++i )

            res[i/32+y*w/32] |= (1<<(31-i%32));

        return res;

       

    }

面试题 08.02. 迷路的机器人

设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。

//1.回溯法

class Solution {

public:

    vector> pathWithObstacles(vector>& grid) {

       

vector> res;

if(grid.empty()) return res;

vector> visit(grid.size(),vector(grid[0].size(),0));

if(grid[0][0]==1||grid[grid.size()-1][grid[0].size()-1]==1) return res;

dfs(grid,res,0,0,visit);

return res;

 

    }

//这个dfs最好时用bool类型,那么可以判断 下一步(x,y+1)或者(x+1,y)能通的话就不用再走了。不通的话就返回。

    bool dfs(vector>& grid,vector>& res,int x,int y,vector>&visit){

if(y==grid[0].size()-1&&x==grid.size()-1) {//终止条件

            res.push_back({x,y});

            return true;

        }

if(grid[x][y]==1||visit[x][y]==1) return false;

visit[x][y]=1;

res.push_back({x,y});

if(y+1

if(x+1

res.pop_back();//两条路都不通,说明(x,y)就不通。

return false;//不通

}

};

//2.动态规划

        vector> res;

        if(grid.empty()) return res;

        int row=grid.size(),col=grid[0].size();

    if(grid[0][0]==1||grid[row-1][col-1]==1) return res;

        //dp记录能否到达该格

        vector> dp(row,vector(col,0));

        dp[0][0]=1;

        for(int i=0;i

            for(int j=0;j

                if(grid[i][j]) continue;

                if(i>0&&j>0) dp[i][j]=dp[i-1][j]||dp[i][j-1];

                if(i>0&&j==0) dp[i][j]=dp[i-1][j];

                if(i==0&&j>0) dp[i][j]=dp[i][j-1];

            }

        }

        if(!dp[row-1][col-1]) return res;//能到最后一个肯定前面就有路径,不能到就没有路径。

        int m=row-1,n=col-1;

        while(m>=0&&n>=0){

            res.push_back({m,n});

            if(m==0&&n==0) break;

            if(m>0&&dp[m-1][n]) m--;

            else if(n>0&&dp[m][n-1]) n--;

        }

        reverse(res.begin(),res.end());

        return res;

 

面试题 08.03. 魔术索引

魔术索引。 在数组A[0...n-1]中,有所谓的魔术索引,满足条件A[i] = i。给定一个有序整数数组,编写一种方法找出魔术索引,若有的话,在数组A中找出一个魔术索引,如果没有,则返回-1。若有多个魔术索引,返回索引值最小的一个。

法1:从前往后遍历O(n);

法2:二分+递归:

int findMagicIndex(vector& nums) {

int res=-1;

binary(nums,0,nums.size()-1,res);

return res;

}

void binary(vecot &nums,int left,int right,int res){

if(left<=right){//这里要相等

int mid=(left+right)/2;

if(nums[mid]==mid){//找到了就尝试在左边能不能找到更小的

if(res==-1||res>mid) res=mid;

binary(nums,left,mid-1,res);

}

else{//没找到就左右继续找

binary(nums,left,mid-1,res);

binary(nums,mid+1,right,res);

}

}

}

你可能感兴趣的:(数据结构与算法)