目录
#16.06 最小差
给定两个整数数组a和b,计算具有最小差绝对值的一对数值(每个数组中取一个值),并返回该数值的差。 <https://leetcode-cn.com/problems/smallest-difference-lcci/>
思路:1. 双指针:对两个数组排序->用双指针分别指向两个数组a[i]、b[j]->哪个更小就哪个走,走到更大时就停下,另一个走。记录最小差。
代码:sort(a.begin(),a.end());
sort(b.begin(),b.end());
int i=0,j=0;
while(i<n&&j<m){
Min=min(Min,abs(a[i]-b[j]));
if(a[i]==b[j]) return 0;
else if(a[i]<b[j]) i++;
else j++;
}
return Min;
2. 二分法: b进行二分和a的每一项比较。
sort(b.begin(),b.end());
long res=INT_MAX;
for(int i=0;i<n;i++){
Int left=0,right=m-1;
While(left<right){
Int mid=(left+mid)/2;
If(b[mid]<a[i]) left=mid+1;
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<m-1) res=min(res,abs(a[i]-b[left+1])) ;//比较三次
}
return res;
#面试题 08.08. 有重复字符串的排列组合
有重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合。
<https://leetcode-cn.com/problems/permutation-ii-lcci/>
回溯法:
每个字符都和第一个交换,得到的新字符串再让之后的每个字符和第二个交换,……以此类推。
void dfs(string &s,int begin){
if(begin==s.size()) {
res.push_back(s);
return;
}
for(int i=begin;i<s.size();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<b;i++)
|
|
return false;
}
面试题 16.10. 生存人数
给定N个人的出生年份和死亡年份,第i个人的出生年份为birth[i],死亡年份为death[i],实现一个方法以计算生存人数最多的年份。
你可以假设所有人都出生于1900年至2000年(含1900和2000)之间。如果一个人在某一年的任意时期都处于生存状态,那么他们应该被纳入那一年的统计中。例如,生于1908年、死于1909年的人应当被列入1908年和1909年的计数。
如果有多个年份生存人数相同且均为最大值,输出其中最小的年份。来自 <https://leetcode-cn.com/problems/living-people-lcci/>
1.暴力法,哈希表,把每年出生的人和死亡的人相见,存入哈希表中。
vector<int> hash(101,0);
for(int i=0;i<birth.size();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.前缀和法,出生时间和死亡时间和某个人没有关系。就像上下公交车一样,只要知道上了几个人,下了几个人就行。
vector<int>live(102,0);
vector<int> gone(102,0);
for(int i=0;i<birth.size();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<int> vals;
ListNode* p=head;
while(p){
vals.push_back(p->val);
p=p->next;
}
int left=0,right=vals.size()-1;
while(left<right){
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<int,bool> 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个节点是同一节点(引用完全相同),则这两个链表相交。
来自 <https://leetcode-cn.com/problems/intersection-of-two-linked-lists-lcci/>
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<first.size()&&j<second.size()&&first[i]==second[j]){
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<a&&begin<b&&first[begin]==second[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<x){//头插
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<int> 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<TreeNode*> 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的个数相同且大小最接近的那两个数(一个略大,一个略小)。
- num的范围在[1, 2147483647]之间;
- 如果找不到前一个或者后一个满足条件的正数,那么输出 -1。
1.解题思路
比 num 大的数:从右往左,找到第一个 01 位置,然后把 01 转为 10,右侧剩下的 1 移到右侧的低位,右侧剩下的位清0。
比 num 小的数:从右往左,找到第一个 10 位置,然后把 10 转为 01,右侧剩下的 1 移到右侧的高位,右侧剩下的位置0。
class Solution {
public:
vector<int> findClosedNumbers(int num) {
int k=0;
vector<int> 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<<k);
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<<j);先全部置为0,后面再加回来
}
}
for(int i=0;i<cnt;i++){
res[0]+=(1<<i);
}
for(k=0;k<=31;k++){//10变成01
if(((num>>k)&1)==0&&((num>>(k+1))&1)==1){
res[1]=(num+(1<<k))-(1<<(k+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<<j);//先全部置为0,后面再加回来
}
}
for(int i=k-1;i>k-1-cnt;i--){
res[1]+=(1<<i);
}
return res;
}
};
法2:用bitset<>
vector<int> 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<<i;
res=res-(num&(1<<(i+1)))+(t1<<1);
int t2=num&(1<<(i+1));
res=res-(num&(1<<i))+(t2>>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<int> 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<int> 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<vector<int>> pathWithObstacles(vector<vector<int>>& grid) {
vector<vector<int>> res;
if(grid.empty()) return res;
vector<vector<bool>> visit(grid.size(),vector<bool>(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<vector<int>>& grid,vector<vector<int>>& res,int x,int y,vector<vector<bool>>&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<grid[0].size()&&dfs(grid,res,x,y+1,visit)) return true;//能通就可以直接返回
if(x+1<grid.size()&&dfs(grid,res,x+1,y,visit)) return true;
res.pop_back();//两条路都不通,说明(x,y)就不通。
return false;//不通
}
};
//2.动态规划
vector<vector<int>> 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<vector<bool>> dp(row,vector<bool>(col,0));
dp[0][0]=1;
for(int i=0;i<row;i++){
for(int j=0;j<col;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<int>& nums) {
int res=-1;
binary(nums,0,nums.size()-1,res);
return res;
}
void binary(vecot<int> &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);
}
}
}