记录力扣上的算法题
1. 最富有客户资产
给你一个 m x n
的整数网格 accounts
,其中 accounts[i][j]
是第 i
位客户在第 j
家银行托管的资产数量。返回最富有客户所拥有的 资产总量 。
客户的 资产总量 就是他们在各家银行托管的资产数量之和。最富有客户就是 资产总量 最大的客户。
int maximumWealth(vector<vector<int>>& accounts) {
int maxwealth=0;
for(auto account:accounts)//使用范围循环,来遍历vector容器中的第一层数组
{
maxwealth=max(maxwealth,accumulate(account.begin(),account.end(),0));//使用accumulate来求第二层容器的和
}
return maxwealth;
}
2.链表的中间结点
给你单链表的头结点 head
,请你找出并返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
ListNode* middleNode(ListNode* head) {
int length=0;
ListNode *l=head;//创建一个链表指针指向头结点
while(l!=nullptr)
{
length++;
l = l->next;
}
l=head;//把创建的指针重新指向头结点
for(int i=0;i<length/2;i++)
{
l=l->next;
}
return l;
注:求中间结点就是返回中间结点以后的链表 ,由于链表无法直接得到长度,所以需要遍历第一遍来得到长度,再去寻找中间结点
还有一个快慢指针的方法,过几天找机会学一下
ListNode* middleNode(ListNode* head) {
ListNode* slow,*fast;
slow=fast=head;
while(fast&&fast->next)
{
slow=show->next;
fast=fast->next->next;
}
return slow;
}
快慢指针解法,在一个链表中有两个指针,一个是快指针一次走两步,一个是慢指针一次一步,当快指针走完时,慢指针刚好走到中间结点
快指针的结束条件为 当前结点和当前结点的后一个结点不能为空指针
3.赎金信
给你两个字符串:ransomNote
和 magazine
,判断 ransomNote
能不能由 magazine
里面的字符构成。
如果可以,返回 true
;否则返回 false
。
magazine
中的每个字符只能在 ransomNote
中使用一次。
bool canConstruct(string ransomNote, string magazine) {
vector<int> v(26);//桶排序
if(ransomNote.size()>magazine.size())
{
return false;
}
for(auto &c:magazine)
{
v[c-'a']++;
}
for(auto &c:ransomNote)
{
v[c-'a']--;
if(v[c-'a']<0)
{
return false;
}
}
return true;
}
用了桶排序
4.合并两个有序链表
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==nullptr)
{
return list2;
}
if(list2==nullptr)
{
return list1;
}
if(list1->val<=list2->val)
{
list1->next=mergeTwoLists(list1->next,list2);
return list1;
}
else
{
list2->next=mergeTwoLists(list2->next,list1);
return list2;
}
}
使用迭代的方法,先选出基底链表,两个链表中data较小的作为基底,作为第一个首元结点,首元结点的下一个通过迭代求出,迭代终止条件是遇到空指针既是其中某一个链表遍历完了,遇到空指针,则返回另一个链表,至此迭代完毕
5.有效的括号
给定一个只包括 '('
,')'
,'{'
,'}'
,'['
,']'
的字符串 s
,判断字符串是否有效。
有效字符串需满足:
- 左括号必须用相同类型的右括号闭合。
- 左括号必须以正确的顺序闭合。
- 每个右括号都有一个对应的相同类型的左括号。
bool isValid(string s) {
unordered_map<char,int> m{{'(',1},{'{',2},{'[',3},{')',4},{'}',5},{']',6}};
stack<char> st;
for(auto c:s)
{
int flag=m[c];
if(flag>=1&&flag<=3)
{
st.push(c);
}
else if(!st.empty()&&flag==m[st.top()]+3)
{
st.pop();
}
else
{
return false;
}
}
if(st.empty())
{
return true;
}
return false;
}
用unordered_map对组把括号和数字对应起来,创建一个栈,遍历数组 如果是左括号就入栈,遇到右括号就判断栈是否为空和栈顶元素的左括号是否是这个右括号对应的,如果是的话就出栈,遍历过程中如果发现栈为空或者栈顶元素和目前元素对应不上,就返回false;
6.两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。
请你将两个数相加,并以相同形式返回一个表示和的链表。
你可以假设除了数字 0 之外,这两个数都不会以 0 开头。
ListNode* compare(ListNode *head1,ListNode *head2)
{//head1 长链表,head2短链表
ListNode *p=head1;
ListNode *q=head2;
while(q!=nullptr)
{
p->val+=q->val;
p=p->next;
q=q->next;
}
p=head1;
while(p->next!=nullptr)
{
if(p->val>=10)
{
p->val-=10;
p->next->val++;
}
p=p->next;
}
if(p->val>=10)
{
ListNode *l3=new ListNode(1);
p->val-=10;
p->next=l3;//在链表末尾加上后继指针为空,data为1的结点
}
p=head1;
return p;
}
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
int size1=0,size2=0;
ListNode *p=l1;
ListNode *q=l2;
while(p!=nullptr)
{
size1++;
p=p->next;
}
while(q!=nullptr)
{
size2++;
q=q->next;
}
if(size1>size2)
{
return compare(l1,l2);
}
else
return compare(l2,l1);
}
下面的函数将两个链表遍历,将长度长的作为基底传入上面的函数,上面的函数将两个链表长度相同的地方相加,但不进位,然后将指针重新指向首元结点,进行遍历,将值大于十的进位,遍历到最后一个结点前停止,对最后一个结点的值进行判断,若大于十,先减去十,然后直接再最后加上一个后继结点为空,值为一的结点
7.删除排序链表中的重复元素
给定一个已排序的链表的头 head
, 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
ListNode* deleteDuplicates(ListNode* head) {
if(!head)
{
return head;//如果传入的是空链表,那么直接返回
}
ListNode *p=head;
while(p->next)
{
int temp=p->next->val;
if(p->val==temp)//如果当前结点的值和下一个结点的值相等,则把下一个结点的后继结点赋值给当前结点的后继结点,继续判断当前结点的值和下一个结点的值的关系,不能直接进行下一个结点的判断,防止出现三个或以上的排序
{
p->next=p->next->next;
}
else
{
p=p->next;
}
}
p=head;
return p;
本题关键在于两个地方,第一个是while循环不能走到最后一个结点,得在最后一个结点就退出循环,防止访问到空指针,第二个是判断当前结点和后继结点相等时,把下一个结点的后继结点赋值给当前结点的后继结点,继续判断当前结点的值和下一个结点的值的关系,不能直接进行下一个结点的判断,防止出现三个或以上的排序
8.岛屿数量
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
class Solution {
public:
int numIslands(vector<vector<char>>& grid){
int wide=grid[0].size();
int length=grid.size();
if(!length)
{
return 0;
}
int island_num=0;
for(int i=0;i<length;i++)
{
for(int j=0;j<wide;j++)
{
if(grid[i][j]=='1')
{
island_num++;
dfs(grid,i,j);
}
}
}
return island_num;
}
void dfs(vector<vector<char>> &grid,int x,int y)
{
int length=grid.size();
int wide=grid[0].size();
grid[x][y]='0';
if(x+1<length&&grid[x+1][y]=='1') dfs(grid,x+1,y);
if(y-1>=0&&grid[x][y-1]=='1') dfs(grid,x,y-1);
if(x-1>=0&&grid[x-1][y]=='1') dfs(grid,x-1,y);
if(y+1<wide&&grid[x][y+1]=='1') dfs(grid,x,y+1);
return;
}
};
题解:如果想要求一个内部嵌套的容器大小,只需要访问该容器任一位置,再访问其成员函数
深度优先搜索,从起始位置开始,先往四个方向搜索,判断是否符合要求,符合要求的话继续迭代,搜索完直接return;
9. 移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
class Solution {
public:
ListNode* removeElements(ListNode* head, int val) {
while(head&&head->val==val)
{
ListNode *l =head->next;
delete(head);
head=l;
}
if(!head) return nullptr;
//定义两个指针
ListNode*fast=head->next;
ListNode*slow=head;
while(fast)
{
if(fast->val==val)
{
ListNode*p=fast->next;
delete(fast);
fast=p;
slow->next=p;
}
else
{
slow=slow->next;
fast=fast->next;
}
}
return head;
}
};
自己写的时候,转牛角尖了,只用了一个指针,判断条件很复杂,不易解出。
这个方法前面和我一样先找到一个值不为目标值得位置,作为起始点,判断条件要加上当前结点不为空且当前结点的值不为目标值,如果循环结束,链表为空的话直接返回空链表,第二步值得学习,使用两个指针,一个指针在前一个指针在后,后面的指针用头结点作为起始结点,如果前面的指针值为目标的值,则fast=fast->next,slow->next=fast;后指针先不动,继续判断前指针的值,如果前指针的值不是目标值,则前后指针共同前进一步,循环直到前指针到空为止.
10.两两交换链表中的结点
给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)。
class Solution {
public:
ListNode* swapPairs(ListNode* head) {
if(!head||!head->next) return head;
//交换两个结点,需要知道三个结点,才能进行两两交换
ListNode *one =head;
ListNode *two =head->next;
ListNode *three=head->next->next;
//交换两个结点
two->next =one;
one->next=swapPairs(three);//重新以三号位为头结点,进行迭代
return two;//返回这两个结点中的头结点
}
};
首先注意审题,不能交换结点中的值,第一次提交的时候就犯了这个错误
迭代思想:因为是两两交换,所以我们一个知道三个结点,才能进行两两交换链表中的结点,首先将第二个结点的下一个赋值为第一个,第一个的后继结点要更改为第三个,但是第三个还需要进行一次两两交换,所以进行迭代,把第三个作为头结点,进行迭代。当结点或者结点的后继节点为空指针的时候,无法交换返回结点,注意的是返回值是两个结点中的头结点,所以是第二个结点。
11.数组相对排序
给定两个数组,arr1
和 arr2
,
arr2
中的元素各不相同arr2
中的每个元素都出现在arr1
中
对 arr1
中的元素进行排序,使 arr1
中项的相对顺序和 arr2
中的相对顺序相同。未在 arr2
中出现过的元素需要按照升序放在 arr1
的末尾。
class Solution {
public:
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
int max_num=*max_element(arr1.begin(),arr1.end());
vector<int> book(max_num+1);
for(auto &c:arr1)
{
book[c]++;
}
vector<int> v;
for(auto &c:arr2)
{
while(book[c]!=0)
{
book[c]--;
v.push_back(c);
}
}
for(int i=0;i<book.size();i++)
{
while(book[i]!=0)
{
v.push_back(i);
book[i]--;
}
}
return v;
}
};
问题一,我一开始是打算用桶排序的但是我看数字大小从零到一千,就不打算用了,结果用别的也没解出来,其实数字大小到几千还是可以用桶排序的,对运行时间基本没有影响。
解法中使用了max_element算法 这个算法给一个迭代器范围,得出该范围中最大值的迭代器,解引用一下就可以得出最大值,那么桶排序时的计数数组就可以节约一点空间了
解法中就是常见的桶排序,遍历第一个数组,把每一个数记录下来,然后遍历第二数组,把计数组中不为零的全加到新数组中去,并将计数数组中的数字减一。遍历完第二数组后,把计数数组中不为零的再尾插到新数组中去。
12.环形链表
给定一个链表的头节点 head
,返回链表开始入环的第一个节点。 如果链表无环,则返回 null
。
如果链表中有某个节点,可以通过连续跟踪 next
指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos
来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos
是 -1
,则在该链表中没有环。注意:pos
不作为参数进行传递,仅仅是为了标识链表的实际情况。
不允许修改 链表。
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode*fast=head;
ListNode*slow=head;
while(fast&&fast->next)
{
fast=fast->next->next;
slow=slow->next;
if(fast==slow)
{
slow=head;
while(slow!=fast)
{
fast=fast->next;
slow=slow->next;
}
return slow;
}
}
return NULL;
}
};
13.重排链表
给定一个单链表 L
的头节点 head
,单链表 L
表示为:
L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:
L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。
void reorderList(ListNode* head) {
vector<ListNode*> v;
ListNode*l=head;
while(l)
{
v.push_back(l);
l=l->next;
}
int i=0,tail=v.size()-1;
while(i!=tail)
{
v[i]->next=v[tail];
i++;
if(i==tail) break;
v[tail]->next=v[i];
tail--;
}
v[i]->next=nullptr;
}
先创建一个vector线性表,把链表结点都存进去,把最后一个结点赋值给第一个结点的后继节点,然后i往后移,并判断两指针是否相遇了,如果相遇了退出,并在该节点的后继节点赋值一个空指针,没有相遇的话就移动tail指针。一直移动直到相遇。
14.用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
class MyStack {
public:
queue<int> q1;
queue<int> q2;
MyStack() {
}
void push(int x) {
q2.push(x);
while(!q1.empty())
{
q2.push(q1.front());
q1.pop();
}
swap(q1,q2);
}
int pop() {
//if(q1.empty()) exit();
int num=q1.front();
q1.pop();
return num;
}
int top() {
return q1.front();
}
bool empty() {
return q1.empty();
}
};
queue.pop()
queue.push()
queue.front()
queue.back();
我希望世界铭记这天,这个队列题我大败
15.最小栈
设计一个支持 push
,pop
,top
操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
MinStack()
初始化堆栈对象。void push(int val)
将元素val推入堆栈。void pop()
删除堆栈顶部的元素。int top()
获取堆栈顶部的元素。int getMin()
获取堆栈中的最小元素
stack<int> s;
int minnum;
MinStack() {
}
void push(int val) {
if(s.empty())
{
minnum=val;
}
if(val<=minnum)
{
s.push(minnum);
s.push(val);
minnum=val;
}
else{
s.push(val);
}
}
void pop() {
if(s.top()==minnum)
{
s.pop();
minnum=s.top();
s.pop();
}
else
{
s.pop();
}
}
int top() {
return s.top();
}
int getMin() {
return minnum;
}
利用stl中的栈容器,进行存取操作,但是这个题目需要在最小常数时间内求得最小值,所以还得额外使用一个变量来保存最小值,当栈为空的时候,直接入栈,若栈不为空,插入的值是否小于等于最小值,如果是小于等于最小值,那么就先把最小值插入再把该值插入,并且修改最小值。这样以来如果是栈的第一个是最小值,但是出栈了,那么就可以令栈顶为最小值,并出栈。
16.设计浏览器历史记录
你有一个只支持单个标签页的 浏览器 ,最开始你浏览的网页是 homepage
,你可以访问其他的网站 url
,也可以在浏览历史中后退 steps
步或前进 steps
步。
请你实现 BrowserHistory
类:
BrowserHistory(string homepage)
,用homepage
初始化浏览器类。void visit(string url)
从当前页跳转访问url
对应的页面 。执行此操作会把浏览历史前进的记录全部删除。string back(int steps)
在浏览历史中后退steps
步。如果你只能在浏览历史中后退至多x
步且steps > x
,那么你只后退x
步。请返回后退 至多steps
步以后的url
。string forward(int steps)
在浏览历史中前进steps
步。如果你只能在浏览历史中前进至多x
步且steps > x
,那么你只前进x
步。请返回前进 至多steps
步以后的url
。
class BrowserHistory {
public:
int pos;
int top;
string history[5000];//用来存放历史记录
BrowserHistory(string homepage) :pos(-1),top(0){
visit(homepage);
}
void visit(string url) {
pos++;
top=pos;
history[top++]=url;
}
string back(int steps) {
if(steps>pos)
{
steps=pos;
}
pos-=steps;
return history[pos];
}
string forward(int steps) {
steps=min(steps,top-pos-1);
pos+=steps;
return history[pos];
}
};
方法一,设计一个数组和两个指针,top指针指向数组中尾后,pos指向当前位置,初始化时候,pos指向-1,top指向0;每次加入一个界面,pos加一,然后把界面的string加入到history[pos]中
然后top++;每次后退和前进,都要进行一次判断,判断后退步数,是否大于pos如果大于则令steps等于pos,然后pos减去steps就是界面在history数组中的下标;前进时,需注意可前进步数为top-pos-1;
方法二
class bothlist
{
public:
string val;
bothlist*prev,*next;
bothlist(string c="",bothlist*p=nullptr,bothlist*n=nullptr)
:val(c),prev(p),next(n){}
};
//用来存放历史记录
class BrowserHistory {
public:
bothlist *top;
bothlist *pos;
BrowserHistory(string homepage) {
bothlist *head=new bothlist(homepage);
top=head;
pos=head;
}
void visit(string url) {
top->next=new bothlist(url,pos);//每次都默认后继节点为空
top=top->next;
pos->next=top;
pos=top;
}
string back(int steps) {
while((steps--)&&pos->prev)
{
pos=pos->prev;
}
return pos->val;
}
string forward(int steps) {
while((steps--)&&pos->next)
{
pos=pos->next;
}
return pos->val;
}
};
使用双向链表的方法
值得注意的地方,自定义双向链表的构造函数的写法,使用的是默认构造的方式,默认前后节点都为空,构造类的时候url和前向结点pos,后继节点为空。
void visit(string url) {
top->next=new bothlist(url,pos);//每次都默认后继节点为空
top=top->next;
pos->next=top;
pos=top;
}
以我现在的实力还写不出思路,仅能自己看懂,等实力进步了再来补充
17.删除链表倒数第n个结点
给你一个链表,删除链表的倒数第 n
个结点,并且返回链表的头结点。
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
while(!head||!head->next) return nullptr;
ListNode *dummy=new ListNode(0,head);
ListNode*fast,*slow;
fast=head;
slow=dummy;
while(n--&&fast)
{
fast=fast->next;
}
//if(n) return;若n不符合要求,则报错
while(fast)
{
fast=fast->next;
slow=slow->next;
}
ListNode*temp=slow->next;
slow->next=slow->next->next;
ListNode*ret=dummy->next;
delete dummy;
delete temp;
return ret;
}
};
亮点:本题也是用到了双指针方法,不过具体有些不同,快指针先走n步,快慢指针再一起开始动
等快指针走到后,慢指针就走到倒数第n个结点的前一个结点,再进行操作。
一是在new操作后必须又delete,防止内存泄露
二是在做链表题目是,时刻注意链表为空的情况是否需要提前判断,
三是在做数组题目时,时刻要注意越界问题